I'm stumled upon a case I don't understand.
I have two related models:
class Course(models.Model):
code = models.CharField(max_length=10, default='')
semester = models.CharField(max_length=10, default='')
class Meta:
unique_together = [['code', 'semester']]
and:
class StudentWork(models.Model):
code = models.CharField(max_length=10, default='')
course = models.ForeignKey(Course,on_delete=models.CASCADE, related_name='student_works')
deadline = models.DateTimeField(blank=True)
In the StudentWorkSerializer I'd like to expand a course field into [code, semester]:
class CourseNaturalSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = ['code', 'semester']
class StudentWorkWithCourseSerializer(serializers.ModelSerializer):
course = CourseNaturalSerializer(read_only=True)
class Meta:
model = StudentWork
fields = ['code', 'course', 'deadline']
This works nicely for GET, e.g. I receive this:
{'code': 'HW1', 'course': {'code': 'T101', 'semester': 'S20'}, 'deadline': '2020-09-04T23:59:00+03:00'}
but this does not work for POST:
POST /studentworks json=dict(code='HW2', course={"code": "T101", "semester": "S20"}, deadline="2020-09-04T23:59")
says in the stacktrace:
django.db.utils.IntegrityError: NOT NULL constraint failed: botdb_studentwork.course_id
So this looks to me that {"code": "T101", "semester": "S20"} does not de-serialize into Course object and it's id is not passed to StudentWork's create?
What should I do?
Thanks in advance!
UPDATE:
I was asked if read_only=True on the related field serializer is intentional?
If I don't set it, I get:
'{"course":{"non_field_errors":["The fields code, semester must make a unique set."]}}'
which makes me thinking that it wants to create a new Course for me (Since code and semester are declared unique_together) at the time I want to create a StudentWork. Which I don't want.
If I than change, say, semester to S21 in the POST data, I get:
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `botdb.serializers.StudentWorkWithCourseSerializer`, or set `read_only=True` on nested serializer fields.
So hence my confusion - I can't figure out how to prevent it from attempting to create a new Course, and just use an existing in a related field.
You can consider passing just course_id in POST.
Related
I would like to have my own error messages, which I've implemented in serializer like this:
class TransactionsValuesSerializer(serializers.ModelSerializer):
class Meta:
model = Translations
fields = ('id', 'value')
extra_kwargs = {"value": {"error_messages": {"blank": f"Error"}}}
It's model
class Translations(models.Model):
class Meta:
db_table = 'merchants__translations'
value = models.TextField()
key = models.ForeignKey(
TranslationsKeys,
on_delete=models.CASCADE,
related_name='translations'
)
translation_language = models.ForeignKey(
TranslationLanguages,
on_delete=models.CASCADE,
related_name='translations'
)
Now, if user do not enter some of the fields for translations, it will show error message 'Error'.
Image
Is there a way to output error message like 'Error in {key}'?
I never saw that usage of extra_kwargs for this. Maybe you should go like this.
Serializers already raise an error for required fields when they're empty. It shows the field name automatically.
The default behaviour is already required=True for serializer fields.
So, I think your serializer is good for now. Then, when you make a request without including value field, serializer should raise something like this.
HTTP 400 BAD REQUEST
{'value': ['This field may not be blank.']}
I think you already have the key name value which is what you want.
I've been using Django for over 3 years now, but have never felt the need to use DRF. However, seeing the growing popularity of DRF, I thought of giving it a try.
Serializing is the concept I find it most difficult. Consider for eg:- I want to save user details. Following is the user related models.
class Users(models.Model):
GENDER_CHOICES = (
('M', 'MALE'),
('F', 'FEMALE'),
('O', 'OTHERS'),
)
first_name = models.CharField(max_length=255, blank=True, null=True)
middle_name = models.CharField(max_length=255, blank=True, null=True)
last_name = models.CharField(max_length=255, blank=True, null=True)
gender = models.CharField(choices=GENDER_CHOICES, max_length=1, blank=True,
null=True)
class UserAddress(models.Model):
ADDRESS_TYPE_CHOICES = (
('P', 'Permanent'),
('Cu', 'Current'),
('Co', 'Correspondence')
)
line1 = models.CharField(max_length=255)
line2 = models.CharField(max_length=255, blank=True, null=True)
pincode = models.IntegerField()
address_type = models.CharField(choices=ADDRESS_TYPE_CHOICES,
max_length=255)
user_id = models.ForeignKey(Users, related_name='uaddress')
class UserPhone(models.Model):
phone = models.CharField(max_length=10)
user_id = models.ForeignKey(Users, related_name='uphone')
class UserPastProfession(models.Model):
profession = models.CharField(max_length=10) # BusinessMan, software Engineer, Artist etc.
user_id = models.ForeignKey(Users, related_name='uprofession')
I'm getting all the user details bundled in one POST endpoint.
{
'first_name': 'first_name',
'middle_name': 'middle_name',
'last_name': 'last_name',
'gender': 'gender',
'address': [{
'line1': 'line1',
'line2': 'line2',
'address_type': 'address_type',
}],
'phone': ['phone1', 'phone2'],
'profession': ['BusinessMan', 'Software Engineer', 'Artist']
}
Without using DRF, I would have made a Users object first, linking it with UserAddress, UserPhone and UserPastProfession object.
How the same could be done using DRF? I mean validating, serializing, and then saving the details. How serializers.py file will be look like?
If you want to make your life easy, you will surely use it.
Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
This gives you a generic way to control the output of your responses, as well as a ModelSerializer class which provides a useful shortcut for creating serializers that deal with model instances and querysets.
They save you from writing a lot of custom code. Let’s look at some examples.
Pretend we have an app that tracks a list of tasks that the user has to complete by a certain date. The Task model might look something like the following:
class Task(models.Model):
title = models.CharField(max_length=255)
due_date = models.DateField()
completed = models.BooleanField(default=False)
When the user requests those tasks, our app returns a response in the form of a JSON-serialized string. What happens when we try to serialize our Django objects using the built-in json library?
import json
task = Task.objects.first()
json.dumps(task)
We get a TypeError. Task is not JSON serializable. To bypass this, we have to explicitly create a dictionary with each of the attributes from Task.
json.dumps({
'title': task.title,
'due_date': task.due_date.strftime('%Y-%m-%d'),
'completed': task.completed
})
Serializing a Python object from a JSON string or from request data is just as painful.
from datetime import datetime
title = request.data.get('title')
due_date = datetime.strptime(request.data.get('due_date'), '%Y-%m-%d').date()
completed = request.data.get('completed')
task = Task.objects.create(title=title, due_date=due_date, completed=completed)
Now, imagine having to follow these steps in multiple views if you have more than one API that needs to serialize (or deserialize) JSON data. Also, if your Django model changes, you have to track down and edit all of the custom serialization code.
Creating and using a serializer is easy:
from rest_framework import serializers
class TaskSerializer(serializers.ModelSerializer):
def create(self, validated_data):
return Task.objects.create(**validated_data)
class Meta:
model = Task
fields = ('title', 'due_date', 'completed')
# Serialize Python object to JSON string.
task_data = TaskSerializer(task).data
# Create Python object from JSON string.
task_data = TaskSerializer(request.data)
task = task_data.create()
If you update the Django model, you only have to update the serializer in one place and all of the code that depends on it works. You also get a lot of other goodies including (as you mentioned) data validation.
Hope that helps!
If I got you correctly, my answer is:
It is not necessary to write one serializer for a model, even for method type (POST,GET etc.). You can pretty much create serializers for your model as much as you need and set fields you want to operate on. You can also set those different serializers as serializer_class property of your APIView class per each method.
I strongly recommend you to take some time to look at the Django Rest Framework Tutorial
below is how your serializer can look.. but please go through this DRF serializer realtionship
from rest_framework.serializers import (
ModelSerializer,
PrimaryKeyRelatedField
)
class UserSerializer(ModelSerializer):
"""
Serializer for the users models.. Please dont forget to import the model
"""
class Meta:
model = Users
field = "__all__"
class UserPhoneSerializer(ModelSerializer):
"""
Serializer for the users address model..
Pass the previously created user id within the post.. serializer will automatically validate
it
"""
user_id = PrimaryKeyRelatedField(queryset=Users.objects.all())
class Meta:
model = UserPhone
field = "__all__"
class UserAddressSerializer(ModelSerializer):
"""
Serializer for the users address model..
Pass the previously created user id within the post.. serializer will automatically validate
it
"""
user_id = PrimaryKeyRelatedField(queryset=Users.objects.all())
class Meta:
model = UserAddress
field = "__all__"
class UserPastProfessionSerializer(ModelSerializer):
"""
Serializer for the UserPastProfession model..
Pass the previously created user id within the post.. serializer will automatically validate
it
"""
user_id = PrimaryKeyRelatedField(queryset=Users.objects.all())
class Meta:
model = UserPastProfession
field = "__all__"
I am not sure if I am doing it wrong or there is some issue when handling unique constraint when working with GenericForeign Relations in Django.
When I try to save an object (in Admin for example) I get unique constraint error (raises 500) form database but not ValidationError on Admin (UI).
Below is my code snippet,
I have one generic relation model as below,
class Targeting(models.Model):
TARGETING_CHOICES = (
('geo', "Geo targeting"),
('other', "Other targeting"),
)
targeting_type = models.CharField(max_length=64, choices=TARGETING_CHOICES, null=False)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
unique_together = ('content_type', 'object_id', 'targeting_type')
And my other model using it is,
class MyModel(models.Model):
name = models.CharField(max_length=60, db_index=True)
targeting = GenericRelation('Targeting')
Exception Raised:
duplicate key value violates unique constraint "mymodel_targeting_targeting_type_0dff10ee_uniq"
DETAIL: Key (targeting_type, content_type_id, object_id)=(geo, 18, 188) already exists.
Is there something wrong with way I implemented it ? or something is not meant to used like this ?
Any sort of help is really appreciated. Thanks
You won't receive ValidationError here, because it can't be validated without additional query and django won't create that query by itself. If you need to have that validation, you need to write it by yourself.
This is my Model:
class Post(models.Model):
user = models.ForeignKey(User)
post = models.CharField(max_length=400)
country = models.ForeignKey(Country, blank=True, null=True)
and this is my serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('user', 'post', 'country',)
def create(self, validated_data):
post = Post(
user = User.objects.get(username='MyUser'),
post = validated_data['post'],
)
if validated_data.get('country', None):
post.country = validated_data['country']
return post
Is there any way for me to tell DRF that if the value of the field is null (because the 'country' field is optional and sometimes not provided) then to skip it and just serialize the other data? Or at least serialize it with a value of None?
I don't think I can use SerializerMethodField (http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield) because the 'country' field is not a read-only field (I write too it too, if it is provided).
I basically want to omit the field (or at least make the value None) when serializing an object If the field is null.
As of DRF 3.2.4, so long as you add
blank=True
to the models field like so:
class Post(models.Model):
country = models.ForeignKey(Country, blank=True)
then DRF will treat the field as optional when serializing and deserializing it (Note though that if there is no null=True on the model field, then Django will raise an error if you try to save an object to the database without providing the field).
See the answer here for more information: DjangoRestFramework - correct way to add "required = false" to a ModelSerializer field?
If you are using pre-DRF 3.2.4, then you can override the field in the serializer and add required=False to it. See the documentation here for more information on specifying or overriding fields explicitily: http://www.django-rest-framework.org/api-guide/serializers/#specifying-fields-explicitly
So something like this (Note that I did not fully test the code below but it should be something along these lines):
class PostSerializer(serializers.ModelSerializer):
country = serializers.PrimaryKeyRelatedField(required=False)
class Meta:
model = Post
fields = ('user', 'post', 'country',)
This thread might be useful:
https://stackoverflow.com/a/28870066/4698253
It basically says that you can override the to_representation() function with a slight modification.
I would have put this in the comments but I don't have enough points yet :(
Use allow_null=True:
allow_null - If set to True, the field will accept values of None or the empty string for nullable relationships. Defaults to False.
serializers.py
class PostSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(allow_blank=True)
class Meta:
model = Post
Using the django-rest-framework is it possible to retrieve content from a related field. So for example I want to create a genre list which contains all projects within it. This is what I have but I keep on getting the error:
'Genre' object has no attribute 'project_set'
models.py
class Genre(models.Model):
name = models.CharField(max_length=100, db_index=True)
class Project(models.Model):
title = models.CharField(max_length=100, unique=True)
genres = models.ManyToManyField(Genre, related_name='genres')
serializers.py
class GenreSerializer(serializers.ModelSerializer):
project_set = serializers.ManyRelatedField()
class Meta:
model = Genre
fields = ('name', 'project_set')
The related name you're using on the Project class is badly named. That related name is how you access the set of projects related to a given genre instance. So you should be using something like related_name='projects'. (As it is you've got it the wrong way around.)
Then make sure that your serializer class matches up with the related name you're using, so in both places project_set should then instead be projects.
(Alternatively you could just remove the related_name='genres' entirely and everything will work as you were expecting, as the default related_name will be 'project_set'.)