Make Django Rest Api User Specific (get_queryset) - django

I want to make a Django rest api that is user specific so that I do /username at the end of the url.
Models:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.IntegerField()
description = models.CharField(max_length=300)
class Meta:
verbose_name_plural = 'User Profiles'
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_data(sender, update_fields, created, instance, **kwargs):
if created:
user = instance
profile = UserProfile.objects.create(user=user, age=18, description='No Description')
class Notes(models.Model):
note = models.CharField(max_length=1000)
parent_user = models.OneToOneField(UserProfile, blank=True, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'Notes'
def __str__(self):
return self.note
Serializers:
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Notes
fields = ('id', 'note', 'parent_user')
urls:
router = routers.DefaultRouter()
router.register('notes', views.UserNoteView)
urlpatterns = [
path('', include(router.urls)),
]
views:
class NoteView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Notes.objects.all()
serializer_class = NoteSerializer
class UserNoteView(NoteView):
def get_queryset(self):
return self.request.parent_user.Notes.all()
My problem is that I can't do for example /William which is the name of my user, and user profile. Someone that knows this must be able to help!

Your notes model is incorrect. Change OneToOneField to ForeignKey. You can change it as below
class Notes(models.Model):
note = models.CharField(max_length=1000)
parent_user = models.ForeignKey(
UserProfile, blank=True, on_delete=models.CASCADE, related_name="notes")
def __str__(self):
return "{note}".format(note=self.note)
Now, change your viewset as below
class UserNoteView(NoteView):
def get_queryset(self):
return self.request.user.userprofile.notes.all()
# or
return Notes.objects.filter(parent_user__user=self.request.user)

Related

Django Modelviewset Filtering

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

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

HyperlinkedModelSerializer custom lookup_field to related_model

i have the below config and i would like to map the url field in UserProfileView to the related user's username instead of the default pk field
currently the url looks likes below, appreciate any help
{
"user": 23,
"bio": "My bio",
"created_on": "2020-06-12T21:24:52.746329Z",
"url": "http://localhost:8000/bookshop/bio/8/?format=api"
},
what i am looking for is
{
"user": 23, <--- this is the user <pk>
"bio": "My bio",
"created_on": "2020-06-12T21:24:52.746329Z",
"url": "http://localhost:8000/bookshop/bio/username/?format=api"
},
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
views.py
class UserProfileViewSets(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication, ]
permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
# lookup_field = 'user.username'
def perform_create(self, serializer):
serializer.save(user=self.request.user)
serializer.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserProfile
fields = ['user', 'bio', 'created_on', 'url']
extra_kwargs = {
'last_updated': {
'read_only': True
},
'user': {
'read_only': True
},
}
after struggling and reading many articles, I did it and posting down the solution if anybody was looking for the same use case.
the fields are being related to each other by OneToOne relationship
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
class User(AbstractBaseUser, PermissionsMixin):
""""
Customizes the default user account
"""
email = models.EmailField(unique=True, help_text='username is the email address')
first_name = models.CharField(max_length=40, blank=False)
last_name = models.CharField(max_length=40, blank=False)
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
username = models.CharField(max_length=15, unique=True, null=True, blank=False,
validators=(validators.UnicodeUsernameValidator, ))
is_borrower = models.BooleanField(default=False)
The serializer is a HyperlinkedModelSerializer, as shown below the user SerializerField is PrimaryKeyRelatedField and it is being related to another column/field in the User model user.username - i made this as the default PrimaryKeyRelatedField is the pk and i dont want to expose that on the API
the url key is customized to be HyperlinkedRelatedField to point to the above field - the user with a viewname user-related
serializer.py
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.PrimaryKeyRelatedField(source='user.username', read_only=True)
url = serializers.HyperlinkedRelatedField(read_only=True, view_name='user-detail', )
class Meta:
model = models.UserProfile
fields = ['user', 'bio', 'created_on', 'url']
extra_kwargs = {
'last_updated': {
'read_only': True
},
'user': {
'read_only': True
},
}
on the views, i defined the lookup_field to be user and override the get_object method as now the queryset should be filtered by the username
views.py
class UserProfileViewSets(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication, ]
permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
lookup_field = 'user'
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_object(self):
queryset = self.filter_queryset(models.UserProfile.objects.get(user__username=self.kwargs.get('user')))
return queryset
EDIT:
I did the requirements in another approach and think this one is more neat way , so below the modifications.
You need to create anew customized HyperLinkedIdentityField where you over right the kwargs, check the below kwargs, the value is mapped to the related model where a OneToOneForgienKey deifined
class AuthorHyperLinkedIdentityField(serializers.HyperlinkedIdentityField):
def get_url(self, obj, view_name, request, format):
if hasattr(obj, 'pk') and obj.pk is None:
return None
return self.reverse(view_name, kwargs={
'obj_username': obj.author.username
}, format=format, request=request)
on the view you overright the lookup_field with the kwargs defined in the CustomizedField
class AuthorViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AuthorSerializer
queryset = models.Author.objects.all()
renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.AdminRenderer]
# the below is not used but i keep it for reference
# lookup_field = 'author__username'
# the below should match the kwargs in the customized HyperLinkedIdentityField
lookup_field = 'obj_username'
The final serializer would look like
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
"""
Serializers Author Model
"""
# first_name = serializers.SlugRelatedField(source='author', slug_field='first_name',
# read_only=True)
# last_name = serializers.SlugRelatedField(source='author', slug_field='last_name',
# read_only=True)
author = serializers.PrimaryKeyRelatedField(queryset=models.User.objects.filter(groups__name='Authors'),
write_only=True)
name = serializers.SerializerMethodField()
username = serializers.PrimaryKeyRelatedField(source='author.username', read_only=True)
# the below commented line is building the URL field based on the lookup_field = username
# which takes its value from the username PrimaryKeyRelatedField above
# url = serializers.HyperlinkedRelatedField(view_name='user-detail', read_only=True)
url = AuthorHyperLinkedIdentityField(view_name='author-detail', read_only=True)
class Meta:
model = models.Author
fields = ['author', 'name', 'username', 'url', ]
def get_name(self, author):
return '%s %s' % (author.author.first_name, author.author.last_name)
below the Author Model for your reference
class Author(models.Model):
"""
A Model to store the Authors info
"""
author = models.OneToOneField(User, on_delete=models.CASCADE, related_name='authors')
is_author = models.BooleanField(default=True, editable=True, )
class Meta:
constraints = [
models.UniqueConstraint(fields=['author'], name='check_unique_author')
]
def __str__(self):
return '%s %s' % (self.author.first_name, self.author.last_name)
def author_full_name(self):
return '%s %s' % (self.author.first_name, self.author.last_name)

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 rest framework, change ForeignKey

i'm fighting with DRF too long so now i must ask question.. How change ForeignKey to another? I have user profile and relation to status model.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ldap_uid = models.CharField(max_length=100, blank=True, null=True, default=None)
redmine_id = models.IntegerField(blank=True, null=True, default=None)
status = models.ForeignKey(Status, models.SET_NULL, blank=False, null=True, default=DEFAULT_STATUS_ID)
location = models.ForeignKey(Location, models.SET_NULL, blank=False, null=True, default=DEFAULT_LOCATION_ID)
online = models.BooleanField(default=False)
class SelectValuesModel(models.Model):
name = models.CharField(max_length=100)
display_name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Meta:
abstract = True
class Status(SelectValuesModel):
pass
class Location(SelectValuesModel):
pass
What is good way to change Profile status to another? I'm trying with something like this without success
views.py
class UserStatusView(viewsets.ViewSet):
def partial_update(self, request, pk=None):
user = User.objects.get(pk=pk)
user_profile = user.profile
new_stauts = Status.objects.get(request.data.status)
serialized_data = ProfileSerializer(user_profile)
if(serialized_data.is_valid()):
serialized_data.save(status=new_stauts)
return Response(serialized_data.errors)
And trying send new id via PATCH. I'm trying tto find solution but no success here too. And how do it good? Make another route for updating Profile status? Or make something like profile/1/update_status/2? Now my routing looks like:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'redmine', views.RedmineCurrentTaskView, base_name='redmine')
router.register(r'parameters', views.ParametersView, base_name='parameters')
router.register(r'update_status', views.UserStatusView, base_name='update_status')
router.register(r'debug', views.DebugStatus, base_name='debug')
urlpatterns = [
path('', views.index, name='index'),
path('api/', include(router.urls))
]
And serializers.py
class SelectValuesSerializer(serializers.ModelSerializer):
class Meta:
fields = ('pk', 'name', 'display_name')
class LocationSerializer(SelectValuesSerializer):
class Meta(SelectValuesSerializer.Meta):
model = Location
class StatusSerializer(SelectValuesSerializer):
class Meta(SelectValuesSerializer.Meta):
model = Status
class ProfileSerializer(serializers.ModelSerializer):
status = StatusSerializer()
location = LocationSerializer()
class Meta:
model = Profile
fields = ('status', 'location', 'online', 'redmine_id')
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('pk', 'first_name', 'profile')
read_only_fields = ('first_name',)
Just pass request.data to the serializer with partial=True argument:
class UserStatusView(viewsets.ViewSet):
def partial_update(self, request, pk=None):
user = User.objects.get(pk=pk)
user_profile = user.profile
serialized_data = ProfileSerializer(user_profile, data=request.data, partial=True)
if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
return Response(serialized_data.errors)
You need to provide status_id with request body like this:
{"status": 1}
UPD
To pass status as id change your serializer to this:
class ProfileSerializer(serializers.ModelSerializer):
location = LocationSerializer()
class Meta:
model = Profile
fields = ('status', 'location', 'online', 'redmine_id')
def to_representation(self, instance):
self.fields['status'] = StatusSerializer()
return super(ProfileSerializer, self).to_representation(instance)
This allows to post status_id, but get status details with your API.