Django IntegrityError with relationship in model - python

I'm trying to create a recipe/ingredient model in Django
In my models.py I got
class Ingredient(models.Model):
name = models.CharField(max_length=20)
class Recipe(models.Model):
name = models.CharField(max_length=50)
ingredients = models.ManyToManyField(Ingredient, blank=True)
But when I create a Recipe or Ingredient in my admin, I get :
IntegrityError at /admin/menuview/ingredient/add/
menuview_ingredient.recipe_id may not be NULL
What am I doing wrong here?

I think you have to give relationship a null=True parameter too.
ingredients = models.ManyToManyField(Ingredients, blank=True, null=True,)

Your problem is similar to this one: Foreign keys and IntegrityError: id may not be NULL
To fix it, you will do something similar to this when saving:
>>> s = Recipe()
>>> s.name = 'Blah'
>>> obj = Ingredient(...)
>>> obj.save()
>>> s.ingredients = obj
>>> s.save()
The Django Doc has more examples for ManyToManyField. For example, for your case:
>>> i = Ingredient.objects.get(id=1)
>>> e = i.recipe_set.create(
... name ='strawberry pancake',
... )
# No need to call e.save() at this point -- it's already been saved.
This is equivalent to (but much simpler than):
>>> i = Ingredient.objects.get(id=1)
>>> e = Recipe(
... ingredients=i,
... name='strawberry pancacke',
... )
>>> e.save(force_insert=True)

Related

Conditionally calling __str__ in models.py in accordance to the "related_name" Django

Okay so here is my models.py first:
class UserFollowing(models.Model):
user = models.ForeignKey(CustomUser, related_name="following", on_delete=models.CASCADE)
following_user = models.ForeignKey(CustomUser, related_name="followers", on_delete=models.CASCADE)
created = models.DateTimeField(default=timezone.now, db_index=True)
def __str__(self):
return f"{self.user} follows {self.following_user}"
So in this case, when I try like this:
>>> user = CustomUser.objects.get(id=1) # just a user
>>> user.following.all() # A follows B
>>> user.followers.all() # B follows A
However, I want to do it like this:
>>> user.following.all() # B (Since A is following B)
>>> user.followers.all() # B (Since B is a follower of A)
But how can I differ the __str__ outputs according to the related_name? I couldn't find any information on this so is there a way?
The __str__ method takes into account the object (self), not the origin of that object. So how you constructed that object, through a .following or .followers is irrelevant. While it might be possible by inspecting the call stack, etc. it is simply not a good idea to do this.
You however do not need this. You are only interested in the CustomUsers that are "followers" or "followees" of a CustomUser, not the UserFollowing model that presents this. Therefore it might be better to span a ManyToManyField between CustomUser and CustomUser with Follow as through=… model:
class CustomUser(models.Model):
# …
followers = models.ManyToManyField(
'self',
symmetrical=False,
through='Follow',
related_name='followees',
through_fields=('followee', 'follower')
)
class Follow(models.Model):
follower = models.ForeignKey(
CustomUser,
related_name='followings',
on_delete=models.CASCADE
)
followee = models.ForeignKey(
CustomUser,
related_name='followedby',
on_delete=models.CASCADE
)
created = models.DateTimeField(auto_now_add=True, db_index=True)
def __str__(self):
return f'{self.user} follows {self.following_user}'
Then you thus can access the CustomUsers who are following user with:
b.followers.all() # [c]
b.followees.all() # [a]
here b is thus following a, and c is following b. These are thus CustomUser objects, not Follow objects.

Make base_field of ArrayField unique for a table in Django

Goal
The following should raise a ValidationError
>>> m1 = MyModel(names=['name1'])
>>> m2 = MyModel(names=['name1', 'name2'])
>>> m1.save()
>>> m2.save()
django.core.exceptions.ValidationError: ...
In plain English, if an element in a model's ArrayField matches an element in the database table a ValidationError should be raised
Failed Solutions
ArrayField docs don't mention the unique keyword so I tried doing this a couple ways (these are minimal code examples).
Adding unique=True to the base_field didn't seem to the anything at all after running makemigrations and migrate
# models.py
...
class MyModel(models.Model)
title = ArrayField(
models.CharField(max_length=255, unique=True),
)
...
# shell_plus from Django-extensions
>>> m1 = MyModel(names=['name1'])
>>> m2 = MyModel(names=['name1'])
>>> m1.save()
>>> m2.save()
# no errors raised
I tried adding unique=True to only the ArrayField. Django raises an error if an array is exactly the same.
# models.py
...
class MyModel(models.Model)
title = ArrayField(
models.CharField(max_length=255),
unique=True,
)
# shell_plus from Django-extensions
>>> m1 = MyModel(names=['name1'])
>>> m2 = MyModel(names=['name1'])
>>> m1.save()
>>> m2.save()
django.core.exceptions.ValidationError: ...
>>> m3 = MyModel(names=['name1', 'name2'])
>>> m3.save()
# no error raised
The above makes sense when I think it about. I then tried to add unique=True to both the base_field and to the ArrayField but the behavior didn't change. I ran makemigrations and migrate.
class MyModel(models.Model)
title = ArrayField(
models.CharField(max_length=255, unique=True),
unique=True,
)
# shell_plus from Django-extensions
>>> m1 = MyModel(names=['name1'])
>>> m2 = MyModel(names=['name1'])
>>> m1.save()
>>> m2.save()
django.core.exceptions.ValidationError: ...
>>> m3 = MyModel(names=['name1', 'name2'])
>>> m3.save()
# no error raised
TL;DR
Can I make the following raise an error with unique=True or do I need to write my own validators?
>>> m1 = MyModel(names=['name1'])
>>> m2 = MyModel(names=['name1', 'name2'])
>>> m1.save()
>>> m2.save()
django.core.exceptions.ValidationError: ...
This is a minimal example of what I ended up doing
# models.py
...
UNIQUE_ARRAY_FIELDS = ('name',)
class MyManager(models.Manager):
def prevent_duplicates_in_array_fields(self, model, array_field):
def duplicate_check(_lookup_params):
fields = self.model._meta.get_fields()
for unique_field in UNIQUE_ARRAY_FIELDS:
unique_field_index = [getattr(field, 'name', '') for field in fields]
try:
# if model doesn't have the unique field, then proceed to the next loop iteration
unique_field_index = unique_field_index.index(unique_field)
except ValueError:
continue
all_items_in_db = [item for sublist in self.values_list(fields[unique_field_index].name).exclude(**_lookup_params) for item in sublist]
all_items_in_db = [item for sublist in all_items_in_db for item in sublist]
if not set(array_field).isdisjoint(all_items_in_db):
raise ValidationError('{} contains items already in the database'.format(array_field))
if model.id:
lookup_params = {'id': model.id}
else:
lookup_params = {}
duplicate_check(lookup_params)
...
class MyModel(models.Model):
name = ArrayField(
models.CharField(max_length=255),
default=list
)
objects = MyManager()
...
def save(self, *args, **kwargs):
self.full_clean()
MyModel.objects.prevent_duplicates_in_array_fields(self, self.name)
super().save(*args, **kwargs)
...

Django Unique Together Combination

My scenario is this:
class Partnership(models.Model):
partner_a = models.ForeignKey(
"Person",
on_delete=models.CASCADE,
related_name='%(class)s_partner_a',
)
partner_b = models.ForeignKey(
"Person",
on_delete=models.CASCADE,
related_name='%(class)s_partner_b',
)
class Meta:
unique_together = (partner_a, partner_b)
This works just fine at refusing a duplicate partnership if a=a and b=b:
Ex:
>>> a = Person('a')
>>> b = Person('b')
>>> Partnership.objects.create(person_a=a, person_b=b)
>>> Partnership.objects.create(person_a=a, person_b=b) # IntegretyError as expected
However, it does not correctly refuse the following
>>> a = Person('a')
>>> b = Person('b')
>>> Partnership.objects.create(person_a=a, person_b=b)
>>> Partnership.objects.create(person_a=b, person_b=a) # Allowed
Is there some class Meta I am missing? Or is there another way to ensure this? (I know I can override the save class, but I'm asking if there is a way to do this without doing that).

Django adding item to many-to-many relationships

I'm new to django thus the question. I've the following Feed object and an User object which have a many-to-many relationship
class Feed(Base):
headline = models.CharField(max_length=255)
link = models.CharField(max_length=255)
summary = models.TextField()
reader = models.ManyToManyField(User, through='Bookmark')
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True, max_length=255)
mobile = PhoneNumberField(null=True)
username = models.CharField(null=True, unique=True, max_length=255)
full_name = models.CharField(max_length=255, blank=True, null=True)
The two are related using the Bookmark object.
class Bookmark(Base):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
feed = models.ForeignKey(Feed, on_delete=models.CASCADE, null=True)
My question is,
How do I add a bookmark(or rather feed) to the user?
How do I fetch all the feeds the User has bookmarked?
Any help appreciated.
Well, let's start from the beginning.
As you probably know, when you generate M2M rels with Django, you use the ManyToManyField. If you do not care about M2M table details, Django will manage it for you. If you want to specify the intermediary table you can use ManyToManyField.through. Exactly as you did. I'm going to semplify your model for explanation purposes. Something like this:
class User(models.Model):
username = models.CharField(null=True, unique=True, max_length=255)
class Feed(models.Model):
headline = models.CharField(max_length=255)
reader = models.ManyToManyField(User, through='Bookmark')
class Bookmark(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
feed = models.ForeignKey(Feed, on_delete=models.CASCADE, null=True)
Let's start Django interactive shell. I assume you have an empty database.
$ django manage.py shell
First of all import your models
>>> from yourAppName.models import *
Now, create some data:
>>> from bat.models import *
>>> u1 = User(username = 'foo')
>>> u1.save()
>>> u2 = User(username = 'bar')
>>> u2.save()
>>> User.objects.all() # get Users u1 and u2
<QuerySet [<User: User object>, <User: User object>]>
>>> f1 = Feed(headline = 'How to use M2M in Django')
>>> f1.save()
>>> Feed.objects.all() # get Feed f1
<QuerySet [<Feed: Feed object>]>
How do I add a bookmark (or rather feed) to the user?
In this case, you cannot use Feed.reader.add(u1), you have to use the Bookmark's Manager since you specified that's your intermediary model.
>>> b1 = Bookmark(user=u1, feed = f1) # add Feed f1 to User u1
>>> b1.save() # save into database
We can also add another bookmark:
>>> f2 = Feed(headline = 'Fetching data in Django like a pro!')
>>> f2.save()
>>> b2 = Bookmark(user=u1, feed = f2) # add Feed f2 to User u1
>>> b2.save() # save into database
You are done! Now, we can check if everything is fine.
>>> brandNewBookMark = Bookmark.objects.all()[0] # get the first bookmark
>>> print(brandNewBookMark.user.username) # it's Foo!
foo
>>> print(brandNewBookMark.feed.headline) # Foo subscribed to f1!
u'How to use M2M in Django'
How do I fetch all the feeds the User has bookmarked?
You can simply leverage the Feed.reader field. E.g.,
>>> for f in Feed.objects.filter(reader = u1):
... print(f.headline)
...
How to use M2M in Django
Fetching data in Django like a pro!
That's it! Further info here.
This is a good example for your problem
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
For this models, if you want to add memberships, you do this:
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
Unlike normal many-to-many fields, you can’t use add(), create(), or set() to create relationships:
>>> # The following statements will not work
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])
You can see this better in Django Docs

Updating Django model using selected collection of objects

I am learning Django and I refer djangobook.com.
I have a model like this:
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __unicode__(self):
return self.name
I have populated some data (as described in the demo) and tried to update the value of a particular field of a record (as explained) and this works perfectly fine:
>>> publisher_list = Publisher.objects.all()
>>> p = publisher_list[0]
>>> p.name = u'Apress'
>>> p.save()
But when I try the below (which I assume is equivalent to the above), it does not work. The name is not updated in the database. What am I doing wrong here?
>>> publisher_list = Publisher.objects.all()
>>> publisher_list[0].name = 'Apress'
>>> publisher_list[0].save()
Reference: http://djangobook.com/en/2.0/chapter05/
Thanks.
Your indexing is retrieving the model from the database multiple times. Bind the model to a name before mutating and saving it.
According to QuerySet.__getitem__(), retrieving by item, for example by qs[0], is not cached and will hit DB for each accessing, unless the queryset has been evaluated and thus cache has been filled:
>>> qs = User.objects.all()[:10]
>>> qs[0] is qs[0] # Similar with your issue, you modified the attribute of the first and tried to save the latter.
False
>>> len(qs) # evaluate and fill cache
10
>>> qs[0] is qs[0]
True
So you could
follow Ignacio's answer, or just p = Publisher.objects.all()[0]
or, evaluate the queryset before indexing. Whether this could be easier depends on your code logic.

Categories