|Version 17 (modified by adrian, 9 years ago) (diff)|
Fields as Descriptors
In this proposal, fields would become descriptors. This means
- They would be left as members by the metaclass.
- The real 'data' would end up either in name-mangled instance members, or in a private dictionary.
- When accessed via the class, they would return themselves. eg Reporter.name would be a field class you can inspect. This would make introspection as in the admin easier.
- When accessed via the instance, they would return a representation of the value they hold. This could be
would change to:article.reporter article.reporter.id
- ForeignKeys and other related fields would place another descriptor on the related class. When accessed via the instance, this would return a lazy collection object. It will act like a set (an ordered one if there is an ordering involved).
This would also support other methods in favour of the get_related_list things.
Other end of ForeignKey! relationship:
reporter.get_article_list() reporter.get_article_list(headline__startswith='This', order_by=['headline']) reporter.add_article(headline="John's second story", pub_date=datetime(2005, 7, 29))
would change to:reporter.article_set reporter.article_set.filter(headline__startswith='This').order_by('headline') reporter.article_set.add(headline="John's second story", pub_date=datetime(2005, 7, 29))
Methods which change the meaning of the lazy collection are as follows:
- .filter : adds query parameters using djangos normal db query syntax.
- .order_by: adds/changes ordering parameters.
The collection supports the appropriate collection protocols : It acts as a set, with ordering if there is an ordering in the underlying query. The query is only done when a collection protocol is used. Sometimes, multiple queries will be used to save traffic, eg if you access reporter.article_set[10000:10020] and then reporter.article_set[:20].
Other methods of a similar style are also possible.
- .add would add a new instance of the related class to the collection. This would register a callback so that it would be written to the db when .save() is called on the parent object.
ManyToMany examples: (from http://www.djangoproject.com/documentation/models/many_to_many/ )
This looks a bit odd because of the clear call . In reality, most of the time you don't want to replace a whole set of many-to-manys, so the new api is more convenient than the old one. The methods are precisely those of a Python 2.4 built in set.
a2.set_publications([p1.id]) a2.set_publications([p1.id, p2.id])
a2.publication_set.clear() a2.publication_set.add(p1) a2.publication_set.clear() a2.publication_set.update([p1,p2])
a2.publication_set.clear() a2.publication_set.add(p1) a2.publication_set.add(p2)
In further discussions about this idea, it seemed consistent that both the manager ( Person.objects ) and relationship end points, (reporter.articles) would act as lazy sets of instances (base class QuerySet?) , with these 'refinement' methods on them (filter, order_by, etc).
Person.objects.get_list() becomes Person.objects
Person.objects.get_list(name__startswith="R") becomes Person.objects.filter(name__startswith="R")
Query sets would be combinable with the standard set operators, which would be less clumsy than the current complex= query syntax. eg ar_list = Person.objects.get_list(complex=Q(name_startswith="R")|Q(name_startswith="A") ) goes to (idiosyncratically)
people = Person.objects ar_set = people.filter(name_startswith="R") | people.filter(name_startswith="A")
When you finally open a ticket on this massive change ;) I'll add a comment that you should feel free to steal code from Dejavu, which has used descriptors successfully for two years now: http://projects.amor.org/dejavu/browser/trunk/units.py -- Robert Brewer
The reason we avoided this in the initial design of Django is that we wanted it to be clear when an action would result in a database query being executed. This explicitness makes it easier to optimise your code - it's obvious when an action will result in an extra query, making it clearer when you should store results in a local variable for later use. -- Simon Willison .
I think the mailing list is better for discussions, but I don't think that you need to worry about the local variable thing. You can still save things in a local variable, exactly as before. The only change is that things will not actually be fetched until required. After that, they will be cached. -- Robert Wittams
simons = Person.objects.filter(name_startswith="Simon") for simon in simons: <--- hits database pass for simon in simons: <--- uses cache pass