Django REST Framework - serializer in many to many relationship - python

models.py
from django.db import models
from django.contrib.auth.models import User
class Element(models.Model):
name = models.CharField(max_length=20)
class Group(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
elements = models.ManyToManyField(Element)
class Meta:
unique_together = ('user', 'name')
serializers.py
class ElementSerializer(serializers.HyperlinkedModelSerializer):
# group = serializers.ReadOnlyField(source='group.id')
class Meta:
model = Element
fields = ('name')
""" def save(self, group=None, *args, **kwargs):
if group is not None:
self.group = group
super(ElementSerializer, self).save(*args, **kwargs) """
class GroupSerializer(serializers.HyperlinkedModelSerializer):
elements = ElementSerializer(many=True)
class Meta:
model = Group
fields = ('url', 'name', 'elements')
views.py
class AddElement(APIView):
def get(self, request, pk, format=None):
serializer = ElementSerializer(context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, pk):
group = DBHandler.get_group_from_id(pk)
serializer = ElementSerializer(data=request.data, context={'request': request})
if not serializer.is_valid():
return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
# serializer.save(group = group)
return Response({}, status=status.HTTP_201_CREATED)
In my view, I'd like to create an Element from given data, then assign this Element to existing Group.
I tried passing group as a serializer save parameter, and it worked okay (commented code), but I don't know where to go next. How can I get an Element instance from ElementSerializer? Or is there another way of working this out?

Related

How to send several fields in the response with a PUT request?

I would like when my PUT request is successful it returns me a response with all the fields in my PlantSerializer because currently the response returns me this:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
7
]
}
But, I would like the response to return this instead:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
"id":7,
"name":"Artichoke",
"image":null
]
}
How can I achieve this result?
Here is my serializers and my model class :
class Plot(models.Model):
name = models.CharField(max_length=50)
garden = models.ForeignKey('perma_gardens.Garden', on_delete=models.CASCADE)
width = models.CharField(max_length=50, blank=True, null=True)
height = models.CharField(max_length=50, blank=True, null=True)
plant = models.ManyToManyField('perma_plants.Plant', related_name='%(class)s_plant', blank=True)
# Here is my serializers :
class GardenSerializer(serializers.ModelSerializer):
class Meta:
model = Garden
fields = ('id', 'name',)
class PlantSerializer(serializers.ModelSerializer):
class Meta:
model = Plant
fields = ('id', 'name', 'image')
class ReadPlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True)
plant = PlantSerializer(many=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'
read_only_fields = [fields]
class WritePlotSerializer(serializers.ModelSerializer):
class Meta:
model = Plot
fields = '__all__'
And here is my views :
class PlotViewSet(viewsets.ModelViewSet):
queryset = Plot.objects.all()
def create(self, request, *args, **kwargs):
serializer = WritePlotSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def partial_update(self, request, *args, **kwargs):
instance = self.queryset.get(pk=kwargs.get('pk'))
serializer = WritePlotSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def get_serializer_class(self):
if self.action in ("list", "retrieve"):
return ReadPlotSerializer
return WritePlotSerializer
In the fuction partial_update you are utilizing the WritePlotSerializer, which only has the plant field implicitly through the fields=__all__ value. This is probably causing drf to use a PrimaryKeyRelatedField, and as such you don't get all the extra fields you defined in your PlantSerializer.
If I understand correctyl you want to use the WritePlotSerializer in the update but use the ReadPlotSerializer when returning the object. You probably should combine both of them in a single serializer by overriding the update method in order to support updating the nested Plant objects. Here is the related documentation.
Alternatively, if you don't want to update the Plants values you can use a slightly modified version of the ReadPlotSerializer in all calls:
class PlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True, read_only=True)
plant = PlantSerializer(many=True, read_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'

Nested serializers. Create method doesnt work (Contact() got an unexpected keyword argument 'course')

I am trying to write a create method but got a error i cannot fix. Would you be kind look at it and give me some advises? Seems like i am doing it totally wrong. I was also fighting with keyword argument but seems liked i fixed it... It should create Courses with Contacts and Branches.
models.py
class Branch(models.Model):
latitude = models.CharField(max_length=150)
longitude = models.CharField(max_length=150)
address = models.CharField(max_length=150)
def __str__(self):
return self.address
class Contact(models.Model):
CHOICES = (
(1,'Facebook'),
(2,'Email'),
(3, 'phone')
)
status = models.IntegerField(choices=CHOICES)
def __str__(self):
return f'{self.status}'
class Category(models.Model):
name = models.CharField(max_length=150)
imgpath = models.CharField(max_length=150)
def __str__(self):
return self.name
class Course(models.Model):
name = models.CharField(max_length=150)
description = models.CharField(max_length=150)
category = models.ForeignKey('Category', on_delete=models.CASCADE,
related_name='category', null=True)
logo = models.CharField(max_length=150)
contacts = models.ManyToManyField(Contact, related_name='contacts', null=True)
branches = models.ManyToManyField(Branch, related_name='branches', null=True)
def __str__(self):
return self.name
serializers.py (Here i am trying to create new course, branches and contacts)
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'imgpath')
class BranchSerializer(serializers.ModelSerializer):
class Meta:
model = Branch
fields = ('latitude', 'longitude', 'address')
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = ('__all__')
class CourseSerializer(serializers.ModelSerializer):
category = CategorySerializer()
contacts = ContactSerializer(many=True)
branches = BranchSerializer(many=True)
class Meta:
model = Course
fields = ['name', 'description','logo', 'category', 'contacts', 'branches']
def create(self, validated_data):
category_data = validated_data.pop('category')
category = Category.objects.create(**category_data)
contacts_data = validated_data.pop('contacts')
branches_data = validated_data.pop('branches')
course = Course.objects.create(**validated_data)
for contact_data in contacts_data:
Contact.objects.create(course=course, **contact_data)
for branch_data in branches_data:
Branch.objects.create(course=course, **branch_data)
return course
views.py
class CoursesView(views.APIView):
def get(self, request, *args, **kwargs):
course = Course.objects.all()
serializer = CourseSerializer(course, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
serializer = CourseSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'data': 'OK'}, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors)
class CoursesDetailView(views.APIView):
def get(self, request, *args, **kwargs):
doctor = Course.objects.get(id=kwargs['course_id'])
serializer = CourseSerializer(doctor)
return Response(serializer.data)
def delete(self, request, *args, **kwargs):
course = Course.objects.get(id=kwargs['course_id'])
course.delete()
return Response({"data": "Delete successful!"})
class CategoryView(views.APIView):
def get(self, request, *args, **kwargs):
category = Category.objects.all()
serializer = CategorySerializer(category, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
serializer = CategorySerializer(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)
class BranchView(views.APIView):
def get(self, request, *args, **kwargs):
branch = Branch.objects.all()
serializer = CategorySerializer(branch, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
serializer = BranchSerializer(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)
class ContactView(views.APIView):
def get(self, request, *args, **kwargs):
contact = Contact.objects.all()
serializer = CategorySerializer(contact, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
serializer = ContactSerializer(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)
Your Contact model has one field: status.
In Course serializer, in create method you have:
Contact.objects.create(course=course, **contact_data). But your Contact model doesn't have course field.
I suggest to start looking here.

DRF: how to change the value of the model fields before saving to the database

If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.

Implement update method on a Nested Serializer in Django 1.11 and django _rest framework

I Implemented a class based view in the views.py though when I tried to update an employee I realized that it's like I'm trying to create new one yet I have the PUT method defined. I have have an issue updating the user details since user field is a Foreign key.
A user with that username already exists.
Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.
views.py
class EmployeeDetailView(APIView):
permission_classes = [AllowAny]
# queryset = Employee.objects.all()
# serializer_class = EmployeeSerializer
"""
Retrieve, update or delete a employee instance.
"""
def get_object(self, pk):
try:
return Employee.objects.get(pk=pk)
except Employee.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
employee = self.get_object(pk)
serializer = EmployeeSerializer(employee)
return Response(serializer.data)
def put(self, request, pk, format=None):
employee = self.get_object(pk)
serializer = EmployeeSerializer(employee, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class EmployeeSerializer(serializers.ModelSerializer):
user = UserSerializer()
contract_type = ContractSerializer(read_only=True)
company = CompanySerializer(read_only=True)
job_title = JobSerializer(read_only=True)
department = DepartmentSerializer(read_only=True)
skill = SkillSerializer(read_only=True)
unit = UnitSerializer(read_only=True)
class Meta:
model = Employee
fields = ['id', 'user', 'hr_number', 'contract_type', 'company',
'tax_id_number', 'joining_date', 'job_title', 'skill', 'unit',
'department', 'identification_number', 'is_manager', 'active']
For writable nested serializer you need to define update or create methods:
class EmployeeSerializer(serializers.ModelSerializer):
user = UserSerializer()
contract_type = ContractSerializer(read_only=True)
company = CompanySerializer(read_only=True)
job_title = JobSerializer(read_only=True)
department = DepartmentSerializer(read_only=True)
skill = SkillSerializer(read_only=True)
unit = UnitSerializer(read_only=True)
class Meta:
model = Employee
fields = ['id', 'user', 'hr_number', 'contract_type', 'company',
'tax_id_number', 'joining_date', 'job_title', 'skill', 'unit',
'department', 'identification_number', 'is_manager', 'active']
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
if user_data:
instance.user.first_name = user_data.get('first_name')
instance.user.last_name = user_data.get('last_name')
# update other user's fields here
instance.user.save()
employee = super(EmployeeSerializer, self).update(instance, validated_data)
return employee

'dict' object has no attribute 'save' POST doesn't work

I'm building a REST web service, the GET method seems to be working all right
but when it comes to POST error message always shows up:
'dict' object has no attribute 'save'
models.py
from django.db import models
class Users(models.Model):
Fullname = models.CharField(max_length=50)
Username = models.CharField(max_length=15)
Password = models.CharField(max_length=8)
Email = models.CharField(max_length=50, unique=True)
Type = models.CharField(max_length=5)
TwitterName = models.CharField(max_length=15, unique=True)
FacebookName = models.CharField(max_length=15, unique=True)
CreationDate = models.DateTimeField()
serializer.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = Users
fields = ('Fullname', 'Username', 'Email', 'Password', 'Type', 'TwitterName', 'FacebookName')
views.py
#api_view(['GET', 'POST'])
def users_list(request, format=None):
if request.method == 'GET':
users = Users.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = UserSerializer(request.DATA, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)
I'm using PyCharm and Django 1.7
If you want to save the POST data then you should pass the data to data keyword argument:
serializer = UserSerializer(data=request.DATA, many=True)
if serializer.is_valid():
...
Also I would suggest you to use Class based views with Mixins as that will make your code much cleaner and shorter:
from rest_framework import generics, mixins
class UserList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Users.objects.all()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)

Categories

Resources