7

I am using django-simple-history (1.8.1) and DRF (3.5.3). I want to get a rest service containing the history of each element. Let's take an example !

models.py

class Product(models.Model):
    name = models.CharField(max_length=50)
    price = models.IntegerField()
    history = HistoricalRecords()

    def __str__(self):
        return self.name

So, what must be serializers.py ? I'd like to GET something like :

[
    {
        "id": 1,
        "name": "Apple",
        "price": 8,
        "history": [
            {
                "history_id": 1,
                "id": 1,
                "name": "Apple",
                "price": 0,
                "history_date": "2016-11-22T08:02:08.739134Z",
                "history_type": "+",
                "history_user": 1
            },
            {
                "history_id": 2,
                "id": 1,
                "name": "Apple",
                "price": 10,
                "history_date": "2016-11-22T08:03:50.845634Z",
                "history_type": "~",
                "history_user": 1
            },
            {
                "history_id": 3,
                "id": 1,
                "name": "Apple",
                "price": 8,
                "history_date": "2016-11-22T08:03:58.243843Z",
                "history_type": "~",
                "history_user": 1
            }
        ]
    }
]

After searching whitout finding the solution, I finally found it by myself. But if someone have a better solution...

cedrik
  • 541
  • 7
  • 17
  • The only issue is that you have to create another table for each table you want the logs. Is it possible to add all the logs to a single table? – Elias Prado Oct 06 '21 at 21:10

4 Answers4

11

I know it's been a year, but anyway, maybe someone finds it useful. Here is my solution (it seems far easier to me):

A new serializer field:

class HistoricalRecordField(serializers.ListField):
    child = serializers.DictField()

    def to_representation(self, data):
        return super().to_representation(data.values())

Now simply use it as a a field in your serializer:

history = HistoricalRecordField(read_only=True)

This makes use of DRF's built in list and dict serializers, only trick is to pass it the correct iterable, which is being done by calling .values() on the simple-history model manager class.

wizpig64
  • 416
  • 4
  • 9
Alexander
  • 195
  • 2
  • 9
10

Here's my solution. In serializers.py :

from rest_framework import serializers
from .models import Product


class sHistory(serializers.ModelSerializer):
    def __init__(self, model, *args, fields='__all__', **kwargs):
        self.Meta.model = model
        self.Meta.fields = fields
        super().__init__()

    class Meta:
        pass


class sProduct(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'

    history = serializers.SerializerMethodField()

    def get_history(self, obj):
        model = obj.history.__dict__['model']
        fields = ['history_id', ]
        serializer = sHistory(model, obj.history.all().order_by('history_date'), fields=fields, many=True)
        serializer.is_valid()
        return serializer.data

It works ! I'm quite proud about it ! any suggestions ?

cedrik
  • 541
  • 7
  • 17
3

There seems to be an even clearer and simpler way

class AnySerializer(serializers.ModelSerializer):    
    history = serializers.SerializerMethodField()

    class Meta:
        model = MyModel
        fields = (....
                  ....
                  'history',
                  )
        read_only_fields = ('history',)

    def get_history(self, obj):
        # using slicing to exclude current field values
        h = obj.history.all().values('field_name')[1:]
        return h
Jekson
  • 2,892
  • 8
  • 44
  • 79
  • 1
    In `get_history `, rather than fetch all history and then slice [which is expensive on the database], you would rather fetch just what you need: `model_history = obj.history.filter(id=obj.pk).values('field1', 'field2', 'field3', 'field4').order_by('-history_date')` – kakoma Feb 11 '21 at 13:15
1

You can create a serializer like this:

class ProductHistorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Product.history.model
        fields = '__all__'

Then in view, You can have the code below:

#...
logs = ProductHistorySerializer(Product.history.filter(price__gt=100), many=True)
return Response({'isSuccess': True, 'data': logs.data})
Ehsan Ahmadi
  • 1,382
  • 15
  • 16