I've been stumped by this for a couple of hours now, and have read through a bunch of documentation and different tutorials, but I still do not know what I'm doing wrong.
I'm trying to make a POST request to create a Comment (my model, views, etc. defined below) and make an association with the User that is making the POST request, but I keep getting the error {"user":["This field is required."]}
I thought that adding
def perform_create(self, serializer):
serializer.save(user=self.request.user)
to my viewset would make this easy, but that doesn't appear to be working...
My models.py looks like:
import uuid
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class Comment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
text = models.TextField(blank=False, null=False)
created = models.DateTimeField(auto_now_add=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.CharField(max_length=256)
content_object = GenericForeignKey('content_type', 'object_id')
serializer.py
from rest_framework import serializers
from .models import Comment
from organization.serializers import UserSerializer
from django.contrib.auth import get_user_model
User = get_user_model()
class CommentSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True, required=False)
user = UserSerializer(required=True)
text = serializers.CharField(required=True)
created = serializers.DateTimeField(read_only=True)
object_id = serializers.CharField(required=False)
class Meta:
model = Comment
fields = ('id', 'user', 'text', 'created', 'object_id')
read_only_fields = ('id', 'created')
views.py
from rest_framework import viewsets
from rest_framework import permissions
from .models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return self.queryset.filter(object_id=self.kwargs['object_id'])
def perform_create(self, serializer):
serializer.save(user=self.request.user)
urls.py
from django.urls import include, path
from rest_framework import routers
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register(r'(?P<content_type>[\w\-]+)/(?P<object_id>[\w\-]+)', CommentViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Serializer validation works before perform_create, change user field to read only and it works as expected.
class CommentSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True, required=False)
user = UserSerializer(read_only=True)
text = serializers.CharField(required=True)
created = serializers.DateTimeField(read_only=True)
object_id = serializers.CharField(required=False)
class Meta:
model = Comment
fields = ('id', 'user', 'text', 'created', 'object_id')
read_only_fields = ('id', 'created')
Ok - I got this working by overriding the create method of CommentViewSet and simplifying my CommentSerializer
serializer.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('id', 'user', 'text', 'created', 'edited', 'object_id', 'content_type')
read_only_fields = ('id', 'created')
views.py
from rest_framework import viewsets
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from django.contrib.contenttypes.models import ContentType
from .models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return self.queryset.filter(object_id=self.kwargs['object_id'])
def create(self, request, **kwargs):
"""
Overrides the standard create method so that we can set User,
object_id and content_type based on requesting user and kwargs
passed in URL
"""
comment_data = self.request.data
# change request data so that it's mutable, otherwise this will raise
# a "This QueryDict instance is immutable." error
comment_data._mutable = True
# set the requesting user ID for the User ForeignKey
comment_data['user'] = self.request.user.id
comment_data['object_id'] = self.kwargs['object_id']
# pass kwarg from URL to `model` to get the corresponding object
content_type = ContentType.objects.get( model=self.kwargs['content_type'] )
# pass the ID from the ContentType object to `content_type`, expected
# by serializer
comment_data['content_type'] = content_type.id
serializer = CommentSerializer(data=comment_data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Related
I am new to mobile app development and I am trying to make my first app with react-native and Django rest-framework as the backend. When I try to run the server and access any model through the django-rest-framework I get
"TypeError: 'type' object is not iterable." I have tried to look up a way to solve it but every way I found online did not help.
Here is my code:
models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator
class Movie(models.Model):
title = models.CharField(max_length=32)
description = models.TextField()
def no_of_ratings(self):
ratings = Rating.objects.filter(movie=self)
return len(ratings)
def avg_rating(self):
sum = 0
ratings = Rating.objects.filter(movie=self)
for rating in ratings:
sum += rating.stars
if len(ratings) > 0:
return sum / len(ratings)
else:
return 0
class Rating(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
stars = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
class Meta:
unique_together = (('user', 'movie'))
index_together = (('user', 'movie'))
serializers.py
from rest_framework import serializer
from .models import Movie, Rating
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields =('id', 'title', 'description', 'no_of_ratings', 'avg_rating')
class RatingSerializer(serializers.ModelSerializer):
class Meta:
model = Rating
fields =('id', 'stars', 'user', 'movie')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields =('id', 'username', 'password')
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
views.py
from django.shortcuts import render
from rest_framework import viewsets, status
from .serializers import MovieSerializer, RatingSerializer, UserSerializer
from .models import Movie, Rating
from rest_framework.response import Response
from rest_framework.decorators import action
from django.contrib.auth.models import User
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission
# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
authentication_classes = (TokenAuthentication)
permission_classes = (IsAuthenticated)
#action(detail=True, methods=['POST'])
def rate_movie(self, request, pk=None):
if 'stars' in request.data:
movie = Movie.objects.get(id=pk)
stars = request.data['stars']
user = request.user
try:
rating = Rating.objects.get(user=user.id, movie=movie.id)
rating.stars = stars
rating.save()
serializer = RatingSerializer(rating, mamy=False)
Rating.objects.create(user=user, movie=movie, stars=stars)
response = {'message': 'Rating Updated', 'result': serializer.data}
except:
rating = Rating.objects.create(user=user, movie=movie, stars=stars)
serializer = RatingSerializer(rating, mamy=False)
response = {'message': 'Rating created', 'result': serializer.data}
return Response(response, status=status.HTTP_200_OK)
else:
response = {'message': 'you need to provide stars'}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
class RatingViewSet(viewsets.ModelViewSet):
queryset = Rating.objects.all()
serializer_class = RatingSerializer
authentication_classes = (TokenAuthentication)
permission_classes = (AllowAny)
def update(self, request, *args, **kwargs):
response = {'message': 'You cant update rating like that'}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
def create(self, request, *args, **kwargs):
response = {'message': 'You cant create rating like that'}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
How can I fix this error?
authentication_classes and permission_classes must be tuple or list. When you put single class in parantheses, it will not behave with it as tuple but just one, so cannot iterate over it.
Change them as below in all your viewsets (Put an extra comma after them to show python that these parantheses demonstrate tuple):
#...
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
#...
I am using Django DRF, and having difficulty in applying SerializerMethodField (https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)
Following is a simple case of typical model, serializer code, which works perfectly.
serializer.py (before)
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
view.py (before)
#api_view(['GET'])
def GetAllUsers(request):
Users = User.objects.all()
serializer = UserSerializer(Users, many=True)
return Response(serializer.data)
In order to deliver additional information which is not included in the model, I changed serializer
serializer.py (after)
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
Now I have to find a way to give obj(which has date_joined inside) to serializer, and I think I have to do it in view.py
But I don't know how to do it.
Thanks
You can pass extra context to serializer like this
view.py
#api_view(['GET'])
def GetAllUsers(request):
Users = User.objects.all()
context = {"extra_obj": extra_obj}
serializer = UserSerializer(Users, many=True, context=context)
return Response(serializer.data)
serializer.py
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
def get_days_since_joined(self, obj):
extra_obj = self.context.get("extra_obj")
return (now() - extra_obj.date_joined).days
reference the official document here
You just have to include SerializerMethodField name and other model fields which you want to return to api.
There is no need to change views.py, you just have to change serializer.py.
In your case serializer.py will be like
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'id',
'name',
'days_since_joined',
]
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
I'm developing a backend with the django rest api. I have the same problem with my advertise model except for my user model. My choice linked to positiveintegerfielder is not displayed in restframework for post method.I'm doing an action like in serializer.py, whose photo I shared to display. When I do this, it is not displayed in restframework and I cannot use the post method.
Thank you very much in advance
serializer.py##
from user.models import User
from rest_auth.serializers import *
class ChoicesSerializerField(serializers.SerializerMethodField):
def __init__(self, choices, **kwargs):
self._choices = choices
super(ChoicesSerializerField, self).__init__(**kwargs)
def to_representation(self, value):
# sample: 'get_XXXX_display'
method_name = 'get_{field_name}_display'.format(field_name=self.field_name)
# retrieve instance method
method = getattr(value, method_name)
# finally use instance method to return result of get_XXXX_display()
return method()
def to_internal_value(self, data):
return getattr(self._choices, data)
class UserSerializer(serializers.ModelSerializer):
gender = ChoicesSerializerField(choices=User.gender)
class Meta:
model = User
fields = ('gender',)
view.py##
from rest_framework.viewsets import ModelViewSet
from user.serializer import UserSerializer
from .models import *
from rest_framework import permissions
from rest_framework.generics import CreateAPIView, ListAPIView, GenericAPIView, get_object_or_404
class CreateUserView(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
User model##
from django.contrib.auth.models import AbstractUser
from django.core.validators import RegexValidator
from django.db import models
from user.choices.choice import MartialStatusChoices,
EducationalStatusChoices, ProfessionChoices, GenderChoices
class Interest(models.Model):
name = models.CharField(max_length=50, null=True, blank=True )
def __str__(self):
return self.name
class User(AbstractUser):
birthday = models.DateField(null=True)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$')
phone_number = models.CharField(validators=[phone_regex],
max_length=17, blank=True)
gender =
models.PositiveIntegerField(null=True,choices=GenderChoices.CHOICES)
martial_status = models.PositiveIntegerField(null=True,
choices=MartialStatusChoices.CHOICES)
educational_status = models.PositiveIntegerField(null=True,
choices=EducationalStatusChoices.CHOICES)
profession = models.PositiveIntegerField(null=True,
choices=ProfessionChoices.CHOICES)
interests = models.ManyToManyField(to=Interest,null=True,
blank=True)
class Meta:
verbose_name= "User"
You should try serializers.ChoiceField.
from user.choices.choice import GenderChoices
class UserSerializer(serializers.ModelSerializer):
gender = serializers.ChoiceField(choices=GenderChoices.CHOICES)
class Meta:
model = User
fields = ('gender',)
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
permission_classes = (permissions.AllowAny,) # remove this if you're using authentication
def get(self, request):
serializer = UserSerializer(instance=request.user)
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
def post(self, request):
serializer = UserSerializer(instance=request.user, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response()
Trying to do a fairly simple project where I can upload multiple images to my server with just a few other fields. I tried to follow this post but wasn't able to get anywhere on it. I know I am going to need to break my models into two with 'PostImage' having a ForeignKey that relates to Post. And I know I will need to modify the serializer in order to allow an array of image files. Any help would be greatly appreciated.
models.py:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length = 100)
content = models.TextField()
image = models.ImageField(upload_to='post_images')
def __str__(self):
return self.title
serializers.py:
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
views.py:
from django.shortcuts import render
# Create your views here.
from .serializers import PostSerializer
from .models import Post
from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
# Create your views here.
class PostView(APIView):
parser_classes = (MultiPartParser, FormParser)
def get(self, request, *args, **kwargs):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
posts_serializer = PostSerializer(data=request.data)
if posts_serializer.is_valid():
posts_serializer.save()
return Response(posts_serializer.data, status=status.HTTP_201_CREATED)
else:
print('error', posts_serializer.errors)
return Response(posts_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You need to have the ForeignKey relation with the Post Model. You can do like this:
models.py
from django.db import models
class Media(models.Model):
detail = models.CharField(max_length=200, default='', null=True, blank=True)
user = models.ForeignKey(to=MyUser, related_name='user_media')
type = models.CharField(max_length=20, choices=Constant.model.MEDIA_TYPE, default='other')
media = models.FileField(max_length=10000, upload_to=upload_media_file, blank=True, null=True)
class Post(models.Model):
title = models.CharField(max_length = 100)
content = models.TextField()
media = models.ForeignKey(Media, related_name='posts')
def __str__(self):
return self.title
serializer.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
media = serializers.ListField(write_only=True,
child=serializers.FileField(max_length=10000000,
allow_empty_file=True,
use_url=False)
class Meta:
model = Post
fields = '__all__'
def is_valid(self, raise_exception=False):
if len(data.getlist('media')) > 5:
raise 'some error'
https://gist.github.com/ranman/3d97ea9054c984bca75e
Desired Behavior
User lookup happens by the username: /api/users/randall
Speaker lookup happens by the username as well: /api/speakers/randall
Constraints
Not all users are speakers. All speakers are users.
models.py
from django.contrib.auth.models import User
class Speaker(models.Model):
user = models.OneToOneField(User)
serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
lookup_field = 'username'
class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.HyperlinkedRelatedField(
view_name='user-detail',
read_only=True,
lookup_field='username'
)
class Meta:
model = Speaker
lookup_field = 'user'
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
class SpeakerViewSet(viewsets.ModelViewSet):
queryset = Speaker.objects.all().select_related('user')
serializer_class = SpeakerSerializer
lookup_field = "user"
I've tried various different invocations of lookup_field and serializer types to get this working to no avail. It may not be possible without a lot more code. I'm just wondering what direction I can take.
This is how I managed to hack it
models.py
from django.db import models
from django.contrib.auth.models import User
class Speaker(models.Model):
user = models.OneToOneField(User)
#property
def user__username(self):
return self.user.username
def __unicode__(self):
return self.user.username
serializers.py
from .models import Speaker
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
lookup_field = 'username'
class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.HyperlinkedRelatedField(
view_name='user-detail',
read_only=True,
lookup_field='username'
)
class Meta:
model = Speaker
fields = ('url', 'user')
lookup_field = 'user__username'
view.py
from .models import Speaker
from .serializers import SpeakerSerializer, UserSerializer
from rest_framework import viewsets
from django.contrib.auth.models import User
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
class SpeakerViewSet(viewsets.ModelViewSet):
queryset = Speaker.objects.all().select_related('user')
serializer_class = SpeakerSerializer
lookup_field = 'user__username'
The only thing I changed from your code is to override the get_object method by filtering with the username instead of the default pk. I also changed the lookup_field to a descriptive name and used ModelSerializer and StringRelated in the serializer.py.
models.py
class Speaker(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
serializer.py
class SpeakerSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True)
class Meta:
model = Speaker
lookup_field = "username"
fields = "__all__"
views.py
class SpeakerViewSet(ModelViewSet):
queryset = Speaker.objects.all().select_related("user")
serializer_class = SpeakerSerializer
lookup_field = "username"
def get_object(self):
"""Return the object for this view."""
return get_object_or_404(self.queryset, user__username=self.kwargs["username"])
urlconf
api/ ^speaker/$ [name='speaker-list']
api/ ^speaker\.(?P<format>[a-z0-9]+)/?$ [name='speaker-list']
api/ ^speaker/(?P<username>[^/.]+)/$ [name='speaker-detail']
api/ ^speaker/(?P<username>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='speaker-detail']
Have you tried this approach?
class SpeakerViewSet(viewsets.ModelViewSet):
queryset = Speaker.objects.all().select_related('user')
serializer_class = SpeakerSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('user', 'user__username',)
I'm fetching user's settings with user's ID [GET / Update]
urls.py
path('user/<int:user_id>/settings/preferences/', UserPreferenceSettingsView.as_view(), name="settings_preferences")
models.py
class UserSetting(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
adult_lock = models.BooleanField(default=False)
child_lock = models.BooleanField(default=False)
promotional_email = models.BooleanField(default=True)
update_email = models.BooleanField(default=True)
updated_at = models.DateTimeField(auto_now=True)
views.py
class UserPreferenceSettingsView(generics.RetrieveUpdateAPIView):
http_method_names = ['get', 'patch']
serializer_class = UserPreferenceSettingsSerializer
def get_object(self):
lookup_field = self.kwargs["user_id"]
return get_object_or_404(UserSetting, user__pk=lookup_field)
If you need to fetch from username just replace user_id to username and url <int:user_id> to <username> or <str:username>