How to upload multiple images in DRF. I'm getting all the list of images while looping through it, but it only saves the last one. I want to save all the image and show it in response. Do I have to create separate serializer for Multiple Image serialization?
#models
class ReviewRatings(models.Model):
user = models.ForeignKey(Account, on_delete=models.CASCADE)
product = models.ForeignKey(Products, on_delete=models.CASCADE)
rating = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(5)])
created_at = models.DateField(auto_now_add=True)
review = models.CharField(max_length=500, null=True)
updated_at = models.DateField(auto_now=True)
def __str__(self):
return self.product.product_name
class ReviewImages(models.Model):
review = models.ForeignKey(ReviewRatings, on_delete=models.CASCADE, related_name='review_images', null=True, blank=True)
images = models.ImageField(upload_to='reviews/review-images', null=True, blank=True)
def __str__(self):
return str(self.images)
#Serilaizer
class ReviewImagesSerializer(ModelSerializer):
class Meta:
model = ReviewImages
fields = ["images"]
class ReviewSerializer(ModelSerializer):
user = SerializerMethodField()
review_images = ReviewImagesSerializer(many=True)
class Meta:
model = ReviewRatings
fields = [
"user",
"rating",
"review",
"created_at",
"updated_at",
"review_images",
]
def get_user(self, obj):
return f"{obj.user.first_name} {obj.user.last_name}"
#Views
class SubmitReview(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, product_slug):
data = request.data
if data["rating"] == "" and data["review"] == "":
review = ReviewRatings.objects.create(
user=request.user,
product=product,
rating=data["rating"],
review=data["review"],
)
review_images =request.FILES.getlist('review_images')
rev = ReviewImages()
for image in review_images:
rev.review=review
rev.images = image
rev.save()
serializer = ReviewSerializer(review, context={'request':request})
return Response(serializer.data, status=status.HTTP_201_CREATED)
#Postman
Response I get on the current implementation
{
"user": "Jackson Patrick Gomez",
"rating": 4.8,
"review": "Pendant -1 review by jackson with image uploadss",
"created_at": "2022-12-05",
"updated_at": "2022-12-05",
"review_images": [
{
"images": "http://127.0.0.1:8000/media/reviews/review-images/pendant3_rAe0hqS.webp"
}
]
}
The response I want to get is like this
{
"user": "Jackson Patrick Gomez",
"rating": 4.8,
"review": "Pendant -1 review by jackson with image uploadss",
"created_at": "2022-12-05",
"updated_at": "2022-12-05",
"review_images": [
{
"images": "http://127.0.0.1:8000/media/reviews/review-images/pendant3_rAe0hqS.webp"
},
{
"images": "http://127.0.0.1:8000/media/reviews/review-images/pendant3_rAe0hqS.webp"
},
{
"images": "http://127.0.0.1:8000/media/reviews/review-images/pendant3_rAe0hqS.webp"
}
]
}
I want to upload multiple images.
Are you not saving the same object instance over and over?
rev = ReviewImages()
for image in review_images:
rev.review=review
rev.images = image
rev.save()
Create a new instance for every image:
for image in review_images:
rev = ReviewImages(review=review, image=image)
rev.save()
I have found out the mistake. I was only creating one instance. Hence only one object was getting created.
#Mistake
rev = ReviewImages()
for image in review_images:
rev.review=review
rev.images = image
rev.save()
#Correct
for image in review_images:
rev = ReviewImages()
rev.review=review
rev.images = image
rev.save()
or
for image in review_images:
ReviewImages.objects.create(images=image, review=review)
Related
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.
I am pretty new to DRF/Django and want to create an endpoint that returns nested json from multiple models in the format:
{
"site": {
"uuid": "99cba2b8-ddb0-11eb-bd58-237a8c3c3fe6",
"domain_name": "hello.org"
},
"status": "live",
"configuration": {
"secrets": [
{
"name": "SEGMENT_KEY", # Configuration.name
"value": [...] # Configuration.value
},
{
"name": "CONFIG_KEY",
"value": [...]
},
"admin_settings": {
'tier'='trail',
'subscription_ends'='some date',
'features'=[]
}
Here are the models:
class Site(models.Model):
uuid = models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True)
domain_name = models.CharField(max_length=255, unique=True)
created = models.DateTimeField(editable=False, auto_now_add=True)
modified = models.DateTimeField(editable=False, auto_now=True)
class AdminConfiguration(models.Model):
TRIAL = 'trial'
PRO = 'pro'
TIERS = [
(TRIAL, 'Trial'),
(PRO, 'Professional'),
]
site = models.OneToOneField(
Site,
null=False,
blank=False,
on_delete=models.CASCADE)
tier = models.CharField(
max_length=255,
choices=TIERS,
default=TRIAL)
subscription_ends = models.DateTimeField(
default=set_default_expiration)
features = models.JSONField(default=list)
class Configuration(models.Model):
CSS = 'css'
SECRET = 'secret'
TYPES = [
(CSS, 'css'),
(SECRET, 'secret')
]
LIVE = 'live'
DRAFT = 'draft'
STATUSES = [
(LIVE, 'Live'),
(DRAFT, 'Draft'),
]
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=255, blank=False)
type = models.CharField(
max_length=255,
choices=TYPES)
value = models.JSONField(
null=True)
status = models.CharField(
max_length=20,
choices=STATUSES)
Logic behind serializer/viewset to achieve mentioned json:
retrieves lookup_field: uuid
filters query param: Configuration.status (either live or draft
filters AdminConfiguration on site id (something like AdminConfiguration.objects.get(Site.objects.get(uuid))
filters Configuration on type = secret
Here are my serializers:
class SiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = [
'uuid',
'domain_name'
]
class AdminSerializer(serializers.ModelSerializer):
class Meta:
model = AdminConfiguration
fields = [
'tier',
'subscription_ends',
'features'
]
class ConfigurationSubSerializer(serializers.ModelSerializer):
class Meta:
model = Configuration
fields = [
'name',
'value',
]
class SecretsConfigSerializer(serializers.ModelSerializer):
site = SiteSerializer()
admin_settings = AdminSerializer()
status = serializers.CharField()
configuration = ConfigurationSubSerializer(many=True, source='get_secret_config')
class Meta:
model = Configuration
fields = [
'site',
'admin_settings',
'status'
'configuration'
]
def get_secret_config(self, uuid):
site = Site.objects.get(uuid=self.context['uuid'])
if self.context['status'] == 'live' or self.context['status'] == 'draft':
return Configuration.objects.filter(
site=site,
status=self.context['status'],
type='secret'
)
Viewset:
class SecretsViewSet(viewsets.ReadOnlyModelViewSet):
model = Site
lookup_field = 'uuid'
serializer_class = SecertsConfigSerializer
filter_backends = (DjangoFilterBackend,)
filterset_fields = ['status'] #query params
def get_serializer_context(self):
return {
'status': self.request.GET['status'],
'uuid': self.request.GET['uuid']
}
def get_serializer(self, *args, **kwargs):
kwargs['context'] = self.get_serializer_context()
return CombinedConfigSerializer(*args, **kwargs)
What am I missing to achieve the desired output?
output from django shell:
from site_config.models import Site, AdminConfiguration, Configuration
from site_config.serializers import SecretsConfigSerializer
site = Site.objects.get(id=2)
s = SecretsConfigSerializer(site)
s.data
### OUTPUT ###
AttributeError: Got AttributeError when attempting to get a value for field `site` on serializer `SecretsConfigSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Site` instance.
Original exception text was: 'Site' object has no attribute 'site'.
Why you don't try something more general and build your response separating the serializers like this (maybe you can use the same serializers in somewhere else):
def get(self, request, *args, **kwargs):
resp = {
'site': None,
'status': None,
'configuration': None,
'admin_settings': None,
}
sites = models.Site.objects.all()
resp['site'] = serializers.SitesSerializer(sites, many=True).data
admin_settings = models.AdminConfiguration.objects.all()
resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
# and so
return Response(resp, status=status.HTTP_200_OK)
** You can try like this. This will also help to findout errors**
def get(self, request, *args, **kwargs):
resp = {
"site": None,
"status": None,
"configuration": None,
"admin_settings": None
}
sites = models.Site.objects.all()
resp['site'] = serializers.SitesSerializer(sites, many=True).data
if resp['site'].is_valid():
admin_settings = models.AdminConfiguration.objects.all()
resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
if resp['admin_settings'].is_valid():
return Response(resp, status=status.HTTP_200_OK)
return Response(resp['admin_settings'].errors, status=status.HTTP_404_NOT_FOUND)
return Response(resp['site'].errors, status=status.HTTP_404_NOT_FOUND)
So let's say I have this 2 models
Poll:
class Poll(models.Model):
title = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_available = models.BooleanField(default=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Options:
class Option(models.Model):
title = models.CharField(max_length=255)
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
def __str__(self):
return f'{self.poll.title} - {self.title}'
These two objects are connected with ForeignKey. Now let's say I have a frontend that renders both options and the question (title of the poll) but I only want make a single API call to the backend. Basically I need the API to looked like this:
{
"title": "Which is the best frontend framework?",
"options":[
{
"id": 1,
"title": "React"
},
{
"id": 2,
"title": "Vue"
},
{
"id": 3,
"title": "Angular"
}
]
}
How what method/technique should I use to merge two objects like this?
Based on DRF docs
class OptionSerializer(serializers.ModelSerializer):
class Meta:
model = Option
fields = ['id', 'title']
class PollSerializer(serializers.ModelSerializer):
options = OptionSerializer(source='option_set', many=True)
class Meta:
model = Poll
fields = ['title', 'options']
I'm using Django 2.2 and Django REST Framework
I have three models like
class Plan(models.Model):
name = models.CharField(_('Plan Name'), max_length=100)
default = models.NullBooleanField(default=None, unique=True)
created = models.DateTimeField(_('created'), db_index=True)
quotas = models.ManyToManyField('Quota', through='PlanQuota')
class Quota(models.Model):
codename = models.CharField(max_length=50, unique=True)
name = models.CharFieldmax_length=100)
unit = models.CharField(max_length=100, blank=True)
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE)
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
I have to get all quota and their value from PlanQuota in the plan serializer while getting a list of plans.
I have the following serializer
class PlanQuotaSerialier(serializers.ModelSerializer):
class Meta:
model = PlanQuota
depth = 1
fields = ['quota', 'value']
class PlanListSerializer(serializers.ModelSerializer):
plan_quota = PlanQuotaSerialier(read_only=True, many=True)
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quota']
But there is no plan_quota in the response.
How can I add all Quota and their value for each plan in a single query (SQL JOIN)?
Edit 2:
Adding source to the serializer field worked
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
And the result is like
"results": [
{
"name": "Test Plan 1",
"default": true,
"plan_quotas": [
{
"quota": {
"id": 1,
"order": 0,
"codename": "TEST",
"name": "Test Domain",
"unit": "count",
"description": "",
"is_boolean": false,
"url": ""
},
"value": 10
},
]
}
]
Can I club all fields from quota with value field in the plan_quotas list?
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE, related_name='plan_quotas')
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
class PlanListSerializer(serializers.ModelSerializer):
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quotas']
This is how I got it solved.
For the first query, added source
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
For removing quota key, added to_presentation() in the PlanQuotaSerializer
def to_representation(self, instance):
representation = super().to_representation(instance)
if 'quota' in representation:
representation['quota']['quota_id'] = representation['quota'].pop('id')
representation.update(representation.pop('quota'))
return representation
I am quite new to Django and Django Rest Framework.
What I like to do in my project is display intermediate model's information using ListAPIView and also include detailed information about another Model connected to the intermediate model with a Foreign Key Relationship in the form of nested representation.
I have 3 models in my project, User, Content, Bookmark.
My model goes like below.
class MyUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.CharField(max_length=50)
joined_date = models.DateTimeField(auto_now_add=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
facebook_id = models.CharField(max_length=50, blank=True)
is_facebook = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
class Content(models.Model):
seq = models.CharField(max_length=20, unique=True)
title = models.CharField(max_length=100, null=True)
start_date = models.DateField(null=True)
end_date = models.DateField(null=True)
place = models.TextField(null=True)
realm_name = models.TextField(null=True)
area = models.TextField(null=True)
price = models.TextField(null=True)
content = models.TextField(null=True)
ticket_url = models.TextField(null=True)
phone = models.TextField(null=True)
thumbnail = models.TextField(null=True)
bookmarks = models.ManyToManyField(User, through='Bookmark')
The last model, an intermediate model to connect MyUser and Content.
class Bookmark(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.ForeignKey(Content, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=200, null=True)
class Meta:
unique_together = (('user', 'content'),)
ordering = ['-created_date']
def __str__(self):
return '{} bookmarked by user {}'.format(self.content, self.user)
I want to use BookmarkListAPIView to show certain user's bookmark information and some detailed information of Contents that the user added such as title, price, and start_date.
Below is my Serializer and ListAPIView.
class BookmarkListView(generics.ListAPIView):
queryset = Bookmark.objects.all()
serializer_class = BookmarkSerializer
pagination_class = DefaultResultsSetPagination
def get_queryset(self):
user = self.request.user
return user.bookmark_set.all().order_by('-created_date')
class BookmarkedContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'start_date', 'price')
class BookmarkSerializer(serializers.ModelSerializer):
content = BookmarkedContentSerializer(many=True, read_only=True)
#bookmark = BookmarkedContentSerializer(many=True, read_only=True)
class Meta:
model = Bookmark
fields = ('content', 'user')
Currently, the API gives me results just like below.
{
"count": 6,
"next": null,
"previous": null,
"results": [
{
"content": 8,
"user": 4
},
{
"content": 6,
"user": 4
},
{
"content": 1,
"user": 4
},
{
"content": 2,
"user": 4
},
{
"content": 3,
"user": 4
},
{
"content": 10,
"user": 4
}
]
}
As mentioned above, I want to use the content ID to fetch more detailed information about each bookmark instance.
It will look like
{
"count": 6,
"next": null,
"previous": null,
"results": [
{ "content_id": 4
"title": A,
"price": 10$,
"user": 4
},
{
"content_id": 6,
"title": B,
"price": 13$,
"user": 4
},
{
"content_id": 1,
"title": C,
"price": 4$,
"user": 4
},
]
}
I tried many things written in the DRF doc but was not able to find any materials related to my situation.
If you have any idea, please help me.
I think the key you're missing is the source keyword argument for each field. Here's some docs on it for you: http://www.django-rest-framework.org/api-guide/fields/#source
And I believe this implementation should work for you or at least be very close:
class BookmarkSerializer(serializers.ModelSerializer):
content_id = serializers.IntegerField(source='content.id')
title = serializers.CharField(source='content.title')
price = serializers.CharField(source='content.price')
user = serializers.IntegerField(source='user.id')
class Meta:
model = Bookmark
fields = ('content_id', 'user', title', 'price')