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?
-
4This blog entry discusses exactly that.Farinha– Farinha2009-05-14 10:12:30 +00:00Commented May 14, 2009 at 10:12
-
This post is pretty old. Is there an easy way to achieve this now?user201788– user2017882011-03-14 03:29:54 +00:00Commented Mar 14, 2011 at 3:29
-
1@seanf What's the answer to this?user201788– user2017882011-03-17 13:10:32 +00:00Commented Mar 17, 2011 at 13:10
13 Answers
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 usingdjango-crumwhich 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.
5 Comments
./manage.py schemamigration app --initial :return _user.value AttributeError: '_thread._local' object has no attribute 'value'AttributeError in get_current_user and return None.Your model can now use the get_current_user function to access the user without having to pass the request object around.. Is this the reason why using a middleware class is better practice than using this snippet or they can be considered as equivalent solutions?created_by field. The snippet only patches the admin.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
8 Comments
pre_save of model? stackoverflow.com/questions/25305186/…request object in __init__ , it must be passed when initialising your form in your view. Ex: myform = MyForm(request.POST, request=request)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()
1 Comment
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
4 Comments
thread local object for this to work properly. In doubt just use django-crum which works and is correct: pypi.org/project/django-crumhere'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)
1 Comment
form.save() again, not object.save(), in case there are commit-dependent customizations in the save() method (which anyway will call save() on the model instance)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()
Comments
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.
Comments
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.
Comments
Based on bikeshedder's answer, I found a solution since his did not actually work for me.
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.valuesettings.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', ]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
1 Comment
AttributeError: '_thread._local' object has no attribute 'value' which makes sense as the request middle ware is not loaded during migration. Or did I missing something here? Could you please clarify?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)
Comments
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
Comments
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)