Automatically associating users with django models on save

I have spent way too much time trying to figure out how to automatically have created_by and modified_by be foreign keys to the current user, without mucking with every view and form. Every example I could find that told me how to do this, including django’s documentation itself, told me I had to customise every view and form, which I found to be rather un-DRY.

Thanks to stealing borrowing some code from django-audit-log (in order to be threadsafe) I figured out how to update these values on any model that has them, without any fiddling with forms or views. I might wrap this up in a little project, but for now, here’s the gist.

Note that you should set editable=False on your created_by and modified_by fields so that they will not otherwise show up in forms.

If anyone is really interested i could wrap this in a self-installable package.

13 thoughts on “Automatically associating users with django models on save

  1. I really like the spirit behind what you are doing.

    I’ve never done anything with Django Middleware before…
    What should my settings file look like?
    Where should I place this code?

  2. you put it in your settings.py – in a middleware thing like:


    MIDDLEWARE_CLASSES = (
        'django.middleware.common.CommonMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'whodid.middleware.WhodidMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        # Uncomment the next line for simple clickjacking protection:
        # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
    )
    Thanks for the typo heads up!

  3. I created a folder in my django project called ‘middleware’ where I placed ‘middleware.py’.  I then opened ‘settings.py’ and added the following line: ‘middleware.middleware.WhodidMiddleware’ at the end of MIDDLEWARE_CLASSES.

    My ‘model.py’ looks like this:
    from django.db import modelsfrom django.contrib.auth.models import Userclass DateRecord(models.Model):    created_by = models.OneToOneField(User, related_name=”%(class)s_creator”, editable=False)    date_created = models.DateTimeField(auto_now_add=True)    modified_by = models.OneToOneField(User, related_name=”%(class)s_modifier”, editable=False)    last_modified = models.DateTimeField(auto_now=True)    def __unicode__(self):        return(            ‘Created By: ‘ + self.created_by.username +            ‘ Create On: ‘ + self.date_created.isoformat() +            ‘ Modified By: ‘ + self.modified_by.username +            ‘ Last Modified: ‘ + self.last_modified.isoformat()        )

    I’m getting the following error:
    AttributeError at /admin/testdate/daterecord/add/’str’ object has no attribute ‘creation_counter’Request Method:POSTRequest URL:http://127.0.0.1:8000/admin/testdate/daterecord/add/Django Version:1.3Exception Type:AttributeError 
    Any help you can offer is appreciated.

  4. The double signal seems to be because as written the code will both enter a created_by_id and modified_by_id on each INSERT. I did the following modification to change that:

    def mark_whodid(self, user, sender, instance, **kwargs):
    if not getattr(instance, ‘created_by_id’, None):
    instance.created_by = user
    elif hasattr(instance,’modified_by_id’): <—– Change if to elif
    instance.modified_by = user

  5. You can also do in in a admin.py file if you are using the django admin.

    class ArticleAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
    obj.user = request.user
    obj.save()

  6. You are a hero.

    I was retrieving the request from inspect.stack, and having some success – it failed in various scenarios however, including when the request came via Django Rest Framework.

    I switched to your middleware and it solves pretty much all of my problems. I also needed the user in various post_save signals, for recording activity via django-activity-stream, and storing an updated_by (even though I don’t really need it), just so that I can pluck it for the activity stuff later is powerful stuff.

  7. If anyone else happens to be using this with Django Rest Framework (DRF), and is having trouble with the authentication (which is separate to regular Django auth) – here is my adjusted solution that demonstrates getting it to work with basic auth.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s