22 февраля 2009 г.

Джанго дб моделс кью - ай лав ю!

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

blog comments powered by Disqus