How to update with SerializerMethodField in Django Rest Framework - python

I have a field in my ModelSerializer which I've set as SerializerMethodField to modify the get behaviour for the field. I could update the data before, now I can't. How can I solve this?
Initially, without using SerializerMethodField, I got data like this:
{
...
"members": [2,3],
...
}
but I added SerializerMethodField to modify the data, then update stopped working.
models.py
# Create your models here.
class Company(models.Model):
members = ArrayField(models.IntegerField(blank=True), blank=True)
...
serializers.py
class AccountSerializer(serializers.ModelSerializer):
user=serializers.StringRelatedField(read_only=False)
class Meta:
model=Account
fields='__all__'
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
members = serializers.SerializerMethodField()
class Meta:
model = Company
fields = '__all__' #('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members')
def get_members(self, obj):
accounts = Account.objects.filter(id__in=obj.members)
return AccountSerializer(accounts, many=True).data
...

You need to use different serializers for update and create. This serializer works for get only.
Or, you can create a custom field. Django Rest Framework How to update SerializerMethodField
Or, there can be other simpler hooks. If 'create' and 'update' worked as you wanted before modifiying members, then you can do as follow to get everything to default for create and update requests.
Instead of using SerializerMethodField, override serializer representation.
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
class Meta:
model = Company
fields = ('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members', 'members_data')
def to_representation(self, obj)
ret = super().to_representation(obj)
ret["members"] = AccountSerializer(accounts, many=True).data
return ret
Override the __init__ method .
.
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
if self.context['request'].method in ['GET']:
self.fields['members'] = SerializerMethodField()
except KeyError:
pass
class Meta:
model = Company
fields = '__all__' #('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members')
def get_members(self, obj):
accounts = Account.objects.filter(id__in=obj.members)
return AccountSerializer(accounts, many=True).data
...
Or, you can create different field for getting members.
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
members_data = SerializerMethodField()
class Meta:
model = Company
fields = ('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members', 'members_data')
def get_members_data(self, obj):
accounts = Account.objects.filter(id__in=obj.members)
return AccountSerializer(accounts, many=True).data
...

Related

How to add a verbose_name in forms.py Django?

class ApplicationForm(BaseForm):
class Meta:
model = Application
fields = ['referencenumber', 'name', 'description', 'owner']
I have the above form from models.py. However I want to put labels on the form that are different than the verbose_name of models.py. I can't edit models.py since we are too far into development.
Any way to do this in forms?
Labels are verbose_names of the model you can change it. This will help you:
Solution #1
class ApplicationForm(BaseForm):
class Meta:
model = Application
fields = ['referencenumber', 'name', 'description', 'owner']
def __init__(self, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
self.fields['referencenumber'].label = "reference number"
self.fields['name'].label = "name"
Solution #2
class ApplicationForm(BaseForm):
class Meta:
model = Application
fields = ['referencenumber', 'name', 'description', 'owner']
labels = {
'referencenumber': 'referencenumber',
'name': 'name',
}

DRF Read only field is still validated

I have a field that I want always to be the user. My serializer is like this:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
read_only_fields = ('user',)
def perform_save(self, serializer):
serializer.save(user=self.request.user)
class MyModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
...
But it gives me the error NOT NULL constraint failed: app_my_model.user_id but the field is read_only... I don't get this.
First of all, there is no method named perform_save() for a serializer, it's for the viewset class. This may be the problem
Use the save() method as below
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
read_only_fields = ('user',)
def save(self, **kwargs):
kwargs['user'] = self.context['request'].user
return super().save(**kwargs)

How to return email verified along with user endpoint?

I have implemented DRF and don't want to force email validation, but still use it(remind them on screen). I would like to just return account_emailaddress.verified along with the user endpoint. What is the best way to achieve this? I tried taking hints from this post, but didn't have success.
The account_emailaddress comes from the django-allauth app I have implemented
https://github.com/pennersr/django-allauth/blob/master/allauth/account/models.py
Serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.CustomUser
fields = ('id', 'first_name', 'last_name', 'email', 'phone_number', 'avatar')
View:
class UserView(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
try:
user = self.queryset.get(pk=kwargs["user"])
return Response(UserSerializer(user).data)
except CustomUser.DoesNotExist:
return Response(
data={
"message": "User with id: {} does not exist".format(kwargs["user"])
},
status=status.HTTP_404_NOT_FOUND
)
def put(self, request, *args, **kwargs):
try:
user = self.queryset.get(pk=kwargs["user"])
serializer = UserSerializer()
user = serializer.update(user, request.data)
return Response(UserSerializer(user).data)
except CustomUser.DoesNotExist:
return Response(
data={
"message": "User with id: {} does not exist".format(kwargs["user"])
},
status=status.HTTP_404_NOT_FOUND
)
Edit: Thanks to schillingt answer I was able to modify the serializer to work as needed
from allauth.account.models import EmailAddress
class UserSerializer(serializers.ModelSerializer):
verified_email = serializers.SerializerMethodField()
class Meta:
model = models.CustomUser
fields = ('id', 'first_name', 'last_name', 'email', 'phone_number', 'avatar', 'verified_email')
def get_verified_email(self, obj):
try:
email_address = EmailAddress.objects.get(user_id=obj.id)
return email_address.verified
except EmailAddress.DoesNotExist:
return None
The way I've done it in the past if I just want the field on a different serializer is to use the SerializerMethod class. I guessed at the class for account_emailaddress.
class UserSerializer(serializers.ModelSerializer):
verified_email = serializers.SerializerMethod()
class Meta:
model = models.CustomUser
fields = (..., 'verified_email')
def _verified_email(self, obj):
try:
return obj.account_emailaddress.verified
except EmailAddress.DoesNotExist:
return None
Then on the viewset, you should include account_emailaddress in the select_related on the queryset property so that it doesn't make an additional query per CustomUser.
You can use a source mapping on a serializer field to include fields from a related model, like so (assuming verified is a boolean field);
class UserSerializer(serializers.ModelSerializer):
verified = serializers.BooleanField(source='account_emailaddress.verified', read_only=True)
class Meta:
model = models.CustomUser
fields = ('id', 'first_name', 'last_name', 'email', 'phone_number', 'avatar', 'verified ')
Please note that, this also assumes that account_emailaddress is a field on CustomUser model or can be accessed like custom_user.account_emailaddress through a OneToOne field.

django rest PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=App.objects.all()) is not JSON serializabl

I have some nested serializers, and I'm going to get the users objects in a JSON format through http requests, using following snippet:
import requests
r = requests.get('http://127.0.0.1:8000/accounts/users',
auth=("admin", "mat"))
But here is what it returns:
PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=App.objects.all()) is not JSON serializabl
I tried a lot of approached include using to_representation method and inheriting from serializers.RelatedField. But always I get the same result.
It seems that I'm doing something wrong. I'd be appreciate if you have any suggestion regarding this?
Here are serializers:
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('name',
'popularity')
class CategorySerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
class Meta:
model = Category
fields = ('en_name',
'fa_name',
'tags')
class CpSerializer(serializers.ModelSerializer):
class Meta:
model = CP
fields = ('en_name',
'fa_name',
'id_number')
class AppSerializer(serializers.ModelSerializer):
category = CategorySerializer()
cp = CpSerializer()
class Meta:
model = App
fields = ('en_name',
'fa_name',
'package_name',
'build_number',
'downloads',
'cp',
'category')
class UserAppSerializer(serializers.ModelSerializer):
class Meta:
model = UserApps
app = AppSerializer() # or even serializers.StringRelatedField()
fields = ('status', 'user_rate', 'comment', 'app')
def to_representation(self, instance):
return None
class UserSerializer(serializers.HyperlinkedModelSerializer):
id_number = serializers.CharField(read_only=True, source='userprofile.id_number')
apps = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('username',
'password',
'first_name',
'last_name',
'email',
'id_number',
'apps')
def get_apps(self, obj):
if obj.username != "admin":
return
else:
apps = UserAppSerializer(read_only=True,
many=True)
return apps
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = ('user', 'id_number')
And here is the get method of my view:
def get(self, request):
print("call get method on {}".format(self.__class__.__name__))
if request.user.username == 'admin':
users = User.objects.all()
lookup_field = 'username'
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
I've defined the apps field in UserProfile as following:
apps = models.ManyToManyField('UserApps')
And in UserApps the app as following:
app = models.ManyToManyField(Application)
And in Application the cp and category as:
cp = models.ForeignKey('cp.CP')
category = models.ForeignKey(Category)
And in category the tags has been defined as:
tags = models.ManyToManyField('Tag')
def get_apps(self, obj):
if obj.username != "admin":
return
else:
apps = UserAppSerializer(read_only=True,
many=True)
return apps
This function is incorrect.
that function is getting user instance as parameter obj and is supposed to return value for app field.
However the function doesn't do anything with the user instance and instead of returning value return an empty instance of UserAppSerializer.
also
app = models.ManyToManyField(Application)
This should've been called apps for clarity
apps = models.ManyToManyField(Application)
Then your serializer function can be re-written like this
def get_apps(self, obj):
if obj.username != "admin":
return
else:
return UserAppSerializer(instance=obj.apps,
read_only=True, many=True).data

Django Rest Framework HyperlinkedIdentityField Error (Requires request)

I am getting this error after serializing a many-to-many relationship, which works fine without the URL but for some reason I am getting this error when I try to include it.
`InitiativeUrlHyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
Here are my views and serializers.
Views.py
class CreateInitiativeAPIView(generics.CreateAPIView):
serializer_class = CreateInitiativeSerializer
class InitiativeListAPIView(generics.ListAPIView):
authentication_classes = [SessionAuthentication, BasicAuthentication, JSONWebTokenAuthentication]
serializer_class = InitiativeListSerializer
permission_classes = [permissions.IsAuthenticated]
queryset = Initiative.objects.all()
class InitiativeDetailAPIView(generics.RetrieveAPIView):
authentication_classes = [SessionAuthentication, BasicAuthentication, JSONWebTokenAuthentication]
serializer_class = InitiativeFullSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = 'id'
def get_object(self):
initiative_id = self.kwargs["initiative_id"]
obj = get_object_or_404(Initiative, id=initiative_id)
return obj
def put(self, request, *args,**kwargs):
return self.update(request, *args, **kwargs)
Serializers.py
class InitiativeUrlHyperlinkedIdentityField(serializers.HyperlinkedIdentityField):
def get_url(self, obj, view_name, request, format):
kwargs = {
'initiative_id': obj.id,
}
return reverse(view_name, kwargs=kwargs, request=request, format=format)
class CreateInitiativeSerializer(serializers.ModelSerializer):
class Meta:
model = Initiative
fields = ['name', 'description', 'image', 'goal']
class InitiativeListSerializer(serializers.ModelSerializer):
url = InitiativeUrlHyperlinkedIdentityField("initiative_detail_api", lookup_field='id')
class Meta:
model = Initiative
fields = [
'url',
'name',
]
class InitiativeFullSerializer(serializers.ModelSerializer):
url = InitiativeUrlHyperlinkedIdentityField("initiative_detail_api", lookup_field='id')
class Meta:
model = Initiative
fields = [
'url',
'id',
'name',
'description',
'image',
'goal']
This is the serializer that gets the information from InitiativeFullSerializer.
class ChapterInitiativePartialListSerializer(serializers.ModelSerializer):
initiative = InitiativeFullSerializer(many=False, read_only=True)
class Meta:
model = ChapterInitiative
fields = [
'initiative'
]
The initial serializer/s which is called via the URL.
class ChapterFullListSerializer(serializers.ModelSerializer):
url = ChapterUrlHyperlinkedIdentityField("chapter_detail_api", lookup_field='id')
active_initiativeset = serializers.SerializerMethodField('get_active')
school = serializers.CharField(source='school.name', read_only=True)
class Meta:
model = Chapter
fields = [
'url',
'id',
'school',
'name',
'payee',
'active_initiativeset'
]
def get_active(self, chapter):
initiatives = chapter.chapterinitiative_set.filter(active=True)
serializer = ChapterInitiativePartialListSerializer(instance=initiatives, many=True)
return serializer.data
Where in my code should I add context={'request': request}?
Hm. You have to check your view classes. There is a method called get_serializer_context() which should add the request by default. See here. I would check this first.

Categories