Got AttributeError when attempting to get a value for field `people` on serializer `commentsSerializer` - django-models

I am building a blog website and I am using Django rest framework
I want to fetch top 2 comments for a particular post along with their related data such as user details.
Now I have user details in two models
User
People
and the comments model is related to the user model using foreign key relationship
Models ->
Comments
class Comment(models.Model):
comment = models.TextField(null=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments_post')
user = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='comments_user')
The People model is also connected to the user model with a foreign key relationship
People Model ->
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='people')
Name = models.CharField(max_length=255,null=True)
following = models.ManyToManyField(to=User, related_name='following', blank=True)
photo = models.ImageField(upload_to='profile_pics', blank=True,null=True)
Phone_number = models.CharField(max_length=255,null=True,blank=True)
Birth_Date = models.DateField(null=True,blank=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
for fetching the comments I am using rest-framework and the serializers look like this
class UserSerializer(serializers.Serializer):
username = serializers.CharField(max_length=255)
class peopleSerializer(serializers.Serializer):
Name = serializers.CharField(max_length=255)
class commentsSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
comment = serializers.CharField(max_length=255)
Created_date = serializers.DateTimeField()
user = UserSerializer()
people = peopleSerializer()
The query to fetch the comments look like this ->
post_id = request.GET.get('post_id')
comments = Comment.objects.filter(post_id=post_id).select_related('user').prefetch_related('user__people').order_by('-Created_date')[:2]
serializer = commentsSerializer(comments, many=True)
return Response(serializer.data)
I am getting this error ->
Got AttributeError when attempting to get a value for field `people` on serializer `commentsSerializer`. The serializer field might be named incorrectly and not match any attribute or key on the `Comment` instance. Original exception text was: 'Comment' object has no attribute 'people'.
Unable to find a way out.

The source is user.people, not people, so:
class commentsSerializer(serializers.Serializer):
# …
people = peopleSerializer(source='user.people')
In the .select_related(…) [Django-doc] to can specify user__people: this will imply selecting user and will fetch the data in the same query, not in an extra query as is the case for .prefetch_related(…) [Django-doc]:
post_id = request.GET.get('post_id')
comments = Comment.objects.filter(
post_id=post_id
).select_related('user__people').order_by('-Created_date')[:2]
serializer = commentsSerializer(comments, many=True)
return Response(serializer.data)
Note: normally a Django model is given a singular name, so Person instead of People.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: created_date instead of Created_date.

Related

how to send request to django database (case: the database has foreignKey)

I am learning reactjs and django by making a blog but I have a question: how to post a request to server when the database has ForeignKey field ?
Example: I am making the comment section
Can someone help me to solve this ? Thanks for helping !
this is my code !
models.py
class Article(models.Model):
title = models.CharField(max_length = 116)
content = models.TextField()
user_token = models.TextField(null = True)
def __str__(self):
return self.title
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete = models.CASCADE)
user = models.CharField(max_length = 116, null = True)
comment = models.TextField()
def __str__(self):
return self.comment
If you want to display all the comments attached to an article, you could make a query in your related view in views.py like so:
comments = Comment.objects.get(article=[Insert your desired article primary key here])
If you have not set your primary key on Article, which is the case in the code snippet you shared, your primary key will be an integer automatically incremented by Django as you create Articles.
Read more here: https://docs.djangoproject.com/en/3.2/topics/db/models/
If you do not want [Insert your desired article primary key here] to be a meaningless integer, you could set your Article's primary key to be the title, like so:
class Article(models.Model):
title = models.CharField(max_length = 116, primary_key=True)
content = models.TextField()
user_token = models.TextField(null = True)
def __str__(self):
return self.title
You would then query the related comments this way:
comments = Comment.objects.get(article=[Insert your article's title here])
Finally, if you do not want to change the primary key, try:
comments = Comment.objects.get(article=Article.objects.get(title=[Insert your article's title here]))

How to add different many-to-many field into a single instance or row of a foreign key in django-rest-framework using model serializer

I am creating an ecommerce website using Django-rest-framework and react. I am trying to add items to cart. I am able to add items to the cart on the frontend but I want to store the cart data to the backend (Django) database so that whenever the user add items to cart and reloads the page the items should still be in his cart like any other ecommerce site. Here is my Code for django models, serializers, viewset.
class Products(models.Model):
title = models.CharField(max_length=100)
image = models.URLField()
description = models.TextField(max_length=500, blank=True, null=True)
category = models.CharField(max_length=50, blank=True, null=True)
rating = models.IntegerField(blank=True, null=True)
price = models.FloatField()
def __str__(self):
return f"{self.id}"
class Cart(models.Model):
product = models.ManyToManyField(
Products, related_name="cart")
buyer = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="cart")
def __str__(self) -> str:
return f"{self.buyer}"
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = '__all__'
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = '__all__'
class ProductsViewSet(viewsets.ModelViewSet):
queryset = Products.objects.all()
serializer_class = ProductsSerializer
class CartViewSet(viewsets.ModelViewSet):
queryset = Cart.objects.all()
authentication_classes = [JWTAuthentication]
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CartSerializer
router = routers.DefaultRouter()
router.register('products', ProductsViewSet, 'products')
router.register('cart', CartViewSet, 'cart')
I am using postman to post the cart items. I am able to add more than one products for a single buyer.
but the problem is when i again add another product to the same user using postman i added before, it creates another row for the same user.
I do not want that. I want a single instance or row of a user in cart table and add as many products as i want. when i post other products for the same user, that product should also get added up in the single user row or instance. What is the best way to achieve my goal.
Here is the issue, Django can't automatically do that because it doesn't know which behaviour is expected. If you look at the code to add a product and look at the code to add a cart, it's exactly the same. So behaviour will also be the same.
For the behaviour that you want, you will have to override the create method of your ModelViewSet.
Here are the steps to achieve what you want -
Check whether or not the user with that id already has a cart.
If they have a cart, then you'll need to fetch the cart object belonging to that user and add products to it.
If they don't, then you'll have to create a new cart object and do the default thing.
class CartViewSet(viewsets.ModelViewSet):
queryset = Cart.objects.all()
authentication_classes = [JWTAuthentication]
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CartSerializer
def create(self, request):
# Checking whether the Cart object already exists for the buyer
cart = Cart.objects.filter(buyer_id = request.data.get("buyer"))
if len(cart)=1:
#If cart exists, we loop through the list of product ids and add them
for product_id in request.data.get("product"):
cart[0].product.add(get_object_or_404(Product, id = product_id ))
if len(cart)=0:
# Doing what normally happens.
return super().create(request)
if len(cart)>1:
#Error in database. One person having multiple carts. Custom error message.
To check out how to add data to many-to-many fields, check this out.

Django: Updating models, new instance created instead of updated

i am trying to make a simple profile edit form for users on a website. I've followed the standard advice for updating, in the docs it says that Django detects the instances primary key and knows to update instead of insert.
only problem is, i get an insert when i am trying to update. I pre populate a form with a model instance (the instance that im trying to edit) but when i try and save it, i get a new instance. When i add the 'force_update=True' line, i get an error message that tells me that no primary key is detected. Not sure why, because im pre populating the form with a model instance, although, obviously the pk is not a part of the form. is there something im missing?
some code:
the model:
class profile(models.Model):
user = models.ForeignKey(User)
first_name = models.CharField(max_length=20, null=True)
last_name = models.CharField(max_length=20, null=True)
DOB = models.DateField(null=True)
age = models.IntegerField(null=True)
public_email = models.EmailField(null=True)
county = models.CharField(max_length=20, null=True)
town = models.CharField(max_length=30, null=True)
the form:
class profileForm(forms.ModelForm):
class Meta:
model = profile
exclude = ['user']
the view:
#login_required()
def edit_profile(request):
if request.POST:
proform = profileForm(request.POST)
if proform.is_valid():
prof = proform.save(False)
prof.user = request.user
prof.save(force_update=True)
return HttpResponseRedirect('/accounts/view_profile/')
else:
c = {}
if profile.objects.filter(user=request.user).exists():
prof = profile.objects.get(user=request.user)
c['proform'] = profileForm(instance=prof)
else:
c['proform'] = profileForm()
return render(request, 'edit_profile.html', c)
any help greatly appreciated!
i got it, turns out i was trying to just calling save() on the form without specifying the particular instance that the form relates to.
code:
#login_required()
def edit_profile(request):
c = {}
if profile.objects.filter(user=request.user).exists():
profModel = profile.objects.get(user=request.user)
c['proform'] = profileForm(instance=profModel)
else:
c['proform'] = profileForm()
if request.POST:
# this line here, added 'instance=profModel' to specify
# the actual instance i want to save
proform = profileForm(request.POST, instance=profModel)
if proform.is_valid():
prof = proform.save(False)
prof.user = request.user
prof.save()
return HttpResponseRedirect('/accounts/view_profile/')
else:
return render(request, 'edit_profile.html', c)
works!

How to structure movies database and user choices?

I would like to create movies database, where user will be able to mark movies he/she watched and liked:
class Movies(ndb.Model):
watched = ndb.UserProperty()
liked = ndb.UserProperty()
Will that work? I use Google accounts.
How should I choose later all movies user liked?
Upd. I've followed systempuntoout approach and use the following code to save user choices:
user = users.get_current_user()
if user:
userschoices = models.UsersChoices(
movie=ndb.Key(models.Movies, movie_id), # TODO: what if movie_id is wrong?
watched=True,
user_id=user.user_id()
)
try:
userschoices.put()
self.response.out.write('1')
except:
self.response.out.write('0')
But if user makes his choice several times, then several records are added to the datastore...
Wouldn't be it better just to save user id and movie id as keyname?
userschoices = models.UsersChoices.get_by_id(user.user_id() + '-' + movie_id)
if userschoices is None:
userschoices = models.UsersChoices(id=user.user_id() + '-' + movie_id)
userschoices.movie = ndb.Key(models.Movies, movie_id) # TODO: what if movie_id is wrong?
userschoices.user_id = user.user_id()
if option == 'liked':
userschoices.liked = True
elif option == 'watched':
userschoices.watched = True
However, with such approach if I don't pass liked, then it overwrites its value with None (the same with watched, if not passed, None is used).
I would go with two different Models, one that stores all the Movies details and one to store the UserChoices :
class Movies(ndb.Model):
title = ndb.StringProperty(required=True)
director = ndb.StringProperty()
whatever = ndb.StringProperty()
class UsersChoices(ndb.Model):
movie = ndb.KeyProperty(kind=Movies, required=True)
watched = ndb.BooleanProperty(required=True)
liked = ndb.BooleanProperty(required=True)
user_id = ndb.StringProperty(required=True)
#classmethod
def get_liked_movies(cls, user_id):
return cls.query(cls.user_id == user_id, cls.liked == true).fetch(10)
#classmethod
def get_watched_movies(cls, user_id):
return cls.query(cls.user_id == user_id, cls.watched == true).fetch(10)
#classmethod
def get_by(cls, user_id, movie_key):
return cls.query(cls.user_id == user_id, cls.movie == movie_key).get()
If you need to store informations about users you should create your UserInfo Model, keyed by user_id from the users API, with all the details Properties your application needs.
class UserInfo(ndb.Model):
#Keyed by user_id
nickname = ndb.StringProperty()
email = ndb.StringProperty()
To create a new UserInfo, you could do:
from google.appengine.api import users
user = users.get_current_user()
userinfo = UserInfo(
id = user.user_id(),
nickname = user.keyname(),
email = user.email()
)
userinfo.put()
Then, when the user is logged in, use his/her user_id to retrieve the watched/liked movies.
from google.appengine.api import users
user = users.get_current_user()
userinfo = ndb.Key(UserInfo, user.user_id()).get()
watched_movies = UsersChoices.get_watched_movies(userinfo.key.id())
liked_movies = UsersChoices.get_liked_movies(userinfo.key.id())
It appears you are trying to model a many-to-many relationship. There are a few ways to model this relationship (see the Many-to-Many section). See also Nick's blog. (Unfortunately, neither of those references are written for NDB, so, for example, you can't use collection_name, i.e., back-references. But they are still useful in showing you how to break up the data into different models.)
Here's one way you could do it, using "join tables"/"relationship models":
class Movie(ndb.Model):
title = ndb.StringProperty(required=True)
class LikedMovie(ndb.Model):
movie = ndb.KeyProperty(kind=Movie, required=True)
user = ndb.StringProperty(required=True) # user.user_id()
class WatchedMovie(ndb.Model):
movie = ndb.KeyProperty(kind=Movie, required=True)
user = ndb.StringProperty(required=True) # user.user_id()
...
movies_user_likes = LikedMovie.query(LikedMovie.user == user.user_id()).fetch()
Depending on how many users your application will support, and how often the database will be updated, it may be more efficient to use repeated properties (i.e., lists of users) instead of join tables:
class Movie(ndb.Model):
title = ndb.StringProperty(required=True)
users_who_watched = ndb.StringProperty(repeated=True) # list of user.user_id()s
users_who_liked = ndb.StringProperty(repeated=True) # list of user.user_id()s
...
movies_user_likes = Movie.query(Movie.users_who_liked == user.user_id()).fetch(projection=[Movie.title])
Note that I used a projection query above, so that the users_who_watched lists are not returned with the query results. You probably don't need these, and this should make fetching significantly faster.
If you expect, say, less than 1,000 users to watch or like a particular movie, the list approach might be better.
For a more advanced technique, see Building Scalable, Complex Apps on App Engine, where Brett shows how to move the repeated/list property into a separate model, using parent keys.

How do I query a many to many relationship model? - Google App Engine

Here are my models:
class User(db.Model):
id = db.StringProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
name = db.StringProperty(required=True)
email = db.StringProperty()
class Page(db.Model):
id = db.StringProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
name = db.StringProperty(required=True)
link = db.StringProperty(required=True)
class UserPage(db.Model):
user = db.ReferenceProperty(User, collection_name='pages')
page = db.ReferenceProperty(Page, collection_name='users')
How would I construct a query to find a users pages?
I found an article that describes a method to do it but is this the best way? http://blog.arbingersys.com/2008/04/google-app-engine-better-many-to-many.html
Your answer will work, but it will perform 7 calls to the datastore:
1 for the call to User.get_by_key_name()
1 for the call to UserPage...fetch()
5 for each dereference of x.page.id inside the loop
An alternative approach which only does 3 calls to the datastore would be something like this:
myuser = User.get_by_key_name("1")
up = UserPage.all().filter('user =', myuser).fetch(5)
keys = [UserPage.page.get_value_for_datastore(x) for x in up]
pages = db.get(keys)
for p in pages:
self.response.out.write(p.id)
See http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine for more details.
After some testing, it appears I can use:
myuser = User.get_by_key_name("1")
up = UserPage.all().filter('user =', myuser).fetch(5)
for x in up:
self.response.out.write(x.page.id)
I would recommend a different approach, that is less "relational-oriented" than your UserPage relationship:
class User(db.Model):
id = db.StringProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
name = db.StringProperty(required=True)
email = db.StringProperty()
class Page(db.Model):
id = db.StringProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
name = db.StringProperty(required=True)
link = db.StringProperty(required=True)
# Users linking to this page
users = db.ListProperty(db.Key)
And then you can get all pages of a specific user with the following query:
Page.gql("WHERE users = :1", user.key())
Please note that you should place the list property of keys on the side where you expect less items. I've assumed you will have less users liked to a page, than pages linked to a user, so I've put it on the Page side, but that will depend on your specific use case.
See here for official recommendations on the many-to-many topic: http://code.google.com/appengine/articles/modeling.html

Resources