Django Rest Framework User Foreign Key - django

I am using the base Django Auth User for my user handling and have authentication working. Now I am trying to create a Post method for my Rest API that automatically gets the user from the request, then gets all of the data input, and saves it.
I have tried various attempts at serialization. I also had this working as just a plain Django website, but now things are getting interesting making it into an API.
Here is my model:
class UserIncome(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
preTaxIncome = models.DecimalField(max_digits=15, decimal_places=2)
savingsRate = models.DecimalField(max_digits=3, decimal_places=2)
taxRate = models.DecimalField(max_digits=3, decimal_places=2)
Here is my Serializer(Base, no attempts at making the foreign key):
class UserIncomeSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserIncome
fields = ('id', 'user', 'preTaxIncome', 'savingsRate', 'taxRate')
Here is the view(Again, just the base. No attempts at foreign key):
class UserIncomeList(APIView):
#List all snippets, or create a new snippet.
def get(self, request, format=None):
userIncome = models.UserIncome.objects.get(user=request.user)
serializer = Serializers.UserIncomeSerializer(userIncome, many=False)
return Response(serializer.data)
def post(self, request, format=None):
serializer = Serializers.UserIncomeSerializer(data=request.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)
Should I just make the foreign key the user ID and get that somehow?
Thank you!

I figured it out!
I removed the user field from the serializer, then in the post method of UserIncomeList I made the save method:
serializer.save(user = request.user)

Related

How can I add a related field to serializer instance?

I have the following model + serializer where I send a post request to create a new model instance. The user sending the post request is related to a Company which I want to pass as related model instance to the serializer.
But how to actually define this instance and attach it to the serializer instance within the post view?
# views.py
class OfferList(APIView):
"""
List all Offers or create a new Offer related to the authenticated user
"""
def get(self, request):
offers = Offer.objects.filter(company__userprofile__user=request.user)
serializer = OfferSerializer(offers, many=True)
return Response(serializer.data)
def post(self, request):
serializer = OfferSerializer(data=request.data)
# Add related company instance
company = Company.objects.get(userprofile__user=request.user)
serializer['company'] = company # this doesn't work
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)
# offer/models.py
class Offer(models.Model):
"""
A table to store Offer instances
"""
# Relations
company = models.ForeignKey(Company, on_delete=models.CASCADE)
..
# serializers.py
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
fields = '__all__'
user/models.py
class UserProfile(models.Model):
"""
Extends Base User via 1-1 for profile information
"""
# Relations
user = models.OneToOneField(User, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
One simple way is to pass your company instance through your serializer. So maybe changing your post method to something like:
from rest_framework.generics import get_object_or_404
def post(self, request):
serializer = OfferSerializer(data=request.data)
# Add related company instance
if serializer.is_valid():
company = get_object_or_404(Company, userprofile__user=request.user) # raising 404 exception if related company does not exist,
# and you're sure that there is one and only one company for this user not more!
serializer.save(company=company)
return Response(serializer.data,
status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
and then change your serializer fields to exclude company field from them (because you are already sending this data through your serializer):
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
fields = ["some_field", "other_field"] # Do not include company in your fields.
# also note that since I didn't know your Offer's fields I used ["some_field", "other_field"] for fields
hope this solves your problem.
Hmm, I think the use of the context for serializers will be a nice way to solve your case.
# serializers.py
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
# now that we do not want company from the request body
exclude = ["company"]
def create(self, validated_data):
# add company to the validated data from the context
# we can feed the context from the APIView
validated_data["company"] = self.context.get("company", None)
...
return super().create(validated_data)
# views.py
class OfferList(APIView):
def post(self, request):
# imo, we do not have to query for UserProfile
# if the company is assigned to the UserProfile instance for the requestor
# then, one2one relation can give us this
context = {"company": request.user.userprofile.company}
serializer = OfferSerializer(data=request.data, context=context)
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)

How to make change in request.data keeping check on serializer validataion in django rest framework?

So I am making quiz app which has following structure
class UserQuizRecord(models.Model):
user = models.OneToOneField(User)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='userrecord')
score = models.FloatField(default=0.00)
I have serializer class of it
class UserQuizRecordSerializer(serializers.ModelSerializer):
class Meta:
model = UserQuizRecord
fields = ('user', 'quiz','score')
Here is the views.py file and the detail route I am providing with data {quiz:1,score:7} I wish to add user to the request.data or serializer at in the views function. I am failing to get the right way to do it.
class QuizView(viewsets.ModelViewSet):
serializer_class = QuizSerializer
queryset = Quiz.objects.all()
model = Quiz
#detail_route(methods=['post'], permission_classes=[AllowAny], url_path='rank')
def rank(self, request, pk=None):
request.data.quiz = Quiz.objects.get(pk=pk)
serializer = UserQuizRecordSerializer(data = request.data )
if serializer.is_valid():
serializer.save()
rank = UserQuizRecord.objects.filter(Q(quiz=request.data.quiz.id),Q(score__gte=request.data.score)).count()
return Response({'rank':rank}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
What is the best way to perform this operation?

Update ONLY an auth.user field using request.user and django rest framework serializers

I'm currently writing an API using the django rest framework for the first time. However, I've run in to a problem when trying to update an object using only the request.user field
from what I understand, when updating an object with request.data fields, all you have to do is:
def put(self, request, pk, format=None):
chore = self.get_object(pk)
serializer = ChoreSerializer(chore, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
however, if I try to leave the data=request.data out, I'll get errors when trying to call .is_valid(). and in turn .save(). I managed to get around this error by serializing the object I want to change based on a primary key, and then de-serializing it again, using the serialized data like this:
seri = ChoreSerializer(chore)
serializer = ChoreSerializer(chore, data=seri.data)
if serializer.is_valid():
serializer.save(assigned_to=self.request.user)
return Response(serializer.data)
My question is whether this is actually the correct way to do this, or whether there's a better method
here's my Model:
class Chore(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, blank=True, default='')
owner = models.ForeignKey('auth.User', related_name='chores')
assigned_to = models.ForeignKey('auth.User',related_name='assigned_to', blank=True, null=True)
and here's my Serializer:
class ChoreSerializer(serializers.ModelSerializer):
class Meta:
model = Chore
fields = ('id', 'name', 'owner', 'assigned_to')
owner = serializers.ReadOnlyField(source='owner.username')
assigned_to = serializers.ReadOnlyField(source='assigned_to.username')
def create(self, validated_data):
return Chore.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.assigned_to = validated_data.get('assigned_to', instance.assigned_to)
instance.save()
return instance

Django Rest Framework return nested object using PrimaryKeyRelatedField

I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.

Registration of an extended user through POST request

I am using Django v1.7dev with the Django Rest Framework v2.3
I have extended the user Class as follows:
class UserProfile(models.Model):
user = models.OneToOneField(User)
gang = models.ForeignKey('Gang', related_name='gangsters')
def __unicode__(self):
return self.user.username
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
I have also created a User serializer endpoint (which does not show the password when I sent a GET request, but accepts it when I send a post, with the purpose of using it for registration:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'email')
def restore_object(self, attrs, instance=None):
user = super(UserSerializer, self).restore_object(attrs, instance)
user.set_password(attrs['password'])
return user
def to_native(self, obj):
ret = super(UserSerializer, self).to_native(obj)
del ret['password']
return ret
I'd love to extend the serializer to include also the gang parameter. Something similar to
gang = serializers.Field(source='profile.gang')
but writable instead of ReadOnly, so that when I register a user I can insert also the gang, possibly in an atomic way. What is the best approach in this case? I have tried to play a bit with other fields types, but unsuccessfully till now.
At the moment I am sending two separate post (one for User and one for UserProfile, but I bet there is a better way...)
There are two general approaches you could take: either update the related field in post_save() or use two serializers and require both to be valid before processing the request.
Check out this question for more details on both.
This is the solution I ended up adopting, based on Carlton's linked question (but with some changes since the user needs to be authenticated for the Login, but cannot be authenticated during the Registration:
class UserAuthView(APIView):
#Login. Returns the current user.
def get(self, request, *args, **kwargs):
# Only UserProfileSerializer is required to serialize data
if (request.user.is_authenticated()):
serializer = UserProfileSerializer(
instance=request.user.profile)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(status=status.HTTP_401_UNAUTHORIZED)
#Register new user.
def post(self, request, format=None):
user_serializer = UserSerializer(data=request.DATA)
errors = dict()
if user_serializer.is_valid():
user =user_serializer.save()
data = request.DATA.copy()
data['user'] = User.objects.latest('id').id
user_profile_serializer = UserProfileSerializer(data=data)
if user_profile_serializer.is_valid():
user_profile_serializer.save()
return Response(user_profile_serializer.data, status=status.HTTP_201_CREATED)
errors.update(user_profile_serializer.errors)
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
errors.update(user_serializer.errors)
return Response(errors, status=status.HTTP_400_BAD_REQUEST)