Django Rest UpdateView: This field is required - django

I have the following view:
class CampaignUpdate(generics.RetrieveUpdateAPIView):
queryset = Campaign.objects.all()
serializer_class = CampaignSerializer
permission_classes = [CampaignDetailPermission]
lookup_field = 'cid'
And I have the following test function:
def test_update_campaign(self):
request = self.factory.put(f'/', {'name': 'Updated Campaign'})
force_authenticate(request, user=self.merchant)
response = CampaignUpdate.as_view()(request, cid=self.campaign.cid)
# Check that the response status code is 200 (OK)
print(response.data)
self.assertEqual(response.status_code, 200)
# Check that the campaign was updated in the database
self.assertEqual(Campaign.objects.get(cid=self.campaign.cid).name, 'Updated Campaign')
And I am getting the following error:
System check identified 4 issues (0 silenced).
..........{'shop': [ErrorDetail(string='This field is required.', code='required')]}
shop field is required for creation but i do not want to update this field everytime I update some other field.
How can I make it optional for update or readonly after creation?
In case, you need to see serializer:
class CampaignSerializer(serializers.ModelSerializer):
class Meta:
model = Campaign
fields = '__all__'

I have updated the serizlier:
class CampaignSerializer(serializers.ModelSerializer):
class Meta:
model = Campaign
# fields = ['cid', 'name', 'shop', 'end_date', 'start_date', 'active']
# read_only_fields = ['cid']
fields = '__all__'
def update(self, instance: Campaign, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.end_date = validated_data.get('end_date', instance.end_date)
instance.start_date = validated_data.get('start_date', instance.start_date)
instance.active = validated_data.get('active', instance.active)
instance.shop = instance.shop
print(instance)
instance.save()
return instance

Related

Update field without rest API

I have a model.py
class UserPaymentInformation(models.Model):
...
awaiting_confirmation = models.BooleanField(default=False)
I want to make awaiting_confirmation = True in code. But forbid awaiting_confirmation update via RestAPI call.
views.py
class UserPaymentInformationUpdateAPIView(generics.UpdateAPIView):
permission_classes = (IsAuthenticatedDriver,)
serializer_class = UserPaymentInformationUpdateSerializer
queryset = serializer_class.Meta.model.objects.all()
def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.serializer_class.Meta.model.objects.get(
user=self.request.user
)
self.mark_user_as_new()
# awaiting_confirmation = True # I WANT SOMETHING LIKE THIS
serializer = self.get_serializer(
instance, data=request.data, partial=partial
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({"result": serializer.data})
serializers.py
class UserPaymentInformationUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = UserPaymentInformation
fields = ("id", "full_name", "card_number", "account_number", "bik", "awaiting_confirmation")
How can I fix update method?
In your UserPaymentInformationUpdateSerializer you could set the read_only_fields = ('awaiting_confirmation',), so your serializer would become:
class UserPaymentInformationUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = UserPaymentInformation
fields = ("id", "full_name", "card_number", "account_number", "bik", "awaiting_confirmation")
read_only_fields = ("awaiting_confirmation",)
This would mean it'd still be returned in the serializer data but it would not be possible to update it through an API request.

How to fix 'Error validation unque field by serializer when update instance'

I have a model of Organisation and three models have Foreign keys to Organisation model. Three nested models is Users ( custom model ), Description and Contacts. Users has unique field email. Description has unique pair of two fields. I have custom serializer to Organisation.
class OrganisationSuperAdminSerializer(serializers.ModelSerializer):
users = UsersSerializer(many=True, required=False)
contacts = ContactsSerializer(many=True, required=False)
description = DescriptionOrganisationSerializer(many=False, required=False)
class Meta:
model = Organisation
fields = '__all__'
def create(self, validated_data):
error_msg = 'Save error'
users_data = validated_data.pop('users')
contacts_data = validated_data.pop('contacts')
description_data = validated_data.pop('description')
organisation = Organisation.objects.create(**validated_data)
try:
for user_data in users_data:
Users.objects.create(organisation=organisation, **user_data)
for contact_data in contacts_data:
Contacts.objects.create(organisation=organisation, **contact_data)
DescriptionOrganisation.objects.create(organisation=organisation, **description_data)
except:
organisation.delete()
raise serializers.ValidationError(error_msg)
return {}
def update(self, instance, validated_data):
pass
When I save, everything goes well. But when I try to update, the serializer fails validation. The error text in the comments.
"""
Класс для работы с данными для супер админа
"""
queryset = Organisation.objects.all()
serializer_class = OrganisationSuperAdminSerializer
permission_classes = [permissions.AllowAny, ]
def update(self, request, pk=None, *args, **kwargs):
serializer: serializers.ModelSerializer = self.get_serializer(self.get_object(), data=request.data)
print(serializer.is_valid()) # False
print(serializer.errors) # {'users': [{'email': [ErrorDetail(string='email must be unique', code='unique')]}], 'description': {'non_field_errors': [ErrorDetail(string='The fields inn, kpp must make a unique set.', code='unique')]}}
return response.Response(status=200)
I don't want to disable validation of unique fields. But I can't find information how to validate through the serializer update.
Other serializers:
class UsersSerializer(serializers.ModelSerializer):
email = serializers.CharField(max_length=128,
validators=[validators.UniqueValidator(
queryset=Users.objects.all(),
message='email must be unique'
)]
)
class Meta:
model = Users
fields = '__all__'
class DescriptionOrganisationSerializer(serializers.ModelSerializer):
organisation = serializers.PrimaryKeyRelatedField(required=False, queryset=DescriptionOrganisation.objects.all())
class Meta:
model = DescriptionOrganisation
fields = '__all__'
class ContactsSerializer(serializers.ModelSerializer):
organisation = serializers.PrimaryKeyRelatedField(required=False, queryset=Contacts.objects.all())
class Meta:
model = Contacts
fields = '__all__'

How to properly serialize an object whose model has a foreign key which also takes an ImageField?

# models.py
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class PostImage(models.Model):
image = models.ImageField(upload_to=unique_upload)
post = models.ForeignKey(
Post, related_name='images', on_delete=models.CASCADE)
This is my model set up for a basic scenario where a user can enter content or upload images as their posts.
I want to bundle my logic to handle creating a post with either content or images or both.
I first started playing around with GenericViewSet and CreateViewSet but was images was never being passed to my serializer.
# views.py
class CreatePostViewSet(generics.CreateAPIView /* viewsets.GenericViewSet */):
permission_classes = (IsAuthenticated,)
queryset = Post.objects.order_by('id')
serializer_class = CreatePostSerializer
def create(self, request, *args, **kwargs):
data = {}
print(request.data)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(created_by=request.user)
# post = serializer.instance
# print(post)
# for im in post.images.all():
# im.save(post=post)
# print(post.images.all())
return Response(data,
status=status.HTTP_201_CREATED,
headers=self.get_success_headers(serializer.data))
# serializers.py
class PostImageSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = ('id', 'url', 'image', 'post',)
read_only_fields = ('post',)
depth = 1
class CreatePostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, required=False)
class Meta:
model = Post
fields = ('id', 'url', 'content', 'images',)
read_only_fields = ('created_by',)
depth = 1
def create(self, validated_data):
# validated_data['images'] is always []
print(validated_data)
raise
images is always [] when I pass it to a serializer, but it does exist in request.data['images'] as [<TemporaryUploadedFile: 1 - 5H5hHgY.png (image/png)>, ...
I was hoping to use ModelSerializer to help auto-resolve the ImageField.
# CreatePostSerializer serializers breaks down to
CreatePostSerializer():
id = UUIDField(read_only=True)
url = HyperlinkedIdentityField(view_name='post-detail')
content = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
images = PostImageSerializer(many=True, required=False):
id = UUIDField(read_only=True)
url = HyperlinkedIdentityField(view_name='postimage-detail')
image = ImageField(max_length=100)
post = NestedSerializer(read_only=True):
id = UUIDField(read_only=True)
content = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
created_by = PrimaryKeyRelatedField(queryset=User.objects.all())
It think request.data['images'] will need to be changed slightly because your PostImageSerializer will be expecting an object containing the "image" key, whereas you are passing the list of TemporaryUploadedFile.
Given request.data['images'] you could do something like the following in your view before you pass the data to the serializer:
images_list: List[TemporaryUploadedFile] = request.data.pop("images")
images = []
for image in images_list:
images.append({
"image": image,
})
request.data["images"] = images
So we are transforming your list of TemporaryUploadedFiles into a list of objects with the image key.
:edit: So you don't want to transform your data at the view to be compatible with the serializer? Then you can change the serializer to be compatible with the data, this involves customizing the create and update methods, I'm just going to show you how to override the create method for now.
class CreatePostSerializer(serializers.ModelSerializer):
images = serializers.ImageField(many=True)
class Meta:
model = Post
fields = ('id', 'url', 'content', 'images',)
read_only_fields = ('created_by',)
depth = 1
def create(self, validated_data):
images = validated_data.pop("images")
post = super().create(validated_data)
for image in images:
serializer = PostImageSerializer(data={"image": image, "post": post.pk}, context=self.context)
serializer.is_valid()
serializer.save()
return post
So you don't want to override the data in the request and you don't want to customize the serializers create method? Change how the serializer converts your initial data into validated data with the validate method (I think this works for nested serializers but its untested):
class CreatePostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, required=False)
class Meta:
model = Post
fields = ('id', 'url', 'content', 'images',)
read_only_fields = ('created_by',)
depth = 1
def validate(self, attrs):
images_list = attrs.pop("images")
images = []
for image in images_list:
images.append({
"image": image,
})
attrs["images"] = images
return attrs
So, I was able to get it to work with #ARJMP's suggestion.
# views.py
class CreatePostViewSet(generics.CreateAPIView):
# authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
queryset = Post.objects.order_by('id')
serializer_class = CreatePostSerializer
def create(self, request, *args, **kwargs):
data = {}
print(request.data)
images = [{'image': i} for i in request.data.pop('images', [])]
serializer = self.get_serializer(
data={'content': request.data['content'], 'images': images})
serializer.is_valid(raise_exception=True)
post = serializer.save(created_by=request.user)
# self.perform_create(serializer)
data['post'] = serializer.data
return Response(data,
status=status.HTTP_201_CREATED,
headers=self.get_success_headers(serializer.data))
# serializers.py
class CreatePostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, required=False)
class Meta:
model = Post
fields = ('id', 'content', 'images',
'is_private', 'created_by',)
read_only_fields = ('view_count', 'created',)
depth = 1
def create(self, validated_data):
images = validated_data.pop('images', [])
p = Post.objects.create(**validated_data)
for im in images:
pi = PostImage.objects.create(image=im['image'], post=p)
return p
My thing is this seems rather convoluted to get it to work. A lot of manipulating it myself. I was really hoping to leverage more of the "magic" stuff that gets done with ModelSerializer and CreateAPIView.
Are there better approaches to doing this?

Django Rest Framework SerializerMethodField only on GET Request

Running into a little snag here with my DRF backend.
I am populating fields with choices on certain models.
I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.
When I request the models, I want the model with whatever the choice field maps to.
I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.
Any help would be appreciated.
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = serializers.BlockSerializer(data=data, context={'request': request})
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkoutGoalSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.
I think this will work like a charm,
class WorkoutGoalSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and self.context['request'].method == 'GET':
self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal') # remove this line
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.
Remember one thing, you should remove the line,
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
serializers.py
class BlockCreateSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
views.py
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def get_serializer_class(self):
if self.action == 'create':
return serializers.BlockCreateSerializer
else:
return self.serializer_class
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = self.get_serializer(data=data)
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)

Test Django REST framework API response with nested resources

I'm writing an REST APIs for items and the menu endpoint is returning a JSON with items groups inside it the items inside the items any extra the user can add to the item.
serializers.py
====================
class ItemExtraSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ItemExtra
fields = ('id', 'name', 'price')
class ItemSerializer(serializers.HyperlinkedModelSerializer):
extras = ItemExtraSerializer(many=True, read_only=True)
class Meta:
model = Item
fields = ('id', 'url', 'name', 'description', 'image', 'code', 'price', 'extras')
class ItemGroupSerializer(serializers.HyperlinkedModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = ItemGroup
fields = ('id', 'url', 'name', 'items')
views.py
=========================
class MenuView(ListAPIView):
serializer_class = ItemGroupSerializer
def get_queryset(self):
"""
Return the items inside their groups for search query
Filtering is one Group name and Item name
:return:
"""
queryset = ItemGroup.objects.all()
search_terms = self.request.query_params.get('q', None)
if search_terms:
queryset = Item.objects.search(search_terms)
return queryset
tests.py
========================
class ItemTestCases(APITestCase):
def setUp(self):
self.sandwich_group, created = ItemGroup.objects.get_or_create(name='Sandwiches')
self.meal_group, created = ItemGroup.objects.get_or_create(name='Meals')
self.shawarma_sandwich, created = Item.objects.get_or_create(name='Shawarma Sandwich',
description='Meat Shawarma Sandwich',
price=1.250,
code='SW-01',
group=self.sandwich_group)
self.burger_sandwich, created = Item.objects.get_or_create(name='Cheese Burger',
description='Single cheese burger Sandwich',
price=1.000,
code='SW-02',
group=self.sandwich_group)
self.burger_sandwich_extra, created = ItemExtra.objects.get_or_create(name='Extra cheese',
price=0.100,
item=self.burger_sandwich)
self.sharawma_meal, created = Item.objects.get_or_create(name='Shawarma Meal',
description='Shawarma Sandwich with fries and drink',
price=2.000,
code='ME-01',
group=self.meal_group)
self.burger_meal, created = Item.objects.get_or_create(name='Burger Meal',
description='Single cheese burger Sandwich',
price=2.250,
code='ME-02',
group=self.meal_group)
self.cheese_meal_extra, created = ItemExtra.objects.get_or_create(name='Extra cheese',
price=0.100,
item=self.burger_meal)
self.factory = APIRequestFactory()
def test_menu_GET_request(self):
item_list = reverse('menu')
response = self.client.get(item_list)
self.assertEqual(response.status_code, status.HTTP_200_OK)
groups = ItemGroup.objects.all()
expected = ItemGroupSerializer(groups)
self.assertContains(response, expected.data)
The output of the test is:
AssertionError: HyperlinkedIdentityField requires the request in the serializer context. Add context={'request': request} when instantiating the serializer.
How to render the serlizer to JSON in order to compare it with the API endpoint JSON?
Update 1:
I figure it out but I think there is a cleaner solution
I've created helper function
def render_many_serializer_as_json(serializer, request, instance):
serializer_data = serializer(instance=instance, many=True, context={'request': request}).data
return JSONRenderer().render(serializer_data)
And rewrite my test
def test_menu_GET_request(self):
item_list = reverse('menu')
request = self.factory.get(item_list, format='json')
response = self.client.get(item_list)
groups = ItemGroup.objects.all()
expected = render_many_serializer_as_json(ItemGroupSerializer, request, groups)
self.assertEqual(response.content, expected)
Why do something complex ?
Just explicitly write down the expected JSON output:
expected = {
'id': 1,
'name': ...,
...,
'items': [{
'id': ..,
'url': ...,
}]
}