Is there more better cancel nested ManyToMany field unique check - django-rest-framework

# models.py
class Student(models.Model):
name = models.CharField( max_length=256)
student_numer = models.CharField(max_length=256)
teachers = models.ManyToManyField(Teacher)
class Teacher(models.Model):
name = models.CharField(max_length=256)
# serializers.py
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
class TeacherSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True)
class Meta:
model = Teacher
fields = '__all__'
def update(self, instance, validated_data):
students = validated_data.pop('students')
# here I want delete all old students, and add all new students
# of course, I have more better way to update it, but here just for simple.
But in fact, if I update teacher instance, and if the students not change, it will
raise: student with this student_numer already exist.
I know why, because StudentSerializer's student_numer field has the validator check unique.
And I can add some code like this to fixed this problem:
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
extra_kwargs = {
'student_numer': {
'validators':[]
}
}
Now I want to know is there any more better way ??

Related

Django REST: Save Many-to-Many Association with many=True

SCENARIO
I have a many-to-many relationship between two models:
Supplier
class Supplier(models.Model):
class Meta:
unique_together = ['supplier_no', 'supplier_name']
ordering = ['supplier_name']
supplier_no = models.IntegerField(blank=False, null=False)
supplier_name = models.CharField(max_length=180)
...
updated = models.DateTimeField(auto_now=True, blank=True)
updated_by = models.ForeignKey(UsaUser, on_delete=models.CASCADE, blank=True, null=True,
related_name='supplierUpdatedByUser')
def __str__(self):
return self.supplier_name
Plant
class Plant(models.Model):
class Meta:
unique_together = ['plant_no', 'plant_name']
ordering = ['plant_no']
plant_no = models.IntegerField(unique=True)
plant_name = models.CharField(max_length=180, unique=True)
...
suppliers = models.ManyToManyField(Supplier, related_name='plants')
updated = models.DateTimeField(auto_now=True, blank=True)
updated_by = models.ForeignKey(UsaUser, on_delete=models.CASCADE, blank=True, null=True,
related_name='plantUpdatedByUser')
def __str__(self):
return self.plant_name
Simply put: a supplier can be active in any number of plants, and a plant can have any number of suppliers.
And here is the SupplierSerializer with the create method in question:
class SupplierSerializer(serializers.ModelSerializer):
plants = PlantSerializer(many=True) # serializes entire supplier objects instead of just returning the pk
class Meta:
model = Supplier
fields = ['id', 'supplier_no', 'supplier_name', ... 'plants']
def create(self, validated_data):
# Add the UsaUser as a blameable field, which will be passed in the 'context' object to the serializer.
validated_data.update({"updated_by": self.context['request'].user})
assoc_plants = validated_data.pop('plants') # remove the many-to-many association from the data before saving
supplier = Supplier.objects.create(**validated_data)
# Now add in the associated plants
for plant in assoc_plants:
supplier.plants.add(plant)
return supplier
...
PROBLEM:
When creating a Supplier, I get the following 400 response:
{"plants":[{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]},{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}]
If I remove the following line from my serializer:
plants = PlantSerializer(many=True)
the problem is resolved. However, I want full plant objects to be returned to my front end, not just the plant ids.
I thought maybe I needed to return the full Plant object since the error says it's looking for a dictionary, but then I get another error:
{"plants":[{"plant_no":["plant with this plant no already exists."],"plant_name":["plant with this plant name already exists."]}]}
Sample Request Payload
{"supplier_no":"5052","supplier_name":"MySupplier","is_active":true,"plants":[1,10]}
^^ 1 and 10 are the pks of the Plants
When passing a whole Plant object:
{"supplier_no":"54564","supplier_name":"MySuppleir","is_active":true,"plants":[{"id":1,"plant_no":1,"plant_name":"AutoPlant1","is_active":true...}]}
I think you want to use the plants that are already created. Then you can use some extra fields.
class SupplierSerializer(serializers.ModelSerializer):
plants = PlantSerializer(many=True, read_only = True) # set it as read_only
plant_ids = serializers.ListField(
child = serializers.IntegerField(),
write_only = True
)
class Meta:
model = Supplier
fields = [..., 'plant_ids'] # add plant_ids
def create(self, validated_data):
plant_ids = validated_data.pop('plant_ids')
supplier = Supplier.objects.create(**validated_data)
supplier.plants.set(plant_ids)
return supplier

Saving(create,update) along with foreignkey value from another model which is related user model

It may be a challenging question if you didn't get rightly. Here I have three models in which department model should be created by taking its place name from Place model which is related to the staff model. The Staff Model is in a OneToOneField relationship with User, so when a user creates a department the place name should be passed like HiddenField in HTML . This place name is related to place model with the user with GenericForeignKey. i have created a serializer which is not working as expected, it is returning the place name ,
.
In shortly I want to create a department while place should be selected from current user ID
class Staff(BaseModel):
ROLES = [
('ADMIN', 'Admin'),
('TEACHER', 'Teacher')
]
auth_user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
school_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
school_id = models.PositiveIntegerField()
school = GenericForeignKey('school_content_type', 'school_id')
role = models.CharField(null=True, blank=True, choices=ROLES, max_length=20)
class Places(BaseModel):
name = models.CharField(max_length=50)
code = models.CharField(max_length=12, unique=True)
class Department(BaseModel):
TYPES = [
('ONLINE', 'Online'),
('OFFLINE', 'OfFline')
]
department_type = models.CharField(max_length=15, choices=TYPES)
service_no = models.CharField(max_length=50)
instructions = models.TextField(null=True, blank=True)
place = models.ForeignKey(Places, to_field='code', db_column='place_code', on_delete=models.PROTECT)
SERIALIZERS
class DepartmentCreateSerializer(serializers.ModelSerializer):
place_code=serializers.CharField(read_only=True)
class Meta:
model=Department
fields = ('department_type','service_no','instructions')
def get_place(self, request):
user_id=self.context['request'].user.id
school_id=Staff.objects.get(auth_user_id= user_id).school_id
places_code_name=Places.objects.get(id= school_id).name
class PlacesSerializer(serializers.ModelSerializer):
class Meta:
model = Places
fields = ('id', 'code', 'name')
from places.serializers import PlacesSerializer
class DepartmentCreateSerializer(serializers.ModelSerializer):
place= PlacesSerializer(read_only=True)
class Meta:
model=Department
fields = ('place','service_no','instructions')
def validate(self, attrs):
palce_obj = self.context['request'].user.staff.place()
attrs.update({'place': place_obj})
attrs = super().validate(attrs)
if not attrs.get('place', None):
raise serializers.ValidationError({'place': ["Place required"]})
return attrs

How to post data to Embedded document with Mongoengine REST

I am trying to use Django with mongoengine to make an API.
So far I can get the objects and delete them. but when I want to post some data. Lets say student + course it is giving an error:
type object 'Course' has no attribute 'objects'
Models en ..
#Model.py
class Course(EmbeddedDocument):
course_name = StringField(max_length=200)
course_fee = StringField(max_length=200)
class Student(Document):
student_name = StringField(max_length=200)
student_contactperson = StringField(max_length=200)
student_adress = StringField(max_length=200)
courses = ListField(EmbeddedDocumentField(Course))
#Serializers.py
class CourseSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Course
fields = ('course_name','course_fee')
class StudentSerializer(DocumentSerializer):
courses = CourseSerializer(many=True)
class Meta:
model = Student
fields = ('student_name','student_contactperson','student_adress','courses')
depth = 2
def create(self, validated_data):
course_data = validated_data.pop('courses')
student = Student.objects.create(**validated_data)
Course.objects.create(student=student, **course_data)
return student
#Views.py
class StudentViewSet(meviewsets.ModelViewSet):
lookup_field = 'name'
queryset = Student.objects.all().order_by('-date_joined')
serializer_class = StudentSerializer
A Document represents a MongoDB document (i.e a record in a collection), a Document class is bound to a particular collection. An EmbeddedDocument represents a structure that gets nested in a Document.
So by design an EmbeddedDocument isn't attached to any collection unless you embed it inside a Document.
This means that you can't query or save an EmbeddedDocument class, you need to query/save the parent Document.
Document.objects is an entry point for querying a collection, it only exists on Document classes. You are calling Course.objects.create but Course is an EmbeddedDocument.
I believe you need to change your code to the following
class StudentSerializer(DocumentSerializer):
...
def create(self, validated_data):
course_data = validated_data.pop('courses')
course = Course(**course_data) # assuming course_data is {course_name: ..., course_fee: ...}
return Student.objects.create(courses=[course], **validated_data)

Django DRF serializer - inserting data containing foreign key relationships

I have the following models:
class Contact(models.Model):
class Meta:
managed = False
db_table = 'contact'
class ContactPhone(models.Model):
contact = models.ForeignKey(Contact, on_delete = models.CASCADE)
number = models.CharField(max_length = 45)
class Meta:
managed = False
db_table = 'contact_phone'
Also, I have the following serializers:
class ContactSerializer(serializers.ModelSerializer):
server_id = serializers.IntegerField(source='id', read_only=True)
class Meta:
model = Contact
fields = '__all__'
class ContactPhoneSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPhone
fields = '__all__'
Now, I have a view that insert phone numbers for an existing contact.
The input is a json that looks like this:
data = {'contact_id': 12322,
'phones':[{'number': '89120000001'}]}
The view:
def insert_contact_phone(request):
for record in request.data['phones']:
data['contact_id'] = request.data['contact_id']
serializer = ContactPhoneSerializer(data = data)
if serializer.is_valid():
serializer.save()
I end up with the following error:
RelatedObjectDoesNotExist at /contacts/edit ContactPhone has no
contact.
What am I doing wrong?
If you specify __all__ for the fields in your ContactPhoneSerializer, it does not include contact_id.
So the contact_id taken from the json input is not serialized. It is basically ignored and when you try to save and create new ContactPhone - it fails, because it does not have contact's foreign key correctly set.
But simply adding contact_id to the serializer's fields won't solve your problem.
In your view, i recommend you to set the contact instead:
data['contact'] = request.data['contact_id']
and pass this to the ContactPhoneSerializer.

How can i add #property field to django rest serializer with '__all__'

I have model
class A(models.Model):
is_enable = models.BooleanField(default=False)
title = models.CharField(max_length=255, blank=True, null=True)
updated_date = models.DateTimeField(auto_now=True)
show_count = models.IntegerField(default=0)
answers_count = models.IntegerField(default=0)
audience = JSONField()
events = JSONField()
rules = JSONField()
message = models.TextField(blank=True)
#property
def conversion(self):
if self.show_count == 0:
return 0.0
return (self.answers_count / self.show_count) * 100
And i have serializer
class ASerializer(serializers.ModelSerializer):
audience = serializers.JSONField()
events = serializers.JSONField()
rules = serializers.JSONField()
class Meta:
model = Trigger
fields = '__all__'
I want to add to response #property conversion` field
and I want to do something like this in serializer
class Meta:
model = Trigger
fields = '__all__' + conversion
I know that i can make something like this
class Meta:
model = Trigger
fields = ('is_enable', 'title' ... 'conversion')
But i want add all fields and conversion field and do this more beautiful
you can use somethig like this:
class ModelMixin:
#classmethod
def _get_model_fields(cls):
all_fields = cls._meta.get_fields()
fields = [i.name for i in all_fields if i.__class__.__name__ not in ['ManyToManyRel', 'GenericRelation', 'ManyToOneRel']]
return fields
#classmethod
def get_serializer_fields(cls):
fields = cls._get_model_fields()
return fields
class A(ModelMixin, models.Model):
...
class ASerializer(ModelSerializer):
custom_field = serializers.JSONField()
class Meta:
model = A
fields = A.get_serializer_fields()
fields.append('cusom_field')
Might still not be as clean as you want it to be, but using <model>._meta.fields you can get all fields of that model, and using the name property of each field you can retrieve the names that refer to them.
As the serializers file is interpreted as python, it supports code execution, meaning you can use all python functionalities when setting properties, including a for loop, which gives us the ability to do this:
fields = (tuple((f.name for f in A._meta.fields)) + ('conversion',))
Needed this for my own model as well and there it seemed to work, so hopefully so does it for you.
You can create a SerializerMethodField in your serializer and later define a method in serializer itself that can call your functions you defined in models using #property
class ASerializer(serializers.ModelSerializer):
audience = serializers.JSONField()
events = serializers.JSONField()
rules = serializers.JSONField()
conversion = serializers.SerializerMethodField()
class Meta:
model = Trigger
fields = '__all__'
def get_conversion(self,obj):
return obj.conversion
This should allow you to keep your code beautiful without modifying much of your code.

Resources