Slide 1

Slide 1 text

Developing  custom  QuerySet   methods   Chibisov  Gennady  

Slide 2

Slide 2 text

https://github.com/chibisov/events-site-example !

Slide 3

Slide 3 text

Model  methods   1. Instance  methods   2. Class  methods   3. QuerySet  methods   4. Manager  methods  

Slide 4

Slide 4 text

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!  

Slide 5

Slide 5 text

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']!  

Slide 6

Slide 6 text

QuerySet  methods   Event.objects.filter(id__lte=10).delete()!

Slide 7

Slide 7 text

QuerySet  methods   Event.objects.all().get_or_create(…)! Event.objects.all().create(…)! Event.objects.all().bulk_create(…)  

Slide 8

Slide 8 text

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()!

Slide 9

Slide 9 text

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! !

Slide 10

Slide 10 text

This  talk   1. Instance  methods   2. Class  methods   3. QuerySet  methods   4. Manager  methods  

Slide 11

Slide 11 text

What  for?  

Slide 12

Slide 12 text

What  for?   1. Readability  

Slide 13

Slide 13 text

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! )!

Slide 14

Slide 14 text

Readability  J   events = Events.objects.filter_for_current_site_type()!

Slide 15

Slide 15 text

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!

Slide 16

Slide 16 text

Readability  J   yac2012.is_for_current_site_type!

Slide 17

Slide 17 text

What  for?   1. Readability   2. Reusability  

Slide 18

Slide 18 text

Reusability  J   class EventDetailView(DetailView): ! queryset = (Event.objects! .filter_for_current_site_type())! ! ! class EventListView(ListView):! queryset = (Event.objects! .filter_for_current_site_type())!

Slide 19

Slide 19 text

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)!

Slide 20

Slide 20 text

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()!

Slide 21

Slide 21 text

First  manager   >>> Event.objects.all().filter_by_published()! ! [, ! ]!

Slide 22

Slide 22 text

Oops…   >>> Event.objects.filter_by_published()! ! Traceback (most recent call last):! File "", line 1, in ! AttributeError: 'EventManager' object has no attribute 'filter_by_published'!

Slide 23

Slide 23 text

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())!

Slide 24

Slide 24 text

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)!

Slide 25

Slide 25 text

OK   >>> Event.objects.filter_by_published()! ! [, ! ]!

Slide 26

Slide 26 text

More   docs.djangoproject.com/en/dev/topics/db/managers/!

Slide 27

Slide 27 text

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)!

Slide 28

Slide 28 text

/events/   •  Yet  another  Conference   •  I.Subbotnik   •  ECIR  

Slide 29

Slide 29 text

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()!

Slide 30

Slide 30 text

/events/yac/   •  Yac  2010   •  Yac  2011   •  Yac  2012  

Slide 31

Slide 31 text

/events/yac/2012/  

Slide 32

Slide 32 text

/events/   class EventTypeListView(ListView):! queryset = EventType.objects.all()!  

Slide 33

Slide 33 text

/events/   •  Yet  another  Conference   •  I.Subbotnik   •  ECIR  (not  moderated)  

Slide 34

Slide 34 text

EventType  QuerySet  method   class EventTypeQuerySet(QuerySet):! def filter_by_published(self):! return self.filter(is_moderated=True)! !

Slide 35

Slide 35 text

/events/   class EventTypeListView(ListView):! queryset = (EventType.objects! .filter_by_published())!  

Slide 36

Slide 36 text

/events/   •  Yet  another  Conference   •  I.Subbotnik  

Slide 37

Slide 37 text

/events/yac/   •  Yac  2010   •  Yac  2011   •  Yac  2012  (too  early)  

Slide 38

Slide 38 text

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)!

Slide 39

Slide 39 text

/events/yac/   class EventTypeDetailView(DetailView):! queryset = (EventType.objects! .filter_by_published())!  

Slide 40

Slide 40 text

/events/yac/   # /templates/events/eventtype_detail.html! ! {% for e in object.event_set! .filter_by_it_time_to_publish %}!

{{ e }}

! {% endfor %}!

Slide 41

Slide 41 text

/events/yac/   •  Yac  2010   •  Yac  2011  

Slide 42

Slide 42 text

/events/yac/2012/  

Slide 43

Slide 43 text

/events/yac/2012/   Show  if    

Slide 44

Slide 44 text

/events/yac/2012/   Show  if   •  It  is  Tme  to  publish  

Slide 45

Slide 45 text

/events/yac/2012/   Show  if   •  It  is  Tme  to  publish   •  Yac  EventType  is  published  

Slide 46

Slide 46 text

/events/yac/2012/   else   404  

Slide 47

Slide 47 text

/events/yac/2012/   class EventDetailView(DetailView):! queryset = (Event.objects! .filter_by_it_time_to_publish())! !

Slide 48

Slide 48 text

/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)!

Slide 49

Slide 49 text

L     EventType.objects.filter_by_published()   not  reused  

Slide 50

Slide 50 text

/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()!

Slide 51

Slide 51 text

/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))!

Slide 52

Slide 52 text

/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)!

Slide 53

Slide 53 text

L     2  DB  queries   Ugly  

Slide 54

Slide 54 text

/events/yac/2012/   class EventDetailView(DetailView):! queryset = (Event.objects! .filter_by_published())!

Slide 55

Slide 55 text

Pre\y   1  query   Reuse  filter_by_published  

Slide 56

Slide 56 text

HOW?  

Slide 57

Slide 57 text

Q  object  

Slide 58

Slide 58 text

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))!

Slide 59

Slide 59 text

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  

Slide 60

Slide 60 text

class EventTypeConditions(object):! @classmethod! def is_published(cls):! return Q(is_moderated=True)! Move  Q  to  funcTon  

Slide 61

Slide 61 text

class EventTypeConditions(object):! @classmethod! def is_published(cls, prefix=None): ! return Q(is_moderated=True)! Prefix  as  param  

Slide 62

Slide 62 text

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  

Slide 63

Slide 63 text

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  

Slide 64

Slide 64 text

>>> EventTypeConditions.is_published()!

Slide 65

Slide 65 text

>>> EventTypeConditions.is_published()! Q(is_moderated=True)!

Slide 66

Slide 66 text

>>> EventTypeConditions.is_published()! Q(is_moderated=True)! ! >>> EventTypeConditions.is_published(! ... prefix='type'! ... )!

Slide 67

Slide 67 text

>>> EventTypeConditions.is_published()! Q(is_moderated=True)! ! >>> EventTypeConditions.is_published(! ... prefix='type'! ... )! Q(type__is_moderated=True)!

Slide 68

Slide 68 text

class EventTypeQuerySet(QuerySet):! def filter_by_published(self):! return self.filter(! EventTypeConditions.is_published()! )! Use  it  for  EventType  

Slide 69

Slide 69 text

May  I?   class EventQuerySet(QuerySet):! ...! def filter_by_with_published_type(self):! return self.filter(! EventTypeConditions.is_published(! prefix='type’! )! )!

Slide 70

Slide 70 text

NO  

Slide 71

Slide 71 text

NO   EventTypeCondiTons  is   for  EventType  

Slide 72

Slide 72 text

NO   EventTypeCondiTons  is   for  EventType   Create   EventCondiTons  for  Event  

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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  

Slide 75

Slide 75 text

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  

Slide 76

Slide 76 text

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  

Slide 77

Slide 77 text

>>> EventConditions.is_with_published_type()!

Slide 78

Slide 78 text

>>> EventConditions.is_with_published_type()! Q(type__is_moderated=True)! !

Slide 79

Slide 79 text

>>> EventConditions.is_with_published_type()! Q(type__is_moderated=True)! ! >>> EventConditions.is_with_published_type(! ... prefix=’event’! ... )!

Slide 80

Slide 80 text

>>> EventConditions.is_with_published_type()! Q(type__is_moderated=True)! ! >>> EventConditions.is_with_published_type(! ... prefix=’event’! ... )! Q(event__type__is_moderated=True)!

Slide 81

Slide 81 text

Use  it  for  Event   class EventQuerySet(QuerySet):! ...! def filter_by_with_published_type(self):! return self.filter(! EventConditions.is_with_published_type()! )!

Slide 82

Slide 82 text

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)!

Slide 83

Slide 83 text

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})!

Slide 84

Slide 84 text

Aaer  refactor   class EventQuerySet(QuerySet):! ...! def filter_by_it_time_to_publish(self):! return self.filter(! EventConditions.is_it_time_to_publish()! )!

Slide 85

Slide 85 text

Combining  methods  

Slide 86

Slide 86 text

class EventConditions(object):! @classmethod! def is_with_published_type(cls, prefix=None):! ...! ! @classmethod! def is_it_time_to_publish(cls, prefix=None): ! ...!

Slide 87

Slide 87 text

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)! )! !

Slide 88

Slide 88 text

>>> EventConditions.is_published()! !

Slide 89

Slide 89 text

>>> EventConditions.is_published()! Q(date_start__lte=datetime(23,11,2012)) &! Q(type__is_moderated=True)! !

Slide 90

Slide 90 text

>>> EventConditions.is_published()! Q(date_start__lte=datetime(23,11,2012)) &! Q(type__is_moderated=True)! ! >>> EventConditions.is_published(! ... prefix=’event’! ... )!

Slide 91

Slide 91 text

>>> EventConditions.is_published()! Q(date_start__lte=datetime(23,11,2012)) &! Q(type__is_moderated=True)! ! >>> EventConditions.is_published(! ... prefix=’event’! ... )! Q(event__date_start__lte=datetime(23,11,2012))&! Q(event__type__is_moderated=True)!

Slide 92

Slide 92 text

Model  instance  methods  

Slide 93

Slide 93 text

    How  to  reuse  condiTons?  

Slide 94

Slide 94 text

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)!

Slide 95

Slide 95 text

True  and  False   =   True  &  False       How  to  reuse  condiTons?  

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

More   https://github.com/chibisov/events-site-example ! /apps/accounts/models.py   /apps/accounts/managers.py     /apps/events/models.py   /apps/events/managers.py    

Slide 98

Slide 98 text

How  to  test  

Slide 99

Slide 99 text

Use  makers   •  make_published   •  make_not_published  

Slide 100

Slide 100 text

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)!

Slide 101

Slide 101 text

More   https://github.com/chibisov/events-site-example ! /apps/accounts/makers/   /apps/accounts/tests/condiTons/     /apps/events/makers/   /apps/events/tests/condiTons/    

Slide 102

Slide 102 text

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  

Slide 103

Slide 103 text

Спасибо