Django Unique Together Combination - python

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).

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.

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

Django IntegrityError with relationship in model

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)

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.

Django Releationship between multiple fields

I just need a little help thinking through this, if someone could be so kind.
class object_a(models.Model):
foo = models.blah
bar = models.blah
class object_b(models.Model):
foop = models.blah
barp = models.blah
In another model I have a class that I want to have a single relationship with both fields. For example, in the admin I want a list of both object_a and object_b objects selectable in some sort of relationship.
I know a generic relationship of some sort can do this, I just can't quite get to the end of the though. all help is appreciated.
Thanks.
Use the contenttypes framework provided by Django:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Other(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
Then you can create some of your main objects:
>>> from myapp.models import object_a, object_b, Other
>>> a = object_a()
>>> a.foo = 'test'
>>> a.bar = 'value'
>>> a.save()
>>> b = object_b()
>>> b.foop = 'random'
>>> b.barp = 'values'
>>> b.save()
And then save references to them in Other objects:
>>> o1 = Other()
>>> o1.content_object = a
>>> o1.save()
>>> o2 = Other()
>>> o2.content_object = b
>>> o2.save()
Now if we ask for all the Other objects and inspect them:
>>> all = Other.objects.all()
>>> all[0].content_object
<object_a: object_a object>
>>> all[0].content_object.foo
u'test'
>>> all[1].content_object
<object_b: object_b object>
>>> all[1].content_object.foop
u'random'
By looking at the fields on the Other object, we can see how Django stores the generic relations:
>>> all[0].content_type
<ContentType: object_a>
>>> all[0].object_id
1
When you run the syncdb command to install your models, Django creates a unique ContentType object for each model. When you use generic relations, you store a foreign key to this content type entry (identifying what type of object is at the other end of the relation), and the unique ID of the model instance. The generic.GenericForeignKey field is not stored in the database, but is a wrapper which takes the content type and object ID and retrieves the actual object for you.

Categories