read_only field in django rest serializer with unique_together contraint - python

I wanted to make email field unique in default django User model. So i made it unique_together=[('email',)] . Now in serializer I want it to be a read_only field.
But Django Rest Framework 3.0 docs says:
There is a special-case where a read-only field is part of a
unique_together constraint at the model level. In this case the field
is required by the serializer class in order to validate the
constraint, but should also not be editable by the user.
The right way to deal with this is to specify the field explicitly on
the serializer, providing both the read_only=True and default=…
keyword arguments.
One example of this is a read-only relation to the currently
authenticated User which is unique_together with another identifier.
In this case you would declare the user field like so:
user = serializers.PrimaryKeyRelatedField(read_only=True,
default=serializers.CurrentUserDefault())
serializers.CurrentUserDefault() represents the current user.I want to set default as user's email . Isn't serializers.CurrentUserDefault() equivalent to request.user . serializers.CurrentUserDefault().email is giving error 'CurrentUserDefault' object has no attribute 'email' How to set email default as user's email ?

This is what the documentation of CurrentUserDefault says:
A default class that can be used to represent the current user. In
order to use this, the 'request' must have been provided as part of
the context dictionary when instantiating the serializer.
You can either do that or you can provide the email id passed in the context data in your views. Override the function get_serializer_context
def get_serializer_context(self):
context = super(YourClass, self).get_serializer_context()
context['email'] = request.user.email
return context
in your views. Your view should be extended from GenericAPIView at some level of inheritance. Now in your serializer, override your __init__ and get your email data.
def __init__(self, instance = None, data = serializers.empty, *args, **kwargs):
self.email = kwargs['context']['email']
Now you can use that in your serializer.

Please check solution 2 from this answer
The code can be:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email',)
extra_kwargs = {
'email': {'read_only': True},
}
def create(self, validated_data):
"""Override create to provide a email via request.user by default."""
if 'email' not in validated_data:
validated_data['email'] = self.context['request'].user.email
return super(UserSerializer, self).create(validated_data)
hope it help :)

Related

Django Rest Framework: allow a serializer field to be created, but not edited

Right now, DRF's read_only argument on a Serializer constructor means you can neither create nor update the field, while the write_only argument on a Serializer constructor allows the field to be created OR updated, but prevents the field from being output when serializing the representation.
Is there any (elegant) way to have a Serializer field that can be created, exactly once, when the model in question is created (when the create() is called on the Serializer), but cannot that later be modified via update?
NB: Yes, I've seen this solution, but honestly I find it ugly and un-Pythonic. Is there a better way?
class TodoModifySerializer(ModelSerializer):
def to_internal_value(self, data):
data = super(TodoModifySerializer, self).to_internal_value(data)
if self.instance:
# update
for x in self.create_only_fields:
data.pop(x)
return data
class Meta:
model = Todo
fields = ('id', 'category', 'title', 'content')
create_only_fields = ('title',)
you can do it in to_internal_value method by remove this data when update
By "not elegant", I'm assuming you only want one serializer for both creates and updates. You could perhaps consider overriding the update method of your serializer and remove the create_only_field from validated_data before saving:
class MySerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
validated_data.pop('create_only_field')
return super().update(instance, validated_data)
class Meta:
model = MyModel
fields = ('id', 'field_one', 'field_two', 'create_only_field')
You would, however, have to supply the old (or some) field value when updating your model.
I don't think there's any, you either specify it like that or make your own serializer, inheriting from DRF's serializer.
In order to make the field REQUIRED and read-only on update I've handled it on field validation.
class MyUserProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username', required=True)
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
class Meta:
model = UserProfile
fields = ['user', 'username', 'first_name', 'last_name', 'phone']
read_only_fields = []
def validate_username(self, value):
if not self.instance and not value: ## Creation and value not provided
raise serializers.ValidationError('The username is required on user profile creation.')
elif value and self.instance != value: ## Update and value differs from existing
raise serializers.ValidationError('The username cannot be modified.')
return value
You can also make the field optional in case of edition, but you need to set the field as required=False and do validation in validate() method, since validate_username() wouldn't be called in creation if not included in the payload.

Is it possible to add non model field to method body in Django?

Is it possible to add non model field for instance to PATCH body? Let's take as an example that I would like to change password of the user. In my model I have only field password however in PATCH I would like to add old_password to authenticate user and then update password from password field from body. Any ideas? I found SerializerMethodField but I am not sure whether it is possible to do what I have described above.
you can simply add your fields to your serializer
class MyPatchSerializer(...):
old_password = serializers.CharField(...)
fields = [..., 'old_password']
if you want to validate this field simply add add a validate_old_pasword(self, value) to your serializer, docs
then in your viewset class you need to override get_serializer_class (docs), this way you tell DRF that if user is sending a PUT/PATCH request instead of default serializer you should use MyPatchSerializer
class MyViewSet(...):
...
def get_serializer(self):
if self.action in ('update', 'partial_update'):
return MyPatchSerializer
return self.serializer_class

How does Django Rest Framework deserialize foreign key relationships?

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.

User registration with django rest framework

I am beginning to use the Django Rest Framework and making a user registration process. I have used this to create a rudimental version and it works fine, but I get the hashed password back in my response, which I don't want. Tried using write_only_fields, but that made no difference.
This is my current serializer:
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = User(email=validated_data['email'], username=validated_data['username'])
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
fields = ('id', 'username', 'email', 'password',)
write_only_fields = ('password',)
How can I prevent DRF to return the created password in the response?
Declare the password field explicitly like this and rest of the code will remain same:
password = serializers.CharField(write_only=True)
Other method can be to delete the password from the to_representation method:
def to_representation(self, instance):
ret = super(MySerializer, self).to_representation(instance)
del ret['password']
return ret
You may use different serializers for creating a user and for showing the user's data. For example, you may inherit from the basic UserSerializer class and thus create something like ReadOnlyUserSerializer, where you completely remove the password field from the Meta.fields property.
The only thing you will need to do is to switch between these serializers properly in ViewSets or whatever you use to render the output.

How to create a django User with Django REST Framework (DRF) 3

I'm trying to use Django Rest Framework 3.1.1 to create a user in a POST. I need to use the built-in method to create the encrypted password so I've tried to override the save method on the ModelSerializer but I clearly don't know Django / DRF well enough to do this. Is there a straightforward way to accomplish this?
When I try the code below, I get an error:
unbound method set_password() must be called with User instance as first argument (got unicode instance
instead)
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def save(self):
email = self.validated_data['email']
username = self.validated_data['username']
password = User.set_password(self.validated_data['password'])
Try doing something like this instead:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
email=validated_data['email']
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
Since you are using a ModelSerializer, you can override the perform_create() function in your view and then set the password for the user.
DRF has provided this hook to add some custom actions which should occur before or after saving an object.
As per DRF3 documentation:
These override points are particularly useful for adding behavior
that occurs before or after saving an object, such as emailing a
confirmation, or logging the update.
from django.contrib.auth.hashers import make_password
class MyView(..):
...
def perform_create(self, serializer):
hashed_password = make_password(serializer.validated_data['password']) # get the hashed password
serializer.validated_data['password'] = hashed_password
user = super(MyView, self).perform_create(serializer) # create a user
Since, Django stores the password in hashed format and not as raw passwords, we use Django's make_password() to get the raw password in hashed format. We then set the password in validated_data of the serializer to this hashed password. This hashed password is then used by the serializer when creating the user by calling the super().

Categories