Django API: Problem with Displaying a Field - python

I am a beginner in Django. Right now, I am working with the APIs. I am facing a problem. I can't view one of the fields, called label, at http://127.0.0.1:8000/gameapi/. Here is the screenshot:
Here are my codes of serializers.py located inside gamreview folder.
from rest_framework import serializers
from .models import Game, Tags
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tags
fields = ['label']
class GameSerializer(serializers.ModelSerializer):
# label_tag = TagSerializer(many=True)
class Meta:
model = Game
fields = ['id', 'title', 'developer', 'platform']
fields = ['id', 'title', 'developer', 'platform','label_tag']
def create(self, validated_data):
label_tag_data = validated_data.pop('label_tag')
game = Game.objects.create(**validated_data)
for tags_data in label_tag_data:
Tags.objects.create(game=game, **tags_data)
return Game.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.developer = validated_data.get('developer', instance.developer)
instance.platform = validated_data.get('platform', instance.platform)
instance.tag = TagSerializer(read_only=True, many=True)
instance.save()
return instance
Here are my codes of models.py under gamreview folder:
from django.db import models
from django.template.defaultfilters import slugify
# Create your models here.
class Tags(models.Model):
label = models.CharField(max_length=20)
def __str__(self):
return self.label
class Game(models.Model):
title = models.CharField(max_length=100)
developer = models.CharField(max_length=100)
platform = models.CharField(max_length=50, default='null')
label_tag = models.ManyToManyField(Tags)
slug = models.SlugField(max_length=150, default='null')
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super().save(*args, **kwargs)
class Review(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
review = models.CharField(max_length=1000)
date = models.DateField(auto_now=True)
slug = models.SlugField(max_length=150, default='null')
def __str__(self):
return self.game
Here are my codes of views.py under gamreview folder:
from django.views import generic
from .models import Game
from rest_framework import generics
from .serializers import GameSerializer
# Create your views here.
class GameListView(generic.ListView):
template_name = 'gamereview/gamelist.html'
context_object_name = 'all_games'
def get_queryset(self):
return Game.objects.all()
class ReviewView(generic.DetailView):
model = Game
template_name = 'gamereview/review.html'
# class GameApiView(generics.ListAPIView):
class GameApiView(generics.ListCreateAPIView):
queryset = Game.objects.all()
serializer_class = GameSerializer
class GameDetailApiView(generics.RetrieveUpdateDestroyAPIView):
queryset = Game.objects.all()
serializer_class = GameSerializer
Here are my codes of urls.py under gamreview folder:
from . import views
from django.urls import path
app_name = 'gamereview'
urlpatterns = [
path('gamereview/', views.GameListView.as_view(), name='gamelist'),
path('gamereview/<slug:slug>/', views.ReviewView.as_view(), name='review'),
path('gameapi/', views.GameApiView.as_view(), name='gamelistapi'),
path('gameapi/<int:pk>/', views.GameDetailApiView.as_view()),
]
I don't get any error while running the server. However, the label field is not showing up.
How can I fix the issue?

You have declared fields twice in GameSerializer- Meta class. Delete the first one.
class GameSerializer(serializers.ModelSerializer):
# label_tag = TagSerializer(many=True)
class Meta:
model = Game
fields = ['id', 'title', 'developer', 'platform'] --> delete this
fields = ['id', 'title', 'developer', 'platform','label_tag']

Django rest framework relies on related_name attribute to the resolve foreign key fields.
I think just changing your Game model so would do it
label_tag = models.ManyToManyField(Tags, related_name="label")

Related

How do I add a post method for the api view so that posts can be commented on?

I'm building an API using DRF, and as I am a beginner, I can't get my head across this.
I'm building an instagram clone using DRF and was able to attach comments and likes to each post object, but I can only attach likes and comments using the admin panel. I can see the total number of likes/comments and the user who added them as JSON Output though. Is there a way to add an add comment_form to the post_detail view?
Here's my models.py file
from __future__ import unicode_literals
import uuid
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
# Create your models here.
class Createpost(models.Model):
id = models.UUIDField(
primary_key = True,
default=uuid.uuid4,
editable= False,
)
author = models.ForeignKey(User , on_delete=models.CASCADE)
title = models.CharField(max_length=50)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
post_image = models.ImageField(upload_to='images/',blank=True)
#property
def total_likes(self):
return Like.objects.filter(post_id = self.id).count()
#property
def likes(self):
array = []
for like in Like.objects.filter(post_id = self.id):
array.append(like.author.username)
return array
#property
def total_comments(self):
return Answers.objects.filter(post_id = self.id).count()
#property
def comments(self):
array = []
for comment in Answers.objects.filter(post_id = self.id):
c = {}
c['body'] = comment.body
c['username'] = comment.author.username
c['created_at'] = comment.created_at
array.append(c)
return array
def __str__(self):
return self.title
class Answers(models.Model):
post = models.OneToOneField(
Createpost,
primary_key=True,
on_delete = models.CASCADE,
)
body = models.TextField()
author = models.ForeignKey(User,on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
Updated_At = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=False)
def __str__(self):
return 'Comment {} by {} '.format(self.body,self.author)
class Like(models.Model):
post = models.OneToOneField(
Createpost,
primary_key=True,
on_delete = models.CASCADE,
)
author = models.ForeignKey(User,on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('post','author',)
Here's my Views.py file:
from django.shortcuts import render
from rest_framework import generics
from .models import Createpost
from .permissions import IsAuthorOrReadOnly
from .serializers import PostSerializer,PostDetailSerializer,CommentSerializer
# Create your views here.
class PostList(generics.ListCreateAPIView):
queryset = Createpost.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthorOrReadOnly,)
queryset = Createpost.objects.all()
serializer_class = PostDetailSerializer
Here's my serializers.py file:
from rest_framework import status
from rest_framework import serializers
from rest_framework.decorators import APIView
from .models import Createpost,Answers
from django.contrib.auth.models import User
class PostSerializer(serializers.ModelSerializer):
totallikes = serializers.ReadOnlyField(source = 'total_likes')
totalcomments = serializers.ReadOnlyField(source = 'total_comments')
class Meta:
fields = ('id','author','title','body','created_at','totallikes','totalcomments')
model = Createpost
class CommentSerializer(serializers.Serializer):
field1 = serializers.CharField()
class PostDetailSerializer(serializers.ModelSerializer):
li_kes = serializers.ReadOnlyField(source = 'likes')
com_ments = serializers.ReadOnlyField(source = 'comments')
class Meta:
fields = ('id','author','title','body','created_at','updated_at','post_image','li_kes','com_ments',)
model = Createpost
I don't see a comment model in your code, so it's hard to know exactly how you're going about this. But one pattern for adding comments to a post object would to create an endpoint that accepts the comment details in the request and saves them to the post. Something like:
from rest_framework import viewsets, status
class PostComment(viewsets.ModelViewSet):
""" API endpoint for adding comments to posts """
def create(self, request):
Data = request.data
payload = {'post': Data['post_id'], 'user': self.request.user.id, 'comment': Data['comment']]}
post = UserPost.objects.get(uniqueID=Data['post_id'])
payload['post'] = post.id
serializer = UserCommentReplyPOSTSerializer(data=payload)
if serializer.is_valid():
serializer.save()
return Response('Comment saved', status=status.HTTP_200_OK)
else:
print(serializer.errors)
return Response('There was a problem with your request', status=status.HTTP_400_BAD_REQUEST)
You would then register this endpoint in your urls.py and use it in your front-end comment POST routine.
Obviously you'd need a Comment model with a foreign key to to your Post model for this to work. I'm assuming you have that and didn't show it.

django custom serializer field not working

I am trying to add an additional field in the serializer but at the moment of adding it I get the following error
error view
During handling of the above exception ('MovieSerializer' object has no attribute 'len_name')
I have seen many posts and all of them are working, what could it be?
this is my code
Model:
from django.db import models
class Movie(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(default=None, null=True)
def __str__(self):
return self.title
serializer:
from rest_framework import serializers
from watchlist_app.models import Movie
from watchlist_app.api.validations import MovieValidations
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField(method_name='len_name')
class Meta:
model = Movie
fields = "__all__"
read_only_fields = ['id', 'len_name', 'created_at']
required_fields = ['title', 'description']
#staticmethod
def create(self, validated_data):
return Movie.objects.create(**validated_data)
#staticmethod
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('description', instance.description)
instance.active = validated_data.get('active', instance.active)
instance.save()
return instance
#staticmethod
def get_len_name(self, obj):
return len(obj.title)
validators = [MovieValidations.validate_title, MovieValidations.validate_description,
MovieValidations.validate_equals]
everything works fine until I add the serializerMethodField
I think you set the function in the wrong place. The function needs to belong to the MovieSerializer.
from rest_framework import serializers
from watchlist_app.models import Movie
from watchlist_app.api.validations import MovieValidations
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField()
class Meta:
model = Movie
fields = "__all__"
...
def get_len_name(self, obj):
return len(obj.title)
use extra_fields in meta class
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField(method_name='len_name')
class Meta:
model = Movie
fields = "__all__"
extra_fields = ['len_name',]
read_only_fields = ['id', 'len_name', 'created_at']
required_fields = ['title', 'description']
*** your serializer methods in wrong indent,fix it -> Go back one indent and remode staticmethod decorator ***

Django dynamic URL with data from DB

Models.py
from django.db import models
# Create your models here.
class reviewData(models.Model):
building_name = models.CharField(max_length=50)
review_content = models.TextField()
star_num = models.FloatField()
class buildingData(models.Model):
building_name = models.CharField(max_length=50)
building_loc = models.CharField(max_length=50)
building_call = models.CharField(max_length=20)
views.py
# Create your views here.
from django.shortcuts import render
from rest_framework.response import Response
from .models import reviewData
from .models import buildingData
from rest_framework.views import APIView
from .serializers import ReviewSerializer
class BuildingInfoAPI(APIView):
def get(request):
queryset = buildingData.objects.all()
serializer = ReviewSerializer(queryset, many=True)
return Response(serializer.data)
class ReviewListAPI(APIView):
def get(request):
queryset = reviewData.objects.all()
serializer = ReviewSerializer(queryset, many=True)
return Response(serializer.data)
urls.py
from django.contrib import admin
from django.urls import path
from crawling_data.views import ReviewListAPI
from crawling_data.views import BuildingInfoAPI
urlpatterns = [
path('admin/', admin.site.urls),
path('api/buildingdata/', BuildingInfoAPI.as_view()),
#path('api/buildingdata/(I want to put building name here)', ReviewListAPI.as_view())
]
I am making review api.
I want to use building name as url path to bring reviews for specific buildings
For example, there are a, b, c reviews
a, b reviews are for aaabuilding
c reviews are for xxxbuilding
api/buildingdata/aaabuilding (only shows aaabuilding review)
{
building_name = aaabuilding
review_content = a
star_num = 5
building_name = aaabuilding
review_content = b
star_num = 3
}
api/buildingdata/xxxbuilding (only shows xxxbuilding review)
{
building_name = xxxbuilding
review_content = c
star_num = 4
}
I've searched some dynamic URL posts, but they were not that I want.
Also, I've posted a question before but there was no answer I was looking for.
Is there any way to bring building name into URL from db?
Please refer to the documentation on path converters. And usage of django's slugify function.
In your situation you will want a slug - but there are limitations to using a slug:
slugs must translate to some unique string, in your case building name. Therefore you should make sure that building name and slug are unique in your model.
You should add a slug field to the model - and also change the review model so it foreign keys to the building model:
from django.utils.text import slugify
class buildingData(models.Model):
building_name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
building_loc = models.CharField(max_length=50)
building_call = models.CharField(max_length=20)
def save(self, *args, **kwargs):
self.slug = slugify(self.building_name)
return super().save(*args, **kwargs)
class reviewData(models.Model):
building = models.ForeignKey(buildingData, related_name='reviews', on_delete=models.CASCADE, null=False, blank=False)
review_content = models.TextField()
star_num = models.FloatField()
Then in your urls.py:
path('api/buildingdata/<slug:slug>/', ReviewListAPI.as_view())
Then in your Views.py:
class ReviewListAPI(APIView):
def get(self, request):
building = get_object_or_404(buildingData, slug=self.kwargs.get('slug')
queryset = building.reviews.all()
serializer = ReviewSerializer(queryset, many=True)
return Response(serializer.data)
Also review pep8 - your class names should really be BuildingData and ReviewData - and you problably dont need Data in the names either.

How to create views.py for multiple models including many-to-many

I'm new to Python and DjangoRestFramework. I am trying to create an image upload system with image-tagging. "Tags" have a many-to-many relationship with "Images". The forms are in the React.js in the front-end. I am trying to understand how to write a view for this. I have not seen a clear solution to this online.
here is upload/models.py
from django.db import models
from django.db.models.fields import UUIDField
from django.contrib.postgres.functions import RandomUUID
def upload_path(instance, filename):
return '/'.join(['images', str(instance.contributor), str(instance.caption), str(instance.date_created), filename])
class Image(models.Model):
image = models.ImageField(upload_to=upload_path)
contributor = models.ForeignKey(
'accounts.User', related_name='+', on_delete=models.CASCADE, default='user0')
caption = models.CharField(max_length=100)
date_created = models.DateTimeField(auto_now_add=True, null=True)
id = UUIDField(primary_key=True, default=RandomUUID, editable=False)
theme = models.CharField(max_length=10)
class Tags(models.Model):
tag = models.ManyToManyField(Image, through='Junction')
class Junction(models.Model):
image = models.ForeignKey(Image, on_delete=models.CASCADE)
tags = models.ForeignKey(Tags, on_delete=models.CASCADE)
upload/serializers.py
from rest_framework import serializers
from .models import Image, Theme, Tags, Junction
class TagsSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryRelatedKeyField(
queryset=Image.object.all(), many=True)
class Meta:
model = Tags
fields = ('Tags')
class ImageSerializer(serializers.ModelSerializer):
tags_list = TagsSerializer(many=True, read_only=True)
class Meta:
model = Image
fields = ('image', 'contributor', 'caption', 'date_created', 'id')
class JunctionSerializer(serializers.ModelSerializer):
class Meta:
model = Junction
fields = ('image', 'theme', 'tags')
Here are two possible solutions for the upload/views.py:
from django.shortcuts import render
from django.http import HttpResponse
from rest_framework import viewsets
from .serializers import JunctionSerializer
from .models import Image, Tags, Junction
#SOLUTION_1:
class JunctionView(viewsets.ModelViewSet):
serializer_class = JunctionSerializer
query_set = Junction.objects.all()
def get_context_data(self, request, *args, **kwargs):
image = request.data['cover']
tags = request.data['tags']
Junction.objects.create(image=image, tags=tags)
return HttpResponse({'message': 'Successful Upload'}, status=200)
#SOLUTION_2
class JunctionView():
????
def get_context_data(self, **kwargs):
context = super(JunctionView, self).get_context_data(**kwargs)
context['image'] = Image.objects.all()
context['tags'] = Tags.objects.all()
return context
Is it necessary to call the context? The second solution that I researched is not specific to Django REST Framework, I think..
One approach is to create a ModelViewSet for the images, since you are working mainly with managing the images.
class ImageViewSet(viewsets.ModelViewSet):
serializer_class = ImageSerializer
query_set = Image.objects.all()
And then using your serializer, just modify your serializer to handle the m2m relations with a through model as described here. So something like:
class ImageSerializer(serializers.ModelSerializer):
tags_list = TagsSerializer(many=True, read_only=True)
class Meta:
model = Image
fields = ('image', 'contributor', 'caption', 'date_created', 'id')
def create(self, validated_data):
tags_list = validated_data.pop('tags_list')
instance = super().create(validated_data)
for tag in tags_list:
Junction.objects.create(image=instance, tag=tag)
This is not tested so it might have some problems here and there but this is the gist.

How do I pass only current user data to be filtered in django-filter?

I am using django-filter to filter my data. The issue is both the available options of the filters and the results are based on all the data, not only on the curent user.
models.py
class Product(models.Model):
title = models.CharField(max_length=264)
description = models.CharField(max_length=264)
date_added = models.DateField(auto_now_add=True)
time_added = models.TimeField(auto_now_add=True)
user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
I used the code below to filter the results only on the current user:
filters.py
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
#property
def qs(self):
parent = super(ProductFilter, self).qs
user = getattr(self.request, "user", None)
return parent.filter(user=user).order_by("-timestamp")
In my views.py I tried to pass the queryset, but nothing changed:
class ProductsFilterView(LoginRequiredMixin, FilterView):
model = Product
filterset_class = ProductFilter
def get_queryset(self, **kwargs):
return self.model.objects.filter(user=self.request.user)
I am not sure how to limit the available filter options only on the current user's data.
So, summarizing how can I pass into the filterset just the current user's data and not pass the whole data into the filterset and then filter them on current user's data.
filters.py
class ProductFilter(django_filters.FilterSet):
class Meta:
title = django_filters.CharFilter(lookup_expr='icontains')
# And you can also do "lookup_expr='year__gt' and also 'year__lt'"
date_added = django_filters.NumberFilter(name='date_added', lookup_expr='year')
class Meta:
model = Product
fields = [] #You can add fields that wants to be displayed
views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from .filters import ProductFilter
# Function Base View
#login_required
def productfilter(request):
user = request.user
product_list = Product.objects.filter(user=user)
product_filter = ProductFilter(request.GET, queryset=product_list)
return render(request, "template_name.html", {'products': product_list})
#Class Based View
class ProductsFilterView(LoginRequiredMixin, FilterView):
context_object_name = 'products'
template_name = 'template_name.html'
filter_set = ProductFilter
def get_queryset(self):
return Product.objects.filter(user=self.request.user)
I hope this was helpful.

Categories