Changing class attributes in decorator

Question:

I’m using django as my framework for some web application.

I implemented a modelview of my own because I have a few querysets and seriazliers in the same view.

For this use, I needed to implement all of the CRUD functions myself:

class Models1AndModel2View(mixins.CreateModelMixin,
                           mixins.RetrieveModelMixin, 
                           mixins.UpdateModelMixin,
                           mixins.DestroyModelMixin,
                           mixins.ListModelMixin,
                           GenericViewSet):
    model1 = Model1.object.all()
    model1_serializer_class = Model1Seriazlizer

    model2 = Model2.object.all()
    model2_serializer_class = Model2Seriazlizer

    def refresh_querysets(func):
        def inner(self, *args, **kwargs):
            value = func(self, *args, **kwargs)
            self.model1 = Model1.object.all()
            self.model2 = Model2.object.all()
            return value
        return inner
 
   @refresh_querysets
   def list(self, request, *args, **kwargs):
       ...

   @refresh_querysets
   def retrieve(self, pk, request, *args, **kwargs):
       ...

   @refresh_querysets
   def update(self, pk, request, *args, **kwargs):
       ...

   @refresh_querysets
   def delete(self, pk, request, *args, **kwargs):
       ...

   @refresh_querysets
   def create(self, request, *args, **kwargs):
       ...


Notice that I’m calling the decorator’s function before the objects refresh.
I noticed that every attribute set after the function calls is not actually set.

For example – some test of mine:

  1. list all the models – 2 models instances
  2. delete one of them
  3. list them again – still 2 models instances (in model1 + model2)
  4. if you query the model1 and model2 you can see that one of the instances is deleted as expected, but the model1 was not refreshed.

I changed the order on the inner function of the decorator, and it worked as expected.

    def refresh_querysets(func):
        def inner(self, *args, **kwargs):
            self.model1 = Model1.object.all()
            self.model2 = Model2.object.all()
            return func(self, *args, **kwargs)
        return inner
Asked By: איתי גיל

||

Answers:

I believe your issue is because the view is temporary.

You save self.model1 on the view which is destroyed and recreated per request.

There is no point updating the self if the object will be destroyed.

Also, you should remember that model1 as set in the class variable is initialized on class creation only, and not on instance creation, meaning it will always contain the original data from when you imported that respective module.

There are 3 options to solve it:

  1. Change the order of the function decorator as you’ve done, setting self.model1 before running the function, or pass it as a parameter.

  2. Change model1 on __init__, and so the changes will apply on instance creation.

  3. While frowned upon, the class attribute model1 is right now equivalent to a "class global" (and has almost all of a global downsides). You can simply change it like so:

    self.__class__.model1 = Model1.object.all()
    

    Keep in mind I do not believe this class attribute should even exist. Should it be immutable, setting it on the instance creation makes more sense.

Either case, I’m not sure if you wish to dump the entire object / DB at every request. I do not know if django smartly caches object.all() (and updates the cache upon modification), but if it doesn’t it’ll be a major slowdown.

Answered By: Bharel
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.