Django Modelviewset Filtering - django

I have two models Category & Post. In Post model there is foreign key of category. Based on category I want to filter the data to show the post category wise. Here's my code.
models.py
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField()
parent = models.ForeignKey('self',blank=True, null=True ,related_name='news', on_delete=models.CASCADE)
class Meta:
unique_together = ('slug', 'parent',)
verbose_name_plural = "Category"
def __str__(self):
full_path = [self.name]
k = self.parent
while k is not None:
full_path.append(k.name)
k = k.parent
return ' -> '.join(full_path[::-1])
class Post(models.Model):
NEWS_TYPE = (('Images','Images'),('Multi-Images','Multi-Images'),('Image-Text','Image-Text'),
('Audio-Video','Audio-Video'),('Audio-Video-Text','Audio-Video-Text'),('Audio','Audio'),
('Audio-Text','Audio-Text'))
POST_STATUS = (('Pending','Pending'),('Verified','Verified'),('Un-Verified','Un-Verified'),
('Published','Published'),('Mint','Mint'))
category = models.ForeignKey(Category, related_name='posts', on_delete=models.CASCADE)
post_type = models.CharField(max_length=100, verbose_name='Post Type', choices=NEWS_TYPE)
title = models.TextField(verbose_name='News Title')
content = models.TextField(verbose_name='News Content')
hash_tags = models.CharField(max_length=255, verbose_name='Hash Tags')
source = models.CharField(max_length=255, verbose_name='News Source')
author = models.ForeignKey(User, related_name='Post', on_delete=models.CASCADE)
views = models.ManyToManyField(User,related_name='Views', blank=True)
likes = models.ManyToManyField(User, related_name='Likes', blank=True)
dislikes = models.ManyToManyField(User, related_name='Dislikes', blank=True)
status = models.CharField(max_length=20, verbose_name='Status', choices=POST_STATUS, default='Pending')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return (self.post_type)+ '-' +self.title
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('category','post_type','title','content','hash_tags','source','author','views',
'likes','dislikes','status')
views.py
class CategoryAPI(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class PostAPI(viewsets.ModelViewSet):
serializer_class = PostSerializer
def get_queryset(self):
news_post = Post.objects.all()
return news_post
def retrieve(self, request, *args, **kwargs):
params = kwargs
print(params['pk'])
category = Category.objects.filter(name=params['pk'])
serializer = CategorySerializer(category, many=True)
return Response(serializer.data)
urls.py
from django.urls import path, include
from rest_framework import routers
from rest_framework.routers import DefaultRouter
from news.views import PostAPI, CategoryAPI
from . import views
router = DefaultRouter()
router.register('posts', views.PostAPI, basename='posts'),
router.register('category', views.CategoryAPI, basename='category'),
urlpatterns = router.urls
I tried solving in these way but it tells 'PostSerializer' object has no attribute 'get_category'. Is there anything i'm doing wrong. Please your support would be helpful. Thank you

I think then your approach should be the other way round, meaning you should add the list of Posts to your Category:
serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('category','post_type','title','content','hash_tags','source','author','views',
'likes','dislikes','status')
class CategorySerializer(serializers.ModelSerializer):
posts = PostSerializer(many=True, read_only=True)
class Meta:
model = Category
fields = ['name', 'slug', 'parent', 'posts']
Attention: I changed the related name of your category field in the Post model to 'posts'
This should show you all Posts when retrieving a category. No need to override any method in your views:
class CategoryAPI(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class PostAPI(viewsets.ModelViewSet):
queryset = Post.obejcts.all()
serializer_class = PostSerializer
If do not want identify the category by id but by category name, e.g.:
http://127.0.0.1:8000/news/category/sports/
add a custom lookup field to your category view, e.g.
class CategoryAPI(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'name'
but make sure the lookup_field is unique

Related

how to save multiple objects to the database in django rest framework views

so what i'm trying to do is add a new product to my data base using django's restapi
but a product may contain multiple categories which are related throught a third many to many
model and extra pictures which are ForeignKeyed to the product
this is my models.py
class Products(models.Model):
product_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=35, null=False, unique=True)
description = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.)
main_image = models.FileField(upload_to='shop/images')
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Category(models.Model):
category_id = models.AutoField(primary_key=True)
category = models.CharField(max_length=20, null=True, blank=True)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'Category'
class ProductsCategory(models.Model):
productscategory_id = models.AutoField(primary_key=True)
category = models.ForeignKey(to=Category, on_delete=models.CASCADE)
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'ProductsCategory'
class Pictures(models.Model):
picture_id = models.AutoField(primary_key=True)
image = models.FileField(upload_to='shop/images')
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'Pictures'
and heres what i've tryed:
#api_view(['POST'])
#permission_classes([IsModerator])
def create_product(request):
product_details = ProductsSerializer(request.POST, request.FILES)
pictures = PicturesSerializer(request.POST, request.FILES, many=True)
category_list = request.POST.getlist("category")
if product_details.is_valid() and validate_file_extension(request.FILES.get("main_image")):
try:
product = product_details.save()
if len(category_list) > 0:
for i in category_list:
category = Category.objects.get(category=i)
ProductsCategory.objects.create(category=category, product=product)
if pictures:
for image in request.FILES.getlist("image"):
if validate_file_extension(image):
Pictures.objects.create(image=image, product=product)
else:
error = {"error": "invalid extra pictures extension"}
return Response(error)
return Response((product_details.data, pictures.data, category_list), status=status.HTTP_201_CREATED)
except Exception as e:
return Response(e)
else:
return Response((product_details._errors, pictures._errors), status=status.HTTP_400_BAD_REQUEST)
and the output:
result
how am i supposed to use this content input?
or if you know a better for my main question of saving multiple models in the database and their relationships please leave an answer, thanks in advance
I suggest you change your models.py structure to this:
from django.db import models
class Category(models.Model):
category = models.CharField(max_length=20, null=True, blank=True)
created_on = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Categories"
class Picture(models.Model):
image = models.FileField(upload_to='shop/images')
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Product(models.Model):
name = models.CharField(max_length=35, null=False, unique=True)
description = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.)
main_image = models.FileField(upload_to='shop/images')
more_images = models.ManyToManyField(Pictures, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now=True)
Then in your serializer.py add:
from rest_framework import serializers
from .models import Category, Picture, Product
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class PictureSerializer(serializers.ModelSerializer):
class Meta:
model = Picture
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
In your views, I suggest you use ViewSets:
views.py
from .models import Category, Picture, Product
from .serializer import CategorySerializer, PictureSerializer, ProductSerializer
from rest_framework import viewsets
# import custom permissions if any
class CategoryViewSet(viewsets.ModelViewSet):
serializer_class = CategorySerializer
queryset = Category.objects.all()
class PictureViewSet(viewsets.ModelViewSet):
serializer_class = PictureSerializer
queryset = Picture.objects.all()
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
permission_classes = [IsModerator]
In your app's urls.py, add the router for your viewsets and it will create the paths for your views automatically:
from django.urls import path
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'category', views.CategoryViewSet, basename='category')
router.register(r'picture', views.PictureViewSet, basename='picture')
router.register(r'product', views.ProductViewSet, basename='product')
urlpatterns = [
path('', include(router.urls)),
]
Changes log:
You do not need to add an ID field to every model, Django does that for you. Unless it's a particular case.
Your database tables are named after your model by default. So no need to specify that too.
I simplified your models' structure to make it cleaner. But it still does what you want it to do.
Django adds an s to create a plural name for every model. So you can name it in singular form unless needed to specify. eg. categories.
The viewsets will reduce your work by providing you with listing and retrieval actions.
To access a specific instance of eg. a product, you will just add a /<product id> after the product listing and creation endpoint.
Note: You have to add the id without the brackets.
I also suggest you go through this DRF tutorial. It will improve your understanding of Django REST framework.

How to use detailview pk in createview

# models.py
class NewBlank(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
title = models.CharField(max_length=50)
description = models.CharField(max_length=100, blank=True)
blank_on_off = models.BooleanField(default=False)
create_date = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now=True)
class BlankContent(models.Model):
refer = models.TextField()
memo = models.TextField()
new_blank = models.ForeignKey('NewBlank', on_delete=models.CASCADE, related_name='blankcontent')
# views.py
class BlankDetail(LoginRequiredMixin, DetailView):
model = NewBlank
template_name = 'blank_app/blank_detail.html'
context_object_name = 'blank'
class BlankContentCreate(CreateView):
model = BlankContent
fields = "__all__"
template_name = 'blank_app/new_blank_content_create.html'
def get_success_url(self):
return reverse_lazy('blank_detail', kwargs={'pk': self.object.new_blank.pk})
# urls.py
urlpatterns = [
path('blank/<int:pk>/', BlankDetail.as_view(), name='blank_detail'),
path('new-blank-content/', BlankContentCreate.as_view(), name='blank_content_create'),
]
There is a creativeview in the detail view and I want to create a model in the detailview when I press it. So even if I don't specify the new_blank part, I want it to be filled automatically according to the pk in the detailview, what should I do?
In case you want to perform some extra work in your DetailView, one of the ways to do that would be to override the get_object method.
from django.views.generic import DetailView
class BlankDetail(LoginRequiredMixin, DetailView):
model = NewBlank
template_name = 'blank_app/blank_detail.html'
context_object_name = 'blank'
def get_object(self):
obj = super().get_object()
# do your thing with obj.pk
pk = self.kwargs.get('pk') # in case you want to access the `pk` from URL

Unable to join two tables with django rest framework

I am trying to join two tables and serialize them as an API. I have referred to the docs of the Django rest framework and tried a code. It didn't work. Could not resolve the problem even after trying so many times. I am trying to get a JSON file like
{
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement'},
{'order': 2, 'title': 'What More Can I Say'},
{'order': 3, 'title': 'Encore'},
...
],
}
But what I get is
{
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
}
This is the model file I am using
Model.py
from django.db import models
from django.contrib.auth.models import User
STATUS_CHOICE = (
('simple', 'simple'),
('intermediate', 'intermediate'),
)
class Quiz(models.Model):
quiz_name = models.CharField(max_length=1000)
video_id = models.ForeignKey("youtube.Youtube", on_delete=models.CASCADE)
questions_count = models.IntegerField(default=0)
description = models.CharField(max_length=70, null=True)
created = models.DateTimeField(auto_now_add=True)
slug = models.SlugField()
pass_mark = models.IntegerField()
class Meta:
ordering = ['created']
def __str__(self):
return self.quiz_name
class Category(models.Model):
category = models.CharField(max_length=20, choices=STATUS_CHOICE, default='simple')
quiz_id = models.ForeignKey(Quiz, on_delete=models.CASCADE)
def __str__(self):
return self.category
class Questions(models.Model):
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
question = models.CharField(max_length=1000)
mark = models.IntegerField()
def __str__(self):
return self.question
class Choice(models.Model):
question = models.ForeignKey(Questions, on_delete=models.CASCADE)
choice_1 = models.CharField(max_length=1000)
choice_2 = models.CharField(max_length=1000)
choice_3 = models.CharField(max_length=1000)
choice_4 = models.CharField(max_length=1000)
answer = models.CharField(max_length=1000, default=choice_1)
def __str__(self):
return self.answer
Serializer.py
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from .models import Category, Quiz, Questions, Choice
from django.contrib.auth import authenticate
from django.contrib.auth.hashers import make_password
class QuizSerializer(serializers.ModelSerializer):
class Meta:
model = Quiz
fields = '__all__'
class QuestionsSerializer(serializers.ModelSerializer):
class Meta:
model = Questions
fields = '__all__'
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = '__all__'
class CategorySerializer(serializers.ModelSerializer):
quiz_name = QuizSerializer(read_only=True)
class Meta:
model = Category
fields = ['id','category','quiz_name']
View.py
from rest_framework import generics, permissions, mixins
from rest_framework.response import Response
from .serializer import CategorySerializer
from .models import Category
class ViewQuiz(generics.ListCreateAPIView):
permission_classes = [
permissions.AllowAny,
]
queryset = Category.objects.all()
serializer_class = CategorySerializer
def list(self, request):
queryset = self.get_queryset()
serializer = CategorySerializer(queryset, many=True)
print(serializer.data)
return Response(serializer.data)
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id','category','quiz_id']
def to_representation(self, instance):
response = super().to_representation(instance)
response['quiz_id'] = QuizSerializer(instance.quiz_id).data
return response
This will produce the result you want, I made an change in how the serializer represent the data. I have some of my serializer doing the same, but my views are working a bit different from yours.
Looks like you are trying to get questions serializes in quiz.
To do that you need to:
1. In Questions model include related_name in quiz field:
class Questions(models.Model):
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name="questions")
question = models.CharField(max_length=1000)
mark = models.IntegerField()
def __str__(self):
return self.question
In QuizSerializer include questions field and set many to True:
class QuizSerializer(serializers.ModelSerializer):
questions = QuestionsSerializer(source="questions", many=True)
class Meta:
model = Quiz
fields = ("questions", ... other needed fields)
Include source attribute in QuizSerializer in CategorySerializer:
class CategorySerializer(serializers.ModelSerializer):
quiz_name = QuizSerializer(read_only=True, source="quiz_id")
class Meta:
model = Category
fields = ['id', 'category', 'quiz_name']
Your Quiz was not serialized because the relation between Category and Quiz in tables are called quiz_id but your field is called quiz_name, so the framework did not know where it should take quiz, because it was looking at quiz_name relation which does not exist.

Django_filter on an apiview

I have been trying to use the Django_filter on an APIView, but it just does not work. I am trying to implement a filter search, on some fields on a model.
below is how the model is set up
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=254, unique=True)
name = models.CharField(max_length=250)
picture = models.TextField(null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(max_length=255, unique=True, blank=True)
class Skill(models.Model):
name = models.CharField(max_length=60)
subcategory = models.CharField(max_length=60, blank=True, null=True)
created_on = models.DateTimeField(auto_now=True)
updated_on = models.DateTimeField(auto_now_add=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.DO_NOTHING)
the views.py set up is also as shown below
from django_filters import rest_framework as filters
class UserFilter(filters.FilterSet):
email = filters.CharFilter(lookup_expr='icontains')
name = filters.CharFilter(lookup_expr='icontains')
profiles__skills = filters.CharFilter(lookup_expr='icontains')
class Meta:
model = User
fields = ('email', 'name', 'profiles__skills')
class ListUsersView(APIView, MyPaginationMixin):
'''
Gets all the users in the database
'''
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [AllowAny]
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
filterset_class = UserFilter
def get(self, request):
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer_context = {"request": request}
serializer = self.serializer_class(page, context=serializer_context, many=True)
return self.get_paginated_response(serializer.data)
and finally my serializer.py
class UserSerializer(serializers.ModelSerializer):
slug = serializers.SlugField(read_only=True)
class Meta:
model = User
fields = ('email', 'name', 'slug', 'picture')
read_only_fields = ('email', 'name', 'slug',)
my urls.py
path('users/', qv.ListUsersView.as_view(), name='list-users'),
this is how my result looks like
please, how can I get the Django filter to work on the APIView
It seems you are trying to get the similar or exact behavior of DRF ListAPIView by using APIView. I would suggest using ListAPIView over APIView in your case.
from rest_framework import generics
from django_filters import rest_framework as filters
class ListUsersView(generics.ListAPIView):
'''
Gets all the users in the database
'''
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [AllowAny]
filterset_class = UserFilter
filter_backends = (filters.backends.DjangoFilterBackend,)
To add filtering capability in APIView,
class MyAPIViewKlass(APIView):
filter_backends = (filters.DjangoFilterBackend,)
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get(self, request, *args, **kwargs):
base_qs = MyModel.objects.all()
filtered_qs = self.filter_queryset(base_qs)
serializer = MySerializer(filtered_qs, many=True)
return Response(serializer.data)

Nested API View of Django REST Framework?

At the moment I developed the following code, for me to get the Contact List of each user. The views return the ID numbers of the Contacts of the User. I need to get, instead of the ID numbers, the 'name' and 'last_name' attribute of said contacts. I am quite new to Django's REST Framework and I'm not quite sure what to do next but I believe I have to nest the APIView. I would really appreciate some help!
views.py
def list_contacts(request, id_profile):
url = request.build_absolute_uri(reverse('api_users:contact_list', kwargs={'pk':id_profile}))
response = requests.get(url)
profile = Profile.objects.get(pk=id_profile)
if response.status_code == status.HTTP_200_OK:
data = response.content
user = json.loads(data)
return render(request, 'profiles/contact_list.html', {'user':user})
models.py
class Profile(models.Model):
id_user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birthday = models.DateField(auto_now=False)
description = models.CharField(max_length=100)
profile_picture = models.ImageField(upload_to='images/profiles/%Y/%m/%d', blank=False)
active = models.BooleanField(default = False)
contacts = models.ManyToManyField('self', blank=True, default='null')
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
deleted_at = models.DateTimeField(blank=True, null=True)
class Meta:
ordering = ('-id',)
def __str__(self):
return self.name+' '+self.last_name
def active_profiles():
return Profile.objects.filter(active=True)
api/views.py
class ContactListView(generics.ListAPIView):
queryset = Profile.objects.all()
serializer_class = UserContactListSerializer
filter_backends = (filters.SearchFilter,)
search_fields = ('name', 'last_name',)
def get(self, request, pk, format=None):
contacts = Profile.objects.get(pk=pk)
serializer = UserContactListSerializer(contacts)
return Response(serializer.data)
api/serializers.py
class UserContactListSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['name','last_name','contacts']
I don't know what exactly is going on in your list_contacts but if you want to use the same serializer as a field in itself, you currently can't.
While Django models allow you to use 'self' as the reference, DRF doesn't.
What you can instead do is create another serializer and use that as the field.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("id", "first_name", "last_name")
class UserContactListSerializer(serializers.ModelSerializer):
contacts = UserSerializer(many=True)
class Meta:
model = Profile
fields = ("id", "first_name", "last_name", "contacts")