Django Rest Framework Api View GET - python

In my codes, I have a model Tweet, and in tweet_list_view, I want to show the list of tweets as API view.
#api_view(['GET'])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(data=qs, many=True)
return Response(serializer.data)
This is what I got as a result.
AssertionError at /tweets/
When a serializer is passed a `data` keyword argument you must call `.is_valid()` before attempting to access the serialized `.data` representation.
You should either call `.is_valid()` first, or access `.initial_data` instead.
So I called the .is_valid method like following:
#api_view(['GET'])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(data=qs, many=True)
if serializer.is_valid():
return Response(serializer.data, status=201)
return Response({}, status=400)
Then I get:
TemplateDoesNotExist at /tweets/
rest_framework/api.html
At serializers.py
class TweetSerializer(serializers.ModelSerializer):
class Meta:
model = Tweet
fields = ['content', 'date_posted', 'likes']
models.py
class Tweet(models.Model):
content = models.TextField(blank=True, null=True)
image = models.FileField(upload_to='images/', blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
likes = models.IntegerField(default=0)
def __str__(self):
return self.content
class Meta:
ordering = ['-date_posted']
It's looking for a template, but it's supposed to use the default Django template. Is there any way to fix this problem?

Update:
forgot the # in front of the api_view. Added it. Also added the renderer_class (jsonrenderer) to make sure avoiding the error.
You need to use the data attribute of the serializer only when you have a post, put or patch view. In your case just try it without the data attribute and it should be fine
from rest_framework.renderers import JSONRenderer
#api_view(['GET'])
#renderer_classes([JSONRenderer])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(qs, many=True)
return Response(serializer.data)
Here you can see it in the tutorial example of the official django rest framework docs

Related

Creating a DetailView of a profile with a queryset of posts created by that user

I'm creating a twitter-like app and I'm stuck on creating a UserProfileView which is supposed to display a certain User's profile, along with a list of posts made by that user below. Though I can't really figure out a way to create a proper view for that.
I'm trying to use class based views for that, the one I'll be inheriting from is probably DetailView (for profile model) and something inside of that which retrieves a queryset of posts made by that user:
My profile model looks like this:
class Profile(models.Model):
user = models.OneToOneField(
User, on_delete=models.CASCADE, primary_key=True)
display_name = models.CharField(max_length=32)
profile_picture = models.ImageField(
default='assets/default.jpg', upload_to='profile_pictures')
slug = models.SlugField(max_length=150, default=user)
def get_absolute_url(self):
return reverse("profile", kwargs={"pk": self.pk})
Post model:
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateField(auto_now_add=True)
content = models.TextField(max_length=280)
image = models.FileField(upload_to='post_images/', blank=True, null=True)
def __str__(self) -> str:
return f'Post by {self.author} on {self.date_posted} - {self.content[0:21]}'
def get_absolute_url(self):
return reverse("post-detail", kwargs={"pk": self.pk})
I've tried creating this method:
class UserProfileView(DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'users/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_posts'] = Post.objects.filter(author=Profile.user)
return context
But this one sadly doesn't work, raising an error of
"TypeError: Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardOneToOneDescriptor object at 0x000001A5ACE80250>."
'ForwardOneToOneDescriptor' object has no attribute 'id' is returned if I replace the filter argument with author=Profile.user.id
I'm not sure whether it's a problem with the way I filtered Posts, or how I used get_context_data.
The object is stored as self.object, so you can filter with:
class UserProfileView(DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'users/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_posts'] = Post.objects.filter(author_id=self.object.user_id)
return context
An alternative might be to use a ListView for the Posts instead, to make use of Django's pagination:
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
class UserProfileView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'users/profile.html'
paginate_by = 10
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.filter(author__profile__slug=self.kwargs['slug'])
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['profile'] = get_object_or_404(Profile, slug=self.kwargs['slug'])
return context
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.

Django - which create() method to override before saving incoming data as object

I have the following model in my models.py file:
class Video(models.Model):
created = models.DateTimeField(auto_now_add=True)
text = models.CharField(max_length=100, blank=True)
video = models.FileField(upload_to='Videos/',
blank=True)
gif = models.FileField(upload_to='Images/',
blank=True)
class Meta:
ordering = ('created', )
Before Django creates an instance of this model, I want to set the gif field on the Django site.
So, the data comes to Django from the client site with the field video set, and based on the video field's content I want to fill the gif field.
For that, which create() method do I need to override for this task?
The create() method of my VideoSerializer class in serializers.py :
def create(self, validated_data):
return Video(**validated_data)
Or the create() method of my VideoCreate class in views.py:
class VideoCreate(generics.CreateAPIView):
queryset = Video.objects.all()
serializer_class = VideoSerializer
parser_classes = (MultiPartParser, )
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.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)
Hope someone can help

Handling related models in DRF and getting the right swagger docs for it

So I’m having a bit of trouble with something I was trying to do.
Basically, I have these models:
class Package(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255, null=False, blank=False)
contact = models.BooleanField(default=False)
downloaded = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
class Item(models.Model):
[…. all the atributes of the item model….]
class PackageItems(models.Model):
package = models.ForeignKey(Package, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
and now I am trying to make an endpoint that allows my users to add “package” and add a pre-existing item in an item model to that newly created package. One package can of course have many items.
SO I wrote a Package serializer then added a SerializerMethodField that allows one to do a get operation on any item that a given package contains. The method makes a call to different serializer.
Here’s the code for both the serializers:
class PackageItemsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PackageItems
fields = ('package', 'item')
class PackagesSerializer(serializers.ModelSerializer):
"""
Creation only requires a title
"""
package_items = serializers.SerializerMethodField(read_only=True)
#swagger_serializer_method(serializer_or_field=packageItemsSerializer)
def get_package_items(self, obj):
packageItems = PackageItems.objects.all().filter(package=obj.id)
return PackageItemsSerializer(packageItems, many=True, context={'request': self.context['request']}).data
def create(self, validated_data):
package = super(packagesSerializer, self).create(validated_data)
return package
class Meta:
model = packages
fields = ('id', 'user', 'title', 'contact', 'downloaded', 'created', 'package_items')
read_only_fields = [ 'user', 'contact', 'downloaded', 'package_items’]
Now for my views, I have decided to do this:
class PackagesViewSet(viewsets.ModelViewSet):
queryset = Packages.objects.all()
serializer_class = PackagesSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return Packages.objects.none()
return Packages.objects.filter(user=self.request.user).order_by('id')
def perform_create(self, serializer):
# sending default values for user, contact and downloaded
serializer.save(user=self.request.user, contact=False, downloaded=False)
def partial_update(self, request, *args, **kwargs):
response_with_updated_instance = super(PackagesViewSet, self).partial_update(request, *args, **kwargs)
return response_with_updated_instance
#action(methods=['post', 'delete'], detail=True, serializer_class=PackageItemsSerializer)
def item(self, request, pk=None):
package = self.get_object()
serializer = self.get_serializer_class()(data=request.data)
if serializer.is_valid():
serializer.save(package=package)
return Response(serializer.data)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
if request.method == 'delete':
serializer.delete(package=package)
This is what works with this:
I get these routes:
[get/post] for /api/packages/
[all methods] for /api/packages/{id}
[post/delete] for /api/packages/{id}/item
I get the right routes, but for one, Swagger gives me the wrong models for post:
{
"title": "string",
"package_items": {
"items": 0
}
}
Not sure why it's expecting this read_only field to be entered (which is a serializermethodfield as well) when I do a post to /api/packages.
Similarly, these fields show up for PUT under /api/packages/{id}.
For the nested "item" route, the POST works well, but the DELETE doesn't. The POST route allows me to enter an item ID and adds it to the given package ID under /api/packages/{id}/item route.
But delete doesn't allow me to enter an item ID to delete.
So I'm thinking my approach to the delete method for the nested ITEM is completely wrong.
I’m new to DRF/django, trying to validate if I’m going in the right direction with this.
How do I get drf-yasg to get me the right model for the HTTP verbs like POST for example?
What's the right way to delete the nested Item?
Am I approaching this problem right? Is there another way to do this more efficiently?
If someone can help me out to answer those questions, it would be really appreciated :)
Thanks in advance folks.
Instead of nesting serializer in serializer.SerializerMethodField directly, nest it like:
package_item = PackageItemsSerializer(source="package_set", many=True, read_only=True)
try this

Return URL in POST-method when working with base64ImageField and nested serializers

First of all, I'm pretty new to Django and equally new to backend development so please go easy on me.
I'm setting up a backend for a mobile application in which a user should be able to post an advert with an image attached to it over Http Requests. Right now, this works like it should, a user sends a POST request containing all the needed information and gets a response back containing all the relevant information, except for the image which is null. I know that the image successfully gets uploaded to the server, however I can't figure out how to return the URL of the image back into the response.
The following is my code:
models.py
class Advert(models.Model):
owner=models.ForeignKey(User, on_delete=models.CASCADE, related_name="adverts")
book_title = models.CharField(max_length=250)
price = models.PositiveSmallIntegerField()
created_at = models.DateTimeField(default=now())
def __str__(self):
return self.book_title + ' - ' + self.contact_info
class AdvertImage(models.Model):
advert = models.ForeignKey(Advert, on_delete=models.CASCADE, related_name="image", null=True)
image = models.ImageField(upload_to = 'ad_images', null=True)
def __str__(self):
return self.image.name
My serializers looks as following:
serializers.py
from rest_framework import serializers
from .models import Advert, AdvertImage
from drf_extra_fields.fields import Base64ImageField
from django.contrib.auth.models import User
class AdvertPostSerializer(serializers.ModelSerializer):
image = Base64ImageField(max_length=None, use_url=True, required=False)
class Meta:
model = Advert
fields = (
'id',
'price',
'book_title',
'image')
def create(self, validated_data):
try:
image_data = validated_data.pop('image')
except:
image_data = None
advert = Advert.objects.create(**validated_data)
if image_data is not None:
image = AdvertImage.objects.create(advert=advert, image=image_data)
return advert
And this is my view:
views.py
class AdvertViewSet(viewsets.ModelViewSet):
queryset = Advert.objects.all()
permission_classes = (AllowAny,)
def get_serializer_class(self):
if self.action == 'create':
return AdvertPostSerializer
return AdvertSerializer
#action(methods=['get'], detail=False)
def newest(self,request):
newest = self.get_queryset().order_by('created_at').last()
serializer = self.get_serializer_class()(newest)
return Response(serializer.data)
def perform_create(self, serializer):
return serializer.save(owner=self.request.user)
To illustrate what happens, here is a POST request:
POST http://localhost:8000/post-advert/
"Authorization: Token 980436128332ce3"
book_title=my book
price=1000
image=data:image/png;base64,iVBORHJvZmlsZSB0e//+egAAAAASUVORK5CYII=
And this is the response:
{
"book_title": "my book",
"id": 45,
"image": null,
"price": 1000,
}
Looking in the database and sending a second GET-request to another view shows that everything is uploaded as it should and the foreign keys and whatnot works like they should, it's just that I have a really hard time figuring out how to send back the URL of the image as a response to a successful POST.
Alright so I managed to come up with a (hacky?) solution.
In serializers.py i put my Base64Field as read_only=True:
class AdvertPostSerializer(serializers.ModelSerializer):
image = Base64ImageField(max_length=None, use_url=True, required=False, read_only=True)
...
Then, in my views.py for my AdvertViewSet, I've overwritten my create() method as such:
def create(self, request, format='json'):
serializer = PostAdvertSerializer(data=request.data)
if serializer.is_valid():
advert = serializer.save()
if advert:
json = serializer.data
advertImageURL = AdvertImage.objects.get(advert=advert).image.url
json['img_link'] = request.build_absolute_uri(advertImageURL)
return Response(json, status=status.HTTP_201_CREATED)
And this returns the full path to my image!
Because your get_queryset() method in
newest = self.get_queryset().order_by('created_at').last()
returns Advert model object:
class AdvertViewSet(viewsets.ModelViewSet):
queryset = Advert.objects.all()
which do not have image field. Then you are creating AdvertPostSerializer object and initializing it with newest queryset which is queryset of Advert model without your image.
serializer = self.get_serializer_class()(newest)
You can somehow obtain AdvertImage object inside #newest action and try to add it to response, but think you can create only one model Advert with your image field and one serializer for it, where you will define Base64ImageField.

Not Null constraint failed on Nested serializer due to "unable do get repr for QuerySet class" error

Trying to get basic Messaging functionality working in my DRF project. I seem to have a problem with the nested serializer validation. I'm new to DRF and have been reading the docs for a few days but I must have missed something.
The error happens inside the line message = Message.objects.create(**validated_data) in my serializer.The request returns an Integrity error, NOT NULL constraint failed on accounts_message.sender_id, but through debugging I have found that this is caused by an error in query.py in the QuerySet method : self: unable to get repr for <class 'django.db.models.query.QuerySet'> model: <class 'accounts.models.Message'> query: none using: none
This seems to be whats causing my issue, since the dict generated by the serializer seems to have all of the data thats being passed in the request, including the supposed null sender_id. However, I am not sure how to fix this issue. Do I need to override repr? or Query Set? This feels like im stuck on something relatively simple.
Serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'email')
extra_kwargs = {
'id': {
'validators': [UnicodeUsernameValidator()],
}
}
class MessageSerializer(serializers.ModelSerializer):
sender = UserSerializer()
recipients = UserSerializer(many=True)
class Meta:
model = Message
fields = ('id', 'subject', 'body', 'read', 'sender', 'recipients')
def create(self, validated_data):
sender_data = validated_data.pop('sender')
recipient_data = validated_data.pop('recipients')
message = Message.objects.create(**validated_data)
for user in recipient_data:
user= User.message_recip.get_or_create(id=user['id'], name=user['name'])
message.recipients.add(user)
for user in sender_data:
user= User.objects.get_or_create(id=user['id'], name=user['name'])
message.sender.add(user)
return message
Models.py
from django.db import models
class User(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=30)
email = models.CharField(max_length=75)
def __str__(self):
return self.name
class Message(models.Model):
id = models.IntegerField(primary_key=True)
subject = models.CharField(max_length=250)
body = models.CharField(max_length=5000)
read = models.BooleanField(default=False)
sender = models.ForeignKey(User, related_name='message_sender')
recipients = models.ForeignKey(User, related_name='message_recip', default=1)
Views.py
class MessageList(APIView):
def get(self, request):
messages = Message.objects.all()
serializer = MessageSerializer(messages, many=True)
return Response(serializer.data)
def post(self, request):
serializer = MessageSerializer(data=request.data)
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)
class User(APIView):
def get(self, request):
user = User.objects.get(id)
serializer = UserSerializer(user, many=False)
return Response(serializer.data)
If you give null=True and blank=True options to your ForeignKeys, you will not get IntegrityError anymore. Since you haven't added null & blank options, Django ORM creates a constraint in your DB and will not let you to add empty values to your foreign keys. Django Model Field Reference

Categories