Django: Получить текущего пользователя в сохранении модели

Я хочу получить текущего зарегистрированного пользователя (request.user) в методе save models.py. Я хочу проверить роль пользователя и посмотреть, сможет ли он выполнять некоторые операции в зависимости от его роли.

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)

Здесь модель Tasks используется как встроенная в модели Timesheet. Я хочу проверить роль текущего пользователя, вошедшего в систему, и обновить другую модель в зависимости от роли пользователя. Здесь мне нужно request.user, чтобы проверить роль текущего пользователя. Я не использую какие-либо формы или шаблоны и полностью использую администратора Django. Есть ли способ получить request.user в методе save или проверить и обновить значения в другой модели в admin.py?

Ответ 1

Вы можете решить эту проблему под другим углом. Вместо изменения метода сохранения моделей вы должны переопределить метод AdminSites save_model. Там у вас будет объект запроса, и вы сможете получить доступ к зарегистрированным данным пользователя, как вы уже указали.

Ознакомьтесь с этой главой документации: документация Django ModelAdmin save_model

Ответ 2

Я нашел способ сделать это, но это связано с объявлением MiddleWare. Создайте файл с именем get_username.py внутри вашего приложения с этим контентом:

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

Отредактируйте свой settings.py и добавьте его в MIDDLEWARE_CLASSES:

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

Теперь, в вашем методе save(), вы можете получить текущее имя пользователя следующим образом:

from get_username import get_username

...

def save(self, *args, **kwargs):
    req = get_username()
    print "Your username is: %s" % (req.user)

Ответ 3

Я не думаю, что метод save_model override является лучшим вариантом. Предположим, например, что вы хотите сохранить информацию о пользователе или проверить модель на основе информации о пользователе, и что save() не приходит из представления или самого админита.

То, что люди спрашивают, это конструкции, подобные тем, что:

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

или

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

Лучший подход, который я нашел до сих пор:

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

Ответ 4

Решение, предложенное @nKn, является хорошей отправной точкой, но когда я попытался реализовать его сегодня, я столкнулся с двумя проблемами:

  1. В текущей версии Django промежуточное программное обеспечение, созданное как обычный объект, не работает.
  2. Юнит-тесты терпят неудачу (так как они обычно выполняются в одном потоке, поэтому ваш "запрос" может остаться между двумя последовательными тестами, если первый тест имеет HTTP-запрос, а второй - нет).

Вот мой обновленный код промежуточного программного обеспечения, который работает с Django 1.10 и не нарушает юнит-тесты:

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)

Ответ 5

Правильный способ - использовать threading.local вместо словаря с threading.current_thread, так как это приведет к утечке памяти, поскольку старые значения будут оставаться там до тех пор, пока приложение работает:

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

Если вы хотите получить доступ к пользователю вместо запроса, вы можете get_request().user или сохранить пользователя вместо запроса на __call__

Ответ 6

Вы пытались использовать следующую библиотеку:

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

Фрагмент того, как он используется на веб-сайте:

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()