Reputation: 1261
There are three models: House_Type, House_Option, and Order
The House_Type model has 2 fields: id and name
House_Option has 3 fields: id, name and type where type is a foreign key linked to House_Type.
and finally, Order consists of many fields, one of which is a ManytoMany field called "choice" that links to House_Option
The way this works is that House_Type has different "types" of houses: for example, apartment, condo, detached house, semi detached house etc..
House_Option has all the possible options for each type: so for example, for the "apartment" type, you have option 1 located on street X, option 2 located on street Y etc..
In the Order model, the user has to choose one "option" of each house "type". So they must pick one apartment option, one house option etc.. Because this is a ManytoMany field, this is possible. However my question is: How do I prevent the user from choosing TWO "apartment" options for example. How do I restrict them to only choosing one (or none) of each?
I was trying to create a def(clean) in the Order model:
def clean(self):
if self.choice.house_option_type.count() > 1:
raise ValidationError('Custom Error Message')
This however returns an attribute error: 'ManyRelatedManager' object has no attribute 'house_option_type'
Any ideas?
Upvotes: 1
Views: 2057
Reputation: 4346
Manage ManyToMany relationship through an explicitly defined model, where both foreign keys are unique for each order. You can do this using unique_together to impose uniqueness constraint on the many to many relationship across same types.
class House_Type(models.Model):
name = models.CharField(...)
class House_Option(models.Model):
name = models.CharField(...)
type = models.ForeignKey(House_Type)
class Order(models.Model):
...
choices = models.ManyToManyField(House_Option, through='Order_options')
...
class Order_options(models.Model):
class Meta:
unique_together = ('order', 'option__type')
...
order = models.ForeignKey(Order)
option = models.ForeignKey(House_Option)
...
Edit, updated syntax and correction.
Yeah it looks like unique_together is applied as DB constraint on the table and won't work across tables. So forget about the above approach.
Still I think the following should work:
If you simply override validate_unique on Order_options and implement uniqueness logic yourself, while being careful how to handle existing and non existent case it should work.
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
class Order_options(models.Model):
...
def validate_unique(self, exclude = None):
super(Order_options, self).validate_unique(exclude)
options = { 'order__id' : self.order.id, 'option__type' : 'self.option.type' }
objs = Order_options.objects.exclude(id=self.id) if self.id else Order_options.objects
if objs.filter(**options).exists():
raise ValidationError({NON_FIELD_ERRORS: ['Error: {0} option type already exists'.format(self.option.type)]})
...
Upvotes: 1
Reputation: 115600
I think that Django does not allow compound Primary Keys and (thus) neither compound Foreign Key constraints (to either a Primary or Unique key) which would solve the problem natively.
There is a ticket for that feature, which has activity: Ticket #373: Add support for multiple-column primary keys
Upvotes: 0
Reputation: 28499
If you want to count the number of choices of a type, you can do this:
if self.choice.filter(type__name = 'condo').count() > 1:
raise ValidationError("Multiple condos selected!");
Upvotes: 0