#28822: Add DBCalculatedField to model to annotate models automatically
-------------------------------------+-------------------------------------
     Reporter:  Ilya                 |                    Owner:  nobody
         Type:  New feature          |                   Status:  new
    Component:  Database layer       |                  Version:  master
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by Ilya:

Old description:

> Models are ultimate source of business knowledge. We must encourage
> people to use them for it instead of spread logic over views, managers
> and "utility" methods.
>
> We have customer  model and there is knowledge: People are allowed to
> drink when they are 18+
>

> So, we implement this logic in model to use it in templates and other
> tools:
>

> {{{
> class Customer(models.Model):
>     first_name = models.CharField(max_length=255)
>     last_name = models.CharField(max_length=255)
>     age = models.IntegerField()
>
>     @property
>     def allowed_to_drink(self):
>         return self.age >= 18
> }}}
>

> Cool, but what if want to query database for only people who are allowed
> to drink?
>

> {{{
> models.Customer.objects.filter(age__gt=18)
>
> }}}
>
> We now have this logic written twice in two different places.
>
> One can use managers with annotate:
>
> {{{
>
> class DrinkersManager(models.Manager):
>     def get_queryset(self):
>         return models.query.QuerySet(self.model,
> using=self._db).annotate(
>             allowed_to_drink=ExpressionWrapper(Q(age__gt=18),
> models.BooleanField()))
>

> class Customer(models.Model):
>     first_name = models.CharField(max_length=255)
>     last_name = models.CharField(max_length=255)
>     age = models.IntegerField()
>
>     objects = DrinkersManager()
> }}}
>

> We now can do both: use it as `.filter(allowed_to_drink=False)` and use
> it for templates: `customer.allowed_to_drink`.
>
> But why do we define all "physical" fields in model and "calculated"
> field as keyword argument (!) inside of different (!!) class?
> Why do we need class at all?
>
> What we suggest:
>
> cl
> {{{
> ass Customer(models.Model):
>     first_name = models.CharField(max_length=255)
>     last_name = models.CharField(max_length=255)
>     age = models.IntegerField()
>     allowed_to_drink = models.DBCalculatedField(Q(age__gt=18)
> models.BooleanField())
> }}}
>

> You just add this field and all queries to this model are annotated
> automatically, so you will have model field and query field.
>
> I believe this syntax is much more clear and we have consensus about in
> on group:
> [https://groups.google.com/forum/#!topic/django-developers/ADSuUUuZp3Q
> ]
> We may also have
>
> {{{
>     full_name = models.DBCalculatedField(F('first_name') + ' ' +
> F('last_name'), models.CharField())
>
> }}}
>
> And for local calculation (may be used by people who want to use this
> logic without of db or before saving)
>
> {{{
>     allowed_to_drink = models.DBCalculatedField(Q(age__gt=18)
> models.BooleanField(), local=lambda c: c.age > 18)
>
> # or
> @allowed_to_drink.local
> def allowed_to_drink_local(self):
>     return self.age > 18
> }}}
>

> Since knowledge is expressed by Django expression language it is possible
> to generate "local calculation" automatically
> (you just need to evalute this simple language), but many people in group
> believe it is not safe since DB may use different logic which may be
> hard to mimic (expecially in database-agnostic way). Tool for "automatic
> local calculation" may be created as external lib, not part of Django
> itself (one tool for each database probably)

New description:

 Models are ultimate source of business knowledge. We must encourage people
 to use them for it instead of spread logic over views, managers
 and "utility" methods.

 We have customer  model and there is knowledge: People are allowed to
 drink when they are 18+


 So, we implement this logic in model to use it in templates and other
 tools:


 {{{

 class Customer(models.Model):
     first_name = models.CharField(max_length=255)
     last_name = models.CharField(max_length=255)
     age = models.IntegerField()

     @property
     def allowed_to_drink(self):
         return self.age >= 18
 }}}


 Cool, but what if want to query database for only people who are allowed
 to drink?


 {{{
 models.Customer.objects.filter(age__gt=18)

 }}}

 We now have this logic written twice in two different places.

 One can use managers with annotate:

 {{{

 class DrinkersManager(models.Manager):
     def get_queryset(self):
         return models.query.QuerySet(self.model, using=self._db).annotate(
             allowed_to_drink=ExpressionWrapper(Q(age__gt=18),
 models.BooleanField()))


 class Customer(models.Model):
     first_name = models.CharField(max_length=255)
     last_name = models.CharField(max_length=255)
     age = models.IntegerField()

     objects = DrinkersManager()
 }}}


 We now can do both: use it as `.filter(allowed_to_drink=False)` and use it
 for templates: `customer.allowed_to_drink`.

 But why do we define all "physical" fields in model and "calculated" field
 as keyword argument (!) inside of different (!!) class?
 Why do we need class at all?

 What we suggest:


 {{{

 class Customer(models.Model):
     first_name = models.CharField(max_length=255)
     last_name = models.CharField(max_length=255)
     age = models.IntegerField()
     allowed_to_drink = models.DBCalculatedField(Q(age__gt=18),
 models.BooleanField())
 }}}


 You just add this field and all queries to this model are annotated
 automatically, so you will have model field and query field.

 I believe this syntax is much more clear and we have consensus about in on
 group:
 [https://groups.google.com/forum/#!topic/django-developers/ADSuUUuZp3Q]
 We may also have

 {{{
     full_name = models.DBCalculatedField(F('first_name') + ' ' +
 F('last_name'), models.CharField())

 }}}

 And for local calculation (may be used by people who want to use this
 logic without of db or before saving)

 {{{
     allowed_to_drink = models.DBCalculatedField(Q(age__gt=18),
 models.BooleanField(), local=lambda c: c.age > 18)

 # or
 @allowed_to_drink.local
 def allowed_to_drink_local(self):
     return self.age > 18
 }}}


 Since knowledge is expressed by Django expression language it is possible
 to generate "local calculation" automatically
 (you just need to evalute this simple language), but many people in group
 believe it is not safe since DB may use different logic which may be
 hard to mimic (expecially in database-agnostic way). Tool for "automatic
 local calculation" may be created as external lib, not part of Django
 itself (one tool for each database probably)

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/28822#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/071.137672e419a25d613fdef65f4deaf3b2%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to