Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/version-2.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Enhancements

* [#2511](https://github.com/netbox-community/netbox/issues/2511) - Compare object change to the previous change
* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment
* [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings

Expand Down
24 changes: 24 additions & 0 deletions netbox/extras/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator
from utilities.utils import shallow_compare_dict
from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
from . import filters, forms
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
Expand Down Expand Up @@ -207,8 +208,31 @@ def get(self, request, pk):
orderable=False
)

objectchanges = ObjectChange.objects.filter(
changed_object_type=objectchange.changed_object_type,
changed_object_id=objectchange.changed_object_id,
)

next_change = objectchanges.filter(time__gt=objectchange.time).order_by('time').first()
prev_change = objectchanges.filter(time__lt=objectchange.time).order_by('-time').first()

if prev_change:
diff_added = shallow_compare_dict(
prev_change.object_data,
objectchange.object_data,
exclude=['last_updated'],
)
diff_removed = {x: prev_change.object_data.get(x) for x in diff_added}
else:
# No previous change; this is the initial change that added the object
diff_added = diff_removed = objectchange.object_data

return render(request, 'extras/objectchange.html', {
'objectchange': objectchange,
'diff_added': diff_added,
'diff_removed': diff_removed,
'next_change': next_change,
'prev_change': prev_change,
'related_changes_table': related_changes_table,
'related_changes_count': related_changes.count()
})
Expand Down
29 changes: 29 additions & 0 deletions netbox/templates/extras/objectchange.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,35 @@
</tr>
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Difference</strong>
<div class="btn-group btn-group-xs pull-right noprint">
<a {% if prev_change %}href="{% url 'extras:objectchange' pk=prev_change.pk %}"{% else %}disabled{% endif %} class="btn btn-default">
<span class="fa fa-chevron-left" aria-hidden="true"></span> Previous
</a>
<a {% if next_change %}href="{% url 'extras:objectchange' pk=next_change.pk %}"{% else %}disabled{% endif %} class="btn btn-default">
Next <span class="fa fa-chevron-right" aria-hidden="true"></span>
</a>
</div>
</div>
<div class="panel-body">
{% if diff_added == diff_removed %}
<span class="text-muted" style="margin-left: 10px;">
{% if objectchange.action == 'create' %}
Object created
{% elif objectchange.action == 'delete' %}
Object deleted
{% else %}
No changes
{% endif %}
</span>
{% else %}
<pre style="background-color: #ffdce0;">{{ diff_removed|render_json }}</pre>
<pre style="background-color: #cdffd8;">{{ diff_added|render_json }}</pre>
{% endif %}
</div>
</div>
</div>
<div class="col-md-7">
<div class="panel panel-default">
Expand Down
16 changes: 16 additions & 0 deletions netbox/utilities/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,19 @@ def querydict_to_dict(querydict):
key: querydict.get(key) if len(value) == 1 and key != 'pk' else querydict.getlist(key)
for key, value in querydict.lists()
}


def shallow_compare_dict(source_dict, destination_dict, exclude=None):
"""
Return a new dictionary of the different keys. The values of `destination_dict` are returned. Only the equality of
the first layer of keys/values is checked. `exclude` is a list or tuple of keys to be ignored.
"""
difference = {}

for key in destination_dict:
if source_dict.get(key) != destination_dict[key]:
if isinstance(exclude, (list, tuple)) and key in exclude:
continue
difference[key] = destination_dict[key]

return difference