Как вы знаете, 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