Как вы знаете, django очень мощный и гибкий фреймворк. Для него создано огромное количество приложений, как и каких-то личных так и публичных. Приложения могут быть как и достаточно монотонными, так и достаточно гибкими и даже встраиваемыми в другие приложения.
Нужно отслеживать кто создал и/или кто обновил данные в какой-нибудь модели.
Решить задачу так, что бы решение можно было бы легко использовать для любых моделей.
Так как мы не знаем, изначально в какой модели будем использовать наш будующий функционал, то лучше всего добавлять его с помощью mixin’ов. Mixin — это класс, в котором содержится добавочный функционал для основного класса. В нашем случае это будут поля created_by, updated_by.
Мы создали три разных класса для всех случаев: когда нам важно только кто создал, когда нам важно только кто обновил, и когда нам важно и кто создал, и кто обновил.
Теперь что бы в нашей модели были данные о том, кто создал и кто обновил, можно задавать модели следующим образом:
Теперь есть поля в модели, но как записать туда того пользователя, который создал, обновил запись. Для этого есть несколько вариантов решений:
Как мне кажется, наиболее универсальный способ это использование сигналов, так как в остальных случаях что-либо переписывается, или решение получается не универсальное.
Сигналы очень мощный инструмент, при помощи которого django позволяет реагировать на изменение системы( в нашем случае на создание или обновление записи в базе данных). Но при использовании их возникает некоторые проблемы:
Проблема в том, что информация о пользователе, который послал запрос храниться обычно в переменной request, которая не попадает в область видимости сигнала pre_save. Для того что бы решить эту проблему, будем использовать класс Singleton, в котором будет храниться информация об пользователе. А указываться пользователи будет при помощи нашего middleware.
Так как сигналы это функции, то почему бы нам не написать декораторы, которые будут изменять поведение наших сигналов, добавляя нужный нам функционал. В данном случае это добавление пользователя в определённое поле. Так же давайте добавим проверку в какое поле надо сохранять пользователя, и если в модели задано поле ‘created_by_field/updated_by_field’, то сохранять будем его в заданное поле иначе в стандартное поле. Так как наш декоратор должен изменить только поведение функции, а не её структу, то будем использовать функцию wraps из functools.
Ну вот в принципе и всё. Теперь осталось всё собрать воедино и сделать наше встраиваемое приложение.
Спасибо за замечания и указания ошибки, особенно в орфографии и пунктуации.
На написание данной статьи меня вдохновил доклад Кори Оордта с PyCon 2011.
UPD.
Задача
Нужно отслеживать кто создал и/или кто обновил данные в какой-нибудь модели.
Идея
Решить задачу так, что бы решение можно было бы легко использовать для любых моделей.
Решение
Модели
Так как мы не знаем, изначально в какой модели будем использовать наш будующий функционал, то лучше всего добавлять его с помощью mixin’ов. Mixin — это класс, в котором содержится добавочный функционал для основного класса. В нашем случае это будут поля created_by, updated_by.
from django.db import models from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ class CreatedByMixin(models.Model): created_by = models.ForeignKey(User, verbose_name=_('Created by'), related_name='%(class)s_created_items', ) class Meta: abstract=True class UpdatedByMixin(models.Model): updated_by = models.ForeignKey(User, verbose_name=_('Updated by'), related_name='%(class)s_updated_items', ) class Meta: abstract = True class CreatedUpdatedByMixin(CreatedByMixin, UpdatedByMixin): class Meta: abstract = True
Мы создали три разных класса для всех случаев: когда нам важно только кто создал, когда нам важно только кто обновил, и когда нам важно и кто создал, и кто обновил.
Теперь что бы в нашей модели были данные о том, кто создал и кто обновил, можно задавать модели следующим образом:
- Как узнать кто создал/обновил запись?
- Сигналы пишутся для определенных моделей. Как универсально написать их или сделать их максимально удобными для использования?
from django.db import models from whovedonethis import models as who_models class TestCreatedUpdated(who_models.CreatedUpdatedByMixin): value = models.CharField('Value', max_length=255)
Сохранение записи
Теперь есть поля в модели, но как записать туда того пользователя, который создал, обновил запись. Для этого есть несколько вариантов решений:
- перегружать метод сохранение в форме для записи
- добавлять пользователя при обработке во view
- перегружать метод save() для модели
- использовать сигналы.
Как мне кажется, наиболее универсальный способ это использование сигналов, так как в остальных случаях что-либо переписывается, или решение получается не универсальное.
Сигналы очень мощный инструмент, при помощи которого django позволяет реагировать на изменение системы( в нашем случае на создание или обновление записи в базе данных). Но при использовании их возникает некоторые проблемы:
- Как узнать кто создал/обновил запись?
- Сигналы пишутся для определенных моделей. Как универсально написать их или сделать их максимально удобными для использования?
Как узнать кто создал/обновил запись?
Проблема в том, что информация о пользователе, который послал запрос храниться обычно в переменной request, которая не попадает в область видимости сигнала pre_save. Для того что бы решить эту проблему, будем использовать класс Singleton, в котором будет храниться информация об пользователе. А указываться пользователи будет при помощи нашего middleware.
class Singleton(type): ''' Singleton pattern requires for LoggedInUser class ''' def __init__(cls, name, bases, dicts): cls.instance = None def __call__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super(Singleton, cls).__call__(*args, **kwargs) return cls.instance class NotLoggedInUserException(Exception): ''' ''' def __init__(self, val='No users have been logged in'): self.val = val super(NotLoggedInUser, self).__init__() def __str__(self): return self.val class LoggedInUser(object): __metaclass__ = Singleton user = None def set_user(self, request): if request.user.is_authenticated(): self.user = request.user @property def current_user(self): ''' Return current user or raise Exception ''' if self.user is None: raise NotLoggedInUserException() return self.user @property def have_user(self): return not user is None from whovedonethis.loggedinuser import LoggedInUser class LoggedInUserMiddleware(object): ''' Insert this middleware after django.contrib.auth.middleware.AuthenticationMiddleware ''' def process_request(self, request): ''' Returned None for continue request ''' logged_in_user = LoggedInUser() logged_in_user.set_user(request) return None
Как удобнее использовать сигналы.
Так как сигналы это функции, то почему бы нам не написать декораторы, которые будут изменять поведение наших сигналов, добавляя нужный нам функционал. В данном случае это добавление пользователя в определённое поле. Так же давайте добавим проверку в какое поле надо сохранять пользователя, и если в модели задано поле ‘created_by_field/updated_by_field’, то сохранять будем его в заданное поле иначе в стандартное поле. Так как наш декоратор должен изменить только поведение функции, а не её структу, то будем использовать функцию wraps из functools.
from functools import wraps from whovedonethis.loggedinuser import LoggedInUser def add_created_user(f): ''' Decorate pre_save signal for adding created user ''' @wraps(f) def wrapper(sender, instance, **kwargs): if not instance.id: created_by_attr = getattr(instance, "created_by_field", "created_by" ) setattr(instance, created_by_attr, LoggedInUser().current_user) return f(sender, instance, **kwargs) return wrapper def add_updated_user(f): ''' Decorate pre_save signal for adding created user ''' @wraps(f) def wrapper(sender, instance, **kwargs): updated_by_attr = getattr(instance, "updated_by_field", "updated_by" ) setattr(instance, updated_by_attr, LoggedInUser().current_user) return f(sender, instance, **kwargs) return wrapper add_created_and_updated_user = lambda x: add_created_user(add_updated_user(x)) add_created_and_updated_user.__doc__ =\ ''' Decorate pre_save signal for adding created and updated user '''
Итог
Ну вот в принципе и всё. Теперь осталось всё собрать воедино и сделать наше встраиваемое приложение.
Репозитории:
- с готовой библиотекой git://github.com/Zapix/whovedonethis.git
- с примером работы этой библиотеки git://github.com/Zapix/test_whovedonethis.git
P.S.
Спасибо за замечания и указания ошибки, особенно в орфографии и пунктуации.
На написание данной статьи меня вдохновил доклад Кори Оордта с PyCon 2011.
UPD.
Пример использования сигнала с декоратором:
from django.db.models.signals import pre_save from django.dispatch import receiver from whovedonethis import decorator from testlib.models import TestCreated @receiver(pre_save, sender=TestCreated) @decorator.add_created_user def pre_save_testcreated_handler(sender, instance, **kwargs): pass
