i want to implement a feature in my rest api that users can add specific keywords for a news feed.
so if the users make a post request with a keyword within, the user object will be added on the predefined keyword (predefined in the database).
I have tried it with this code, but always if i try to simulate the post request with postman and i have this problem:
the keyword will be added but not the provided json data, its just a empty string and the post request returns also an empty keyword...
I hope you are able to help me and maybe you could give me an advice how to just allow the static keywords which are already defined and allow user only have a keyword once (no double keywords with same value)
Made with this headers:
[{"key":"Content-Type","value":"application/json","description":""}]
[{"key":"Authorization","value":"Token xxxxxxx","description":""}]
Body:
{
"name": "keyword1"
}
Authorization works, so the user added to the empty keyword
I am very new to django and i am doing this project to improve my skills, so please be lenient to me :) So it could be that its completly wrong, please give me some advices to solve my problem
These are the snippets for the implementation:
models.py
class Keywords(models.Model):
name = models.CharField(max_length=200)
user = models.ManyToManyField(User)
def __str__(self):
return self.name
serializers.py
class KeywordSerializer(serializers.Serializer):
class Meta:
model = Keywords
fields = ('id', 'name', 'user')
def create(self, validated_data):
keyword = Keywords(**validated_data)
keyword.save()
keyword.user.add(self.context['request'].user)
return keyword
views.py
class KeywordAPI(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
keyword = serializer.save()
return Response({
"name": KeywordSerializer(keyword, context=self.get_serializer_context()).data,
})
Try this snippet:
class KeywordSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Keywords
fields = '__all__'
def create(self, validated_data):
user = validated_data.pop('user')
kw = Keywords.objects.create(**validated_data)
kw.user.add(user)
kw.save()
return kw
and views:
from rest_framework import viewsets, permissions
class KeywordAPI(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
queryset = Keywords.objects.all()
The input payload be as
{
"name":"kw1"
}
NOTE
Here I used ModelSerializer class, because it's very handy for CURD applications and HiddenField is something like write_only=True parameter for fields.
References:
DRF - Modelviewset
HiddenField
CurrentUserDefault
There are few things you are doing wrong
First
Your model's name is Keywords it shouldn't be plural use Keyword and user field is ManyToMany so you should pluralise it
class Keyword(models.Model):
name = models.CharField(max_length=200)
users = models.ManyToManyField(User)
def __str__(self):
return self.name
Second
You are using Serializer instead of ModelSerializer
class KeywordSerializer(serializers.ModelSerializer):
class Meta:
model = Keywords
fields = ('id', 'name', 'users')
read_only_fields = ('users',)
def create(self, validated_data):
keyword = super().create(validated_data)
keyword.users.add(self.context['request'].user)
return keyword
Third
You don't have to write creation logic yourself use existing mixins
from rest_framework import mixins
class KeywordAPI(mixins.CreateModelMixin, generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
For those who maybe have the same problem, here is the whole solution:
Thanks to Sardorbek Imomaliev and Jerin Peter George for helping me
serializer:
class KeywordSerializer(serializers.ModelSerializer):
users = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Keyword
fields = '__all__'
def create(self, validated_data):
users = validated_data.pop('users')
if Keyword.objects.filter(**validated_data).exists():
kw = Keyword.objects.filter(**validated_data).get()
kw.users.add(self.context['request'].user)
return kw
else:
raise serializers.ValidationError("This Keyword has not been set yet.")
model:
class Keyword(models.Model):
name = models.CharField(max_length=200)
users = models.ManyToManyField(User)
def __str__(self):
return self.name
view:
class KeywordAPI(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
queryset = Keyword.objects.all()
Related
i'm using DRF modelviewset to create an api for users model. Can anyone explain or help how can I pass a url in react-native for the detailview of user. Right now i'm getting the detailview in this manner 127.0.0.1:8000/users/users/2/. But everytime i don't want to pass the id in the url.
models.py
class User(AbstractUser):
"""This Class is used to extend the in-build user model """
ROLE_CHOICES = (('CREATOR','CREATOR'),('MODERATOR','MODERATOR'),('USERS','USERS'))
GENDER_CHOICES = (('MALE','MALE'),('FEMALE',"FEMALE"),('OTHER','OTHER'))
date_of_birth = models.DateField(verbose_name='Date of Birth', null=True)
profile_image = models.ImageField(upload_to='media/profile_images', verbose_name='Profile Image', default='media/profile_images/default.webp', blank=True)
bio = models.TextField(verbose_name='Bio')
role = models.CharField(max_length=10, verbose_name='Role', choices=ROLE_CHOICES, default='USERS')
gender = models.CharField(max_length=6, verbose_name='Gender', choices=GENDER_CHOICES)
serializers.py
class UserSerializer(serializers.ModelSerializer):
following = serializers.SerializerMethodField(read_only=True)
followers = serializers.SerializerMethodField(read_only=True)
class Meta:
model = User
fields = ('first_name','last_name','username','password','email','date_of_birth',
'profile_image','bio','role','gender', 'following','followers')
extra_kwargs = {'is_active':{'write_only':True},
'password':{'write_only':True}}
def create(self, validated_data):
logger.info('Information Stored!')
return User.objects.create_user(**validated_data)
def update(self, *args, **kwargs):
user = super().update( *args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
views.py
class UserAPI(viewsets.ModelViewSet):
serializer_class = UserSerializer
# permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
def get_queryset(self):
users = User.objects.all()
return users
urls.py
router = DefaultRouter()
router.register('users', views.UserAPI, basename='users'),
router.register('following', views.FollowingAPI, basename='following'),
urlpatterns = router.urls
How can i solve this. Need your help please. Thank you
You can make use of #action decorator.
Give this a try:
class UserAPI(viewsets.ModelViewSet):
...
#action(detail=False)
def profile(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Now go to 127.0.0.1:8000/users/profile/, you should see the current authenticated user's data.
this may be helpful.
You can use user = request.user.id , by in this way you can get current login user.
I have a modelset view in which different customs functions are defined based on the requirement. I have to write another get function in which I want to use the same serializer class. But the field which I have defined in the serializer class in pkfield but for the get function, I want it as a stringfield rather than pk field. How to achieve that??
Also, I have defined depth=1, which is also not working.
class Class(TimeStampAbstractModel):
teacher = models.ForeignKey(
Teacher,
on_delete=models.CASCADE,
null=True,
related_name="online_class",
)
subject = models.ForeignKey(
Subject,
on_delete=models.SET_NULL,
null= True,
related_name= "online_class",
)
students_in_class = models.ManyToManyField(Student, related_name="online_class")
My view:
class ClassView(viewsets.ModelViewSet):
queryset = Class.objects.all()
serializer_class = ClassSerializer
serializer_action_classes = {
'add_remove_students': AddStudentstoClassSerializer,
'get_all_students_of_a_class': AddStudentstoClassSerializer,
}
def get_serializer_class(self):
"""
returns a serializer class based on the action
that has been defined.
"""
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(ClassView, self).get_serializer_class()
def add_remove_students(self, request, *args, **kwargs):
"""
serializer class used is AddStudentstoClassSerializer
"""
def get_all_students_of_a_class(self,request,pk=None):
"""
for this I function too, I want to use the same AddStudentstoClassSerializer class but
there is a problem. The field students_in_class is already defined as pkfield, whereas I
want to use it as a stringfields in the response of this function
""""
My serializer:
class AddStudentstoClassSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many=True, queryset=Student.objects.all()
)
class Meta:
model = Class
fields = ["students_in_class"]
depth = 1
def update(self, instance, validated_data):
slug = self.context["slug"]
stu = validated_data.pop("students_in_class")
/................other codes....../
return instance
Here we can see the student_in_class is defined as pkfield which is ok when using the update api, but when I want to use the get api and call get_all_students_of_a_class I want the field to be stringfield or some other field. How to do that? Also depth= 1 is also not working.
Update:
Treid the following but still not working:
def to_representation(self, instance):
rep = super().to_representation(instance)
# rep["students_in_class"] = instance.students_in_class
rep['students_in_class'] = StudentSerializer(instance.students_in_class).data
return rep
class StudentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Student
fields = ['user', 'college_name', 'address']
what i got in the response is
{
"students_in_class": {}
}
it is empty dict. what should be done!
You can override you to_representation method like this.
class AddStudentstoClassSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many=True, queryset=Student.objects.all()
)
class Meta:
model = Class
fields = ["students_in_class"]
def to_representation(self, instance):
data = {
"students_in_class": # Write your logic here
}
return data
def update(self, instance, validated_data):
slug = self.context["slug"]
stu = validated_data.pop("students_in_class")
/................other codes....../
return instance
I want to save a simple model with Django REST Framework. The only requirement is that UserVote.created_by is set automatically within the perform_create() method. This fails with this exception:
{
"created_by": [
"This field is required."
]
}
I guess it is because of the unique_together index.
models.py:
class UserVote(models.Model):
created_by = models.ForeignKey(User, related_name='uservotes')
rating = models.ForeignKey(Rating)
class Meta:
unique_together = ('created_by', 'rating')
serializers.py
class UserVoteSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
created_by = UserSerializer(read_only=True)
class Meta:
model = UserVote
fields = ('id', 'rating', 'created_by')
views.py
class UserVoteViewSet(viewsets.ModelViewSet):
queryset = UserVote.objects.all()
serializer_class = UserVoteSerializer
permission_classes = (IsCreatedByOrReadOnly, )
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
How can I save my model in DRF without having the user to supply created_by and instead set this field automatically in code?
Thanks in advance!
I had a similar problem and I solved it by explicitly creating and passing a new instance to the serializer. In the UserVoteViewSet you have to substitute perform_create with create:
def create(self, request, *args, **kwargs):
uv = UserVote(created_by=self.request.user)
serializer = self.serializer_class(uv, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I was able to solve this with one-liner in views.py
def create(self, request, *args, **kwargs):
request.data.update({'created_by': request.user.id})
return super(UserVoteViewSet, self).create(request, *args, **kwargs)
Since this view expects user to be authenticated, don't forget to extend permission_classes for rest_framework.permissions.IsAuthenticated
The other weird way you can do is use signals like this
#receiver(pre_save, sender=UserVote)
def intercept_UserVote(sender, instance, *args, **kwargs):
import inspect
for frame_record in inspect.stack():
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
break
else:
request = None
instance.pre_save(request)
Then basically you can define pre_save in your model
def pre_save(self, request):
# do some other stuff
# Although it shouldn't happen but handle the case if request is None
self.created_by = request.user
The advantage of this system is you can use same bit of code for every model. If you need to change anything just change in pre_save(). You can add more stuff as well
Add the following to the ViewSet:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
And the following on the Serializer:
class Meta:
extra_kwargs = {
'user': {
'required': False,
},
}
Below code worked for me.
Even I was facing same error after many experiments found something, so added all fields in serializer.py in class meta, as shown below -
class Emp_UniSerializer( serializers.ModelSerializer ):
class Meta:
model = table
fields = '__all__' # To fetch For All Fields
extra_kwargs = {'std_code': {'required': False},'uni_code': {'required': False},'last_name': {'required': False},'first_name': {'required': False}}
Here, we can update any field which are in "extra_kwargs", it wont show error ["This field is required."]
I am trying to get only courses belonging to a particular user below I have the model, serializer and view I am using to try and achieve this. If I delete the entire get_queryset function from the view the api returns the appropriate user and every course created by every user. If get_queryset remains, the api always returns user not found and still gives every course that exists. Can anyone point me to how I can achieve my desired result.
view:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
# queryset = User.objects.all()
serializer_class = UserSerializer
def get_queryset(self):
user = self.request.user
queryset = User.objects.all()
if user is not None:
queryset = queryset.filter(courses__owner_id=user.id)
return queryset
serializer
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, queryset=Course.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'courses']
Model
class Course (models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
pub_date = models.DateField(default=date.today)
owner = models.ForeignKey('auth.User', related_name='courses', on_delete=models.CASCADE)
def __str__(self):
return self.title
You need to filter objects by user
class CreatePostsView(viewsets.ModelViewSet):
model = Post
serializer_class = PostsSerializer
def get_queryset(self):
user = self.request.user
return Post.objects.filter(owner=user)
class CoursesByOwnerView(RetrieveModelMixin, GenericViewSet):
serializer_class = YourModelSerializer
authentication_classes =[TokenAuthentication,]
permission_classes = [IsAuthenticated,]
def list(self, request, *args, **kwargs):
course_taker = self.request.user
courses = YourModel.objects.filter(owner=course_taker).values('your_model_fields')
return Response(courses)
Given your answer in the comments:
Either you use self.request.user given by the authentication middleware. In this case, it will only work for authenticated users, and you can't see courses for another User.
Either you use the endpoint users/<int:pk>/ you mentioned. In this case, you can fetch the user with:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
serializer_class = UserSerializer
def get_queryset(self):
return UserDetail.objects.filter(pk=self.kwargs["pk"])
See this thread if you need another example: Django 2.0 url parameters in get_queryset
EDIT: In both cases, change your UserSerializer with:
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'courses']
Please consider these three models:
class Movie(models.Model):
name = models.CharField(max_length=254, unique=True)
language = models.CharField(max_length=14)
synopsis = models.TextField()
class TimeTable(models.Model):
date = models.DateField()
class Show(models.Model):
day = models.ForeignKey(TimeTable)
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
And each of them has their serializers:
class MovieSerializer(serializers.HyperlinkedModelSerializer):
movie_id = serializers.IntegerField(read_only=True, source="id")
class Meta:
model = Movie
fields = '__all__'
class TimeTableSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TimeTable
fields = '__all__'
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = '__all__'
And their routers
router.register(r'movie-list', views.MovieViewSet)
router.register(r'time-table', views.TimeTableViewSet)
router.register(r'show-list', views.ShowViewSet)
Now I would like to get all the TimeTable objects (i.e. date list) by filtering all the Show objects by a specific movie object. This code seems to be the working and getting the list like I want it
m = Movie.objects.get(id=request_id)
TimeTable.objects.filter(show__movie=m).distinct()
But I have no clue how to use this in django rest framework? I tried doing this way (which I am pretty sure its wrong), and I am getting error:
views.py:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
movie = Movie.objects.get(id=movie_id)
queryset = TimeTable.objects.filter(show__movie=movie).distinct()
serializer_class = TimeTableSerializer
urls.py:
router.register(r'date-list/(?P<movie_id>.+)/', views.DateListViewSet)
error:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
NameError: name 'movie_id' is not defined
How can I filter using viewsets in django rest framework? Or if there is any other prefered way than please list it out. Thank you.
ModelViewSet by design assumes that you want to implement a CRUD(create, update, delete)
There is also a ReadOnlyModelViewSet which implements only the GET method to read only endpoints.
For Movie and Show models, a ModelViewSet or ReadOnlyModelViewSet is a good choice whether you want implement CRUD or not.
But a separate ViewSet for a related query of a TimeTable which describes a Movie model's schedule doesn't looks so good.
A better approach would be to put that endpoint to a MovieViewSet directly. DRF provided it by #detail_route and #list_route decorators.
from rest_framework.response import Response
from rest_framework.decorators import detail_route
class MovieViewSet(viewsets.ModelViewset):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
#detail_route()
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
This endpoint will be available by a movie-list/:id/date_list url
Docs about extra routes
Register your route as
router.register(r'date-list', views.DateListViewSet)
now change your viewset as shown below,
class DateListViewSet(viewsets.ModelViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
Use a retrieve method, which will match any GET requests to endpoint /date-list/<id>/.
Advantage is that you don't have to explicitly handle the serialization and returning response you make ViewSet to do that hard part. We are only updating the queryset to be serialized and rest framework does the rest.
Since ModelViewSet is implemented as,
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
Its implementation includes the following methods (HTTP verb and endpoint on bracket)
list() (GET /date-list/)
create()(POST /date-list/)
retrieve()(GET date-list/<id>/)
update() (PUT /date-list/<id>/)
partial_update() (PATCH, /date-list/<id>/
destroy() (DELETE /date-list/<id>/)
If you want only to implement the retrieve() (GET requests to endpoint date-list/<id>/), you can do this instead of a `ModelViewSet),
from rest_framework import mixins, views
class DateListViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
The error
class DateListViewSet(viewsets.ModelViewSet, movie_id): NameError: name 'movie_id' is not defined
happens because movie_id is being passed as parent class of DataListViewSet and not as parameter as you imagined
This example in the documentation should be what you are looking for.
Adjust your URL:
url(r'date-list/(?P<movie_id>.+)/', views.DateListView.as_view())
Adjust your Model:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Your view would look like this:
class DateListView(generics.ListAPIView):
serializer_class = TimeTableSerializer
def get_queryset(self):
movie = Movie.objects.get(id=self.kwargs['movie_id'])
return TimeTable.objects.filter(show__movie=movie).distinct()
Another way to do it would be:
Adjust your URL:
router.register(r'date-list', views.DateListViewSet)
Adjust your Model:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Your view would look like this:
class DateListViewSet(viewsets.ModelViewSet):
serializer_class = TimeTableSerializer
queryset = TimeTable.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('show__movie_id')
Which will allow you to make requests such as:
http://example.com/api/date-list?show__movie_id=1
See documentation
Ivan Semochkin has the correct answer but the detail decorator is deprecated. It was replaced by the action decorator.
from rest_framework.decorators import action
class MovieViewSet(viewsets.ModelViewset):
#action(detail=True)
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
To improve #all-is-vanity answer, you can explicitly use movie_id as a parameter in the retrieve function since you are overriding the lookup_field class property:
def retrieve(self, request, movie_id=None):
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
You can also call self.get_object() to get the object:
def retrieve(self, request, movie_id=None):
movie = self.get_object()
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)