Replace PrimaryKeyRelatedField with another field - python

I have models which consist in a User model and a Present one. A User can have multiple Present but a Present has a unique User:
My models.py is:
from django.db import models
from django.contrib.auth.models import User
class Present(models.Model):
name = models.CharField(max_length=15)
price = models.FloatField()
link = models.CharField(max_length=15)
isAlreadyBought = models.BooleanField()
user = models.ForeignKey(User, related_name='presents', on_delete=models.CASCADE)
My serializers.py are:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from blog.models import Present, Location
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
presents = serializers.PrimaryKeyRelatedField(many=True, queryset=Present.objects.all(), required=False)
class Meta:
model = User
fields = ('username', 'password', 'email', 'presents')
def create(self, validated_data):
user = super().create(validated_data)
if 'password' in validated_data:
user.set_password(validated_data['password'])
user.save()
return user
class PresentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), read_only=False, many=False)
class Meta:
model = Present
fields = ('name', 'link', 'price', 'isAlreadyBought', 'user')
def create(self, validated_data):
return Present.objects.create(**validated_data)
Currently, if I want to get the all the presents associate with a given User, I use the primary key (in views.py):
class PresentsOfUser(viewsets.ModelViewSet):
queryset = Present.objects.all().filter(user=33)
serializer_class = PresentSerializer
However, I would rather use the username field of the User instead of the primary key.
I have tried using a SlugRelatedField but I am not sure this is the right way to achieve my goal:
class PresentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='username', read_only=False, many=False)
class Meta:
model = Present
fields = ('name', 'link', 'price', 'isAlreadyBought', 'user')
def create(self, validated_data):
return Present.objects.create(**validated_data)
And with this modification, I now use the following View to get the user 'Marcel' whose id is 33:
class PresentsOfUser(viewsets.ModelViewSet):
queryset = Present.objects.all().filter(user='Marcel')
serializer_class = PresentSerializer
But in this case, I get:
ValueError: invalid literal for int() with base 10: 'Marcel'
But if I replace user='Marcel' by user=33 (as previously), I get:
[{"name":"Nintendo","link":"fake_link","price":50.8,"isAlreadyBought":true,"user":"Marcel"},{"name":"Gamecube","link":"fake_link","price":50.8,"isAlreadyBought":true,"user":"Marcel"}]
where the user field is now the username and not the user's id.
However, I do not understand why filtering with user='Marcel' fails...

I ended up by overriding the get_querysetmethod while keeping the PrimaryKeyRelatedField in my serializer (with user__username='Marcel'as mishbah suggested):
class PresentsOfUser(viewsets.ModelViewSet):
serializer_class = PresentSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Present.objects.all()
username = self.kwargs['user']
if username is not None:
queryset = queryset.filter(user__username=username)
if len(queryset) == 0:
raise Http404
return queryset
It also works when replacing PrimaryKeyRelatedField by SlugRelatedField:
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='username', read_only=False, many=False)
and adding to_field='username'in the ForeignKey of my Present model:
user = models.ForeignKey(User, related_name='presents', to_field='username', on_delete=models.CASCADE)

I think your issue is w/ this line:
queryset = Present.objects.all().filter(user='Marcel')
With the assumption Marcel is username with pk => 33
You can't filter using a string, instead something like this:
queryset = Present.objects.all().filter(user__username='Marcel')
Hope that helps.

Related

Use a related field in lookup_field in Django REST framework

I have a user model and a profile model that is created automatically when a user is registered. In my view for retrieving the user profile, I am trying to get the user profile using the username but DRF uses pk by default. So I added a lookup field with the username but DRF can't seem to resolve it:
my profile model:
class Profile(TimeStampedModel):
user = models.OneToOneField('User', on_delete=models.CASCADE, related_name='customer_profile')
bio = models.TextField(blank=True)
image = models.URLField(blank=True)
def __str__(self):
return self.user.username
my profile serializer:
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.StringRelatedField(source='user.username', read_only=True)
bio = serializers.CharField(allow_blank=True, allow_null=True)
image = serializers.SerializerMethodField()
class Meta:
model = Profile
fields = ['username', 'bio', 'image']
def get_image(self, obj):
if obj.image:
return obj.image
return 'https://static.productionready.io/images/smiley-cyrus.jpg'
my profile retrieving view:
class ProfileRetrieveAPIView(generics.RetrieveAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
lookup_field = 'username'
def retrive(self, request, username, *args, **kwargs):
try:
profile = Profile.objects.select_related('user').get(
user__username = username
)
except Profile.DoesNotExist:
ProfileDoesNotExist
serializer = self.serializer_class(profile)
return Response(serializer.data, status=status.HTTP_200_OK)
my profile endpoint:
urlpatterns = [
# ....
path('profiles/<str:username>', views.ProfileRetrieveAPIView.as_view()),
]
I am getting the error:
Cannot resolve keyword 'username' into field. Choices are: bio, created_at, id, image, updated_at, user, user_id
What am I doing wrong?
Use nested serialzer.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username',)
And in your profile serializer:
username = UserSerializer(many=False, read_only=True)
Or use SerializerMethodField()
username = SerializerMethodField()
And define whats in in:
def username(self,obj):
//code
You can use this
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username', read_only=True)
bio = serializers.CharField(allow_blank=True, allow_null=True)
image = serializers.SerializerMethodField()
class Meta:
model = Profile
fields = ['username', 'bio', 'image']
def get_image(self, obj):
if obj.image:
return obj.image
return 'https://static.productionready.io/images/smiley-cyrus.jpg'

How to post with references in Django Rest Framework

I have Comment model related with User model
# models.py
class Comment(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
more fields...
....
In the serializer I want to do a create (POST) of a user comment.
But the post method is not enabled, only the put or patch method
Example: User Jon wants to create a comment
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class UserCommentSerializer(serializers.ModelSerializer):
# id of comment
url = serializers.HyperlinkedIdentityField(
view_name="user-comments-detail",
read_only=True
)
id = serializers.CharField(read_only=True)
comment = CommentSerializer()
class Meta:
model = User
fields = ['id', 'comment']
def create(self, validated_data):
comment_data = validated_data.pop('comment')
user = User.objects.create(**validated_data)
Comment.objects.create(user=user, **comment_data)
return user
I want to new comment, referencing the user
# views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserCommentSerializer
But I have an exception, that the user has no comment RelatedObjectDoesNotExist
My url like this
http://localhost:8000/user-comments/10
10 is a user_id pk
{} object post
Example: Comment.objects.create(user=pk, {})
Currently, only put and patch is enabled, but what I want to do is post of user
{
"url": "http://localhost:8000/user-comments/10",
"id": "10",
"comment": null
}
Comment does not exist
Any idea or suggestion?
You actually need just one serializer for that.
This will create a comment for the current logged in user.
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
read_only_fields = ['user']
def create(self, validated_data):
# get the user who sent the request
user = self.context['request'].user
return Comment.objects.create(user=user, **validated_data)
# views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def get_serializer_context(self):
# this is important since you want to pass the request object to your serializer
context = super().get_serializer_context()
context.update({"request": self.request})
return context
The exception you are getting is because ModelSerializer is linked to a specific model, in this case you linked UserCommentSerializer to model User. Variable Meta.fields specifies which fields of the model are being returned by the serializer and, so, you are getting an exception because such variable is set to ('id', 'comment') and model User doesn't have a field comment.
You can achieve what you want, this way:
class UserCommentSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id']
extra_kwargs = {'comment': {'write_only': True}}
def create(self, validated_data):
comment_data = validated_data.pop('comment')
user = User.objects.create(**validated_data)
Comment.objects.create(user=user, comment=comment_data)

Update a field value in model Django Rest

serializers.SerializerMethodField() is only meant to read value ie
retrieve, it will not update the lang value.
I want to create an API that can update a field value. Mainly, I want to change the field lang in the User model. Here is my code
models.py
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
member_since = models.DateTimeField(default=timezone.now)
lang = models.CharField(max_length = 5, default = 'en')
def get_lang(self):
return self.lang
serializers.py
from django.contrib.auth import get_user_model
User = get_user_model()
class ProfileRetrieveUpdateSerializer(serializers.ModelSerializer):
member_since = serializers.SerializerMethodField()
lang = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'member_since','lang'
]
def get_member_since(self, obj):
return obj.profile.member_since.date()
def get_lang(self, obj):
return obj.profile.get_lang()
def update(self, instance, validated_data):
print(validated_data)
setattr(instance.profile, "lang", validated_data.get("lang",instance.profile.lang))
instance.save()
return instance
views.py
from django.contrib.auth import get_user_model
from .serializers import (
CurrentUserDetailSerializer, ProfileRetrieveUpdateSerializer,
UserLoginSerializer, UserSerializerWithToken,
)
User = get_user_model()
class ProfileRetrieveUpdateAPIView(RetrieveUpdateAPIView):
"""
View that returns user profile data.
"""
permission_classes = [AllowAny]
queryset = User.objects.all()
serializer_class = ProfileRetrieveUpdateSerializer
lookup_field = 'username'
lookup_url_kwarg = 'username'
axios.patch(url, {'lang': 'fr') (additional, I don't think it's wrong here)
When I call API, I get that the validated_data value is {}!
I think the problem is in the serializers.py module.
lang = serializers.CharField(source = 'get_lang')
def update(self, instance, validated_data):
setattr(instance.profile, "lang", validated_data.get("get_lang"))
instance.save()
return instance
reason: serializers.SerializerMethodField() is only meant to read value ie retrieve, it will not update lang value.

Django CreateView - Display only particular objects in foreignkey field

I have a CreateView view that holds a bunch of fields that need to be filled by the user when creating a new contact. Now, I want the user to be able to see and choose only from the categories that they'd created.
This is the model of Category:
class Category(models.Model):
class Meta:
verbose_name = _('category')
verbose_name_plural = _('categories')
name = models.CharField(max_length=100, unique=True)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
def __unicode__(self):
return self.name
This is the view:
class ContactCreate(LoginRequiredMixin, generic.edit.CreateView):
model = models.Contact
success_url = reverse_lazy('site:contacts')
fields = ['firstname', 'lastname', 'phone1', 'phone2', 'email', 'city', 'category']
template_name = 'site/contacts.html'
context_object_name = 'all_contacts'
What I need the user to see is a select that has only the categories which include the appropriate profile foreign key associated with them.
I'd be glad to get some help with this. Thank you!
You can override the get_form method of the view and set the queryset of the appropriate field:
class ContactCreate(LoginRequiredMixin, generic.edit.CreateView):
# ...
def get_form(self, *args, **kwargs):
form = super(ContactCreate, self).get_form(*args, **kwargs)
form.fields['categories'].queryset = Category.objects.filter(profile=self.request.user.profile)
return form
This, of course, assumes that your Profile model has a OneToOneField to User with related_name 'profile', otherwise you'd have to adjust the filtering.

Django REST: create CRUD operations for OneToOne Field

I'm trying to create basic CRUD operations for OneToOne field.
The user is not required to set the profile when signing in. How do I create/update/delete profile when needed (assuming the user is already in the DB)?
My models are the default User models from Django REST and:
class UserProfile(models.Model):
user = models.OneToOneField(User)
location = models.CharField(max_length=50,blank=True)
title = models.CharField(max_length=80,blank=True)
#picture = models.ImageField(upload_to='user_imgs', blank=True)
website = models.URLField(blank=True)
My Viewsets are:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_fields = ['id', 'username', 'email', 'first_name', 'last_name']
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
filter_fields = ['user_id', 'location', 'title', 'website']
And serializes:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
email = serializers.EmailField()
fields = ('id','username', 'email', 'first_name', 'last_name')
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user_id = serializers.CharField(source='user.id')
class Meta:
model = UserProfile
fields = ('user_id', 'location','title','website')
I belive you want to restrict the profile creation to the current logged in user. You can filter the queryset of profiles to the current user, this way only that user's profile will be accessible by the logged in user.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_fields = ['id', 'username', 'email', 'first_name', 'last_name']
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
filter_fields = ['user_id', 'location', 'title', 'website']
def get_queryset(self):
return super(UserProfileViewSet, self).get_queryset().filter(
user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=user)
You make the user field read only and is being saved in the above method perform_create and assigned always to the current user.
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserProfile
fields = ('user', 'location','title','website')
read_only_fields = ('user',)
It should focus to define view how to receive request and process raw data, not the model and serializer of the field definition.
I give you a CRUD example for basic User operation as the reference:
lu = LibraryUser(library_membership_number= '...', user_id = user)
class ExampleAPIView(APIView):
def get(self, request):
username = request.query_params.get('username', '')
user = User.objects.get(username=username)
return Response(ExampleSerializer(user).data)
def post(self, request):
username = request.data.get('username', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
user = User.objects.create_user(username=username, email=email, password=password)
user.save()
Response({'status': 'ok'}})
def put(self, request):
username = request.data.get('username', '')
old_password = request.data.get('old_password', '')
new_password = request.data.get('new_password', '')
user = authenticate(username=username, password=old_password)
if not user:
return Response({'status': 'fail'}})
user.set_password(new_password)
return Response({'status': 'ok'}})
def delete(self, request):
username = request.query_params.get('username', '')
user.objects.get(username=username).delete()
return Response({'status': 'ok'}})
Accord to the example, these are my definitions for each method:
GET: Retrieve the user profile
POST: Create a new user
PUT: Change the user of the password
DELETE: Delete the user
So, it will implement Basic CRUD api for user instance.
I hope that it can help you how to design api.
If you don't still understand how to operate model, I will more introduce the example:
class ExampleAPIView(APIView):
def get(self, request):
username = request.query_params.get('username', '')
userprofile = UserProfile.objects.get(user__username=username)
return Response(ExampleSerializer(userprofile).data)
def put(self, request):
username = request.data.get('username', '')
userprofile = UserProfile.objects.get(user__username=username)
if not userprofile :
return Response({'status': 'fail'}})
userprofile.location = ...
userprofile.title = ...
userprofile.website = ...
userprofile.save()
return Response({'status': 'ok'}})

Categories