JohnnyCash
JohnnyCash

Reputation: 1261

Django ManytoMany field duplicates, attribute error: 'ManyRelatedManager'

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

Answers (3)

enticedwanderer
enticedwanderer

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

ypercubeᵀᴹ
ypercubeᵀᴹ

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

kdt
kdt

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

Related Questions