I'm currently working on an app for logging changes to model fields. The changes are logged as a JSON string {"field": field.name, "old_value": old_value, "new_value": new_value} in a textfield.

How can I get specific keys/values of the JSON string? Current code:

views.py

def userChangelog(request, pk):

    user = User.objects.get(pk = pk)
    changelogs = ChangeLog.objects.filter(user_id = pk)

    context = {
        'user': user,
        'changelogs': changelogs,
    }

    return render(request, 'users/backend/user/user_changelog.html', context)

template

{% for changelog in changelogs %}
    <p>1. {{ changelog.user }}</p>
    <p>2. {{ changelog.changes }}</p>
    <p>3. {{ changelog.date_of_change }}</p>
{% endfor %}

Ideally, I would like to do <p>2. {{ changelog.changes.field }} changed from {{ changelog.changes.old_value }} to {{ changelog.changes.new_value }}</p>, but can't seem to figure out how to translate only part of the query to json.loads, and display it in the template.

Edit - ChangeLog model

class ChangeLog(models.Model):
    user = models.ForeignKey(User, related_name = 'changed_by', on_delete = models.CASCADE)
    content_type = models.ForeignKey(ContentType, models.SET_NULL, verbose_name = _('content type'), blank = True, null = True,)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    changes = models.TextField(_('changes'), blank = True)
    date_of_change = models.DateTimeField(_('change time'), default = timezone.now, editable = False,)

    objects = ChangeLogManager()

    class Meta:
        verbose_name = _('Change log entry')
        verbose_name_plural = _('Change log entries')

    def change_message(request, obj, old_instance):

        new_instance = obj.objects.get(pk = old_instance.pk)

        ct = ContentType.objects.get_for_model(new_instance)

        for field in obj._meta.get_fields():
            if isinstance(field, models.ManyToOneRel):
                continue

            old_value = getattr(old_instance, field.name)
            new_value = getattr(new_instance, field.name)

            if old_value != new_value:
                change_message = json.dumps({"field": field.name, "old_value": old_value, "new_value": new_value})

                ChangeLog.objects.log_update(
                    user = request,
                    content_type = ct,
                    content_object = new_instance,
                    object_id = new_instance.pk,
                    changes = change_message,
                    date_of_change = timezone.now()
                )

score:2

Accepted answer

If you do not wish to use a third party package. You could add a property to your ChangeLog model that parses and returns the changes JSON. cached_property is useful as it stops you parsing the changes on every access

@cached_property
def changes_dict(self):
    return json.loads(self.changes)

Than you can use it in your template

{% for changelog in changelogs %}
    <p>1. {{ changelog.user }}</p>
    <p>2. {{ changelog.changes_dict.field }}</p>
    <p>2. {{ changelog.changes_dict.old_value }}</p>
    <p>2. {{ changelog.changes_dict.new_value }}</p>
    <p>3. {{ changelog.date_of_change }}</p>
{% endfor %}

Related Query