0

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.

Vikramark
  • 137
  • 13
  • https://docs.djangoproject.com/en/3.0/topics/db/examples/many_to_many/ – iklinac Jul 30 '20 at 14:41
  • @iklinac This link does not explain how many_to_many works with the through model. It only explains the simple manytomany relationship. – Vikramark Jul 30 '20 at 14:55
  • 1
    https://docs.djangoproject.com/en/3.0/topics/db/models/#extra-fields-on-many-to-many-relationships – iklinac Jul 30 '20 at 14:57
  • @iklinac Thanks, this does explain, the add() and clear() methods. But, in the set() method, it only allows for same ```through_fields``` for the set of through classes. How do I use set() with unique through_fields for each of the related objects in the list? – Vikramark Jul 30 '20 at 15:17

2 Answers2

2

First, some general comments. I'm no expert, but 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.

Guzman Ojero
  • 2,812
  • 1
  • 20
  • 20
2

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

Yeonghun
  • 400
  • 5
  • 13