Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing custom Queryset methods

Developing custom Queryset methods

Chibisov Gennady

Разработка и тестирование кастомных QuerySet-методов

… а также методов менеджеров и инстансов моделей на основе предикатов, не нарушая принципов DRY.

Moscow Python Meetup

November 21, 2012
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. Instance  methods   class Event(models.Model): ! name = models.CharField(max_length=255)! slug

    = models.SlugField(unique=True)! date_start = models.DateTimeField()! ! def __unicode__(self):! return self.name ! ! @property! def is_published(self):! # uses current instance data! return (self.date_start <=! datetime.datetime.now())! ! ! >>> yac2012 = Event.objects.get(slug='yac2012')! >>> yac2012.__unicode__()! 'Yet another Conference 2012'! >>> yac2012.is_published! True!  
  2. Class  methods   class Event(models.Model): ! name = models.CharField(max_length=255)! slug

    = models.SlugField(unique=True)! date_start = models.DateTimeField()! ! @classmethod! def get_fields_for_csv(cls):! return ['name', 'slug']! ! ! >>> Event.get_fields_for_csv()! ['name', 'slug']!  
  3. Manager  methods   class RegionManager(models.Manager):! def sync_with_geobase(self):! for region in

    regions_from_geobase():! try:! region_instance = self.model.objects.get(geobase_id=region.id)! # can modify any instance! region_instance.name_ru = region.name! region_instance.name_en = region.ename! region_instance.save()! except self.model.DoesNotExist:! # can create new instances! region_instance = self.model.objects.create(! geobase_id=region, ! name_ru=region.name, ! name_en=region.ename! )! # can remove instance! if region.is_deprecated:! region_instance.delete()! ! ! >>> Region.objects.sync_with_geobase()!
  4. Manager  methods   # app/management/commands/sync_with_geobase.py! class Command(BaseCommand):! help = u"""Syncs

    regions with geobase"""! ! def handle(self, *args, **options):! Region.objects.sync_with_geobase()! ! ! $ ./manage.py sync_with_geobase! !
  5. Readability  L   events = Events.objects.all()! # filter for current

    site type! if settings.IS_INTERNAL_SITE:! events = events.filter(! Q(is_published_external=True) | ! Q(is_published_internal=True)! )! else:! events = events.filter(! is_published_for_external=True! )!
  6. Readability  L   yac2012 = Events.objects.get(slug='yac2012')! ! # is for

    current site type?! if settings.IS_INTERNAL_SITE:! is_for = (yac2012.is_published_for_internal ! and yac2012.is_published_for_external)! else:! is_for = yac2012.is_published_for_external!
  7. Reusability  J   class EventDetailView(DetailView): ! queryset = (Event.objects! .filter_for_current_site_type())!

    ! ! class EventListView(ListView):! queryset = (Event.objects! .filter_for_current_site_type())!
  8. First  manager   # apps/events/managers.py ! from datetime.datetime import now!

    from django.db.models.query import QuerySet! from django.db import models! ! ! class EventQuerySet(QuerySet):! def filter_by_published(self):! return self.filter(date_start__lte=now())! ! ! class EventManager(models.Manager):! def get_query_set(self):! return EventQuerySet(self.model, ! using=self._db)!
  9. First  manager   # apps/events/models.py! from django.db import models! from

    apps.events.managers import EventManager! ! ! class Event(models.Model):! name = models.CharField(max_length=255)! slug = models.SlugField(unique=True)! date_start = models.DateTimeField()! ! objects = EventManager()!
  10. Oops…   >>> Event.objects.filter_by_published()! ! Traceback (most recent call last):!

    File "<console>", line 1, in <module>! AttributeError: 'EventManager' object has no attribute 'filter_by_published'!
  11. Add  proxy  method   class EventManager(models.Manager):! def get_query_set(self):! return EventQuerySet(self.model,

    ! using=self._db)! ! def filter_by_published(self):! return (self.get_query_set()! .filter_by_published())!
  12. Or  try  to  proxy  all  not  found   class EventManager(models.Manager):!

    def get_query_set(self):! return EventQuerySet(self.model, ! using=self._db)! ! def __getattr__(self, name):! if name.startswith('_'):! raise AttributeError(name)! else:! return getattr(self.get_query_set(), ! name)!
  13. apps.events.models   class EventType(models.Model):! """! Example: Yet Another Conference! """!

    name = models.CharField(max_length=255)! slug = models.SlugField(unique=True)! is_moderated = models.BooleanField(default=False)!
  14. apps.events.models   class Event(models.Model):! """! Example: Yac 2012! """! name

    = models.CharField(max_length=255)! slug = models.SlugField(unique=True)! type = models.ForeignKey(EventType)! date_start = models.DateTimeField()!
  15. /events/yac/   •  Yac  2010   •  Yac  2011  

    •  Yac  2012  (too  early)  
  16. Event  QuerySet  method   import datetime! from datetime.datetime import now!

    ! class EventQuerySet(QuerySet):! def filter_by_it_time_to_publish(self):! d = now() + datetime.timedelta(days=2)! return self.filter(date_start__lte=d)!
  17. /events/yac/   # /templates/events/eventtype_detail.html! ! {% for e in object.event_set!

    .filter_by_it_time_to_publish %}! <p>{{ e }}</p>! {% endfor %}!
  18. /events/yac/2012/   Show  if   •  It  is  Tme  to

     publish   •  Yac  EventType  is  published  
  19. /events/yac/2012/   class EventDetailView(DetailView):! queryset = (Event.objects! .filter_by_it_time_to_publish())! ! def

    get_queryset(self):! q = super(EventDetailView, ! self).get_queryset()! return q.filter(type__is_moderated=True)!
  20. /events/yac/2012/   class EventDetailView(DetailView):! queryset = (Event.objects! .filter_by_it_time_to_publish())! ! def

    get_queryset(self):! q = super(EventDetailView, ! self).get_queryset()! pks = (EventType.objects! .filter_by_published()! .values_list('pk', flat=True))!
  21. /events/yac/2012/   class EventDetailView(DetailView):! queryset = (Event.objects! .filter_by_it_time_to_publish())! ! def

    get_queryset(self):! q = super(EventDetailView, ! self).get_queryset()! pks = (EventType.objects! .filter_by_published()! .values_list('pk', flat=True))! return q.filter(type__pk__in=pks)!
  22. class EventTypeQuerySet(QuerySet):! def filter_by_published(self):! return self.filter(Q(is_moderated=True))! ! ! class EventQuerySet(QuerySet):!

    ...! def filter_by_with_published_type(self):! return self.filter(Q(type__is_moderated=True))!
  23. class EventTypeQuerySet(QuerySet):! def filter_by_published(self):! return self.filter(Q(is_moderated=True))! ! ! class EventQuerySet(QuerySet):!

    ...! def filter_by_with_published_type(self):! return self.filter(Q(type__is_moderated=True))! Diff  
  24. class EventTypeConditions(object):! @classmethod! def is_published(cls, prefix=None):! if prefix:! field =

    '%s__is_moderated' % prefix! else:! field = 'is_moderated'! ! return Q(is_moderated=True)! Field  name  with  prefix  
  25. class EventTypeConditions(object):! @classmethod! def is_published(cls, prefix=None):! if prefix:! field =

    '%s__is_moderated' % prefix! else:! field = 'is_moderated'! ! return Q(**{field: True})! Use  prefixed  field  
  26. class EventConditions(object):! @classmethod! def is_with_published_type(cls):! event_prefix = 'type' ! return

    EventTypeConditions.is_published(! prefix=event_prefix! ) ! Create  Event  condiTons  
  27. class EventConditions(object):! @classmethod! def is_with_published_type(cls, prefix=None): ! event_prefix = 'type'

    ! return EventTypeConditions.is_published(! prefix=event_prefix! )! Prefix  as  param  
  28. class EventConditions(object):! @classmethod! def is_with_published_type(cls, prefix=None):! event_prefix = 'type' !

    if prefix:! prefix = '%s__%s' % prefix, event_prefix! else:! prefix = event_prefix! ! return EventTypeConditions.is_published(! prefix=event_prefix! )! Add  prefix  
  29. class EventConditions(object):! @classmethod! def is_with_published_type(cls, prefix=None): event_prefix = 'type'! if

    prefix:! prefix = '%s__%s' % prefix, event_prefix! else:! prefix = event_prefix! ! return EventTypeConditions.is_published(! prefix=prefix! )! Use  prefix  
  30. Lets  refactor   class EventQuerySet(QuerySet):! ...! def filter_by_it_time_to_publish(self):! d =

    now() + datetime.timedelta(days=2)! return self.filter(date_start__lte=d)!
  31. class EventConditions(object):! @classmethod! def is_with_published_type(cls, prefix=None):! ...! ! @classmethod! def

    is_it_time_to_publish(cls, prefix=None): ! if prefix:! field = '%s__date_start__lte' % prefix! else:! field = 'date_start__lte'! ! d = now() + datetime.timedelta(days=2) ! return Q(**{field: d})!
  32. class EventConditions(object):! @classmethod! def is_with_published_type(cls, prefix=None):! ...! ! @classmethod! def

    is_it_time_to_publish(cls, prefix=None): ! ...! ! @classmethod! def is_published(cls, prefix=None):! return (! cls.is_with_published_type(prefix) & ! cls.is_it_time_to_publish(prefix)! )! !
  33. How  to  reuse  condiTons?   class  Event(models.Model):      

       ...          @property          def  is_with_published_type(self):                  return  EventConditions.is_with_published_type(model_instance=self)            @property          def  is_it_time_to_publish(self):                  return  EventConditions.is_it_time_to_publish(model_instance=self)            @property          def  is_published(self):                  return  EventConditions.is_published(model_instance=self)!
  34. True  and  False   =   True  &  False  

        How  to  reuse  condiTons?  
  35. True  or  False   =   True  |  False  

        How  to  reuse  condiTons?  
  36. Use  makers   class TestEventIsPublished(TestConditionBase):! def test_1(self):! makers.event.make_with_published_type(self.instance)! makers.event.make_it_time_to_publish(self.instance)! !

    self.assertConditionTrue(msg=msg)! ! def test_2(self):! makers.event.make_with_published_type(self.instance)! makers.event.make_not_it_time_to_publish(self.instance)! ! self.assertConditionFalse(msg=msg)! ! def test_3(self):! makers.event.make_it_time_to_publish(self.instance)! makers.event.make_not_with_published_type(self.instance)! ! self.assertConditionFalse(msg=msg)!
  37. class DistributionSendedConditions(object):! @classmethod! def is_ready_to_start_with_in_progress_status(cls, prefix=None):! k = {! 'prefix':

    prefix! }! ! return (! (cls.is_with_status(status='in_progress', **k)) &! ((cls.is_last_message_sended_more_than_5_minutes_ago(**k)) |! (cls.is_not_has_messages(**k) & ! cls.is_started_sending_more_than_5_minutes_ago(**k)))! )! More  complex  condiTons