I've been struggling to understand how one would update a M2M-field in the django rest framework that is between a custom user and some random field. I should mention I'm using Djoser as authentication.
Lets say I have a custom user
Models:
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
paying_user = models.BooleanField(default=False)
subscribed_companies = models.ManyToManyField('myapp.Company')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserAccountManager()
def __str__(self):
return f"{self.email}' account"
class Company(models.Model):
name = models.CharField(max_length=150)
def __str__(self):
return self.name
class Meta:
ordering = ['name']
My serializers
Imports - serializers.py:
from django.contrib.auth import get_user_model
from djoser.serializers import UserCreateSerializer
from rest_framework import serializers
from apartments.models.company_model import Company
User = get_user_model()
class UserCreateSerializer(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model = User
fields = ('email','password', 'paying_user', 'subscribed_companies')
class UserCompanyListSerializer(serializers.ModelSerializer):
#Is this a reasonable way to serialize a M2M-field?
subscribed_company_ids = serializers.PrimaryKeyRelatedField(many=True, read_only=False,
queryset=Company.objects.all(), source='subscribed_companies')
class Meta:
model = User
fields = [
'subscribed_company_ids'
]
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name',)
As you can see, I've attached a M2M-field on the custom user itself, instead of using a OneToOne-field where I store custom data. I'm not sure this is the best way to do it.
The idea is that a user should be able to, on the front end, have a list of companies it wants to subscribe to once they are logged in. That means I'll have many users that can subscribe to many companies.
Where I'm really doubting myself is how I handled the class based views.
Since I can retrieve the ID from request.user.id and since I want to replace the entire list of companies, I don't need the PK which identifies a specific company.
Therefore, in the put method, I removed the PK parameter. This works.
So my question is - Is there a more clean way to do it? Looking at posts at stackoverflow I couldn't find a decent answer that involved authentication. Am I approaching it wrong?
class UserCompanies(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get(self, request):
user_id = request.user.id
instance = CustomUser.objects.get(id=user_id)
serializer = UserCompanyListSerializer(instance)
return Response(serializer.data)
def put(self, request, format=None):
user_id = request.user.id
instance = CustomUser.objects.get(id=user_id)
serializer = UserCompanyListSerializer(instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How a GET request response would look to localhost:8000/usercompanies/:
{
"subscribed_company_ids": [
2,
1,
3
]
}
How a PUT request response would look to localhost:8000/usercompanies/:
{
"subscribed_company_ids": [
2,
1,
3,
5,
4,
]
}
Feedback would be much appreciated, I'm a total DRF newbie.
Related
I have imported User model and customized it a/c to my need and make OneToOne Relation with UserProfileModel Model. While retrieving data I got this error.
"The serializer field might be named incorrectly and not match any attribute or key on the AnonymousUser instance.
Original exception text was: 'AnonymousUser' object has no attribute 'gender'."
My Model is :
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='userprofilemodel')
gender = models.CharField(max_length=20)
locality = models.CharField(max_length=70)
city = models.CharField(max_length=70)
address = models.TextField(max_length=300)
pin = models.IntegerField()
state = models.CharField(max_length=70)
profile_image = models.FileField(upload_to='user_profile_image', blank=True)
My Serializer looks like:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = ['id', 'name' , 'email','mobile',]
class UserProfileModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model= UserProfileModel
fields = ['user','gender' , 'locality','city','address','pin','state','profile_image', ]
My view looks like:
class UserProfileDataView(APIView):
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
I want to retrieve profile data of the logged user using UserProfileModel Model
Your first issue in that you are passing a User instance to the UserProfileModelSerializer, which is expecting a UserProfileModel instance. To fix this you need to change:
serializer = UserProfileModelSerializer(request.user)
to
serializer = UserProfileModelSerializer(request.user.userprofilemodel)
where userprofilemodel is the related_name you have set on the user field in your UserProfileModel.
Second issue is, as Mohamed Beltagy said, you're allowing anyone to access the view, including unauthenticated users. Django rest framework has a built in mixin that you can use to restrict access to authenticated users only (https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated).
from rest_framework.permissions import IsAuthenticated
class UserProfileDataView(APIView):
permission_classes = [IsAuthenticated]
the problem here is you are passing an anonymous user which has no profile ( you permit non-authenticated users access this view)
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
if request.user.is_authenticated:
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)
Hi I'm wondering what the best practice is for creating a new model entry with a user based off the request in Django Rest Framework?
Models:
class Asset(models.Model):
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, related_name="assets")
name = models.CharField(max_length=200)
amount = models.DecimalField(max_digits=10, decimal_places=2)
Serializers:
class AssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = '__all__'
Views
class CreateAssetView(generics.CreateAPIView):
serializer_class = AssetSerializer
<Doesn't seem to work, possibly since user isn't in the json>
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Basically I want to be able to send a POST request {name: 'myasset', amount: '50'} to this endpoint and have a new Asset saved with the User field obtain from the request. What is the best way to do this? Thanks
*** EDIT ***
Thought of a better solution:
class CreateAssetView(generics.CreateAPIView):
serializer_class = AssetSerializer
queryset = Asset.objects.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
However this means I must send a dummy user_id in the POST request from the front-end. I'm not sure how this can be avoided. Any suggestions highly welcome.
I do most often this thing using function-based views not class-based ones. :
Basically, that will also be able to send a POST request and will save the user who requested the post request.
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def perform_create(request, pk):
user = request.user
asset= Asset.objects.get(id=pk)
data = request.data
# Create Asset
asset = Asset.objects.create(
user=user,
name=user.first_name,
amount=data['amount '],
)
asset.save()
return Response('Asset Added')
And to return the data I create another view for the serialized data
where needed. I guess there would be other approaches I'm sure but
this one is much simple and easy to do.
Since Post "author" cannot be null then we need to provide a user,
one way to do this is to put the user instance in the request.data from the frontend...
the example below is assigning the user instance to request.data from the backend after the request is made!
...models.py
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
content = models.CharField(max_length=255)
...views.py
class PostCreate(CreateAPIView):
queryset = Post
serializer_class = PostSerializer
# override this method
def create(self, request, *args, **kwargs):
request.data['author'] = request.user.pk
return super().create(request, *args, **kwargs)
everyone, I am absolutely a beginner in DjangoRestFramework. I have confusion to deal with relationships in DRF. How do I save foreign keys data using APIView?
models
class User(AbstractUser):
email = models.EmailField(max_length=255, unique=True)
is_client = models.BooleanField(default=False)
is_professional = models.BooleanField(default=False)
class Client(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='client')
##
class Professionals(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='professional')
##
class HireProfessional(models.Model):
client = models.ForeignKey(Client, related_name='user', on_delete=models.CASCADE)
professional = models.ForeignKey(Professionals, on_delete=models.CASCADE, related_name="professsional")
hire_start_date_time = models.DateTimeField(default=timezone.now)
Serializers
class ProfessionalSerializer(serializers.ModelSerializer):
profilePicture = serializers.ImageField(allow_empty_file=True, use_url='professional/profiles', required=False)
skill = SkillSerializer(many=True,read_only=True)
class Meta:
model = Professionals
fields = fields = ['first_name', 'last_name', 'profilePicture', 'profession', 'phone_number', 'experience', 'skill', 'charge_fee', 'about_me']
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['user_id', 'first_name', 'last_name', 'phone_number', 'profilePicture']
class UserSerializer(serializers.ModelSerializer):
client = ClientSerializer()
professional = ProfessionalSerializer()
class Meta:
model = User
fields = ('email', 'username', 'is_client', 'is_professional', 'client', 'professional')
class HireProfessionalSerializer(serializers.ModelSerializer):
client = ClientSerializer()
professional = professionalSerializer()
class Meta:
model = HireProfessional
fields = ['id','client', 'professional', 'hire_start_date_time']
views ##Edited
class HireProfessionalCreateApp(APIView):
permission_classes = (IsAuthenticated, IsClientUser,)
def current_user(self):
user = self.request.user.client
return user
def post(self, request, username, format=None):
try:
professional = Professionals.objects.get(user__username=username)
# print('hello', professional.user_id)
data = request.data
data['client'] = self.current_user()
data['professional'] = professional.user_id
serializer = HireProfessionalSerializer(data=data)
data = {}
if serializer.is_valid():
hire = serializer.save()
hire.save()
return JsonResponse ({
"message":"professional hired success.",
# "remaning_time":remaning_datetime,
"success" : True,
"result" : serializer.data,
"status" : status.HTTP_201_CREATED
})
else:
data = serializer.errors
print(data)
return Response(serializer.data)
except Professionals.DoesNotExist:
return JsonResponse ({"status":status.HTTP_404_NOT_FOUND, 'message':'professional does not exists'})
This is a hiring app.
Client able to hire to professional
client=logged in user
professional=passed id or username through URL
like: path('hire-professional/<professional id or username>', views)
Does anybody have any idea? how to solve it.
Consider using a ModelViewSet instead of an APIView since you're directly modifying the HireProfessional model. Additionally, the model uses ForeignKey fields for Client and Professional, you do not need to include their respective serializers in HireProfessionalSerializer.
Implementing just the ModelViewSet (and adding it to the urls.py using a router) will mean that a user can select the client & professional themself, which means we're not done yet. It is recommended to use a router in DRF instead of adding all the views to urls.py manually.
You can use the ModelViewSet to override the perform_create and perform_update functions in which you autofill serializer fields. :
default create function
def perform_create(self, serializer):
serializer.save()
autofill example
def perform_create(self, serializer):
# client is derived from logged in user
client = Client.objects.get(user=self.request.user)
# Autofill FK Client.
serializer.save(client=client)
Now the client is autofilled, but the professional isn't yet. If you want to also autofill the professional you will have to look into using a nested router since you want to retrieve the PK(id) from the URL. If you do so your URL would look something like this:
url='/professionals/{professionals_pk}/hire/'
I hope this gives you an idea of where to start. If you have any questions do let me know.
I am trying to create a simple API to get a user register.
I am using the default User table for authentication purpose, created another table called "phone" with one to one relation with User.
I am trying to add "phone" field just above the password. (I hope the image attached is visible).
**
Serializer.py
class UserRegisterSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetailsModel
fields = ('phone', 'user')
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=68, min_length=6, write_only=True)
class Meta:
model = User
fields = ('username','first_name', 'last_name','email','password')
read_only_fields = ('id',)
**
models.py<<
**
class UserDetailsModel(models.Model):
phone = models.IntegerField()
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
user = models.OneToOneField(get_user_model(),primary_key='email' , on_delete=models.CASCADE)
def __str__(self):
return str(self.user)
**
views.py
**
class RegisterView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
class DetailsRegisterView(generics.GenericAPIView):
serializer_class = UserRegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
**
urls
**
urlpatterns = [
path('',RegisterView.as_view()),
path('details', DetailsRegisterView.as_view())
]
**
You probably can use source in a serializer with a FK
class RegisterSerializer(...)
...
phone = serializers.CharField(..., source='userdetails.phone')
see also : the doc
I have some doubt in create case, in a update case this code work fine.
see also : How to serialize a relation OneToOne in Django with Rest Framework?
and an other way to resolve your issue : nested serializer
Updated code:
serializers>
from django.contrib.auth.models import User
from django.http import JsonResponse
from rest_framework import serializers
from .models import UserDetailsModel
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username','first_name', 'last_name','email','password')
read_only_fields = ('id',)
class UserRegisterSerializer(serializers.ModelSerializer):
user = RegisterSerializer(required=True)
class Meta:
model = UserDetailsModel
fields = ('phone','user')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = RegisterSerializer.create(RegisterSerializer(), validated_data=user_data)
data, created = UserDetailsModel.objects.update_or_create(user=user,
phone=validated_data.pop('phone'))
return data
class DetailView(serializers.ModelSerializer):
user = RegisterSerializer(read_only=True)
class Meta:
model = UserDetailsModel
fields = ('user','phone')
Remaining code stays the same.
I'm implementing some voting functionality in an application, where a logged-in user specifies a post that they would like to vote for using a payload like this:
{
"post": 1,
"value": 1
}
As you can tell, the a user field is absent - this is because it gets set in my viewset's perform_create method. I've done this to ensure the vote's user gets set server side. This is what the viewset looks like:
class CreateVoteView(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = VoteSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Here is what the model looks like:
class Vote(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='votes', null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='votes', null=False)
class Values(models.IntegerChoices):
UP = 1, _('Up')
DOWN = -1, _('Down')
value = models.IntegerField(choices=Values.choices, null=False)
class Meta:
unique_together = ('post', 'user')
and finally, the serializer:
class VoteSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['post', 'value']
From what I understand, in order for DRF to enforce a unique together validation, both fields (in my case, user and post) must be included in the serializer's fields. As I've mentioned, I'd like to avoid this. Is there any other way of implementing this type of validation logic?
EDIT:
To clarify: the records do not save - I receive this error:
django.db.utils.IntegrityError: (1062, "Duplicate entry '1-3' for key 'api_vote.api_vote_post_id_user_id_73614533_uniq'")
However, my goal is to return a Bad Request instead of an Internal Server Error much like I would when traditionally using a DRF serializer and excluding required fields from a payload.
To output a custom error message due to the IntegrityError, you can override the create method in your serializer:
from django.db import IntegrityError
class VoteSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['post', 'value']
def create(self, validated_data):
try:
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
except IntegrityError:
error_msg = {'error': 'IntegrityError message'}
raise serializers.ValidationError(error_msg)
You can try this on your views
try:
MoviesWatchList.objects.create(user=request.user, content=movie)
return response.Response({'message': f'{movie} added in watchlist.'}, status=status.HTTP_201_CREATED)
except:
return response.Response({'message': f'{movie} already added to watchlist.'}, status=status.HTTP_304_NOT_MODIFIED)