[Django]-DRY way to add created/modified by and time

116👍

The create/modification dates can be handled by Django now, so they can be implemented like:

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    modified_date = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

By adding this to a abstract model base class, it can be easily added to all models of the application, e.g.:

class Pizza(BaseModel):
    ....

class Topping(BaseModel):
    ...

Storing the user is harder, since the request.user is not available. As SeanOC mentioned, this is a separation of concerns between the web request, and model layer. Either you pass this field all the time, or store request.user in a threadlocal. Django CMS does this for their permission system.

from django.utils.deprecation import MiddlewareMixin

class CurrentUserMiddleware(MiddlewareMixin):
    def process_request(self, request):
        set_current_user(getattr(request, 'user', None))

And the user tracking happens elsewhere:

from threading import local
_thread_locals = local()

def set_current_user(user):
    _thread_locals.user=user

def get_current_user():
    return getattr(_thread_locals, 'user', None)

For non-web environments (e.g. management commands), you’d have to call set_current_user at the start of the script.

👤vdboor

12👍

For timestamped models you probably want to look at django-model-utils or django-extensions. They each include abstract base classes which automatically handle of a created and last modified timestamp. You can either use these tools directly or look at how they solved the problem and come up with your own solution.

As for your other questions:

Can you do someting like @login_required e.g. @audit_changes

Potentially yes but you’d have to be very careful to keep things thread-safe. What you potentially could do is in your @audit_changes decorator, set a flag to enable auditing in a threadlocal. Then either in the save method of your models or in a signal handler, you could check for your audit flag and record your audit info if the flag had been set.

Can you get access to the request and current user in the model and centralise logic there?

Yes, but you’ll be making a tradeoff. As you’ve touched on a little bit, there is a very clear and intentional separation of concerns between Django’s ORM and it’s request/authentication handling bits. There are two ways ways to get information from the request (the current user) to the ORM (your model(s)). You can manually manage updating the creator/modifier information on your objects or you can set up a mechanism to automatically handle that maintenance work. If you take the manual approach (passing the information through method calls from the request in the view to the ORM), it will be more code to maintain/test but you keep the separation of concerns in place. With the manual approach, you will be in much better shape if you ever have to work with your objects outside of the request/response cycle (e.g. cron-scripts, delayed tasks, interactive shell). If you are ok with breaking down that separation of concerns, then you could setup something where you set a thread local with the current user in a middleware and then look at that thread local in the save method of your model. Converse to the manual approach, you’ll have less code to deal with but you’ll have a much harder time if you ever want to work with your objects outside of the request/response cycle. Additionally, you will have to be very careful to keep everything thread-safe with the more automated approach.

👤SeanOC

-2👍

Can you import the User model object and call get_current()?

Also, I think you can call views in the admin.py.

Leave a comment