Hey, this is something I'd like to get involved in. Last year I began prototyping something (albeit with an awful name) that resembled sqlalchemy's hybrid properties.
I'll spend a bit of time in a few hours sprucing it up to better match their naming, and link it here if anyone is interested. On 15 November 2017 at 17:06, Tom Forbes <[email protected]> wrote: > SQLAlchemy has this, through a feature called Hybrid Attributes: > http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html > > Every Django project i've worked on has had to tackle this problem, often > poorly. You usually end up with an instance method and a kind of duplicate > manager method, or some specific queries spread throughout the code. > > The annotate feature is quite cool, but I think Django would benefit from > something like this. > > > On 15 Nov 2017 16:49, "Matthew Pava" <[email protected]> wrote: > > I handle that situation quite differently. > > I have a model manager that annotates different fields to the queryset > depending on when I need them. > > For your example, I would have something like this: > > > > class CustomerQuerySet(models.QuerySet): > def with_allowed_to_drink(self): > return self.annotate(allowed_to_drink=Case(When(age__gte=18, > then=True), default=False)) > > > > > > Then I actually created a decorator that applies the manager to instances > on a model. It is not as generic as it could be since it depends on > function names having the word “with” in them, but I’m sure we could make > it more general. > > def queryset_on_instance(model: Model): > > > > > > > > *"""Takes the queryset of a model and creates new properties for each > instance of the model based on the custom queryset class. Some code is > copied from django.models.managers; most of this was inspired by it. > The queryset methods that are copied must have 'with' in their name. > Tip: Use this as a decorator on model classes """ *def > create_method(method_name): > def the_method(self): > return getattr(model._default_manager, method_name)().get(pk= > self.pk) > > return the_method > > queryset_class = getattr(model._default_manager, "_queryset_class", > None) > predicate = inspect.isfunction > for name, method in inspect.getmembers(queryset_class, predicate > =predicate): > if 'with' in name: > # Only copy missing attributes. > if hasattr(model, name): > continue > # Only copy public methods or methods with the attribute > `queryset_only=False`. > queryset_only = getattr(method, 'queryset_only', None) > if queryset_only or (queryset_only is None and > name.startswith('_')): > continue > # Copy the method onto the model. > setattr(model, name, create_method(name)) > return model > > > > > > And then on your Customer model: > > @queryset_on_instance > class Customer(models.Model): > age = models.IntegerField() > objects = CustomerQuerySet.as_manager() > > > > > > In your code or template, you would access the value like so: > > customer.with_allowed_to_drink.allowed_to_drink > > > > And you can filter by the value like so: > > Customers.objects.with_allowed_to_drink().filter(allowed_to_drink=True) > > > > I do agree that it would be nice to have something out of the box that > could handle custom properties in the QuerySet calls, or, as you put it, > calculated fields. > > > > > > *From:* [email protected] [mailto:django-developers@goog > legroups.com] *On Behalf Of *[email protected] > *Sent:* Wednesday, November 15, 2017 7:21 AM > *To:* Django developers (Contributions to Django itself) > *Subject:* models.CalculatedField feature > > > > Hello. > > > > I think I just invented very useful feature. > > I have the following model > > > > class Customer(models.Model): > > age = models.IntegerField() > > > > I want to show special link in template for customer if she is allowed to > drink. > > > > class Customer(models.Model): > > age = models.IntegerField() > > > > @property > > def allowed_to_drink(self): > > return self.age >= 18 > > > > And in template I can do > > > > {% if customer.allowed_to_drink %}. > > > > But in another view I want to show only those customers, who are allowed > to drink. > > So, in view I write > > > > drinkers = [customer for customer in models.Customer.objects.all() if > customer.allowed_to_drink]. > > > > But what if I have one million customers? It is insane to check this field > on Python side instead of database side. > > I use ORM: > > models.Customer.objects.filter(age__gte=18).all() > > > > And DRY principle is now violated. > > See, I have knowledge "allowed_to_drink -> (age >= 18) " and I want to > write in once, and then use both in ORM queries and object method. > > > > And I am not alone here: > > https://stackoverflow.com/questions/31658793/django-filter- > query-on-with-property-fields-automatically-calculated > > https://stackoverflow.com/questions/2143438/is-it-possible- > to-reference-a-property-using-djangos-queryset-values-list > > > > Here is what I suggest > > > > class Customer(models.Model): > > age = models.IntegerField() > > allowed_to_drink = models.CalculatedField(Q(age__gte=18)) > > > > # Using in ORM > > Customer.objects.filter(allowed_to_drink=True) > > # It actually converted to > > Customer.objects.filter(Q(age__gte=18)) > > > > # Using it in code > > Customer().allowed_to_drink > > # This part a little bit tricky, since you will need to check in on Python > side. > > # "age__gte" should be converted to > > funcs = { > > "gte": lambda instance, field, value: getattr(instance, field) >= value > > } > > instance_field, func = "age__gte".split("__") > > funcs[func](instance, instance_field, 18) > > > > But we already have code to convert Q to SQL, so it should be easy to do > it for python. > > > > Some more ideas: > > * "Q" may have lazy evalutation here for cases like > "models.CalculatedField(Q(date__gte=now()))". Could be implemented with > lambdas, or we can simply allow only literal here. > > * With Postrges you can even store calculated field in database (it has > calculated column), but it works only for literals. > > > > I think this functionality should be supported by Django core because it > affects admin, forms (calculated fields can't be changed), migrations and > ORM. > > > > > *Ilya Kazakevich* > > PyCharm developer. > > > > http://www.jetbrains.com > > The Drive to Develop > > > > -- > You received this message because you are subscribed to the Google Groups > "Django developers (Contributions to Django itself)" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at https://groups.google.com/group/django-developers. > To view this discussion on the web visit https://groups.google.com/d/ms > gid/django-developers/58aa034b-99de-4bad-98e4-5c200501b777% > 40googlegroups.com > <https://groups.google.com/d/msgid/django-developers/58aa034b-99de-4bad-98e4-5c200501b777%40googlegroups.com?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > > -- > You received this message because you are subscribed to the Google Groups > "Django developers (Contributions to Django itself)" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at https://groups.google.com/group/django-developers. > To view this discussion on the web visit https://groups.google.com/d/ms > gid/django-developers/2ea79561931841e59e6e5d59b1cbd631%40ISS1.ISS.LOCAL > <https://groups.google.com/d/msgid/django-developers/2ea79561931841e59e6e5d59b1cbd631%40ISS1.ISS.LOCAL?utm_medium=email&utm_source=footer> > . > > For more options, visit https://groups.google.com/d/optout. > > > -- > You received this message because you are subscribed to the Google Groups > "Django developers (Contributions to Django itself)" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at https://groups.google.com/group/django-developers. > To view this discussion on the web visit https://groups.google.com/d/ > msgid/django-developers/CAFNZOJNETxp5zzkqNVxs_ > RHCxi6s8dewcYB4TOW_MR-R60i8iw%40mail.gmail.com > <https://groups.google.com/d/msgid/django-developers/CAFNZOJNETxp5zzkqNVxs_RHCxi6s8dewcYB4TOW_MR-R60i8iw%40mail.gmail.com?utm_medium=email&utm_source=footer> > . > > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at https://groups.google.com/group/django-developers. To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CALs0z1bxWea8%2BQxoK9AgA-xH_DF3E%2BaBHLd00ezOR-hnGdFnbw%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.
