Расширяем Models, Managers и QuerySets в Django

оригинал

В последнем своём проекте я исследовал Django. мне нравиться расширять Models методами, которые можно бы было вызывать из контроллера в Django, как я привык делать в своем фреймворке на php. В php это делалось просто - вы могли просто писать несколько новых функций в классе. В Django это чуть-чуть сложнее. Итак, что я изучил.

Модели

Во-первых, модели. Вы можете писать новые методы в определении класса модели, так же как и в php. Но есть и различия. В php, например, я могу написать метод для управления объектом, или моделью. Например, $ball->explode(). И могу написать статическую функцию для управления таблицей модели, например, Ball::get_exploded().

Методы в Django модели оперируют только с созданным объктом. Например:

  1. class Ball(models.Model):
  2. def explode(self):
  3. self.exploded = true;
  4. self.lifetime = datetime.now() - self.created_at

Мы будем использовать эту функцию в других местах, чтобы знать как долго накачивали шар, перед тем как он взорвался.

  1. ball = Ball.objects.get(id=1)
  2. ball.explode()
  3. ball.save()

Manager

С помощью Manager у нас есть доступ к таблице. Это больше похоже на статические методы, которые мы использовали в php. Свойство по умолчанию управляет менеджером объекта.

Manager`ы предаставляют неплохой набор методов для выборки объектов. Тем не менее, я заметил, что использую одни и те же комбинации методов для получения объектов. Естественно, переместить эти комбинации в свои собственные методы - вполне согласуется с принципом DRY. Покажу на примере:

  1. class BallManager(models.Manager):
  2. def get_exploded(self):
  3. return self.filter(exploded=True)
  4. class Ball(models.Model):
  5. objects = BallManager()

Теперь создадим новый метод для получения всех взорванных шаров из базы.

  1. Ball.objects.get_exploded()

Это в высшей степени полезно, когда вы начинаете делать сложные запросы в различных представлениях. Давайте покажу реальный пример из моего проекта:

  1. def due_this_week(self):
  2. return self.extra(where=["due_date > now() - interval '1 day'", "due_date < now() + interval '7 days'", "not(due_date isnull)"])

Это гарантирует, что если я решу что мой запрос не верный, или захочу расширить выборку, будет достаточно изменить метод. Думаю преимущества очевидны. Manager методы обычно возвращают набор QuerySet, так что давайте посмотрим, чем может быть полезно расширение QuerySet.

QuerySet

Добавив эти методы, их можно вызвать из свойства Manager, но мы можем захотеть использовать их позже, в запросах. Сейчас они доступны через Ball.objects.get_exploded().

Добавив метод в QuerySet в Ball, мы сможем использовать get_exploded() после filter().filter().extra().

Однако, добавление метода к QuerySet и к Manager, означало бы написание этого метода дважды.

QuerySetManager

Порывшись в документации я нашел QuerySetManager, который позволяет добавлять методы в QuerySet и Manager одновременно. Мы определяем QuerySetManager, и говорим модели использовать его как Manager. Затем мы можем определить QuerySet внутри определения Model, т.к. Python позволяет определять вложенные классы.

Поехали.

  1. class QuerySetManager(models.Manager):
  2. def get_query_set(self):
  3. return self.model.QuerySet(self.model)
  4. def __getattr__(self, attr, *args):
  5. return getattr(self.get_query_set(), attr, *args)

Manager.get_query_set - функция, которая вызывается внутри класса, всякий раз, когда необходимо вернуть QuerySet. Переписав ее, мы сможем возвращать различные QuerySet, один из которых мы расширим новыми методами.

__getattr__ подобна магическим функциям в php: любое обращение к аттрибутам (т.е. методам или свойствам), которые не существуют, вызовет __getattr__, перед тем как кинеться исключение AttributeError. Это позволяет нам написать все методы QuerySet, и затем любой метод Manager, попробует получить метод из QuerySet.

Вместе с QuerySetManager мы можем определить QuerySet для использования в нашей модели Ball.

  1. class Ball(models.Model):
  2. objects = QuerySetManager()
  3. class QuerySet(QuerySet):
  4. def get_exploded(self):
  5. return self.filter(exploded=True)

Теперь мы можем использовать наш метод так, как хотим.

  1. Ball.objects.get_exploded() # вызывается Manager
  2. Ball.objects.filter(size=4).get_exploded().order_by('created_at') # вызывается QuerySet

0 Responses to Расширяем Models, Managers и QuerySets в Django

Leave a Reply