Значится, дано следующий фрагмент models.py
:
class Attribute(models.Model): ATTRIBUTE_PERMISSIONS = ( ('public', _('Public')), ('semi-private', _('Semi-Private')), ('private', _('Private')) ) property_ = models.ForeignKey('Property') type_ = models.ForeignKey('AttributeType') value = models.CharField(max_length=255) permission = models.PositiveIntegerField(choices=ATTRIBUTE_PERMISSIONS, default=ATTRIBUTE_PUBLIC) granted_users = models.ManyToManyField('auth.User', blank=True, null=True) class AttributeType(models.Model): slug = models.SlugField(max_length=32) """ Other implementation of AttributeType class was stripped """ class Property(models.Model): owner = models.ForeignKey('auth.User') """ Other implementation of Property class was stripped """
Теперь, внимание, задание: надо найти все объекты Property
, у которых:
- значение публичного аттрибута
attr1
равно"Public"
; - значение полу-приватного аттрибута
attr2
равно"Semi-Private"
; - значение приватного аттрибута
attr2
равно"Private"
.
Казалось бы, как сложно и тут будет много запросо к базе данных, ведь это ж Django
, это ж никому не нужный ORM. Вот если бы настрочить большой и сложный SQL, эх. Но, вот именно тут на арену и выходит django.db.models.Q
.
Судите сами,
- для того чтобы отфильтровать проперти по значению публичного аттрибута, нам надо просто отфильтровать проперти по
.filter(attribute_type___slug='attr1', attribute__permission='public', attribute__value='Public')
- для фильтра по значению полу-приватного аттрибута, надо дополнительно ввести фильтр проверки на вхождение пользователя в список разрешенных пользователей или этот пользователь является автором проперти:
.filter(attribute_type___slug='attr2', attribute__permission='semi-private', attribute__value='Semi-Private', attribute__granted_users=USER).filter(attribute_type___slug='attr2', attribute__permission='semi-private', attribute__value='Semi-Private', owner=USER)
- для фильтра же по значению приватного аттрибута, мы просто оставляем вторую часть предыдущего фильтра:
.filter(attribute_type___slug='attr2', attribute__permission='private', attribute__value='Private', owner=USER)
Как-то слишком монструозно выходит, просто фильтровать. Давайте, лучше создадим хелпер, который будет учитывать особенности всех этих фильтров, проще говоря склеим все фильтры воедино при помощи Q
. А затем просто будем фильтровать проперти по названию аттрибута и его значению:
def filter_by_attribute(queryset, **kwargs): # Public attributes filter basequery = Q(attribute__permission='public') if 'user' in kwargs: user = kwargs.pop('user') # Semi-private attributes filter basequery |= Q(attribute__permission='semi-private') & \ Q(Q(attribute__granted_users=user) | \ Q(owner=user)) # Private attributes filter basequery |= Q(attribute__permission='private') & Q(owner=user) for slug, value in kwargs.items(): query = basequery & \ Q(attribute__type___slug=slug) & \ Q(attribute__value=value) queryset = queryset.filter(query) return queryset queryset = Property.objects.all() queryset = filter_by_attribute(queryset, attr1='Public', attr2='Semi-Private', user=USER) queryset = filter_by_attribute(queryset, attr2='Private', user=USER)
Вот и все, мы отфильтровали все проперти по необходимым значениям аттрибутов, в то же время сохранив для этих аттрибутов сменные права доступа. И да, выборку всех этих проперти выполнил один большой запрос, сгенерированный джанговским ORM.
зы. Я знаю, что filter_by_attribute
лучше было бы прицепить к кастомному PropertyManager
'у, но это выходит за рамки моей сегодняшней темы ;)