some context here... I have a Room object that has a list of pictures. Right now I'm stuck at the POST method (create a new room with pictures). But I keep getting the validation error "this field is required".
model
class Room(models.Model):
CONDO = 'condo'
HOUSE = 'house'
OTHER = 'other'
ROOM_TYPES = [(CONDO, 'condo'), (HOUSE, 'house'), (OTHER, 'other')]
name = models.CharField(max_length=50)
price = models.IntegerField()
type = models.CharField(max_length=5, choices=ROOM_TYPES)
location = models.CharField(max_length=100)
info = models.TextField(max_length=500, blank=True)
owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='listings', null=True)
start_date = models.DateField()
end_date = models.DateField()
is_available = models.BooleanField(default=True)
include_utility = models.BooleanField(default=False)
allow_pet = models.BooleanField(default=False)
include_parking = models.BooleanField(default=False)
include_furniture = models.BooleanField(default=False)
class RoomPicture(models.Model):
room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name='images', null=True)
image = models.ImageField(upload_to='images/', blank=True, null=True)
class Meta:
unique_together = ['room', 'image']
serializer
class RoomPictureSerializer(serializers.ModelSerializer):
class Meta:
model = RoomPicture
fields =['id','image']
class RoomSerializer(serializers.ModelSerializer):
# images = serializers.ImageField(upload_to='images/', blank=True, null=True)
images = RoomPictureSerializer(many=True)
class Meta:
model = Room
fields = ['id', 'owner', 'name', 'price', 'type', 'location', 'info',
'start_date', 'end_date', 'is_available' , 'include_utility' ,'allow_pet',
'include_parking', 'include_furniture', 'images']
def create(self, validated_data):
images_data = validated_data.pop('images')
room = Room.objects.create(**validated_data)
for image_data in images_data:
RoomPicture.objects.create(room=room, image=image_data["image"])
return room
views
def convert(image):
dict = {}
# dict['id'] = 0
dict['image'] = image
return dict
class RoomViewSet(viewsets.ModelViewSet):
# permission_classes = [
# permissions.IsAuthenticated,
# ]
parser_classes = (MultiPartParser, FormParser)
serializer_class = RoomSerializer
queryset = Room.objects.all()
def create(self, request):
# converts querydict to original dict
dict_data = dict((request.data).lists())
dict_data['images'] = list(map(convert, dict_data['images']))
print(request.data)
query_dict = QueryDict('', mutable=True)
query_dict.update(MultiValueDict(dict_data))
print(query_dict)
serializer = RoomSerializer(data=query_dict)
if serializer.is_valid():
serializer.save(owner=request.user)
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
What I don't understand is that in my view, I explicitly passed in query_dict which has an 'images' key. But for some reason the validator does not recognize it.
so the request.data looks like this
<QueryDict: { 'name': [ 'The Building'], 'price': [ '900'], 'type': [ 'condo'], 'location': [ '100 Trump Rd'], 'info': [ '1 bedroom + 1 bath / share kitchen & living room'], 'start_date': [ '2021-09-01'], 'end_date': [ '2021-12-31'], 'is_available': [
'true'], 'include_utility': [ 'true'], 'include_parking': [ 'true'], 'include_furniture': [ 'true'], 'allow_pet': [ 'true'], 'images': [<InMemoryUploadedFile: Phillip-Square-Blair-House-Conference-Room.jpg (image/jpeg)>,
<InMemoryUploadedFile: Phillip-Square-Blair-House-Gym.jpg (image/jpeg)>]}>
and the tuned query_dict looks like this
<QueryDict: {'name': ['The Building'], 'price': ['900'], 'type': ['condo'], 'location': ['100 Trump Rd'], 'info': ['1 bedroom + 1 bath / share kitchen & living room'], 'start_date': ['2021-09-01'], 'end_date': ['2021-12-31'], 'is_available': ['true'], 'include_utility': ['true'], 'include_parking': ['true'], 'include_furniture': ['true'], 'allow_pet': ['true'], 'images': [{'image': <InMemoryUploadedFile: Phillip-Square-Blair-House-Conference-Room.jpg (image/jpeg)>}, {'image': <InMemoryUploadedFile: Phillip-Square-Blair-House-Gym.jpg (image/jpeg)>}]}>
whether I pass in serializer = RoomSerializer(data=request.data) or serializer = RoomSerializer(data=query_dict) I always get the same error...
Can somebody help me? Thanks in advance!
The default ModelSerializer implementation does not allow nested serializer data so serializer.is_valid() will return false hence the error.
Even using the drf_writable_nested module doesn't work for it as well.
Based on what you're trying to do, there are 3 options that I can advise:
Write your serializer explicitly with serializers.Serializer though, that will be a lot of code but, it'll satisfy the nesting.
Create separate views and serializers for the RoomPicture model. This way, on your client-side, you'll initially create the Room object then upload the images afterwards.
If you have a specific amount of images for the Room object, you can add image fields to the Room model e.g
class Room(models.Model):
...
image1 = models.ImageField(upload_to='images/', blank=True, null=True)
image2 = models.ImageField(upload_to='images/', blank=True, null=True)
image3 = models.ImageField(upload_to='images/', blank=True, null=True)
...
#property
def images(self):
images_list = []
image_fields = [self.image1, self.image2, self.image3]
for image_count, image_field in enumerate(image_fields, start=1):
if image_field:
image_dict = {'path': image_field.url, 'count': image_count}
images_list.append(image_dict)
return images_list
If the images can be more than 5, you might want to use option 2.
You'll have to add images to your RoomSerializer as a ReadOnlyField(), it'll serialize the data as a list of objects just like what you'll get from using FK.
You'll be able to create and update your Room data with the images altogether.
To delete a particular image, just set the field as null. That's why I added count to the image_dict data to help know which image to update from the client-side.
You should also install django-cleanup to help delete the previous image after an update has been made to any of the image fields.
Related
I want to serialize data from nested queryset:
I have working code but output from serializer showing too many data. I want hide this for security reason.
example output:
(...)
"gallery": "[{"model": "mainapp.imagesforgallery", "pk": 1, "fields": {"user": 1, "image": "uploads/2022/8/6/drw/Adapta-KDE-theme_JOgL4kO.webp", "thumbnail": ""}}]"
(...)
this is models.py
class ImagesForGallery(models.Model):
user = models.ForeignKey(UserProfile, null=True, blank=True, on_delete=models.CASCADE)
image = models.ImageField(upload_to=user_directory_path, blank=True, null=True)
thumbnail = models.ImageField(upload_to='uploads/', blank=True, null=True)
def __str__(self):
return 'User: {} || Image: {}'.format(self.user, self.image)
class Gallery(models.Model):
project = models.ForeignKey(Projects, null=True, blank=True, on_delete=models.CASCADE)
project_gallery = models.ManyToManyField(ImagesForGallery, blank=True, related_name='project_gallery')
def __str__(self):
return '{}'.format(self.project)
This is my view
class HomeView(viewsets.ModelViewSet):
serializer_class = ProjSerializer
queryset = Proj.objects.all()
def list(self, request, *args, **kwargs):
response = super(HomeView, self).list(request, args, kwargs)
gal = Gallery.objects.all()
for d in response.data:
for g in gal:
if d['uuid'] == str(g.project.uuid):
qs = g.project_gallery.get_queryset()
serialized_obj = serializers.serialize('json', qs)
d['gallery'] = serialized_obj
return response
This code compares the project model to the photo gallery model. If uuid is correct, include this gallery in the project and send json.
I'm not sure the code is efficient and safe. The question is how to modify the code so that it does not show the model name.
You need to use your ProjSerializer to serialize your queryset
if d['uuid'] == str(g.project.uuid):
qs = g.project_gallery.get_queryset()
serialized_obj = ProjSerializer(qs, many=True).data
d['gallery'] = serialized_obj
Can someone help me figure out why some fields are not parsed correctly using nested serializers with Django and Django-rest-framework?
I've researched the issue on SO, and the only cause for this happening I've found is that the request is sent as Form-data and not Json, but I've checked that response.content_type equals application/json - so this shouldn't be the issue here.
This is what my validated_data looks like (note that 3 of the fields only contain an empty OrderedDict):
{'author': OrderedDict(),
'disclosed_at': datetime.datetime(2021, 10, 19, 12, 0, tzinfo=<DstTzInfo 'Europe/Stockholm' CEST+2:00:00 DST>),
'event_date': datetime.date(2021, 10, 20),
'event_type': OrderedDict(),
'subject_companies': [OrderedDict()]}
This is what request.data looks like (where you can see that all fields are there, and having the fields that each serializer have specified, provided below this paragraph):
{'event_type': {'pk': 1}, 'author': {'pk': 1}, 'event_date': '2021-10-20', 'disclosed_at': '2021-10-19 12:00:00', 'subject_companies': [{'pk': 1}]}
This is where I'm sending the request from (test.py):
def test_authenticated_creating_event_for_own_organisation(self):
view = NewsEventList.as_view()
url = reverse('news_event_list', kwargs={'pk': self.organisation.pk})
request = self.client.post(url, data=json.dumps(self.payload_event), content_type='application/json')
force_authenticate(request, user=self.user)
response = view(request, pk=self.organisation.pk)
json_data = json.dumps(response.data, indent=4)
json_ = (json.loads(json_data))
self.assertEqual(response.status_code, 201, 'Should return 201 - Created')
return response
Models
class NewsEvent(TimeStampedModel):
event_type = models.ForeignKey('publication.eventtype', on_delete=models.SET_NULL, related_name='events_type', null=True)
author = models.ForeignKey('core.organisation', on_delete=models.CASCADE, related_name='events_author', null=True)
subject_companies = models.ManyToManyField('core.organisation', related_name='events_companies')
legacy_id = models.CharField(max_length=128, blank=True)
event_date = models.DateField(null=True, blank=True)
event_time = models.TimeField(null=True, blank=True)
disclosed_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return '{}: {}'.format(self.author, self.event_type)
class Meta:
ordering = ('pk',)
class EventType(models.Model):
language = models.ForeignKey(
'core.Language',
default=get_default_language,
null=True,
on_delete=models.SET_NULL,
related_name='event_contents'
)
name = models.CharField(max_length=64, default=None)
key = models.CharField(max_length=64, default=None)
def __str__(self):
return '{}'.format(self.name)
class Meta:
ordering = ('pk',)
The view
class NewsEventList(generics.ListCreateAPIView):
permission_classes = (IsAuthenticatedAndOfSameOrganisationEvents,)
serializer_class = NewsEventSerializer
def get_queryset(self):
org_pk = self.kwargs.get('pk', None)
try:
org_obj = Organisation.objects.get(pk=org_pk)
except Organisation.DoesNotExist:
return ValidationError('Organisation does not exist')
news_events = NewsEvent.objects.filter(author=org_obj)
return news_events
Serializers
class OrganisationNameSerializer(ModelSerializer):
class Meta:
model = Organisation
fields = ['pk']
class EventTypeSerializer(ModelSerializer):
class Meta:
model = EventType
fields = ['pk']
class HeadlineSerializer(ModelSerializer):
class Meta:
model = EventHeadline
fields = ['news_event', 'language', 'headline']
class NewsEventSerializer(ModelSerializer):
event_type = EventTypeSerializer()
author = OrganisationNameSerializer()
subject_companies = OrganisationNameSerializer(many=True)
class Meta:
model = NewsEvent
fields = ['pk', 'event_type', 'author', 'event_date', 'event_time', 'disclosed_at', 'subject_companies', 'created_at', 'updated_at']
def create(self, validated_data):
# Get PK for organisation from URL
org_pk = self.context.get('request').parser_context.get('kwargs', {}).get('pk', {})
org_obj = Organisation.objects.get(pk=org_pk)
print(self.context.get('request').data)
pprint(validated_data)
Also for reference I printed the serializer.data for an already existing instance of a NewsEvent:
news_event_test = NewsEvent.objects.all()[0]
serializer = NewsEventSerializer(news_event_test)
print(serializer.data)
{'pk': 1, 'event_type': OrderedDict([('pk', 1)]), 'author': OrderedDict([('pk', 1)]), 'event_date': None, 'event_time': None, 'disclosed_at': None, 'subject_companies': [OrderedDict([('pk', 1)])], 'created_at': '2021-10-25T09:32:41.562428+02:00', 'updated_at': '2021-10-25T09:32:41.562487+02:00'}
I've also tried doing a "pop" of each of the fields from the validated_object, but only the ones that don't is an empty OrderedDict work, such as disclosed_at, but if I try to do:
event_type = validated_data.pop('event_type')
I get:
KeyError: "Got KeyError when attempting to get a value for field `event_type` on serializer `NewsEventSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'event_type'."
event_type = models.ForeignKey('publication.EventType', on_delete=models.SET_NULL, related_name='events_type', null=True)
author = models.ForeignKey('core.Organisation', on_delete=models.CASCADE, related_name='events_author', null=True)
subject_companies = models.ManyToManyField('core.Organisation', related_name='events_companies')
Replace those lines and delete previous migrations and create new ones and try.
The reason was that I was doing a write operation (POST request, create action in Django/DRF terms) and the pk field on the serializers is read_only by default.
To validate that data during write ops, you'll need to set the field explicitly and set read_only=False. For example:
class RedditTestSerializer(serializers.ModelSerializer):
class Meta:
model = models.Task
fields = ("pk",)
This solved the problem, but you may want to consider using
PrimaryKeyRelatedField if your use case is only setting the primary key
of a related object.
When I create this model, the values are nulls.
class TestRequest(models.Model):
class Meta:
verbose_name_plural = "TestRequest"
title = models.CharField(max_length=256, null=True, blank=True)
testConfiguration = models.ForeignKey(
TestConfiguration, on_delete=models.PROTECT, null=True, blank=True)
testDescription = models.ForeignKey(
TestDescription, on_delete=models.PROTECT, null=True, blank=True)
The serializer:
class TestRequestSerializer(serializers.ModelSerializer):
class Meta:
model = TestRequest
fields = [
'id',
'title',
'testConfiguration',
'testDescription',
]
depth = 2
The view:
#api_view(['PUT'])
def TestRequestUpdate(request, pk):
testRequest = TestRequest.objects.get(id=pk)
serializer = TestRequestSerializer(instance=testRequest, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And when I want to update them later from the front-end with this state:
id: 98
title: "title"
testConfiguration: 31
testDescription: 32
I get this response:
{
"id": 98,
"title": "title",
"testConfiguration": null,
"testDescription": null
}
Why can't I update it?
EDIT: I added my solution as an answer.
You can modify your views with the following code:
testRequest = TestRequest.objects.get(id=pk)
import json
data = json.loads(json.dumps(request.data))
if data.get('testConfiguration', None) is None and testRequest.testConfiguration:
data.update({'testConfiguration': testRequest.testConfiguration.id})
if data.get('testDescription', None) is None and testRequest.testDescription:
data.update({'testDescription': testRequest.testDescription.id})
serializer = TestRequestSerializer(instance=testRequest, data=data)
My solution was that i removed the depth value from the serializer for the POST requests and added a separate serializer with the depth value for the GET requests.
I'm trying to add content for the first time using a DRF back-end written by someone else. I am receiving this error...
django_1 | AssertionError: The `.update()` method does not support writable nested fields by default.
django_1 | Write an explicit `.update()` method for serializer `myapp.tracker.serializers.MedicationSerializer`, or set `read_only=True` on nested serializer fields.
How can I write the Update method?
I've done rails before so I'm familiar with the concepts? So when I see a method "create" in my serilaizers.py I think "there must be a way to write a def Update here" But since I'm new to django I have NO IDEA what that method should actually look like ^_^. This is where I'm stuck.
Here is the serializers.py , models.py, and views.py code specific to the model I am trying to update...let me know if I need to post another files contents.
What should my def update method look like?
my serializer.py
class MedicationSerializer(serializers.HyperlinkedModelSerializer):
cat = CatSerializer()
class Meta:
model = Medication
fields = (
'id',
'cat',
'name',
'duration',
'frequency',
'dosage_unit',
'dosage',
'notes',
'created',
'modified',
'showRow',
)
def create(self, validated_data):
cat_data = validated_data.pop('cat')
cat_obj = Cat.objects.get(**cat_data)
medication = Medication.objects.create(cat=cat_obj, **validated_data)
return medication
the models.py looks like this
class Medication(models.Model):
cat = models.ForeignKey(Cat, blank=True, null=True)
name = models.CharField(max_length=100)
duration = models.TextField(blank=True, null=True)
frequency = models.CharField(max_length=2)
dosage_unit = models.CharField(max_length=2, default=Weight.MILLILITERS)
dosage = models.IntegerField(blank=True, null=True)
notes = models.CharField(max_length=2048, blank=True, null=True)
created = models.DateTimeField(blank=True, null=True)
modified = models.DateTimeField(blank=True, null=True)
showRow = models.BooleanField(default=True)
def save(self, *args, **kwargs):
# Save time Medication object modified and created times
self.modified = datetime.datetime.now()
if not self.created:
self.created = datetime.datetime.now()
super(Medication, self).save(*args, **kwargs)
def __str__(self):
if self.cat:
cat_name = self.cat.name
else:
cat_name = "NO CAT NAME"
return "{cat}: {timestamp}".format(cat=self.cat.name, timestamp=self.created)
and the views.py ....
class MedicationViewSet(viewsets.ModelViewSet):
queryset = Medication.objects.all()
serializer_class = MedicationSerializer
filter_fields = ('cat__slug', 'cat__name')
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
Thank you for your time.
Try adding read-only=True to your Catserializer
class MedicationSerializer(serializers.HyperlinkedModelSerializer):
cat = CatSerializer(read_only=True)
class Meta:
model = Medication
fields = (
'id',
'cat',
'name',
'duration',
'frequency',
'dosage_unit',
'dosage',
'notes',
'created',
'modified',
'showRow',
)
def create(self, validated_data):
cat_data = validated_data.pop('cat')
cat_obj = Cat.objects.get(**cat_data)
medication = Medication.objects.create(cat=cat_obj, **validated_data)
return medication
Just like Youtube, I want to see what categories my post(outfit) is in.
My approach is...
Get whole list of categories belong to user
Get whole list of categories belong to user and the post
compare them and return JSON
While building a Seriailizer, I feel like I'm totally stock. So frustraing... :(
Please feel free to change my approach if you have any better idea.
Expected JSON outputis below
{
...
"categories": [
{
"id": 1,
"name": "asd"
"added": True <- since the post is added to 'asd'
},
{
"id": 2,
"name": "workout"
"added": True
},
...
{
"id": 5,
"name": "bgm"
"added": False <- since the post is not added to 'bgm'
},
]
}
Here is views.py
class OutfitDetailView(generics.RetrieveAPIView):
queryset = Outfit.objects.all()
serializer_class = OutfitDetailSerializer
lookup_field = 'pk'
Here is serializers.py (this is a problem)
class OutfitDetailSerializer(serializers.ModelSerializer):
...
def get_categories(self, obj):
# Right Here! Get whole categories associated with user
all_categories = Category.objects.filter(owner=self.context['request'].user)
# And the categories associated with user AND the post
categories = obj.categories.filter(owner=self.context['request'].user)
return CategorySerializer(categories, many=True).data
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = (
'id',
'name',
'added' <- here I want to add 'added' field but I don't know how.
)
In case you need a model
class Outfit(models.Model):
...
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
content = models.CharField(max_length=30)
...
class Category(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
outfits = models.ManyToManyField(Outfit, related_name="categories", blank=True)
main_img = models.ImageField(
upload_to=upload_location_category,
null=True,
blank=True)
...
if i understand you correct, you can try:
class OutfitDetailSerializer(serializers.ModelSerializer):
def get_categories(self, obj):
user = self.context['request'].user
categories = Category.objects.filter(owner=user)
added = categories.extra(select={'added': '1'}).filter(outfits__pk=obj.pk)
added = list(added.values('added', 'name', 'id'))
added_f = categories.extra(select={'added': '0'}).exclude(outfits__pk=obj.pk)
added_f = list(added_f.values('added', 'name', 'id'))
categories = added + added_f
return CategorySerializer(categories, many=True).data
Note in the values you need add all fields you need for the CategorySerializer
class CategorySerializer(serializers.ModelSerializer):
added = serializers.BooleanField()
class Meta:
model = Category
fields = (
'id',
'name',
'added'
)