3

I need to return an Image directly in the body request, but i get in response a generated file with no extension and an empty array/json inside...

I'm using python 3, Django==1.10.5 and djangorestframework==3.5.3

My models.py

class Image(models.Model):
    class Meta:
        verbose_name = _('Image')
        verbose_name_plural = _('Images')

    creation_date = models.DateTimeField(
        help_text=_('Creation date'),
        auto_now_add=True,
        editable=False
    )

    modified_date = models.DateTimeField(
        help_text=_('Last modification date'),
        auto_now=True
    )

    image_file = models.ImageField(upload_to='', null=True)

My serializers.py

class ImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = Image
        fields = ('image_file',)

My views.py

class ImageViewSet(NestedViewSetMixin, viewsets.ModelViewSet):

    http_method_names = ['get', 'put']
    queryset = Image.objects.all()
    serializer_class = ImageSerializer
    pagination_class = None

    def get_queryset(self, *args, **kwargs):

        image = Image.objects.last()
        filename = image.image_file
        size = filename.size
        response = FileResponse(open(filename.path, 'rb'), content_type="image/png")
        response['Content-Length'] = size
        response['Content-Disposition'] = "attachment; filename=%s" % 'notification-icon.png'
        return response 

If someone find what I'm doing wrong, I will really appreciate. Thx a lot ;p

Edit : i have tryed with django.core.files.File, filewrapper and tryed to desactivate the serializer without effect.

Bat
  • 143
  • 1
  • 3
  • 11

2 Answers2

6

You can use base64 module to encode the image file to base64 format (a raw string), and then assigned it to a field to transfer your image.

I have updated your ImageSerializer to include a new filed named base64_image which is encoded from the model's image file with base64 format.

The following example is for your reference:

serialisers.py

from django.core.files import File
import base64

class ImageSerializer(serializers.ModelSerializer):

    base64_image = serializers.SerializerMethodField()

    class Meta:
        model = Image
        fields = ('base64_image', 'id')

    def get_base64_image(self, obj):
        f = open(obj.image_file.path, 'rb')
        image = File(f)
        data = base64.b64encode(image.read())
        f.close()
        return data

views.py

class ImageViewSet(viewsets.ModelViewSet):
    http_method_names = ['get', 'put']
    queryset = Image.objects.all()
    serializer_class = ImageSerializer
    pagination_class = None

urls.py

from rest_framework.routers import DefaultRouter

# Register viewset
router = DefaultRouter()
router.register(r'image', ImageViewSet)
urlpatterns += router.urls

Finally, you can open a browser with url: http://localhost:8000/image/, and you will get the response like that:

[
    {
        "base64_image": "iVBORw0KGg.....",
        "id": 1
    }
]

How to show your image in front-end or app?

When your front-end get the json above, you need to convert the base64 format back to image raw data.

For Javascript:

document.getElementById('img').setAttribute( 'src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEU.......');

How can I set Image source with base64

For iOS:

how to decode BASE64 encoded PNG using Objective C

For Android:

How to convert a Base64 string into a BitMap image to show it in a ImageView?

I have upload an example to GitHub. Hope it would help.

Community
  • 1
  • 1
Enix
  • 4,415
  • 1
  • 24
  • 37
  • Wow thx a lot for your answer, i Will try tomorow morning. But do you Know if a solution exist for put the file directly without the base64 encoding ? Json response is not an obligation. Thx again – Bat Mar 18 '17 at 18:49
  • there is a discussion about whether using base64 or other technology to transfer file in json: http://stackoverflow.com/questions/1443158/binary-data-in-json-string-something-better-than-base64. I think base64 is the most simple way to achieve your goal. – Enix Mar 19 '17 at 10:04
  • Yep sure but my boss want that my server return a binary file in the body... First try i was returning the url and this was working well, but he asks to me to change that ! Grrrr Thx for the link I will have a look right now. And thx again for you base64 download. I will use it for sure ;p – Bat Mar 19 '17 at 15:05
0

I took the solution from https://www.py4u.net/discuss/185229 Answer number one and fixed it (it did not work for me, gave UnicodeDecodeError because file mus have been opened like open(img.path, 'rb'))

Full solution:

views.py

class PassthroughRenderer(renderers.BaseRenderer):
"""
    Return data as-is. View should supply a Response.
"""
media_type = ''
format = ''

def render(self, data, accepted_media_type=None, renderer_context=None):
    return data

class MeasuringPointImagesViewset(AbstractViewset):
    queryset = MeasuringPointImage.objects.all()
    serializer_class = MeasuringPointImageSerializer
    filter_class = MeasuringPointImageFilter

    @action(
        methods=[RequestMethods.GET.value.lower()],
        detail=True,
        url_path='download',
        url_name='download',
        renderer_classes=(PassthroughRenderer,)
    )
    def download(self, request, *args, **kwargs):
    
        instance: MyClassImage = self.get_object()
    
        img = instance.image
        response = FileResponse(open(img.path, 'rb'),     content_type='image/*')
        response['Content-Length'] = os.path.getsize(img.name)
        response['Content-Disposition'] = "attachment; filename=%s" % img.name
        return response