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

5

5

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.

Seraglio answered 19/8, 2019 at 9:50 Comment(2)
If you have django.contrib.sites installed, you can use Site.objects.get_current() to get the hostname (which will be the site you configured in the db). And settings.MEDIA_URL is your path prefix.Irrelevancy
If there is no request object, then you can't access the host so you have to use a pre-defined host and add it to the URL or use one of the allowed hosts in settings.Hsining
S
4

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.

Seraglio answered 21/8, 2019 at 6:31 Comment(1)
Thank you for the link! One small detail: it is needed to add the slash '/' at the endAppetizing
G
6

I solved the problem by adding , context={'request': request} in view.

serializer = Business_plansSerializer(business_plans[start:end], many=True, context={'request': request})
Garofalo answered 9/11, 2021 at 15:23 Comment(0)
S
4

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.

Seraglio answered 21/8, 2019 at 6:31 Comment(1)
Thank you for the link! One small detail: it is needed to add the slash '/' at the endAppetizing
H
3

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.

Highlands answered 19/8, 2019 at 10:36 Comment(3)
First process gives me a an error like this 'return self.context['request'].build_absolute_uri(obj.image.url), KeyError: 'request''. Since there is no request object in celery. The second process would have worked if images were stored locally. But they are hosted in aws s3(cloud front) in the live server. Staging and development have stored locally.Seraglio
@KarthikRP please see the update section of the answerHighlands
Will check and update. Seems crequest is for cases where you want to save a request use it somewhere in your code and delete it. Since this is a periodic celery task, there is no request in the first place to set and make use of.Seraglio
S
0

A better way is to add context= {'request': request}) in your serializer object and use it to be the url for example

class Student(TimeStampedUUID):

    user = models.OneToOneField(User,on_delete=models.CASCADE,related_name="Student user")
    organization = models.ForeignKey(Organization,on_delete=models.CASCADE)
    first_name = models.CharField(max_length=255,verbose_name=_("Student First Name"))
    last_name = models.CharField(max_length=255,verbose_name=_("Student Last Name"))
   
    profile_picture = ProcessedImageField(upload_to='caregiver_profile_pictures', default='default.png',
                                           processors=[ResizeToFill(100, 50)],
                                           format='JPEG',
                                           options={'quality': 80},validators=[ FileExtensionValidator(allowed_extensions=['jpg', 'png', 'jpeg'])])
    
    

    @property
    def profile_picture_url(self):
        try:
            url = self.profile_picture.url
        except:
            url ='' 
        return url

and then in the view you can do

serialized_student = StudentSerializer(students,many=True,context= {'request': request})
Sagunto answered 28/9, 2024 at 12:33 Comment(0)
M
-1

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__'
Matronymic answered 19/8, 2019 at 10:51 Comment(1)
Nice., Doesn't help me though. I've a development server, staging server which has DEBUG set to True, and the production the media is hosted in AWS S3(Cloud front)Seraglio

© 2022 - 2025 — McMap. All rights reserved.