Django serializer Imagefield to get full URL - python

I am beginner to Django and currently, I can construct model like this.
models.py
class Car(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
photo = models.ImageField(upload_to='cars')
serializers.py
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('id','name','price', 'photo')
views.py
class CarView(APIView):
permission_classes = ()
def get(self, request):
car = Car.objects.all()
serializer = CarSerializer(car)
return Response(serializer.data)
For photo, it doesn't show full URL. How can I show full URL?

Django is not providing an absolute URL to the image stored in a models.ImageField (at least if you don't include the domain name in the MEDIA_URL; including the domain is not recommended, except of you are hosting your media files on a different server (e.g. aws)).
However, you can modify your serializer to return the absolute URL of your photo by using a custom serializers.SerializerMethodField. In this case, your serializer needs to be changed as follows:
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
def get_photo_url(self, car):
request = self.context.get('request')
photo_url = car.photo.url
return request.build_absolute_uri(photo_url)
Also make sure that you have set Django's MEDIA_ROOTand MEDIA_URL parameters and that you can access a photo via your browser http://localhost:8000/path/to/your/image.jpg.
As piling pointed out, you need to add the request while initialising the serializer in your views.py:
def my_view(request):
…
car_serializer = CarSerializer(car, context={"request": request})
car_serializer.data

For future visitors, there is no need to add another field to the serializer if the view method already returns a serialized object. The only thing required is to add the context since it is needed to generate hyperlinks, as stated in the drf documentation
#list_route()
def my_view(self, request):
qs = Object.objects.all()
return Response(MySerializer(qs, many=True, context={'request': request}).data)

Serializer class
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.ImageField(max_length=None, use_url=True, allow_null=True, required=False)
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
View
class CarView(APIView):
def get(self, request, *args, **kwargs):
queryset = Car.objects.all()
serializer = CarSerializer(queryset, many=True, context={"request":request})
return Response(serializer.data, status=status.HTTP_200_OK)

It's better to use this code, due to the above code doesn't check the image is null able or not.
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
def get_photo_url(self, car):
request = self.context.get('request')
if photo and hasattr(photo, 'url'):
photo_url = car.photo.url
return request.build_absolute_uri(photo_url)
else:
return None

serializers.py
class BannerSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
def get_image(self, obj):
return self.context['request'].build_absolute_uri( obj.image.url)
views.py
banner = Banner.objects.all()
banner_data = BannerSerializer(banner,many=True, context={'request': request})
data = banner_data.data
return Response({"data":data})

I read the implement of the Serializer, and find the simplest way is to extends ImageField:
from django.db import models
class ImageField(models.ImageField):
def value_to_string(self, obj): # obj is Model instance, in this case, obj is 'Class'
return obj.fig.url # not return self.url
class Class(models.Model):
name = models.CharField(max_length=50)
intro = models.CharField(max_length=200)
# fig = models.ImageField(upload_to="classes")
fig = ImageField(upload_to="classes")
def __str__(self):
return repr(self,"name")

Related

Django Rest Framework: Use URL parameter in serializer

My goal is to use a URL parameter as a variable in a function and output it in the serializer. But the following solution doesnt work. Anybody know why?
The URL looks like this:
http://127.0.0.1:8000/v1/water-bodies/?from=2013-02-17&to=2013-02-18
models.py
class Application(models.Model):
"""Database models for application information"""
name = models.CharField(max_length=255)
machine_name = models.SlugField(max_length=255, allow_unicode=True)
description = models.TextField(blank=True, null=True)
indice_to_use = models.ForeignKey('Indice', on_delete=models.PROTECT, blank=True, null=True)
def __str__(self):
return self.name
views.py
class DetailView(APIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["date_from"] = self.request.query_params.get("from", None)
return context
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
serializer = OsdSerializer(application)
return Response({"Everything you need to analyzing "+application.name: serializer.data})
serializer.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
def get_alternate_name(self, obj):
return self.context.get('date_from')
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', ]
We would be required to update the code at two places.
DetailView in views.py.
In this, we are updating context to include data_from. (please note, we can also access this directly in the serialzier)
class DetailView(APIView):
...
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
serializer = OsdSerializer(application, context={
"date_from": request.query_params.get("from")
})
return Response({"Everything you need to analyzing "+application.name: serializer.data})
...
OsdSerializer in the serializers.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
alternate_name = serializers.SerializerMethodField()
def get_alternate_name(self, obj):
return self.context.get('date_from')
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', 'alternate_name']
Another approach would be to access the request object directly from the context of the serializer. By default, the serializer context contains the request object in it. To do so, just update the serializer as mentioned below
OsdSerializer in the serializers.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
alternate_name = serializers.SerializerMethodField()
def get_alternate_name(self, obj):
return self.context.get('request').query_params.get('from', None)
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', 'alternate_name']
First you need to change from accessing the parameter from kwargs to request.GET instead.
If you have a url like this in django '/user/<int:user_id>' then you access through kwargs.
But if you send a url parameter, like this '/user/?user_id=1'. You access through the request object.
In your situation, I think rest_framework will send the request to the serializer automatically. So you can do something like this:
def get_alternate_name(self, obj):
date_from = self.request.GET.get('from')
date=self.context.get('request').parser_context.get('kwargs').get(
'edate')

How can I pass an object selected by the user, to another view and retrieve a specific column from that object?

I'm trying to figure out the most efficient way to GET values inside my stock_list column from the current object that's being viewed by a user.
BucketDetail is used to retrieve the specific object selected by the user via item = self.kwargs.get('pk')
class BucketDetail(generics.RetrieveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = BucketListSerializer
queryset = Bucket.objects.all()
def get_object(self, queryset=queryset, **kwargs):
item = self.kwargs.get('pk')
return get_object_or_404(Bucket, slug=item)
How can I pass the object instance, from BucketDetail to BucketData view, followed by getting the column, stock_list, from the current object instance?
class BucketData(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
stocks = Bucket.objects.get(stock_list)
...
data = response.json()
return Response(data, status=status.HTTP_200_OK)
above is what I have so far, stocks = Bucket.objects.get(stock_list) does not work like I thought.
models.py
class Bucket(models.Model):
category_options = (
('personal', 'Personal'),
('social', 'Social'),
)
class BucketObjects(models.Manager):
def get_queryset(self):
return super().get_queryset()
...
slug = models.SlugField(unique=True, blank=True)
stock_list = ArrayField(models.CharField(max_length=6,null=True),size=30,null=True)
...
objects = models.Manager()
bucketobjects = BucketObjects()
class Meta:
ordering = ('-created',)
def total_stocks_calc(self):
self.stock_count = Bucket.objects.aggregate(Sum('stock_list', distinct=True))
self.save()
You should use model pk or your unique slug to retrieve stock_list data within your BucketData view. You also should use a serializer for the data. Pass your slug or pk from urls file to your view. Try something like this (code is not tested and can be improved):
Serializer:
class StockListSerializer(ModelSerializer):
stock_list = serializers.ListField(child=serializers.CharField())
class Meta:
model = Bucket
fields = ("stock_list",)
View:
class BucketData(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
slug = kwargs.get("slug")
bucket_object = get_object_or_404(Bucket, slug=slug)
serialzer = StockListSerializer(bucket_object.stock_list)
return Response(serialzer.data, status=status.HTTP_200_OK)

DRF: how to change the value of the model fields before saving to the database

If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.

Get Image Field Absolute Path in Django Rest Framework - Non Request Flows

I've a periodic celery task which needs to store representation of a object in a specific json field.
Here is the simplified model structure.
Parent <-- ChildWrapper <-- Child Image
So basically I've a 'ChildImage' model referring to 'ChildWrapper' which in turn refers to 'Parent'.
class Parent(TimeStampedModel):
label = models.CharField(max_length=30, unique=True)
live_content = JSONField(blank=True, null=True)
is_template = models.BooleanField(default=False)
reference_image = models.ImageField(upload_to=get_web_row_reference_image_path, blank=True, null=True)
# Around 8 Other Fields
def __str__(self):
return '%s' % self.label
class ChildWrapper(TimeStampedModel):
name = models.CharField(max_length=25, blank=True, null=True)
row = models.ForeignKey(Parent, on_delete=models.CASCADE, related_name='web_column')
order = models.PositiveIntegerField(default=0)
# Around 20 Other Fields
def __str__(self):
return '%s' % self.name
class ChildImage(TimeStampedModel):
image = models.ImageField(upload_to=get_web_image_path)
column = models.ForeignKey(ChildWrapper, on_delete=models.CASCADE, related_name='web_image')
# Around 10 Other Fields
def __str__(self):
return '%s' % self.column
This is the serializers defined for the models.
class ChildImageSerializer(serializers.ModelSerializer):
class Meta:
model = ChildImage
fields = '__all__'
class ChildWrapperSerializer(serializers.ModelSerializer):
web_image = ChildImageSerializer(read_only=True, many=True)
class Meta:
model = ChildWrapper
fields = '__all__'
class ParentSerializer(serializers.ModelSerializer):
web_column = ChildWrapperSerializer(many=True, read_only=True)
class Meta:
model = Parent
fields = '__all__'
Here is the periodic celery task which does the required
#app.task(bind=True)
def update_data(self):
# Get Parent By a condition.
parent = Parent.objects.filter(to_update=True).first()
parent.live_content = None
parent.live_content = ParentSerializer(parent).data
print(parent.live_content)
parent.save()
The above task gets output of child image something like this with imagefield being relative path instead of absolute path.
{
"id": 1
"image": '/api/col/info.jpg'
}
Is there any way to get the absolute path for the image field?
{
"id": 1
"image": "http://localhost:8000/admin/media/api/col/info.jpg"
}
PS:
I cannot pass Request context to serializer as ParentSerializer(parent, context={'request': request}) as there is no request object involved here.
I think you have two ways to resolve this.
First one, is to pass request. You can take this approach:
class ChildImageSerializer(serializers.ModelSerializer):
img_url = serializers.SerializerMethodField()
class Meta:
model = ChildImage
fields = '__all__'
def get_img_url(self, obj):
return self.context['request'].build_absolute_uri(obj.image.url)
class ChildWrapperSerializer(serializers.ModelSerializer):
web_image = serializers.SerializerMethodField()
class Meta:
model = ChildWrapper
fields = '__all__'
def get_web_image(self, obj):
serializer_context = {'request': self.context.get('request') }
children = ChildImage.objects.filter(row=obj)
serializer = ChildImageSerializer(children, many=True, context=serializer_context)
return serializer.data
class ParentSerializer(serializers.ModelSerializer):
web_column = serializers.SerializerMethodField()
class Meta:
model = Parent
fields = '__all__'
def get_web_column(self, obj):
serializer_context = {'request': self.context.get('request') }
children = ChildWrapper.objects.filter(row=obj)
serializer = ChildWrapperSerializer(children, many=True, context=serializer_context)
return serializer.data
Here I am using SerializerMethodField to pass the request on to the next serializer.
Second approach is to use Django Sites Framework(mentioned by #dirkgroten). You can do the following:
class ChildImageSerializer(serializers.ModelSerializer):
img_url = serializers.SerializerMethodField()
class Meta:
model = ChildImage
fields = '__all__'
def get_img_url(self, obj):
return 'http://%s%s%s' % (Site.objects.get_current().domain, settings.MEDIA_URL, obj.img.url)
Update: I totally missed the celery part. For production, I don't think you need to worry as they are in S3, the absolute path should be coming from obj.image.url. And in dev and stage, you can get the absolute path using the given example. So, try like this:
class ChildImageSerializer(serializers.ModelSerializer):
img_url = serializers.SerializerMethodField()
class Meta:
model = ChildImage
fields = '__all__'
def get_img_url(self, obj):
if settings.DEBUG: # debug enabled for dev and stage
return 'http://%s%s%s' % (Site.objects.get_current().domain, settings.MEDIA_URL, obj.img.url)
return obj.img.url
Alternatively, there is a way to get request using django-crequest in celery, but I am not sure if its convenient to you.
Got it working,
Added MEDIA_URL to my settings file as mentioned here.
It seems DRF uses MEDIA_URL as a default prefix for urls(FileField & ImageField), even for non request/response flows.
Since I had a different settings file for staging, development and production it was easier for me to set different URLs for each environment.
Even though I'm not using 'django-versatileimagefield' library, the suggestion there still worked.
I solved the problem by adding , context={'request': request} in view.
serializer = Business_plansSerializer(business_plans[start:end], many=True, context={'request': request})
Another solution is hard code the host:
from django.conf import settings
IMG_HOST = {
'/home/me/path/to/project': 'http://localhost:8000',
'/home/user/path/to/project': 'https://{AWS_HOST}',
}[str(settings.BASE_DIR)]
class ChildImageSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
def get_image(self, obj):
if obj.image:
return IMG_HOST + obj.image.url
class Meta:
model = ChildImage
fields = '__all__'

JSONField serializes as json for POST, but string for GET

There is likely a very simple problem with my code, but I've been slamming my head against this problem for a couple days and can't make any headway.
Important Packages:
Django==1.11.3
django-cors-headers==2.1.0
djangorestframework==3.7.0
drf-nested-routers==0.90.0
psycopg2==2.7.3
pycparser==2.18
Here is what is happening:
I create a model via an AJAX call
My server correctly serializes the brainstorm_data field as a json object.
Now I navigate my user to the next page and fetch the current model
For some reason, brainstorm_data is now be returned as a string. Anytime I call a GET request on this resource I always get a string representation of the JSON object.
Here is the code associated:
models.py
from django.contrib.postgres.fields import JSONField
class Adventure(TimeStampedModel,
models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User)
world = models.ForeignKey(World)
theme = models.ForeignKey(Theme, default=1)
brainstorm_data = JSONField()
image_src = models.CharField(max_length=400, null=True, blank=True)
sentence_summary = models.TextField(null=True, blank=True)
paragraph_summary = models.TextField(null=True, blank=True)
page_summary = models.TextField(null=True, blank=True)
outline_complete = models.BooleanField(default=False)
brainstorm_complete = models.BooleanField(default=False)
private = models.BooleanField(default=False)
def __str__(self):
return self.name
views.py
class MyAdventuresViewSet(viewsets.ModelViewSet):
queryset = Adventure.objects.all()
serializer_class = AdventureSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return Adventure.objects.filter(user=self.request.user)
def create(self, request, *args, **kwargs):
user = self.request.user
world = World.objects.filter(user=user).first()
if not world:
world = World.objects.create(name='My World', user=user,
description="This is a default world we created for your adventures",
image_src=static('worlds/images/world_placeholder.png'))
data = request.data.copy()
data['user'] = user.pk
data['world'] = world.pk
data['theme'] = 1 # default theme
data['brainstorm_data'] = default_brainstorm
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
adventure = serializer.save()
Storyboard.objects.create(adventure=adventure, raw=default_storyboard['raw'], html=default_storyboard['html'])
return JsonResponse(serializer.data)
#detail_route(methods=['post'])
def complete_outline(self, request, pk):
adventure = Adventure.objects.get(pk=self.kwargs['pk'])
complete_adventure_outline(adventure)
serializer = self.get_serializer(data=adventure)
serializer.is_valid(raise_exception=True)
return JsonResponse(serializer.data, status=200)
#detail_route(methods=['post'])
def genres(self, request, pk):
genre_names = request.data
genre_models = Genre.objects.filter(name__in=genre_names)
adventure = self.get_object()
adventure.genre_set.set(genre_models)
adventure.save()
serializer = AdventureSerializer(adventure)
return JsonResponse(serializer.data)
serializers.py
class AdventureSerializer(serializers.ModelSerializer):
genre_set = serializers.StringRelatedField(many=True, read_only=True)
character_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
location_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
storyboard = serializers.PrimaryKeyRelatedField(read_only=True)
theme = serializers.PrimaryKeyRelatedField(queryset=Theme.objects.all())
class Meta:
model = Adventure
fields = '__all__'
mixins
# this is a dictionary used to default brainstorm data each time an adventure is created
default_brainstorm = {
"nodes": [...],
"edges": [...]
}
You can override the to_internal_value and to_representation in a new serializer field to handle the return data for JSON field.
class JSONSerializerField(serializers.Field):
"""Serializer for JSONField -- required to make field writable"""
def to_internal_value(self, data):
return data
def to_representation(self, value):
return value
And in turn, you would use this Field in a serializer:
class SomeSerializer(serializers.ModelSerializer):
json_field = JSONSerializerField()
class Meta:
model = SomeModelClass
fields = ('json_field', )
This should solve your problem :)
When I originally created the columns I did it with a different json field package. The base DB columns was actually text instead of json or jsonb. Creating new columns (django json fields), migrating the data, and then shifting the data back got my database back in a consistent order.

Categories

Resources