Suppose I have the following structure:
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo, related_name='bars')
I want the individual Bar objects to be keyed on both an auto-incrementing ID and the parent Foo object. For each Foo I want the Bars underneath to always be id'd 1,2,3. The ultimate aim is to access Bars via URIs such as:
/foos/1/bars/1
/foos/2/bars/1
Notice that there are two Bars with the same ID, but the primary key comes from the uniqueness of the id's of both the Bar and its parent Foo.
I thought I'd found my answer in the unique_together attribute of the Meta class:
class Bar(models.Model):
foo = models.ForeignKey(Foo, related_name='bars')
class Meta:
unique_together = ('foo', 'id')
But unfortunately this still results in a unique ID for each Bar. I always want the first Bar for each Foo to have an ID of 1.
You can't do that with the ID, because that's allocated by the database and is always unique across the whole table. If you really want this, you will have to define a separate field and increase it each time you create a bar for your foo.
A naive implementation might be something like:
class Bar(models.Model):
foo_order = models.IntegerField()
foo = models.ForeignKey(Foo, related_name='bars')
class Meta:
unique_together = ('foo', 'foo_order')
def save(self, *args, **kwargs):
if not self.foo_order:
self.foo_order = self.bar_set.count() + 1
super(Bar, self).save(*args, **kwargs)
(Note that this is probably subject to all sorts of race conditions, so be careful.)
Now you can use the field combination in your view to get the relevant Bar:
def bar_view(request, foo_id, order):
my_bar = Bar.objects.get(foo_id=foo_id, foo_order=order)
As a side note, I don't actually need to store this identifier on any object. Rather than
/foos/1/bars/1
resulting in a lookup for a Bar with pk set to 1, I can just take the request and return something like
Foo.objects.get(id=1).bars.all()[0]
I.e. thereby mapping to the first Bar. so
/foos/n/bars/m
maps to the mth Bar under the Foo with primary key n.
Daniel's solution actually answers my original question, so I'm accepting that as the answer.
Related
I have three models in my Django application - simplified for demonstration. The first one is Base. The second one is Bill and has foreign key "base" to Base model. The third one is Point, and has a foreign key to Bill.
class Base(models.Model):
type = models.CharField()
desc = models.IntegerField()
class Bill(models.Model):
base = models.ForeignKey("Base", related_name="bills")
total_value = models.DecimalField()
class Point(models.Model):
bill = models.ForeignKey("Bill", related_name="points")
value = models.DecimalField()
My goal is to have a property for Base called def bill(self), that would sum up all "bill" objects fields and returned a bill-like instance. I would like to get a base object:
my_base = Base.objects.get()
bill = Base.bill <-- I can figure this out with #property
points = bill.points <-- this does not work through #property function.
My current solution is like this, to get the first part working:
class Base():
...
#property
def bill(self):
sums = self.bills.aggregate(total_value=Sum('total_value'))
return Bill(**sums)
The second part, that would sum up bill-related-points and return them in a my_base.bill.points, does not work. If I filter for points and try to assign them to Bill(**sums).points = filtered_points, I get an error: Direct assignment to the reverse side is prohibited, or Unsaved model instance cannot be used in an ORM query
Is there a more elegant solution to this? A good option would be to initate class as a #property like so:
class Base(model.Model):
...
class bill(...):
self.total_value = .
self.points = .
but I don't believe that is achievable.
Thank you
How to define OneToOne relationship to the same Model?
I have a model called Order which can be paired with another one Order. Now I'm trying to figure out how to handle models for this relationship.
My ideas:
class Order(models.Model):
paired_order = models.OneToOneField(self)
OR:
class Pairing(models.Model):
order1 = models.ForeignKey(Order, related_name='pairing')
order2 = models.ForeignKey(Order, related_name='pairing')
What do you think? Which is more efficient?
I want to have simple calling of paired ones. So I would do something like:
order.paired_order
OR:
order.pairing.paired
I want this relation symmetrical so for each pair of orders I call the same thing and get paired order.
Pairing model would be a good solution because I can add additional information to this relationship, but there is a problem that I would have to detect which order is it, so I couldn't call order.pairing.order1 because I don't know whether I'm not calling the same order.
EDIT:
>>> from _app import models
>>> order1 = models.Order(flight_number="xxx")
>>> order2 = models.Order(flight_number="yyy", paired_order=order1)
>>> order1.paired_order.flight_number
RETURNS None object has not ....
The problem is that when I set order1 is a paired order for order2, I want the same thing in opposite direction. So order1.paired_order = order2 do this as well order2.paired_order = order1.
Pairing model would be a good solution because I can add additional
information to this relationship.
In that case, you could model that group of "orders" (you've called it Pairing) and add a shortcut to retrieve the paired order.
class OrderPair(models.Model):
pass
# additional information goes here
class Order(models.Model):
pair = models.ForeignKey(to="OrderPair", related_name="orders")
# you'll have to add custom validation
# to make sure that only 2 orders can belong to the same "OrderPair"
#property
def paired_order(self):
return self.pair.orders.exclude(id=self.id).first()
Once you've got this working, you might also want to cache the paired order to avoid too many queries. In that case, you don't want a related name so you can use + (the less explicit thing in Django ever).
class Order(models.Model):
...
cached_paired_order = models.ForeignKey(to='self', related_name='+')
#property
def paired_order(self):
if self.cached_paired_order:
...
else:
...
Having this problem myself, the term 'symmetrical' was key to finding the answer: https://code.djangoproject.com/ticket/7689
class Order(models.Model):
paired_order = models.OneToOneField(self)
def save(self, *args, **kwargs):
super(Order, self).save(*args, **kwargs)
self.paired_order.paired_order = self
super(Order, self.paired_order).save()
The ForeignKey accepts as an argument not just a class, but also a string name of the form ForeignKey('ModelNameInSameModelsPyFile') or ForeignKey('app_name.ModelName).
In your case, it could be like
class Order(models.Model):
paired = models.ForeignKey('Order', null=True)
You can read more at https://docs.djangoproject.com/en/1.8/ref/models/fields/#foreignkey
All,
Is there a straightforward way for me to display a form that uses a modelchoicefield for models that have yet to be saved?
class Foo(models.Model):
name = models.CharField(max_length=42)
class Bar(models.Model):
name = models.CharField(max_length=42)
foo = models.ForeignKey("Foo",blank=True,null=True)
class BarForm(ModelForm):
class Meta:
model = Bar
pass
def create_new_bar(request):
foos = [Foo(name='foo1'), Foo(name='foo2'), Foo(name='foo3')]
bar = Bar(name='bar',foo=foos[0])
form = BarForm(instance=bar)
return render_to_response('bar_template.html',{"form" : form},context_instance=RequestContext(request))
But the "select" widget that gets rendered in the form has empty content. Presumably, this is because there is nothing to bind the queryset to for the ModelChoiceField that Bar's ForeignKey to Foo uses. (Since nothing in "foos" has been saved and therefore does not exist in the database).
Is there another way to deal with this besides writing a custom formfield/widget?
Thanks.
You can add additional field to BarForm with predefined choices - fooempty for example. That field will be shown in case if Foo table is empty. Try this (not tested):
class BarForm(ModelForm):
FOO_CHOICES = (
(0, 'Choice1'),
(1, 'Choice2'),
(2, 'Choice3'),
)
fooempty = forms.ChoiceField(required=False, label='lbl', choices=FOO_CHOICES)
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
if self.fields['foo'].queryset.count(): # Foo empty?
self.fields['fooempty'].widget = forms.HiddenInput()
else:
self.emptyflag
self.fields['foo'].widget = forms.HiddenInput()
def save(self, commit=True):
bar = super(BarForm, self).save(commit=False)
if self.emptyflag:
bar.foo_id = self.cleaned_data['fooempty']
bar.save()
return bar
EDIT: actually, now that I think about it. Those Foos won't have pks. So there is no way that could ever work. The form needs to know the pk of the Foos. You really just need to save them first. How would django know which one is which when it posts back the form? IGNORE ANSWER BELOW.
I think you can change the choices variable on the field. You will need to that in the form. So pass the list of Foos to the init of the form. Something like this:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, foo_list, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
self.fields['foo'].choices = foo_list
Then in the view:
foos = [Foo(name='foo1'), Foo(name='foo2'), Foo(name='foo3')]
bar = Bar(name='bar', foo=foos[0])
form = BarForm(foos, instance=bar)
I know I've done something like this before. I just can't remember the exact way. Something like this approach should work though.
Consider this Django schema:
class Foo(models.Model):
# ...
class FooOption(models.Model):
foo = models.ForeignKey(foo, related_name='options')
key = models.CharField(max_length=64)
value = models.TextField()
class Meta:
unique_together = [('foo', 'key')]
Essentially, FooOptions works a key-value set for each Foo.
<edit>
There's a known set of keys that the system uses,
Every Foo has a number of (key, value) pairs, where values are arbitrary,
Or equivalently, Every Foo can have one value for every single key.
</edit>
As there's limited number of keys in FooOption, I'd like to rephrase this relation a bit using Django's ORM. The current design pictures the relation as a 1-n between a Foo and FooOptions; I'd like the code to picture it as 1-1 between a Foo and each specific FooOption key.
This would allow me to access each options like this:
class Foo(models.Model):
# ...
opt1 = OptionField('colour')
opt2 = OptionField('size')
foo = Foo()
foo.opt1 = 'something'
foo.save()
Especially, I'd like to be able to select_related specific FooOptions when querying for many Foos, to obtain an ORM-ed equivalent of:
SELECT foo.*, opt_colour.value, opt_size.value
FROM foo
LEFT JOIN foo_option opt_colour
ON foo.id = foo_option.foo_id AND foo_option.key = 'id'
LEFT JOIN foo_option opt_size
ON foo.id = foo_option.foo_id AND foo_option.key = 'size';
Is it possible to code such custom OptionField? How?
I may be misinterpreting your design, but it looks like you should actually be going with a Many-to-many relationship. You seem to want to have multiple options for each Foo instance (i.e. colour, size, etc.) and I imagine you would want a specific colour or size to be available to describe various Foo's.
class Foo(models.Model):
options = models.ManyToManyField('FooOption')
# Other attributes.
class FooOption(models.Model):
key = models.CharField(max_length=64)
value = models.TextField()
class Meta:
unique_together = [('key', 'value')]
I think the best way to ask this question is with some code... can I do this:
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self.foo
def set_foo(self, input):
self.foo = input
foo = property(get_foo, set_foo)
or do I have to do it like this:
class MyModel(models.Model):
_foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self._foo
def set_foo(self, input):
self._foo = input
foo = property(get_foo, set_foo)
note: you can keep the column name as 'foo' in the database by passing a db_column to the model field. This is very helpful when you are working on an existing system and you don't want to have to do db migrations for no reason
A model field is already property, so I would say you have to do it the second way to avoid a name clash.
When you define foo = property(..) it actually overrides the foo = models.. line, so that field will no longer be accessible.
You will need to use a different name for the property and the field. In fact, if you do it the way you have it in example #1 you will get an infinite loop when you try and access the property as it now tries to return itself.
EDIT: Perhaps you should also consider not using _foo as a field name, but rather foo, and then define another name for your property because properties cannot be used in QuerySet, so you'll need to use the actual field names when you do a filter for example.
As mentioned, a correct alternative to implementing your own django.db.models.Field class, one should use the db_column argument and a custom (or hidden) class attribute. I am just rewriting the code in the edit by #Jiaaro following more strict conventions for OOP in python (e.g. if _foo should be actually hidden):
class MyModel(models.Model):
__foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
#property
def foo(self):
if self.bar:
return self.bar
else:
return self.__foo
#foo.setter
def foo(self, value):
self.__foo = value
__foo will be resolved into _MyModel__foo (as seen by dir(..)) thus hidden (private). Note that this form also permits using of #property decorator which would be ultimately a nicer way to write readable code.
Again, django will create _MyModel table with two fields foo and bar.
The previous solutions suffer because #property causes problems in admin, and .filter(_foo).
A better solution would be to override setattr except that this can cause problems initializing the ORM object from the DB. However, there is a trick to get around this, and it's universal.
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def __setattr__(self, attrname, val):
setter_func = 'setter_' + attrname
if attrname in self.__dict__ and callable(getattr(self, setter_func, None)):
super(MyModel, self).__setattr__(attrname, getattr(self, setter_func)(val))
else:
super(MyModel, self).__setattr__(attrname, val)
def setter_foo(self, val):
return val.upper()
The secret is 'attrname in self.__dict__'. When the model initializes either from new or hydrated from the __dict__!
It depends whether your property is a means-to-an-end or an end in itself.
If you want this kind of "override" (or "fallback") behavior when filtering querysets (without first having to evaluate them), I don't think properties can do the trick. As far as I know, Python properties do not work at the database level, so they cannot be used in queryset filters. Note that you can use _foo in the filter (instead of foo), as it represents an actual table column, but then the override logic from your get_foo() won't apply.
However, if your use-case allows it, the Coalesce() class from django.db.models.functions (docs) might help.
Coalesce() ... Accepts a list of at least two field names or
expressions and returns the first non-null value (note that an empty
string is not considered a null value). ...
This implies that you can specify bar as an override for foo using Coalesce('bar','foo'). This returns bar, unless bar is null, in which case it returns foo. Same as your get_foo() (except it doesn't work for empty strings), but on the database level.
The question that remains is how to implement this.
If you don't use it in a lot of places, simply annotating the queryset may be easiest. Using your example, without the property stuff:
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
Then make your query like this:
from django.db.models.functions import Coalesce
queryset = MyModel.objects.annotate(bar_otherwise_foo=Coalesce('bar', 'foo'))
Now the items in your queryset have the magic attribute bar_otherwise_foo, which can be filtered on, e.g. queryset.filter(bar_otherwise_foo='what I want'), or it can be used directly on an instance, e.g. print(queryset.all()[0].bar_otherwise_foo)
The resulting SQL query from queryset.query shows that Coalesce() indeed works at the database level:
SELECT "myapp_mymodel"."id", "myapp_mymodel"."foo", "myapp_mymodel"."bar",
COALESCE("myapp_mymodel"."bar", "myapp_mymodel"."foo") AS "bar_otherwise_foo"
FROM "myapp_mymodel"
Note: you could also call your model field _foo then foo=Coalesce('bar', '_foo'), etc. It would be tempting to use foo=Coalesce('bar', 'foo'), but that raises a ValueError: The annotation 'foo' conflicts with a field on the model.
There must be several ways to create a DRY implementation, for example writing a custom lookup, or a custom(ized) Manager.
A custom manager is easily implemented as follows (see example in docs):
class MyModelManager(models.Manager):
""" standard manager with customized initial queryset """
def get_queryset(self):
return super(MyModelManager, self).get_queryset().annotate(
bar_otherwise_foo=Coalesce('bar', 'foo'))
class MyModel(models.Model):
objects = MyModelManager()
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
Now every queryset for MyModel will automatically have the bar_otherwise_foo annotation, which can be used as described above.
Note, however, that e.g. updating bar on an instance will not update the annotation, because that was made on the queryset. The queryset will need to be re-evaluated first, e.g. by getting the updated instance from the queryset.
Perhaps a combination of a custom manager with annotation and a Python property could be used to get the best of both worlds (example at CodeReview).