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.
Related
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.
I have a user serializer in DRF that looks like this:
class UserSerializer(serializers.ModelSerializer):
class Meta: # password should exist only if POST
model = User
fields = ['first_name', 'last_name',
'password', 'email', 'username']
write_only_fields = ['password']
And this is what it looks like when I checked the shell.
UserSerializer():
first_name = CharField(allow_blank=True, max_length=30, required=False)
last_name = CharField(allow_blank=True, max_length=150, required=False)
password = CharField(max_length=128)
email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
username = CharField(help_text='Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=150, validators=[<django.contrib.auth.validators.UnicodeUsernameValidator object>, <UniqueValidator(queryset=User.objects.all())>])
In my view if I check is_valid() on a serializer with data that already exists in the database, the function returns True when it should return False and then a django error is raised:
django.db.utils.IntegrityError: duplicate key value violates unique constraint "auth_user_username_key"
DETAIL: Key (username)=(myrandomusername) already exists.
Why is this happening?
Serializer does not care about whether there is an exception at the lower level, it only cares about the serialization/deserialization. So when you pass a username in POST that already exists, the IntegrityError is raised on the model layer (after serializer passed the data), not on serializer so it has no idea of it.
Serializer only checked if the deserialization goes on properly i.e. all the data you passed conform to the definition of fields in the serializer. If they are valid, it will pass it on to the next step.
Also, Serializer.is_valid only handles ValidationError, and keeps a dictionary to refer the errors. For errors, it decides whether to raise a ValidationError (from the errors) or not based on raise_exception.
You should look at the create method of the serializer (ModelSerializer and subclasses) to handle database level exceptions, as all object creation logic of ModelSerializer goes in there. (Also look at update method for updating).
DRF provides ModelSerializer to ensure the creation and update of model objects from deserialized data, it should be treated as an extension to the definition of basic serializers.
I am using a m2m field with a through model in DRF. Everything is working fine, EXCEPT when I try to nest the membership serializer.
models.py
class SweepStakes(models.Model):
name = models.CharField(max_length=255)
class Event(models.Model):
sweepstakes = models.ManyToManyField(SweepStakes, through='EventSweepStakesMembership')
class EventSweepStakesMembership(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
sweepstakes = models.ForeignKey(SweepStakes, on_delete=models.CASCADE)
enabled = models.BooleanField(default=False)
serializers.py
class EventSweepStakesSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source='sweepstakes.name')
class Meta:
model = EventSweepStakesMembership
fields = ('name', 'enabled',)
class EventSerializer(BaseTenantSerializer):
sweepstakes = EventSweepStakesSerializer(many=True, read_only=True)
class Meta:
model = Event
fields = ('sweepstakes',)
At this point, if I hook the EventSweepStakesMembership model and the EventSweepStakesSerializer up to a view, I get back exactly what I expect, output like this:
{"name": "thingy", "enabled" true}
However, when I hook the Event model and EventSerializer serializer into a view, the sweepstakes field returns an empty dictionary instead of the nested representation, like so:
{"sweepstakes": [{}]}
Note that it is NOT an empty array, in other words it does see the related through model, but simply does not serialize it correctly when displaying.
There is no error, it is just empty. I have tried increasing the depth of the Event serializer to no avail.
Am I missing something or maybe even going about this all wrong?
Thanks!
Got it, thanks to this answer:
https://stackoverflow.com/a/17263583/1366989
The missing element here was the source kwarg on the EventSerializer. So, it now looks like this, and is working as expected:
class EventSerializer(BaseTenantSerializer):
sweepstakes = EventSweepStakesSerializer(
source='eventsweepstakesmembership_set', many=True, read_only=True
)
In the tutorial, there's this loose one-to-one mapping between serializer fields and model fields. I can expect that if a serializer field and a model field are both CharFields it will save a string of characters when deserializing into a model instance:
models.py:
class Deck(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, unique=True, blank=False, null=False)
serializers.py:
class DeckSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Deck
fields = ('url', 'id', 'created', 'name')
extra_kwargs = {
'url': {
'view_name': 'goals:deck-detail',
}
}
But when I try a relationship, the serializer field is a ReadOnlyField, which from what I understand is essentially a Charfield, but the model field is a ForeignKeyField, and to add to the confusion, it seems like I'm saving an object in the views when I override perform_create:
models.py:
class Deck(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, unique=True, blank=False, null=False)
user = models.ForeignKey('users.User', related_name='decks', on_delete=models.CASCADE, null=False)
serializers.py:
class DeckSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = Deck
fields = ('url', 'id', 'created', 'name', 'user')
extra_kwargs = {
'url': {
'view_name': 'goals:deck-detail',
}
}
views.py:
class DeckList(generics.ListCreateAPIView):
serializer_class = DeckSerializer
def get_queryset(self):
return Deck.objects.all().filter(user__username=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
What's going on here? When dealing with relationships, why is it that I am saving an object by overriding perform_create in the views, declaring in the serializers a CharField, and in the models declaring a relationship field?
What's missing in my understanding, or what's really happening under the hood such that the user field (ForeignKey) can be represented as a string but saved as an object?
Edit:
If I'm overriding serializer.save(user=user) in the views and the serializers.py has the user field as
user = serializers.CharField(read_only=True)
And I want to override the save method in serializers.py, how do I pass the proper data so that it will know how to serialize? Do I just grab the whole User object, save it, and it'll do the rest? Is the serializers.save() override in the views the same as serializers.save() in serializers.py?
Not 100% sure that I've understood what you're asking, but if the question is:
What's happening under the hood when a ForeignKey field is saved in the Django ORM?
Then the answer is that:
The relation is saved in the DB as an (e.g.) int field which stores the primary key of the related object.
The ForeignKey field reference section in the Django docs explains how this part of the ORM works, and the "Database Representation" subsection likely touches on the specific bit you're interested in.
For example, for your case of User being a related field in the Deck model the underlying table would likely look like this (assuming postgresql):
myapp_deck
id int
created timestamp
name varChar
user_id int
The Deck -> User relation is mapped by the DB storing the pk for the related User object in the user_id field in the myapp_deck table.
So, all Django (and, consequently, DRF) needs to do to change the User in the Deck model is change the user_id in the myapp_deck table to the PK of a different User object.
Hope this helps, and please let me know if I've missed the point of your question.
Edited to Add Example of Custom .create() method
If you want to override the custom "save" method in a serializer then the methods to override are create() and update() accordingly (see Serializer "Saving instances" section in the DRF docs).
An example of this might be:
class DeckSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
... Rest of your serializer code ...
def create(self, validated_data, **kwargs):
user_obj = User.objects.get(pk=validated_data["user"])
deck = Deck.objects.create(
name=validated_data["name"],
user=user_obj,
)
return deck
Note: This assumes that the pk of the related User object is passed throught the serializer, validates OK, and is available in the validated_data dict.
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