OK, no need for everyone to shout -- your message is heard loud and
clear. I'll go and find something else to work on.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CA%2Bm8oA-hGgjDQhpQvL9%2Bk18VEjdeUf%3D8tNu1hsek%3Dp2nkEUzrA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAG_XiSAK0eJOkSw_ENVRc0SUiNc1LrTEnk%3DZqcf8eJL%3DD4KbTw%40mail.gmail.com.
--
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/464cb3e1-6cb5-4057-9035-f57d9693f5bb%40googlegroups.com.
--
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 django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/673dc9ed-da09-46d5-902e-311756613c81%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CA%2Bm8oA8JDnOWHh%2BHXRgANjVLu31%3D34WC%2Btc1%2Bdu8QyQpySiAxA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/3b3712f9-de74-424e-8e21-2af16003f8bd%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CA%2Bm8oA8upah5K9N2FOnT6fDaPiWwfNi0ykYTSvCX_0MYmfRjmg%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CALMtK1G8qFMOt8GXURYc25x%2BJy4m6R1Vkc_VsY943_c0XQQccA%40mail.gmail.com.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/3b3712f9-de74-424e-8e21-2af16003f8bd%40googlegroups.com.
--
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 django-developers+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CA%2Bm8oA8upah5K9N2FOnT6fDaPiWwfNi0ykYTSvCX_0MYmfRjmg%40mail.gmail.com.
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 django-developers+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
--
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 django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/2130B21B-41FA-4928-BC9C-5F5BAADAD7E0%40polytechnique.org.
Aymeric,
Thanks for your input. I feel some of your concerns have been addressed in the DEPs I made, which have included quite a bit of input from this thread along with the original design. That said, some of the points you've raised are new and haven't been raised by other people, so I'll give you a full reply.
1) That's still something I have to do, but more time consuming than other parts of creating the proposal and I've put it off until I have a while to do it. The original implementation was done without too much consideration of prior attempts, because I was more interested in getting something working than I was in learning from the past.
2) I agree. The new syntax I've proposed is to add one or more class-level methods to the django.db.models API. The new syntax for "inline" fields is:
class MyModel(models.Model):
x = models.IntegerField()
y = models.IntegerField()
point = models.constrain(x, y, unique=True)
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/aff3be14-2f93-4d06-b69e-7fa1877247a4%40googlegroups.com.
@Aymeric
Here's why I think the `isnull` column is necessary.
Say you've got a custom field implementation:
class Point(models.CompositeField):
x = models.IntegerField()y = models.IntegerField()# etc.
and you use this to define a coordinate system to locate objects in your models. You justify that whenever you provide a point, you'll want both an x and y coordinate, so you make both of the subfields non-null.
But then you come across a model which may or may not have a location. You'd like to say
class MaybeLocatable(models.Model):
point = Point(null=True)
but you can't, because both of your subfields are non-nullable, so you define a nonsensical condition to apply to your Point
"If I don't have an x-coordinate, then the point value should be considered null"
so you update the Point field appropriately (and apply all the necessary migrations).
You then want to query for all `MaybeLocatable` objects that are considered null. You can query for point__isnull=True (which is supposed to be supported). How does one define the lookup transformation for that?
If there was an implicit 'isnull' column added, then the lookup transformation for both `point__isnull=True` and `point=None` become a lookup for the database column `point__isnull = True`. If you don't add that extra column, how does the framework what transform to make? It could query for both `point__x__isnull` and `point__y__isnull`, but that wouldn't match the semantics for the column.
And you also can't say "well, look at the field definition and if a column is nullable, then use it as a marker, because what if you instead had:
class Point(models.CompositeField):
x = models.IntegerField(null=True)
y = models.IntegerField()
z = models.IntegerField(null=True)
as your original class? `x` could still be used as the "marker" column, but any transformation you'd make with the above rule. So the framework can't actually define a workable `isnull` query (or an 'exact' query when the python value is `None`). The whole issue of the "marker" column is fraught anyway, because it breaks all the NOT NULL constraints on other columns anyway.
So instead of all that, the user decides that they're going to solve the problem with
class MaybeLocation(models.Model):
has_point = models.BooleanField()
point = models.Point(default=Point(0,0))
which is exactly the same solution as including the implicit `isnull` field when the field is created with `null=True`, except the user has to do it explicitly, and implement the logic surrounding the definition themselves.
They also can't define a new CompositeField like
class MaybePoint(...):
has_point = models.BooleanField()
point = Point()
because of the inheritance limitations (which I might have to lift in the medium-long term anyway, but which I'd like to preserve for as long as possible to keep the initial implementation and API simple).
The point is (no pun intended) that I can't really think of a good way to map a python None to the values of a composite field _without_ the extra implicit column. It's not the nicest solution in the world, but it works.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CA%2Bm8oA_h%3D%3D%2B2Wnzmv%3D%3D7de7qYd0rSGXB4kYb7tduX2Ou3XtZUg%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAGdCwBt%2BhSB%3D-5vZH9N5LBkP9hTwyYbgyKV3EWyW6uLSYcYm5A%40mail.gmail.com.
--
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 django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CALMtK1GCSfemy%2BN0HLPMbsovwgcSy%2BwEw6QF-%2BoZ-tfBCTSkUw%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/0f8b0979-7e15-45d2-ab76-88dc747a0bf7%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CA%2Bm8oA_mryzSJtdp4mB7KZ%2BRJxVqAX-nE7tY-5tJ%3DORwyrx9Rg%40mail.gmail.com.
Considering the past two proposals I've made here, I doubt I'll get more than an echo chamber effect on this one.
For the past week or so I've spent a bit of time on a feature I've always wanted to see land in django -- composite fields. The tickets have been open in the bug tracker for quite some time (and there's a few related ones, such as multi-column primary keys that can all be killed with the one stone).
The work is available on this branch of my fork of django for the moment -- I haven't opened up a PR yet because there's still some features that are still to be implemented that will be explained below, but I want to give everybody a chance to tell me where I can stick it before I spend *too* much time on it.
So, without further ado, the proposal.Composite Fields - ImplementedA composite field is an encapsulation of the functionality of a subset of fields on a model. Composite fields can be defined in one of two ways:
1. Statically declared composite fields
A statically declared composite field is defined in the same way a django model is defined. There are two customisable transformation functions, CompositeField.value_to_dict(self, value) and CompositeField.value_from_dict(self, value) which can be used to associate the field with a python object.
All the serialization functions are implemented via the implementations of the subfields.
For example,
class MoneyField(models.CompositeField):
currency_code = models.CharacterField(max_length=3)
amount = models.DecimalField(max_digits=16, decimal_digits=4)
## Overriding __init__ can be used to pass field parameters to the subfields
def value_from_dict(self, value):
if value is None:
return None
return Money(**value)
def value_to_dict(self, value):
if value is None:
return None
return {attr: getattr(value, attr) for attr in ('currency_code', 'amount')}
2. Inline composite fields.
An inline composite field is declared at the field definition site on the body of a model, by providing the subfields as the 'fields' argument of the CompositeField constructor. There are no transformation parameters available to override when declaring a composite field in this fashion -- the value of the field is always available as a python `dict` as an attribute on the MyModel
class MyModel(models.Model):
id = models.CompositeField(fields = [
('a', models.IntegerField()),
('b', models.CharField(max_length=30)
], primary_key=True)
This method for defining composite fields has a few drawbacks, but can be useful if the only reason to add the composite field to the model was to implement a unique_together or index_together constraint *
* Although it's still possible to do that directly on class Meta.
3. Null
Setting the value of a multi-column field to NULL is different than setting any of the individual subfields to NULL. But there are cases (e.g. Money) where we would like to be able to set `null=True` on a composite field, but still retain 'NOT NULL' constraints on each of the subfield columns.
To solve this problem, every table which implements a CompositeField will also add an implicit (semi-hidden) `isnull` subfield on the attribute, which keeps track of whether it is the value of the composite field that is null, or any of the particular subfields.
3. Querying.
The syntax for querying over the subfields of a composite field will be familiar to anyone who has queried over a relationship attribute in django.
model.objects.filter(price__currency_code='USD', price__amount__lt=Decimal('50.00')).all()
In addition, composite fields can be queried via EXACT and IN lookups. It is possible to implement custom lookups for specific statically defined fields, but not recommended and not part of the official API.4. Restrictions
The following restrictions are currently imposed on the use of composite fields. None of these are restrictions that can't be worked around in future extensions, but they're restrictions which considerably simplify both the implementation and API.
- No related fields as a subfield of a composite field
- No nested composite fields
- No inheritance of composite fields (apart from inheriting from CompositeField itself).
5. Changes to the Field API
As discussed in the other thread I posted. I've changed the implementation so that _get_cache_name can still be dependent on the name, but I think using attname is more useful anyway.
Composite Fields -- unimplemented
These features are still not implemented
- multi column primary keys. unique_together and index_together are implemented and adding a primary key constraint should be a similar operation.
- some small issues with multi-table inheritance.
- more test coverage
- proper documentation
- anything that comes out of this thread.