Changing serializer fields on the fly - django

For example I have the following serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
But I don't want to output password on GET (there are other fields in my real example of course). How can I do that without writing other serializer? Change the field list on the fly. Is there any way to do that?

You appear to be looking for a write-only field. So the field will be required on creation, but it won't be displayed back to the user at all (the opposite of a read-only field). Luckily, Django REST Framework now supports write-only fields with the write_only attribute.
In Django REST Framework 3.0, you just need to add the extra argument to the extra_kwargs meta option.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
extra_kwargs = {
'password': {
'write_only': True,
},
}
Because the password should be hashed (you are using Django's user, right?), you are going to need to also hash the password as it is coming in. This should be done on your view, most likely by overriding the perform_create and perform_update methods.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def perform_create(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
def perform_update(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
In Django REST Framework 2.x, you need to completely redefine the password field on the serializer.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = (
'userid',
'password'
)
In order to hash the password ahead of time in Django REST Framework 2.x, you need to override pre_save.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def pre_save(self, obj, created=False):
obj.password = make_password(obj.password)
super(UserViewSet, self).pre_save(obj, created=created)
This will solve the common issue with the other answers, which is that the same serializer that is used for creating/updating the user will also be used to return the updated user object as the response. This means that the password will still be returned in the response, even though you only wanted it to be write-only. The additional problem with this is that the password may or may not be hashed in the response, which is something you really don't want to do.

this should be what you need. I used a function view but you can use class View or ViewSet (override get_serializer_class) if you prefer.
Note that serializer_factory also accept exclude= but, for security reason, I prefer use fields=
serializer_factory create a Serializer class on the fly using an existing Serializer as base (same as django modelform_factory)
==============
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
#api_view(['GET', 'POST'])
def user_list(request):
User = get_user_model()
if request.method == 'GET':
fields=['userid']
elif request.method == 'POST':
fields = None
serializer = serializer_factory(User, UserSerializer, fields=fields)
return Response(serializer.data)
def serializer_factory(model, base=HyperlinkedModelSerializer,
fields=None, exclude=None):
attrs = {'model': model}
if fields is not None:
attrs['fields'] = fields
if exclude is not None:
attrs['exclude'] = exclude
parent = (object,)
if hasattr(base, 'Meta'):
parent = (base.Meta, object)
Meta = type(str('Meta'), parent, attrs)
if model:
class_name = model.__name__ + 'Serializer'
else:
class_name = 'Serializer'
return type(base)(class_name, (base,), {'Meta': Meta, })

Just one more thing to #Kevin Brown's solution.
Since partial update will also execute perform_update, it would be better to add extra code as following.
def perform_update(self, serializer):
if 'password' in self.request.data:
password = make_password(self.request.data['password'])
serializer.save(password=password)
else:
serializer.save()

As far as i can tell from the docs, the fastest way would be to simply have 2 serializers becalled conditionally from your view.
Also, the docs show this other alternative, but it's a little too meta:
http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
it involves creating smart initializer methods, gives an example. i'd just use 2 serializers, if i'd know those changes are the only ones i'll make. otherwise, check the example

Related

how to post foreign key with name of user other than id in django rest framework

hello everything is working fine, but if i want to post any dat in rest framework i need to pass the id of user , which is impractical how can i make it to accept the name of the user and post the the request, find my code below
serializers.py
class Authserializers(serializers.ModelSerializer):
class Meta:
model = Unique
fields = '__all__'
views.py
#api_view(['POST'])
def auth_post_data(request):
serializer = Authserializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
when i type the name of the user as string it does not accept the input, plz help me in this situation
You need to write a custom method for .create() to support a writeable nested serializer.
Then you can pass a request like this:
{
"user": {
"username": "SomeName"
},
"other_field": "value"
}
EDIT
Here's a rough example. Be advised that it has not validation to make sure the username is valid etc. And since it don't know how your models look like I can't make it specific to yours. So you'll have to modify it. Also, you should probably don't want to have __all__ on fields on a serializer with User as model.
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class Authserializers(serializers.ModelSerializer):
# Create field user, a nested serializer.
user = UserSerializer()
def create(self, validated_data):
# Extract field 'user' from POST data.
user_data = validated_data.pop('user')
# Find a user with that username
user = User.objects.get(username=user_data['username'])
# Create instance of you model "Unique" here
return Unique.objects.create(
user=user,
**validated_data # will set other fields that were passed.
)
class Meta:
model = Unique
fields = '__all__'

Django: How to use model properties within custom methods in a serializer?

from rest_framework import serializers
class UserProfileSerializer(serializers.ModelSerializer):
"""Serializes data for User."""
url = serializers.URLField(source='profile_url')
def profile_url(self):
"""Return user profile endpoint."""
return reverse('user:profile_view', kwargs=self.context.get('id', None))
class Meta:
model = User
fields = ('id', 'url')
How can I access User.id within profile_url? There doesn't appear to be any id parameter within the context or at least I don't think I'm accessing properly.
Actually, you may want to change the field on your serializer to be:
url = serializers.SerializerMethodField('profile_url')
This will give the method profile_url an extra parameter, the object that is being serialized. So in your case, this becomes:
def profile_url(self, obj):
"""Return user profile endpoint."""
return reverse('user:profile_view', args=[obj.id,])

How to deal with user_id field properly?

I'm working myself through a lot of examples, but I can't find a way that works 100%.
class QuestionViewSet(viewsets.ModelViewSet):
queryset = QNAQuestion.objects.all()
serializer_class = QuestionSerializer
permission_classes = (IsOwnerOrReadOnly, )
filter_fields = ('id', 'user')
filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
This so far works fine, but it still requires the user_id to be given by the user input even though it's ignored and relpaced by request.user.
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
user = serializers.ReadOnlyField()
model = QNAQuestion
fields = ('id','user','subject', 'body', 'solution')
So I think I have to modify my serializer. I tried HiddenInput and ReadOnly, but both don't really do the trick. If I make it hidden, than the the user_id is not required anymore, but it's also hidden when looking at existing objects. If I make it read only it's not required, but saving the serializer doesn't work anymore. I get the error message that the django object is not serializable to JSON.
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `user` attribute.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if not request.user.is_authenticated():
return False
if request.method in permissions.SAFE_METHODS:
return True
# Instance must have an attribute named `owner`.
return obj.user == request.user
So how can I get it fixed? The user_id should be visible, but I want it to be request.user and i don't want it to be required on creating new objects. Ideally it should also be hidden when using the auto generated api gui.
Thank you for your time. Sorry for spelling or grammar mistakes, I'm not a native speaker.
Try to make the field only required=False instead of Hidden or ReadOnly.
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = QNAQuestion
fields = ('id','user','subject', 'body', 'solution')
extra_kwargs = {
'user': {'required': False}
}

Django Rest Framework return nested object using PrimaryKeyRelatedField

I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.

field added dynamically to a ModelForm at __init__ does not save

I'm using Django profiles and was inspired by James Bennett to create a dynamic form (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ )
What I need is a company field that only shows up on my user profile form when the user_type is 'pro'.
Basically my model and form look like:
class UserProfile(models.Model):
user_type = models.CharField(...
company_name = models.CharField(...
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
And I add the company_name field in init like James Bennett showed:
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
self.fields['company_name'] = forms.CharField(...
The problem is that, when I try to save() an instance of UserProfileForm, the field 'company_name' is not saved...
I have gone around this by calling the field explicitly in the save() method:
def save(self, commit=True):
upf = super(UserProfileForm, self).save(commit=False)
if 'company_name' in self.fields:
upf.company_name = self.cleaned_data['company_name']
if commit:
upf.save()
return upf
But I am not happy with this solution (what if there was more fields ? what with Django's beauty ? etc.). It kept me up at night trying to make the modelform aware of the new company_name field at init .
And that's the story of how I ended up on stackoverflow posting this...
I would remove this logic from form and move it to factory. If your logic is in factory, you can have two forms:
UserProfileForm
ProUserProfileForm
ProUserProfileForm inherits from UserProfileForm and changes only "exclude" constant.
You will have then following factory:
def user_profile_form_factory(*args, instance=None, **kwargs):
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
cls = ProUserProfileForm
else:
cls = UserProfileForm
return cls(*args, instance, **kwargs)
It seems I found a solution:
def AccountFormCreator(p_fields):
class AccountForm(forms.ModelForm):
class Meta:
model = User
fields = p_fields
widgets = {
'photo': ImageWidget()
}
return AccountForm
#...
AccountForm = AccountFormCreator( ('email', 'first_name', 'last_name', 'photo', 'region') )
if request.POST.get('acforms', False):
acform = AccountForm(request.POST, request.FILES, instance=request.u)
if acform.is_valid():
u = acform.save()
u.save()
ac_saved = True
else:
acform = AccountForm(instance = request.u)
When are you expecting the user_type property to be set? This seems like something that should be handled by javascript rather than trying to do funny things with the model form.
If you want the company_name field to appear on the client after they've designated themselves as a pro, then you can 'unhide' the field using javascript.
If instead, they've already been designated a pro user, then use another form that includes the company_name field. You can sub-class the original model form in the following manner.
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
class UserProfileProForm(UserProfileForm):
class Meta:
exclude = None # or maybe tuple() you should test it
Then in your view, you can decide which form to render:
def display_profile_view(request):
if user.get_profile().user_type == 'Pro':
display_form = UserProfileProForm()
else:
display_form = UserProfileForm()
return render_to_response('profile.html', {'form':display_form}, request_context=...)
This would be the preferred way to do it in my opinion. It doesn't rely on anything fancy. There is very little code duplication. It is clear, and expected.
Edit: (The below proposed solution does NOT work)
You could try changing the exclude of the meta class, and hope that it uses the instances version of exclude when trying to determine whether to include the field or not. Given an instance of a form:
def __init__(self, *args, **kwargs):
if self.instance.user_type == 'pro':
self._meta.exclude = None
Not sure if that will work or not. I believe that the _meta field is what is used after instantiation, but I haven't verified this. If it doesn't work, try reversing the situation.
def __init__(self, *args, **kwargs):
if self.instance.user_type != 'pro':
self._meta.exclude = ('company_name',)
And remove the exclude fields altogether in the model form declaration. The reason I mention this alternative, is because it looks like the meta class (python sense of Meta Class) will exclude the field even before the __init__ function is called. But if you declare the field to be excluded afterwards, it will exist but not be rendered.. maybe. I'm not 100% with my python Meta Class knowledge. Best of luck.
What about removing exclude = ('company_name',) from Meta class? I'd think that it is the reason why save() doesn't save company_name field