["python","django"]
DRY way to add created/modified by and time

Having something like created_by created_date modified_by modified_date Would be a very common pattern for a lot of tables. 1) You can set created date automatically (but not others) in model.py with created_date = models.DateTimeField(auto_now_add=True, editable=False) 2) You could do created/modified dates (but not by/user as don't have request context) in model.py with def save(self): if self.id: self.modified_date = datetime.now() else: self.created_date = datetime.now() super(MyModel,self).save() 3) You could set the created/modifed date and by in admin.py - but this doesn't deal with non admin updates def save_model(self, request, obj, form, change): if change: obj.modified_by = request.user obj.modified_date = datetime.now() else: obj.created_by = request.user obj.created_date = datetime.now() obj.save() 4) And the final place would be in the view.py which can do all 4, but doesn't cover admin updates. So realistically have to have logic spread out, at a minimum repeated in 3 & 4 (or a method on the model called from both, which will be missed) Whats a better way? (I've been working with python/django for a couple of days so could easily be missing something obvious) Can you do someting like @login_required e.g. @audit_changes Can you get access to the request and current user in the model and centralise logic there?
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.