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)
Related
I have a POST method which is going to be used to retrieve a JSON object, which is then going to be used to retrieve the first_name, last_name, and username -- although I can't figure out how to get the fields (i.e. username) after I serialize it. What's the best way to go about that?
views.py
#api_view(['POST'])
def createUser(request):
# Making a Connection w/ MongoClient
client = MongoClient('mongodb+srv://test_user:0FP33TLJVWrjl8Vy#cluster0.5sacp.mongodb.net/sample_clubs?retryWrites=true&w=majority')
# Getting the Database
db = client['sample_clubs']
# Getting the Collection/Table
collection = db['users']
serializer = MyUserSerializer(data=request.data)
# Gives bug if next 2 lines aren't here
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
serializers.py
class MyUserSerializer(serializers.ModelSerializer):
def get_first_name(self, obj):
# obj is model instance
return obj.first_name
def get_last_name(self, obj):
# obj is model instance
return obj.last_name
def get_user_name(self, obj):
# obj is model instance
return obj.user_name
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'username']
# fields = '__all__'
models.py
class MyUser(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
username = models.CharField(max_length=200)
def __str__(self):
return self.username
A serializer's save method in DRF will return the instance that has been saved. So you can simply call any of its field like this:
if serializer.is_valid():
obj = serializer.save()
print(obj.user_name)
The data will also be available through the serializer's validated data:
if serializer.is_valid():
print(serializer.validated_data.get('user_name')
You can also use the raw JSON that's been generated by serializer:
# note that serializer.data won't be available if 'is_valid()` returns False
print(serializer.data["user_name"])
Also, you shouldn't return serializer.data outside of the is_valid scope. If is_valid() is False, then there won't be any data so you will run to an error. The proper way would be this:
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
If you only want to return the user_name as response:
if serializer.is_valid():
obj = serializer.save()
return Response({"username": obj.user_name})
return Response(serializer.errors)
I am trying to add a request.user to a manytomany field to create an item. But I receive this error:
TypeError at /api/v1/movies/
'User' object is not iterable
Models.py
class Movie(models.Model):
class Movie(models.Model):
title = models.CharField(max_length=100)
genre = models.CharField(max_length=100)
year = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
owners = models.ManyToManyField('auth.User', related_name='movies')
views.py
# Create a new movie
def post(self, request):
serializer = MovieSerializer(data=request.data)
if serializer.is_valid():
serializer.save(owners=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Per comment and updated question, the desired behavior is multiple users. Assuming that your MovieSerializer is a subclass of ModelSerializer and that you have used the default (PrimaryKeyRelatedField) binding for the owners serializer field, you should be able to add the requestor in the initial data:
def post(self, request):
serializer = MovieSerializer(data=dict(request.data, owners=[request.user.id]))
if serializer.is_valid():
serializer.save()
...
NOTE: since owners is a ManyToMany relation, you need to stick the user in an array, and for serialization this needs to be the pk of the user, not the object.
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)
I'm new to Django and I'm trying to save Unit and current_user foreign keys in the database based pk obtained but every time I try doing so but run into two types of error serializers.is_valid() raises an exception error or the serializer returns "Invalid data. Expected a dictionary, but got Unit."
I have tried a very very ugly way of bypassing serializers by getting rid but i get ee8452a4-2a82-4804-a010-cf2f5a41e006 must be an instance of SavedUnit.unit.I have also tried saving the foreign key directly using SavedUnit.objects.create() without luck
model.py
class SavedUnit(models.Model):
"""
Saving units for later models
relationship with units and users
"""
id = models.UUIDField(primary_key=True, default=hex_uuid, editable=False)
unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
user = models.ForeignKey('accounts.User', on_delete=models.CASCADE, related_name='user')
published_at = models.DateTimeField(auto_now_add=True)
serializers.py
class SavedSerializer(serializers.ModelSerializer):
unit = UnitSerializer()
class Meta:
model = SavedUnit
fields = [
'id',
'unit'
]
views.py
class SavedUnitView(APIView):
"""
Query all the unites saved
"""
#staticmethod
def get_unit(request, pk):
try:
return Unit.objects.get(pk=pk)
except Unit.DoesNotExist:
return Response(status=status.HTTP_400_BAD_REQUEST)
#staticmethod
def post(request, pk):
if request.user.is_authenticated:
unit = get_object_or_404(Unit, id=pk)
serializers = SavedSerializer(data=unit)
if serializers.is_valid(raise_exception=True):
created = SavedUnit.objects.get_or_create(
user=request.user,
unit=unit)
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_401_UNAUTHORIZED)
def get(self, request):
units = SavedUnit.objects.filter(user=self.request.user.id)
try:
serializers = SavedSerializer(units, many=True)
return Response(serializers.data, status=status.HTTP_200_OK)
except Unit.DoesNotExist:
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Your code is using serializer only for validation, but it is possible use it to insert or update new objects on database calling serializer.save().
To save foreign keys using django-rest-framework you must put a related field on serializer to deal with it. Use PrimaryKeyRelatedField.
serializers.py
class SavedSerializer(serializers.ModelSerializer):
unit_id = serializers.PrimaryKeyRelatedField(
source='unit',
queryset=Unit.objects.all()
)
unit = UnitSerializer(read_only=True)
class Meta:
model = SavedUnit
fields = [
'id',
'unit_id',
'unit'
]
views.py
class SavedUnitView(APIView):
permission_classes = (permissions.IsAuthenticated,) # For not handling authorization mannually
def post(request):
serializer = SavedSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # Trigger Bad Request if errors exist
serializer.save(user=request.user) # Passing the current user
return Response(serializer.data, status=status.HTTP_201_CREATED)
Now, the id of the unit will be passed in the request body like this
POST /saved-units/
Accept: application/json
Content-Type: application/json
Authorization: Token your-api-token
{
"unit_id": 5 # Id of an existing Unit
}
Actually the problem is here:
def post(request, pk):
if request.user.is_authenticated:
unit = get_object_or_404(Unit, id=pk)
serializers = SavedSerializer(data=unit) <-- Here
You are passing a unit instance, but it should be request.data, like this:
serializers = SavedSerializer(data=request.data)
( I am unsure about what you are doing, if you already have PK, then why are you even using serializer? because you don't need it as you already have the unit, and you can access current user from request.user, which you are already doing. And I don't think you should use #staticmethod, you can declare the post method like this: def post(self, request, pk) and remove static method decorator)
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.