Django: Populate user ID when saving a model

DjangoDjango ModelsDjango Authentication

Django Problem Overview


I have a model with a created_by field that is linked to the standard Django User model. I need to automatically populate this with the ID of the current User when the model is saved. I can't do this at the Admin layer, as most parts of the site will not use the built-in Admin. Can anyone advise on how I should go about this?

Django Solutions


Solution 1 - Django

> UPDATE 2020-01-02
> ⚠ The following answer was never updated to the latest Python and Django versions. Since writing this a few years ago packages have been released to solve this problem. Nowadays I highly recommend using django-crum which implements the same technique but has tests and is updated regularly: https://pypi.org/project/django-crum/

The least obstrusive way is to use a CurrentUserMiddleware to store the current user in a thread local object:

current_user.py

from threading import local

_user = local()

class CurrentUserMiddleware(object):
    def process_request(self, request):
        _user.value = request.user

def get_current_user():
    return _user.value

Now you only need to add this middleware to your MIDDLEWARE_CLASSES after the authentication middleware.

##settings.py

MIDDLEWARE_CLASSES = (
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
    'current_user.CurrentUserMiddleware',
    ...
)

Your model can now use the get_current_user function to access the user without having to pass the request object around.

##models.py

from django.db import models
from current_user import get_current_user

class MyModel(models.Model):
    created_by = models.ForeignKey('auth.User', default=get_current_user)

##Hint:

If you are using Django CMS you do not even need to define your own CurrentUserMiddleware but can use cms.middleware.user.CurrentUserMiddleware and the cms.utils.permissions.get_current_user function to retrieve the current user.

Solution 2 - Django

If you want something that will work both in the admin and elsewhere, you should use a custom modelform. The basic idea is to override the __init__ method to take an extra parameter - request - and store it as an attribute of the form, then also override the save method to set the user id before saving to the database.

class MyModelForm(forms.ModelForm):

   def __init__(self, *args, **kwargs):
       self.request = kwargs.pop('request', None)
       return super(MyModelForm, self).__init__(*args, **kwargs)


   def save(self, *args, **kwargs):
       kwargs['commit']=False
       obj = super(MyModelForm, self).save(*args, **kwargs)
       if self.request:
           obj.user = self.request.user
       obj.save()
       return obj
       

Solution 3 - Django

Daniel's answer won't work directly for the admin because you need to pass in the request object. You might be able to do this by overriding the get_form method in your ModelAdmin class but it's probably easier to stay away from the form customisation and just override save_model in your ModelAdmin.

def save_model(self, request, obj, form, change):
    """When creating a new object, set the creator field.
    """
    if not change:
        obj.creator = request.user
    obj.save()

Solution 4 - Django

This whole approach bugged the heck out of me. I wanted to say it exactly once, so I implemented it in middleware. Just add WhodidMiddleware after your authentication middleware.

If your created_by & modified_by fields are set to editable = False then you will not have to change any of your forms at all.

"""Add user created_by and modified_by foreign key refs to any model automatically.
   Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry

class WhodidMiddleware(object):
    def process_request(self, request):
        if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
            if hasattr(request, 'user') and request.user.is_authenticated():
                user = request.user
            else:
                user = None

            mark_whodid = curry(self.mark_whodid, user)
            signals.pre_save.connect(mark_whodid,  dispatch_uid = (self.__class__, request,), weak = False)

    def process_response(self, request, response):
        signals.pre_save.disconnect(dispatch_uid =  (self.__class__, request,))
        return response

    def mark_whodid(self, user, sender, instance, **kwargs):
        if 'created_by' in instance._meta.fields and not instance.created_by:
            instance.created_by = user
        if 'modified_by' in instance._meta.fields:
            instance.modified_by = user

Solution 5 - Django

here's how I do it with generic views:

class MyView(CreateView):
    model = MyModel
    
    def form_valid(self, form):
        object = form.save(commit=False)
        object.owner = self.request.user
        object.save()
        return super(MyView, self).form_valid(form)

Solution 6 - Django

If you are using class based views Daniel's answer needs more. Add the following to ensure that the request object is available for us in your ModelForm object

class BaseCreateView(CreateView):
    def get_form_kwargs(self):
        """
        Returns the keyword arguments for instanciating the form.
        """
        kwargs = {'initial': self.get_initial()}
        if self.request.method in ('POST', 'PUT'):
            kwargs.update({
                'data': self.request.POST,
                'files': self.request.FILES,
                'request': self.request})
        return kwargs

Also, as already mentioned, you need to return the obj at the end of ModelForm.save()

Solution 7 - Django

what is the problem with using something like:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        exclude = ['created_by']

    def save(self, user):
        obj = super().save(commit = False)
        obj.created_by = user
        obj.save()
        return obj

Now call it like myform.save(request.user) in the views.

here is ModelForm's save function, which has only a commit parameter.

Solution 8 - Django

Based on bikeshedder's answer, I found a solution since his did not actually work for me.

  1. app/middleware/current_user.py

     from threading import local
    
     _user = local()
    
     class CurrentUserMiddleware(object):
    
     def __init__(self, get_response):
     	self.get_response = get_response
    
     def __call__(self, request):
     	_user.value = request.user
     	return self.get_response(request)
     	
     def get_current_user():
     	return _user.value
     
    
  2. settings.py

     MIDDLEWARE = [
     	'django.middleware.security.SecurityMiddleware',
     	'django.contrib.sessions.middleware.SessionMiddleware',
     	'django.middleware.common.CommonMiddleware',
     	'django.middleware.csrf.CsrfViewMiddleware',
     	'django.contrib.auth.middleware.AuthenticationMiddleware',
     	'django.contrib.messages.middleware.MessageMiddleware',
     	'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
         'common.middleware.current_user.CurrentUserMiddleware',
     ]
    
  3. model.py

     from common.middleware import current_user
     created_by = models.ForeignKey(User, blank=False, related_name='created_by', editable=False, default=current_user.get_current_user)
    

I'm using python 3.5 and django 1.11.3

Solution 9 - Django

For future references, best solution I found about this subject:

https://pypi.python.org/pypi/django-crum/0.6.1

This library consist of some middleware. After setting up this libary, simply override the save method of model and do the following,

from crum import get_current_user        


def save(self, *args, **kwargs):
    user = get_current_user()
    if not self.pk:
	    self.created_by = user
    else:
	    self.changed_by = user
    super(Foomodel, self).save(*args, **kwargs)

if you create and abstract model and inherit from it for all your model, you get your auto populated created_by and changed_by fields.

Solution 10 - Django

From the Django documentation Models and request.user:

> " To track the user that created an object using a CreateView, you can > use a custom ModelForm. In the view, ensure that you > don’t include [the user field] in the list of fields to edit, and override > form_valid() to add the user:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author

class AuthorCreate(LoginRequiredMixin, CreateView):
    model = Author
    fields = ['name']

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)

Solution 11 - Django

The 'save' method from forms.ModelForm returns the saved instanced.

You should add one last line to MyModelForm:
...
return obj

This change is necessary if you are using create_object or update_object generic views.
They use the saved object to do the redirect.

Solution 12 - Django

I don't believe Daniel's answer is the best there is since it changes the default behaviour of a model form by always saving the object.

The code I would use:

forms.py

from django import forms

class MyModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        super(MyModelForm, self).__init__(*args, **kwargs)

    def save(self, commit=True):
        obj = super(MyModelForm, self).save(commit=False)

        if obj.created_by_id is None:
            obj.created_by = self.user

        if commit:
            obj.save()
        return obj

Solution 13 - Django

Note sure if you were looking for this, but adding the following

user = models.ForeignKey('auth.User')

to a model will work to add the user id to the model.

In the following, each hierarchy belongs to a user.

class Hierarchy(models.Model):
    user = models.ForeignKey('auth.User')
    name = models.CharField(max_length=200)
    desc = models.CharField(max_length=1500)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionseanfView Question on Stackoverflow
Solution 1 - DjangobikeshedderView Answer on Stackoverflow
Solution 2 - DjangoDaniel RosemanView Answer on Stackoverflow
Solution 3 - DjangoTim FletcherView Answer on Stackoverflow
Solution 4 - DjangomindlaceView Answer on Stackoverflow
Solution 5 - DjangoyretutaView Answer on Stackoverflow
Solution 6 - DjangoMonsterView Answer on Stackoverflow
Solution 7 - DjangosuhailvsView Answer on Stackoverflow
Solution 8 - DjangoleilaView Answer on Stackoverflow
Solution 9 - DjangodurdenkView Answer on Stackoverflow
Solution 10 - DjangoMartin CRView Answer on Stackoverflow
Solution 11 - DjangoFlorentinView Answer on Stackoverflow
Solution 12 - DjangojgadelangeView Answer on Stackoverflow
Solution 13 - DjangoDeven KalraView Answer on Stackoverflow