Search code examples
djangodjango-rest-frameworktemporal-database

Temporal Database with Django Rest Framework using perform_update


We are trying to implement a temporal database so that we are able to track changes made

All our models have the following fields

vt = models.DateTimeField(db_column='VT', default=datetime(3000, 12, 31, 23, 00, 00, 000000))  # Value To
vflag = models.IntegerField(db_column='VFlag', default=1)  # Version Flag 1 = Current, 0 = Old   

When using the Django rest framework I’ve tried to modify the perform_update in my viewset to duplicate the existing record, make the updates and then set temporal fields appropriately.

It works when I have 1 record and the first update

However once I try and make a second update it fails and create a duplicate of the changes and overrides the very first record.

Original Record

Currency = AUD, VFlag = 1, VT = time1

Perform update - success

Currency = USD, VFlag = 1, VT = time2

Currency = AUD, VFlag = 0, VT = time1

Next perform update currently produces - fails

Currency = GBP, VFlag = 1, VT = time3

Currency = GBP, VFlag = 1, VT = time3

Currency = USD , VFlag = 0, VF = time2

Expected update output

Currency = GBP, VFlag = 1, VT = time3

Currency = USD, VFlag = 0, VT = time2

Currency = AUD, VFlag = 0, VT = time1

Is a temporal database a possibility in django rest?

Is anybody able to please point in the right direction

Below is my view set code

class OptionMasterViewSet(viewsets.ModelViewSet):
serializer_class = OptionMasterSerializer
paginate_by = None
queryset = OptionMaster.objects.filter(vflag=1)
# queryset = OptionMaster.objects.all()

def get_queryset(self):
    queryset = OptionMaster.objects.filter(vflag=1)
    contractcode = self.request.query_params.get('contractcode', None)
    if contractcode is not None:
        queryset = queryset.filter(contractcode=contractcode, vflag=1)
    return queryset

def perform_update(self, serializer):

    changes = serializer.validated_data
    original_object = serializer.instance

    vt = datetime.now()

    changes['vf'] = vt

    #Build the old record
    old_record = {}
    for field in original_object._meta.get_fields():           
        old_record[field.name] = getattr(original_object, field.name)

    old_record['vflag'] = 0                
    old_record['vt'] = vt

    old_record = OrderedDict(old_record)

    #Save the new rrecord
    serializer.save()

    #Create the old record
    obj = OptionMaster.objects.create(**old_record)

    return serializer

my serialize

class OptionMasterSerializer(TemporalModelSerializer):

class Meta:
    model = OptionMaster
    fields = '__all__'

potential temporal serializer

class TemporalHyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
vt = serializers.HiddenField(default=datetime(3000, 12, 31, 00, 00, 00, 000000))
vflag = serializers.HiddenField(default=1)

class TemporalModelSerializer(serializers.ModelSerializer):
vt = serializers.HiddenField(default=datetime(3000, 12, 31, 23, 00, 00, 000000))
vflag = serializers.HiddenField(default=1)

class TemporalModel(models.Model):
vt = models.DateTimeField(db_column='VT')  # Field name made lowercase.
vflag = models.IntegerField(db_column='VFlag')  # Field name made lowercase..
class Meta:
        abstract = True

Solution

  • The solution to my problem was to FILTER and UPDATE using a dictionary data.

     self.Meta.model.objects.filter(pk=instance.pk, vflag=1).update(**new_record)
    

    This was my final working TemporalModelSerializer below

        class TemporalModelSerializer(serializers.ModelSerializer):
            vf = serializers.HiddenField(default=timezone.now())
            vt = serializers.HiddenField(default=datetime(3000, 12, 31, 23, 00, 00, 000000))
            vflag = serializers.HiddenField(default=1)
            vu = serializers.HiddenField(default='Theodore')
    
            class Meta:
                model = None
                fields = '__all__'
    
            def update(self, instance, validated_data):
    
                time_now = timezone.now()
    
                old_record = {}
                new_record = {}
    
                for (key, value) in validated_data.items():
                    old_record[key] = getattr(instance, key)
                    new_record[key] = validated_data[key]
                    setattr(instance, key, value)
    
                old_record['vt'] = time_now
                old_record['vflag'] = 0
                new_record['vf'] = time_now
    
                self.delete_me(old_record)
    
                self.Meta.model.objects.filter(pk=instance.pk, vflag=1).update(**new_record)
    
                return instance
    
            def delete_me(self, old_record):
                obj = self.Meta.model.objects.create(**old_record)