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:
- How to find out who created / updated the record?
- Signals are written for specific models. How to universally write them or make them as comfortable as possible to use?
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:
- overload the save-to-write method
- add user when processing in view
- overload the save () method for the model
- use signals.
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?
- Signals are written for specific models. How to universally write them or make them as comfortable as possible to use?
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:
- with the finished library git: //github.com/Zapix/whovedonethis.git
- with an example of the work of this library git: //github.com/Zapix/test_whovedonethis.git
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