61

I want to get the currently logged in user (request.user) in the save method of models.py. I want to check the role of the user and see if it can perform some operations based on his role.

models.py:

class TimeSheet(models.Model):
    check_in_time = models.TimeField()
    check_out_time = models.TimeField()

class Tasks(models.Model):
    time_sheet = models.ForeignKey(TimeSheet)
    project = models.ForeignKey(Project)
    start_time = models.TimeField()
    end_time = models.TimeField()

    def save(self, *args,**kwargs):
        project = SpentTime.objects.get(project__project__id = self.project.id)
        start = datetime.datetime.strptime(str(self.start_time), '%H:%M:%S')
        end = datetime.datetime.strptime(str(self.end_time), '%H:%M:%S')
        time = float("{0:.2f}".format((end - start).seconds/3600.0))

        if common.isDesigner(request.user):
            SpentTime.objects.filter(project__project__id = self.project.id).update(design = float(project.design) + time)

        if common.isDeveloper(request.user):
            SpentTime.objects.filter(project__project__id = self.project.id).update(develop = float(project.develop) + time)

        super(Tasks, self).save(*args, **kwargs)

Here the Tasks model is being used as inline in Timesheet model. I want to check the role of the currently logged in user and update another model based on the user's role. Here I need request.user to check the role of the current user. I am not using any forms or templates and completely making use of Django admin. Is there any method to get request.user in the save method or to check and update the values in another model in admin.py?

Francisco
  • 10,918
  • 6
  • 34
  • 45
arulmr
  • 8,620
  • 9
  • 54
  • 69

6 Answers6

44

You can tackle this problem from another angle. Instead of changing the models save method you should override the AdminSites save_model method. There you'll have the request object and can access the logged in user data as you already pointed out.

Have a look at this chapter of the docs: Django ModelAdmin documentation save_model

Francisco
  • 10,918
  • 6
  • 34
  • 45
Jens
  • 20,533
  • 11
  • 60
  • 86
  • 1
    Thanks Jens. I tried that. Since I'm using inlines here I couldn't complete it with admin save_model. But I have completed it by adding a created_by user foreignkey field. – arulmr Jun 12 '12 at 10:10
  • 5
    If you're using inlines, then you use `save_formset` https://docs.djangoproject.com/en/1.4/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_formset – Chris Pratt Jun 12 '12 at 15:05
  • 1
    Nice one @ChrisPratt. Did'nt know this method. A few lines below there is also a method `save_related`. Maybe that'll do the trick too: https://docs.djangoproject.com/en/1.4/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related – Jens Jun 13 '12 at 11:57
  • 2
    Right. Actually, if you're using Django 1.4, `save_related` is the way to go. Unfortunately I'm still stuck in Django 1.3.1 land, and `save_related` didn't exist yet. But, `save_formset` can still be used to accomplish the task, either way. – Chris Pratt Jun 13 '12 at 14:42
  • Note that for django 1.10, this is https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model – Mike 'Pomax' Kamermans Dec 19 '16 at 22:32
  • This only seems to work when creating an object from admin. Any way to make it work from any view? – Loner May 07 '20 at 19:28
  • Hey, if I override that and I have a custom save view, I should implement that logic there seperately too right? – MareksNo Oct 04 '20 at 17:00
  • [django-crum](https://django-crum.readthedocs.io/en/latest/) is a middleware exactly for this. – Torsten Nov 04 '21 at 23:37
27

I found a way to do that, it involves declaring a MiddleWare, though. Create a file called get_username.py inside your app, with this content:

from threading import current_thread

_requests = {}

def get_username():
    t = current_thread()
    if t not in _requests:
         return None
    return _requests[t]

class RequestMiddleware(object):
    def process_request(self, request):
        _requests[current_thread()] = request

Edit your settings.py and add it to the MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = (
    ...
    'yourapp.get_username.RequestMiddleware',
)

Now, in your save() method, you can get the current username like this:

from get_username import get_username

...

def save(self, *args, **kwargs):
    req = get_username()
    print "Your username is: %s" % (req.user)
nKn
  • 13,691
  • 9
  • 45
  • 62
  • 7
    It may create memory issue over the period of time if threads are being killed and being created again and again. You should actually add current_thread().user = user and should access the property in that manner. Once the thread will be killed, memory will be released as well. – Hrishabh Gupta Aug 22 '17 at 12:22
  • Hrishabh Gupta => if you have a better and complete answer then why don't you write it in full? This empty comment didn't help me at all, it only caused more doubts. – Marcos Paolo Nov 06 '22 at 18:04
  • Error in Django 4: RequestMiddleware() takes no arguments. Edit: Adding MiddlewareMixin fixes this – Michael Jan 30 '23 at 18:16
11

The solution proposed by @nKn is good starting point, but when I tried to implemented today, I faced two issues:

  1. In the current Django version middleware created as plain object doesn't work.
  2. Unittests are failing (since they usually run in single thread, so your 'request' will can be sticked between two consequent tests if the first test has HTTP-request and the second hasn't).

Here is my updated middleware code, which works with Django 1.10 and doesn't break unittests:

from threading import current_thread

from django.utils.deprecation import MiddlewareMixin


_requests = {}


def current_request():
    return _requests.get(current_thread().ident, None)


class RequestMiddleware(MiddlewareMixin):

    def process_request(self, request):
        _requests[current_thread().ident] = request

    def process_response(self, request, response):
        # when response is ready, request should be flushed
        _requests.pop(current_thread().ident, None)
        return response

    def process_exception(self, request, exception):
        # if an exception has happened, request should be flushed too
         _requests.pop(current_thread().ident, None)
Ihor Pomaranskyy
  • 5,437
  • 34
  • 37
  • Do you have an example for your test? In my test always comes AttributeError: 'NoneType' object has no attribute 'user'? Thanks! – Sascha Rau Sep 04 '17 at 16:00
  • @Sascha Rau unfortunately, only tests from my commercial code, with some cohesion to my project structure. The behavior you describe is totally OK if you try to reach request in the code where the request wasn't actually made. To be safe, try somethins like ```request = current_request() ; if request is not None: ...``` – Ihor Pomaranskyy Sep 05 '17 at 19:35
8

The correct way is to use threading.local, instead of using a dictionary with threading.current_thread, since it will lead to a memory leak, since the old values will stay there for as long the application is running:

import threading

request_local = threading.local()

def get_request():
    return getattr(request_local, 'request', None)

class RequestMiddleware():
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        request_local.request = request
        return self.get_response(request)

    def process_exception(self, request, exception):
        request_local.request = None

    def process_template_response(self, request, response):
        request_local.request = None
        return response

If you want to access the user instead of the request, you can do get_request().user, or save the user instead of the request on __call__

Francisco
  • 10,918
  • 6
  • 34
  • 45
  • Shall this middleware be as soon or as last as possible in the list? Or it does not matter at all? – karlosss Apr 09 '19 at 17:11
  • @karlosss it doesn't really matters, since you already have access to the `request` object inside of middlewares, so you won't use `get_request` there, and it doesn't modifies requests – Francisco Apr 10 '19 at 19:00
7

I don't think that save_model method override is the best option. Imagine, for instance, that you want to save the user info or validate the model based on user info and that save() does not come from a view or the adminsite itself.

What people are asking are constructions like those one:

def save(..)
    self.user = current_user()

or

def save(..)
    user = current_user()
    if user.group == 'XPTO':
        error('This user cannot edit this record')

The best approach I found so far is:

https://bitbucket.org/q/django-current-user/overview

Josir
  • 1,282
  • 22
  • 35
4

Have you tried using the following library:

https://pypi.org/project/django-currentuser/

Snippet of how it is used from the website:

from django_currentuser.middleware import (get_current_user, get_current_authenticated_user)
# As model field:
from django_currentuser.db.models import CurrentUserField
class Foo(models.Model):
    created_by = CurrentUserField()
Mihir
  • 59
  • 1
  • 6