📜 ⬆️ ⬇️

Who created, who updated or write their embedded application on django

As you know, django is a very powerful and flexible framework. For him, created a huge number of applications, as well as some personal and public. Applications can be quite monotonous as well as flexible enough and even embedded in other applications.

Task


It is necessary to keep track of who created and / or who updated the data in any model.

Idea


Solve the problem so that the solution could be easily used for any models.

Decision


Models

Since we do not know, initially in what model we will use our future functionality, it is best to add it with the help of mixins. Mixin is a class that contains additional functionality for the main class. In our case, these will be the created_by, updated_by fields.
')
from django.db import models from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ class CreatedByMixin(models.Model): created_by = models.ForeignKey(User, verbose_name=_('Created by'), related_name='%(class)s_created_items', ) class Meta: abstract=True class UpdatedByMixin(models.Model): updated_by = models.ForeignKey(User, verbose_name=_('Updated by'), related_name='%(class)s_updated_items', ) class Meta: abstract = True class CreatedUpdatedByMixin(CreatedByMixin, UpdatedByMixin): class Meta: abstract = True 


We have created three different classes for all cases: when only who created for us is important, when only who updated for us is important, and when it is important for us who created and who updated.
Now, in our model there would be data about who created and who updated, you can set the models as follows:



 from django.db import models from whovedonethis import models as who_models class TestCreatedUpdated(who_models.CreatedUpdatedByMixin): value = models.CharField('Value', max_length=255) 


Saving Record

Now there are fields in the model, but how to write there the user who created, updated the record. There are several solutions for this:



As it seems to me, the most universal way is the use of signals, since in other cases something is being rewritten, or the solution is not universal.

Signals are a very powerful tool by which django allows you to react to a change in the system (in our case, to create or update a record in the database). But when using them, some problems arise:


How to find out who created / updated the record?


The problem is that the information about the user who sent the request is usually stored in the variable request, which does not fall within the scope of the pre_save signal. In order to solve this problem, we will use the class Singleton , in which information about the user will be stored. And users will be indicated using our middleware .

 class Singleton(type): ''' Singleton pattern requires for LoggedInUser class ''' def __init__(cls, name, bases, dicts): cls.instance = None def __call__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super(Singleton, cls).__call__(*args, **kwargs) return cls.instance class NotLoggedInUserException(Exception): ''' ''' def __init__(self, val='No users have been logged in'): self.val = val super(NotLoggedInUser, self).__init__() def __str__(self): return self.val class LoggedInUser(object): __metaclass__ = Singleton user = None def set_user(self, request): if request.user.is_authenticated(): self.user = request.user @property def current_user(self): ''' Return current user or raise Exception ''' if self.user is None: raise NotLoggedInUserException() return self.user @property def have_user(self): return not user is None from whovedonethis.loggedinuser import LoggedInUser class LoggedInUserMiddleware(object): ''' Insert this middleware after django.contrib.auth.middleware.AuthenticationMiddleware ''' def process_request(self, request): ''' Returned None for continue request ''' logged_in_user = LoggedInUser() logged_in_user.set_user(request) return None 


How convenient to use signals.

Since signals are functions, why don't we write decorators that will change the behavior of our signals by adding the functionality we need. In this case, adding a user to a specific field. Also, let's add a check to which field the user should be saved, and if the model has the 'created_by_field / updated_by_field' field set, then we will save it to the specified field, otherwise to the standard field. Since our decorator has to change only the behavior of the function, and not its structure, we will use the wraps function of functools.

 from functools import wraps from whovedonethis.loggedinuser import LoggedInUser def add_created_user(f): ''' Decorate pre_save signal for adding created user ''' @wraps(f) def wrapper(sender, instance, **kwargs): if not instance.id: created_by_attr = getattr(instance, "created_by_field", "created_by" ) setattr(instance, created_by_attr, LoggedInUser().current_user) return f(sender, instance, **kwargs) return wrapper def add_updated_user(f): ''' Decorate pre_save signal for adding created user ''' @wraps(f) def wrapper(sender, instance, **kwargs): updated_by_attr = getattr(instance, "updated_by_field", "updated_by" ) setattr(instance, updated_by_attr, LoggedInUser().current_user) return f(sender, instance, **kwargs) return wrapper add_created_and_updated_user = lambda x: add_created_user(add_updated_user(x)) add_created_and_updated_user.__doc__ =\ ''' Decorate pre_save signal for adding created and updated user ''' 


Total


Well, that's basically all. Now it remains to put everything together and make our embedded application.

Repositories:


PS

Thank you for your comments and mistakes, especially in spelling and punctuation.

I was inspired to write this article by Cory Oordt ’s report from PyCon 2011 .

UPD.
An example of using a signal with a decorator:

 from django.db.models.signals import pre_save from django.dispatch import receiver from whovedonethis import decorator from testlib.models import TestCreated @receiver(pre_save, sender=TestCreated) @decorator.add_created_user def pre_save_testcreated_handler(sender, instance, **kwargs): pass 

Source: https://habr.com/ru/post/130566/


All Articles