Vikramark
Vikramark

Reputation: 307

How to use add(), set() and clear() methods of ManyRelatedManager in Django ManyToManyField with a through class?

I have models as follows,

Class Bank(models.Model):
    customers = models.ManyToManyField(Customer,'banks', through='bank_customer')

Class Customer(models.Model):
    name = models.CharField(max_length=50)

Class Bank_customer(models.Model):
    customer = models.ForeignKey(Customer,on_delete=models.CASCADE)
    bank = models.ForeignKey(Bank,on_delete=models.CASCADE)
    city = models.ForeignKey(City,on_delete=models.CASCADE)
    
Class City(models.Model):
    name = models.CharField(max_length=100)   

How do I add Customer objects to Bank? The following does not work

bank.customers.add(customer)

Here bank and customer are saved instances of their classes. Doing this violates not_null constraint for city ForeignKey in Bank_customer table.

Upvotes: 3

Views: 2197

Answers (2)

Guzman Ojero
Guzman Ojero

Reputation: 3467

I would make this changes to the models to start.

  • Be carefull with the reserved word class, it must be all lowercase.

  • When you define the classes, the order is important. The classes that inherit or are backward-related must be defined last. (eg: Bank must be defined after Customer, if not you'll have an error like "Customer is not defined" or something like that).

  • I think that you may need a name attribute/field in the Bank model. I mean, banks have a name.

  • In the Bank class I think it's better to explicitly use the related name keyword: related_name='banks'. It makes readability better.

  • It's more Pythonic to name classes using the CapWords convention (PEP 8). Use BankCustomer instead of Bank_customer model.


About your specific question.

I did this:

c1 = Customer(name="Guido van Rossum")
c1.save()
b1 = Bank(name="Bank of America")
b1.save()

Then I did b1.customers.add(c1) and this error was raised: IntegrityError: NOT NULL constraint failed: app2_bankcustomer.city_id. It seems that it was expecting city_id to have null=True in the BankCustomer.

I continued with:

ct1 = City('New York')
ct1.save()
bc1 = BankCustomer(customer=c1, bank=b1, city=ct1)
bc1.save()

And then when I did b1.customers.add(c1) no errors were raised.

The problem is the IntegrityError error, apparently:

django.db.utils.IntegrityError: NOT NULL --> this is a very common error for beginners. It means that you have a field that is null=False (aka the default) and you're trying to create that model WITHOUT that field having any data. (source)

So you can make a little change to the BankCustomer. You add null=True in the city field.

class BankCustomer(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    bank = models.ForeignKey(Bank, on_delete=models.CASCADE)
    city = models.ForeignKey(City, null=True, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.customer} - {self.bank} - {self.city}'

(Don't forget to run makemigrations and migrate)

After this, no errors are raised.

Some other sources that discuss the issue:

django.db.utils.IntegrityError: NOT NULL constraint failed: app.area_id

django.db.utils.IntegrityError: NOT NULL constraint failed: products_product.image ERROR WITH IMAGE FIELD

https://code.djangoproject.com/ticket/21783

https://forum.djangoproject.com/t/polls-not-null-constraint-failed/4396/2

https://github.com/sibtc/django-beginners-guide/issues/20


If you check the admin you'll see that the Bank model only accepts name and no customers.

enter image description here

Which I think is logical because you want to use the BankCustomer (in your code: Bank_customer).

I hope this helps.

Upvotes: 3

Yeonghun
Yeonghun

Reputation: 410

I can't find the django doc for it. however looking at the Django3.1's source code of ManyRelatedManager__add, it accepts through_defaults parameter

def add(self, *objs, through_defaults=None):
...
                self._add_items(
                    self.source_field_name, self.target_field_name, *objs,
                    through_defaults=through_defaults,
                )
...

so, instead of

bank.customers.add(customer)

try

bank.customers.add(customer, through_defaults={'city_id':SOME_CITY_OBJ_ID_YOU_WANT})

this works on .set() function as well

Upvotes: 2

Related Questions