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
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)
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?
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
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.
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)