Значится, дано следующий фрагмент 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'у, но это выходит за рамки моей сегодняшней темы ;)