Model using ForeignKey giving conflicting errors in views.py. Code will work in one function, but not another. Not able to filter data - django-models

I have been working on the watchlist function for this project and am really struggling to understand what is happening. I seem to get the error str returned non-string (type tuple) with the.
item_exists = Watchlists.objects.filter(user=request.user, item=item).exists()
in function watchlist_remove; however, I do not receive this error in function watchlist_add function.
Additionally, when I try to loop over the items in function watchlist, I am getting this error 'WSGIRequest' object has no attribute 'item'
listings = Watchlists.objects.filter(item=request.item)
when I very clearly have an item field in my model as well as a related_name"item" and I am returning self.item and saved it into the db with the variable name item.
Here is my model
class Watchlists(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
related_name="user")
item = models.ForeignKey(Listings, on_delete=models.CASCADE, null=True, blank=True,
related_name="item")
def __str__(self):
return f"{self.item}", self.item, str(self.item), str(self.user)
I figure there has to be an issue with my model as other people seem to be able to use these lines of code without issue, but I am hesitant to make any changes to the model until I know what the problem is.
That said, I realize that it is returning both the item and the user and thus a tuple; however, I am unsure as to why the filter function and the return statements in my model aren't fixing this when these recommendations have worked in other posts.
Please advise.
#login_required
def watchlist(request):
listings = Watchlists.objects.filter(item=request.item)
return render(request, 'auctions/watchlist.html', {
"listings" : listings,
})
#login_required
def watchlist_add(request, listing_id):
if request.method == "POST":
listing = Listings.objects.get(pk=listing_id)
item = Listings.objects.get(pk=listing_id)
item_exists = Watchlists.objects.filter(user=request.user, item=item).exists()
if item_exists:
return render(request, "auctions/listing.html", {
"listing" : listing,
'item_exists': item_exists
})
else:
Watchlists.objects.create(user=request.user, item=item)
return render(request, "auctions/listing.html", {
"listing" : listing,
'item_exists': item_exists
})
#login_required
def watchlist_remove(request, listing_id):
item = Watchlists.objects.get(pk=listing_id)
if request.method == "POST":
listing = Listings.objects.get(pk=listing_id)
item_exists = Watchlists.objects.filter(user=request.user, item=item).exists()
if item_exists:
return render(request, "auctions/listing.html", {
"listing" : listing,
'item_exists': item_exists
})
else:
item.delete()
return render(request, "auctions/listing.html", {
"listing" : listing,
})
'''

Related

How can I automaticall add the currently logged in user to django models in react [duplicate]

I have the following code working perfectly. I can create a Post object from DRF panel by selecting an image and a user. However I want DRF to populate the user field by the currently logged in user.
models.py
class Post(TimeStamped):
user = models.ForeignKey(User)
photo = models.ImageField(upload_to='upload/')
hidden = models.BooleanField(default=False)
upvotes = models.PositiveIntegerField(default=0)
downvotes = models.PositiveIntegerField(default=0)
comments = models.PositiveIntegerField(default=0)
serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'user', 'photo']
views.py
class PhotoListAPIView(generics.ListCreateAPIView):
queryset = Post.objects.filter(hidden=False)
serializer_class = PostSerializer
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
How can I do this?
Off the top of my head, you can just override the perform_create() method:
class PhotoListAPIView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Give that a shot and let me know if it works
You can use CurrentUserDefault:
user = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
It depends on your use case. If you want it to be "write-only", meaning DRF automatically populates the field on write and doesn't return the User on read, the most straight-forward implementation according to the docs would be with a HiddenField:
class PhotoListAPIView(generics.ListCreateAPIView):
user = serializers.HiddenField(
default=serializers.CurrentUserDefault(),
)
If you want want it to be readable, you could use a PrimaryKeyRelatedField while being careful that your serializer pre-populates the field on write - otherwise a user could set the user field pointing to some other random User.
class PhotoListAPIView(generics.ListCreateAPIView):
user = serializers.PrimaryKeyRelatedField(
# set it to read_only as we're handling the writing part ourselves
read_only=True,
)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Finally, note that if you're using the more verbose APIView instead of generics.ListCreateAPIView, you have to overwrite create instead of perform_create like so:
class PhotoListAPIView(generics.ListCreateAPIView):
user = serializers.PrimaryKeyRelatedField(
read_only=True,
)
def create(self, validated_data):
# add the current User to the validated_data dict and call
# the super method which basically only creates a model
# instance with that data
validated_data['user'] = self.request.user
return super(PhotoListAPIView, self).create(validated_data)
You can avoid passing the user in your request and you won't see it in the output but DRF will populate it automatically:
from rest_framework import serializers
class MyModelSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.MyModel
fields = (
'user',
'other',
'fields',
)
As of DRF version 3.8.0 (Pull Request discussion), you can override save() in serializer.
from rest_framework import serializers
...
class PostSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Post
fields = ['id', 'user', 'photo']
def save(self, **kwargs):
"""Include default for read_only `user` field"""
kwargs["user"] = self.fields["user"].get_default()
return super().save(**kwargs)
#DaveBensonPhillips's answer might work in your particular case for some time, but it is not very generic since it breaks OOP inheritance chain.
ListCreateAPIView inherits from CreateModelMixin which saves the serializer already. You should always strive to get the full chain of overridden methods executed unless you have a very good reason not to. This way your code stays DRY and robust against changes:
class PhotoListAPIView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.validated_data['user'] = self.request.user
return super(PhotoListAPIView, self).perform_create(serializer)
You will have to override the default behavior of how generics.ListCreateAPIView creates an object.
class PhotoListAPIView(generics.ListCreateAPIView):
queryset = Post.objects.filter(hidden=False)
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get_serializer_class(self):
if self.request.method == 'POST':
return CreatePostSerializer
else:
return ListPostSerializer
def create(self, request, *args, **kwargs):
# Copy parsed content from HTTP request
data = request.data.copy()
# Add id of currently logged user
data['user'] = request.user.id
# Default behavior but pass our modified data instead
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
The .get_serializer_class() is not necessary as you can specify which fields are read-only from your serializer, but based on the projects I have worked on, I usually end up with 'asymmetric' serializers, i.e. different serializers depending on the intended operation.
Try this:
def post(self, request, format=None)
serializer = ProjectSerializer(data=request.data)
request.data['user'] = request.user.id
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST
This is what works for me in serializers.py, where I am also using nested data. I want to display created_by_username without having to lookup other users.
class ListSerializer(serializers.ModelSerializer):
"""
A list may be created with items
"""
items = ItemSerializer(many=True)
# automatically set created_by_id as the current user's id
created_by_id = serializers.PrimaryKeyRelatedField(
read_only=True,
)
created_by_username = serializers.PrimaryKeyRelatedField(
read_only=True
)
class Meta:
model = List
fields = ('id', 'name', 'description', 'is_public',
'slug', 'created_by_id', 'created_by_username', 'created_at',
'modified_by', 'modified_at', 'items')
def create(self, validated_data):
items_data = validated_data.pop('items', None)
validated_data['created_by_id'] = self.context['request'].user
validated_data['created_by_username'] = self.context['request'].user.username
newlist = List.objects.create(**validated_data)
for item_data in items_data:
Item.objects.create(list=newlist, **item_data)
return newlist
I wrote an extension to DRF's serializer below
from rest_framework import serializers
class AuditorBaseSerializer(serializers.Serializer):
created_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
updated_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
def save(self, **kwargs):
# if creating record.
if self.instance is None:
kwargs["created_by"] = self.fields["created_by"].get_default()
kwargs["updated_by"] = self.fields["updated_by"].get_default()
return super().save(**kwargs)
and it can be used as follows
class YourSerializer(serializers.ModelSerializer, AuditorBaseSerializer):
class Meta:
model = SelfEmployedBusiness
fields = (
'created_by',
'updated_by',
)

Unable to pass django model object to serializer

I am trying to pass a django model object to a field in a serializer that is for a foreign key field in the model. However, I get the error: "Object of type AuthorUser is not JSON serializable."
Here is the model the serializer is for:
class Article(models.Model):
title = models.CharField(max_length=200, unique=True)
author = models.ForeignKey(AuthorUser, on_delete=models.CASCADE)
body = models.TextField()
posted=models.BooleanField(default=False)
edited = models.BooleanField(default=False)
ready_for_edit = models.BooleanField(default=False)
Here is the serializer (author is the field specifically that is giving me trouble):
class CreateArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'body', 'author']
And here is the view that has the code that causes the error (the POST method is the part that causes the error):
#api_view(['GET', 'POST'])
def articles(request):
if request.method == 'GET':
articles = Article.objects.all()
serializer = CreateArticleSerializer(articles, many=True)
return Response(serializer.data)
if request.method == 'POST':
if request.user.is_authenticated:
author = AuthorUser.objects.get(id=request.data['author'])
request.data['author'] = author
print(request.data)
serializer = CreateArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return HttpResponse(status=401)
Any help is appreciated! Just to let you know, when creating these articles using an id, it works, however, it creates a new field in Article called author_id. Then when I try to access author it gives me author_id so that doesn't work.
You need to change your serializer as follows:
class CreateArticleSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(queryset=AuthorUser.objects.all(),
slug_field='id')
class Meta:
model = Article
fields = ['title', 'body', 'author']
Now if you will pass id in your view while calling serializer it will create the object of model. Hope this will work for you.

Retrieving and Posting data from/to a related table in Django APIView

I am working on a Django application with two Models.
Messages - that contain twitter style messages
Feedback - response to messages including comments, likes, dislikes
I am writing APIView for Feedback, but it is not GETing or POSTing the relevant messages even though I can browse through the admin panel.
The code is as follows:
Models:
class Messages(models.Model):
postIdentifier = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
message = models.TextField(null=False)
class Feedback(models.Model):
isLiked = models.BooleanField(null=True)
isDisliked = models.BooleanField(null=True)
comment = models.TextField(null=True)
post = models.ForeignKey(Messages, on_delete=models.CASCADE)
Serializers:
class MessagesSerializer(serializers.ModelSerializer):
class Meta:
model = Messages
fields = ['postIdentifier', 'title', 'message']
class FeedbackSerializer(serializers.ModelSerializer):
message = MessagesSerializer()
class Meta:
model = Feedback
fields = ['isLiked', 'isDisliked', 'comment', 'post']
APIView for Feedback model
def get(self, request, id):
related_message_object = Messages.objects.filter(id)
feedback = Feedback.objects.filter('post'= related_message_object)
serializer = FeedbackSerializer(feedback)
return Response({
'data': serializer.data
})
def post(self, request, *args, **kwargs):
serializer = FeedbackSerializer(data= request.data)
if serializer.is_valid():
serializer.save()
return Response({
'message': 'Feedback data posted successfully!',
'data': serializer.data
})
The URLPattern for the feedback is
path('feedback/< id >/', FeedbackAPIView.as_view()),
#I am aware of spaces here on either side of id as I am unable to post the id with brackets on.
I am going through the rest_framework documentation, but I can not find relevant documentation with regard to the problem. Can anyone kindly point out my mistake in the get() method of FeedbackAPIView. I am hoping to achieve that when I browse feedback/2/ I want to see feedback relavant to Message with postIdentifier = 2. Any feedback is much appreciated!
As the feedback resource url is path('feedback/< id >/', FeedbackAPIView.as_view()),
then at def get(self, request, id): you should filter feedback by id :
feedback = Feedback.objects.filter(id=id).first()

Django - save form into database

Hey guy I need your help
so I have this model:
class PreferedShops(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
shop = models.ForeignKey(Shops, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.user.username, self.shop.name
and this is the form:
class LikeShopForm(forms.ModelForm):
class Meta:
model = PreferedShops
fields = ['date_posted']
and this is the view:
def shops(request):
if request.method == 'POST':
form = LikeShopForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.shop = request.shop
u.save()
return redirect('shops')
else:
form = LikeShopForm()
return render(request, "shops.html", {'form': form})
the probleme that I have is when I click on Like Button, I want that the form takes automatically the user and the name of the shop, and then save them into the DB
the user and the shop's name should be hidden
when I click submit I have this error 'WSGIRequest' object has no attribute 'shop'
please help me to take the shop's name automatically and save it in the db
Well the shop is not part of the request (strictly speaking, user is not either, but it is frequently added to the request object in the middelware by looking at the session).
You thus need to encode it into the URL, or in the POST parameters. For example with:
# app/urls.py
from django.urls import path
from app.views import like_shop
urlpatterns = [
path('shop/<int:shop_id>/like', like_shop, name='like_shop'),
]
Then in the view we obtain a parameter shop_id that contains the id of the related Shops object:
from django.shortcuts import get_object_or_404
from app.models import Shops
def like_shop(request, shop_id):
if request.method == 'POST':
shop = get_object_or_404(Shops, id=shop_id)
form = LikeShopForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.shop = shop
u.save()
return redirect('shops')
else:
form = LikeShopForm()
return render(request, "shops.html", {'form': form, 'shop_id': shop_id})
Then the request in the POST should point to:
<!-- shops.html -->
<form action="{% url 'like_shop' shop_id=shop_id %}" method="post">
<!-- ... -->
</form>
The URL to this page then thus looks like /shops/123/like with 123 the id of the shop. If you thus want to pass the shop "implicitly", you need to encode it in the URL. Otherwise, you should make it a field of the form, such that the user can pick an option. Personally I find it very strange that you use date_posted as a form field, since typically this is an field that I would expect to be filled in with the timestamp when the user likes the shop.
Note: the name of the models is normally singular, so Shop instead of Shops.

Django throws column cannot be null error when POSTing to REST Framework

Django REST Framework is reporting an error that a value is null even though I am sending the value when I POST the data.
The error Django reports is:
django.db.utils.IntegrityError: (1048, "Column 'owner_id' cannot be null")
[04/Apr/2016 18:40:58] "POST /api/items/ HTTP/1.1" 500 226814
The Angular 2 code that POSTs to Django REST Framework API is:
let body = JSON.stringify({ url: 'fred', item_type: 'P', owner_id: 2 });
let headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post('http://127.0.0.1:8000/api/items/',
body, {
headers: headers
})
.subscribe(
data => {
alert(JSON.stringify(data));
},
err => alert('POST ERROR: '+err.json().message),
() => alert('POST Complete')
);
My Django API view looks like this:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all().order_by('-date_added')
serializer_class = ItemSerializer
"""
Use the API call query params to determing what to return
API params can be:
?user=<users_id>&num=<num_of_items_to_return>&from=<user_id_of_items_to_show>
"""
def get_queryset(self):
this_user = self.request.query_params.get('user', None)
restrict_to_items_from_user_id = self.request.query_params.get('from', None)
quantity = self.request.query_params.get('num', 20)
if restrict_to_items_from_user_id is not None:
queryset = Item.objects.filter(owner=restrict_to_items_from_user_id, active=True).order_by('-date_added')[0:int(quantity)]
elif this_user is not None:
queryset = Item.objects.filter(active=True, credits_left__gt=0).exclude(pk__in=Seen.objects.filter(user_id=this_user).values_list('item_id', flat=True))[0:int(quantity)]
else:
queryset = Item.objects.filter(active=True, credits_left__gt=0)[0:int(quantity)]
print("User id param is %s and quantity is %s" % (user_id,quantity))
return queryset
The associated model is:
class Item(models.Model):
ITEM_TYPES = (
('V', 'Vine'),
('Y', 'YouTube'),
('P', 'Photo'), # Photo is stored by us on a CDN somewhere
('F', 'Flickr'),
('I', 'Instagram'),
('D', 'DeviantArt'),
('5', '500px'),
)
owner = models.ForeignKey(User, on_delete=models.CASCADE) # Id of user who owns the item
title = models.CharField(max_length=60, default='') # URL of where item resides (e.g. Vine or YouTube url)
url = models.CharField(max_length=250, default='') # URL of where item resides (e.g. Vine or YouTube url)
item_type = models.CharField(max_length=1, choices=ITEM_TYPES) # Type of item (e.g. Vine|YoutTube|Instagram|etc.)
keywords = models.ManyToManyField(Keyword, related_name='keywords')
# E.g. Art, Travel, Food, etc.
credits_applied = models.IntegerField(default=10, help_text='Total number of credits applied to this item including any given by VeeU admin')
# Records the total number of credits applied to the Item
credits_left = models.IntegerField(default=10, help_text='The number of credits still remaining to show the item')
# Number of credits left (goes down each time item is viewed
credits_gifted = models.IntegerField(default=0, help_text='The number of credits this item has been gifted by other users')
# Number of credits users have gifted to this item
date_added = models.DateTimeField(auto_now_add=True) # When item was added
liked = models.IntegerField(default=0) # Number of times this item has been liked
disliked = models.IntegerField(default=0) # Number of times this item has been disliked
active = models.BooleanField(default=True, help_text='If you mark this item inactive please say why in the comment field. E.g. "Inapproriate content"')
# True if item is available for showing
comment = models.CharField(max_length=100, blank=True) # Comment to be applied if item is inactive to say why
# Add defs here for model related functions
# This to allow url to be a clickable link
def item_url(self):
return u'%s' % (self.url, self.url)
item_url.allow_tags = True
def __str__(self):
return '%s: Title: %s, URL: %s' % (self.owner, self.title, self.url)
I can't see what is wrong with my POST call, or the Django code.
EDIT: Added serializer code
Here is the associated serializer
class ItemSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.SerializerMethodField()
def get_username(self, obj):
value = str(obj.owner)
return value
def get_keywords(self, obj):
value = str(obj.keywords)
return value
class Meta:
model = Item
fields = ('url', 'item_type', 'title', 'credits_applied', 'credits_left', 'credits_gifted', 'username', 'liked', 'disliked')
Your serializer doesn't have an owner field and your view doesn't provide one. As it's non null in your model the DB will complain about this.
You should override the view's perform_update and add the owner as extra argument to the serializer
You need to pass the Resource URI for the field which is foreign key,
here owner is the FK, so the
ownerIns = User.objects.get(id=2)
let body = JSON.stringify({ url: 'fred', item_type: 'P', owner: ownerIns, owner_id: ownerIns.id });
I ran into a similar issue using Angular and Flask. This may because your CORS headers are not set correctly for your Django app which makes your Angular app not allowed to post to the backend. In Flask, I fixed it using this code:
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response
I'm not sure how to do this in Django, but this may be a great first stop for you since this is likely your issue.
Is your ItemSerializer code correct? The rest looks fine to me.
You should be having 'owner_id' in your serializer fields i think.
Take a look at this answer, add the related fields in this manner.
https://stackoverflow.com/a/20636415/5762482

Resources