Django adding item to many-to-many relationships - python

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

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.

How can I access a specific field of a queryset in Django?

I have these models in my Django app:
class Book(models.Model):
title = models.CharField(max_length=50, unique=True)
owner = models.CharField(max_length=30)
price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
book_rating = models.ForeignKey('Rating', null=True)
RATE_CHOICES = zip(range(1,6), range(1,6))
class Rating(models.Model):
user = models.ForeignKey(User)
this_book = models.ForeignKey(Book)
rate = models.DecimalField(max_digits=2, decimal_places=1, choices=RATE_CHOICES)
comment = models.TextField(max_length=4000, null=True)
I am trying to access the Ratings of each instance of the Book model. Here is what I've tried so far in the shell:
from django.contrib.contenttypes.models import ContentType
>>> ctype = ContentType.objects.get_for_model(Rating)
>>> ctype
<ContentType: rating>
>>> book_titles = ctype.model_class().objects.filter(this_book__title='My Test Book')
>>> book_titles
<QuerySet [<Rating: My Test Book - parrot987 - 3.0>, <Rating: My Test Book - 123#gmail.com - 5.0>]>
How can I access the two rating values of each object (5.0 and 3.0) without all of this other data?
Can this be done in such a way that I am able to average the numbers and return the final value?
For 1. you can use (relevant documentation):
Rating.objects.filter(this_book__title='My Test Book').values('rate')
If you just want a flat list you can use values_list('rate', flat=True) instead of values('rate').
For 2 (relevant documentation):
from django.db.models import Avg
Rating.objects.filter(this_book__title='My Test Book').aggregate(Avg('rate'))
This will return a dictionary where the key is rate__avg and the value is the average of the ratings.
Please see the following for Many to One fields django - Get the set of objects from Many To One relationship
To access the rating, you can use a for loop and access the individual values e.g.
total = 0
for rating in book_titles.book_set.all()
total += rating.rate
Good luck!

Django 1.7 modelform_factory form is always invalid with factory_boy created model

I'm using Django 1.7 and factory_boy to create some models. Here's the code:
In models.py:
class Address(models.Model):
first_line = models.CharField(max_length=50, blank=True)
second_line = models.CharField(max_length=50, blank=True)
city = models.CharField(max_length=30, blank=True)
state = models.CharField(max_length=2, blank=True)
zipcode = models.CharField(max_length=5, blank=True)
zipcode_ext = models.CharField(max_length=4, blank=True)
The associated factory in factories.py (same directory):
class AddressFactory(factory.django.DjangoModelFactory):
class Meta:
model = Address
first_line = "555 Main St."
second_line = "Unit 2"
city = "Chicago"
state = "IL"
zipcode = "60606"
zipcode_ext = "1234"
Now, given this code in the django shell:
>>> from models import Address
>>> from django.forms.models import modelform_factory
>>> AddressForm = modelform_factory(Address)
>>> from factories import AddressFactory
>>> a = AddressFactory.create()
>>> af = AddressForm(instance = a)
>>> af.is_valid()
False
For some reason, the call to is_valid seems to always return false, and I can't figure out why. There don't seem to any errors on the form, and clean(), clean_fields(), and validate_unique() all do not seem to raise errors on the instance.
Why does is_valid always return false?
This has nothing to do with factory_boy. Forms are always invalid if they don't have any data, which yours doesn't. The instance parameter is used to populate the form's initial data for display, and to determine the object ID for updating, but it isn't used to set that data on POST.
I'm not quite sure what you want to do with the form there, but you'd need to convert it to a POST dictionary and pass it to the form's data parameter for it to be valid.

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)

Django - reverse lookups with ManyToManyField

I'm trying to follow the code from the django docs:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
>>> 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()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
My model looks like this:
class Trip(models.Model):
members = models.ManyToManyField(User,blank=True,null=True,through='TripReservation')
But when I call user.group_set.all() for a given user instance, I get an error that there is no attribute group_set
First, are you using a through Model? You have through in there, but you don't have it listed. If you aren't you don't need it.
I would add a related_name, like so:
class Trip(models.Model):
members = models.ManyToManyField(User,blank=True,null=True, related_name='user_trips')
Then you should be able to call:
user.user_trips.all()
I called it 'user_trips' rather than 'trips' becuase if it isn't a unique name it can cause conflicts.
If you are using a through Model, it would look more like this:
#User is defined in django.auth
class Trip(models.Model):
members = models.ManyToManyField(User,blank=True,null=True, related_name='user_trips', through='TripReservation')
class TripReservation(models.Model):
user = models.ForeignKey(User)
trip = models.ForeignKey(Trip)
registered = models.DateField()
Understand that with this way, the TripReservation refers to a particular Users reservation to the Trip, not the whole trip, and information about the trip should be properties on the Trip model itself. So, TripReservation.registered, is when that particular user registered for the trip.
The user trips lookup would be the same:
user.user_trips.all()

Categories