Search code examples
djangodjango-rest-frameworkdjango-4.0

How to serialize an already existing image to save in a models.ImageField param?


I want to create Note object which one of the fields of the model is an ImageField using Django Rest Framework. I can already create objects and update all different fields using my API, except for images.

My code:

models.py

class Note(OwnedModel):
    note_id = models.UUIDField(primary_key=True,
                               default=uuid.uuid4,
                               editable=False)
    # note_owner = models.ForeignKey(, null=True, blank=True, on_delete=models.SET_NULL)
    note_name = models.CharField(max_length=50)
    body = models.TextField()
    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)
    qr_image        = models.ImageField(upload_to='notes', null=True)


    def __str__(self):
        return self.note_name[0:50]

    class Meta:
        ordering = ['-updated']

views.py

@api_view(['GET', 'POST'])
def getNote(request, pk=None):
    if request.method == 'GET':
        note = Note.objects.get(note_id=pk)
        serializer = NoteSerializer(note, many=False)
        return Response(serializer.data)

    elif request.method == 'POST':

        _data = request.data.copy()
        owner = request.user.id
        _data["owner"] = owner

        # Generate QR code
        qr_image = generate_qr(_data["note_name"])

        # HOW TO PASS THE IMAGE TO THE SERIALIZER?
        _data["qr_image"] = qr_image
        # _data["qr_image"] = qr_image[0]
        # _data["qr_image"] = qr_image[1]


        serializer = NoteSerializer(data=_data)
        
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(status=status.HTTP_400_BAD_REQUEST)

serializers.py

class NoteSerializer(ModelSerializer):
    class Meta:
        model = Note
        fields = '__all__'

qr_code.py

import qrcode

def generate_qr(qr_file_name=None):

    qr = qrcode.QRCode(
        version=1,
        # error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=4,
        border=3,
    )
    qr.add_data(qr_file_name)
    qr.make(fit=True)
    img = qr.make_image()
    # img = qr.make_image(fill_color="black", back_color="white")
    path='images/notes/'+str(qr_file_name)+'.jpg'
    img.save(path)

    return path, img

The QR code is properly generated and saved in the upload path perfectly.

What I cannot manage to build line _data["qr_image"] = qr_image correctly, or if I need to return the image in a different way from the generate_qr function. Everything else it is working well (for example create the Note object form the Admin and uploading the qr image).


Solution

  • @api_view(['GET', 'POST'])
    def getNote(request, pk=None):
        if request.method == 'GET':
            # automatically raise 404 if obj does not exist
            note = get_object_or_404(Note.objects, note_id=pk)
    
            # many=False is default, dont need that
            serializer = NoteSerializer(note)
            return Response(serializer.data)
    
        elif request.method == 'POST':
            serializer = NoteSerializer(data=request.data)
    
            # raise_exception=True will raise validation error (400) automatically
            serializer.is_valid(raise_exception=True)
            
            # get note name after validation to be sure it has proper length etc.
            qr_image = generate_qr(serializer.validated_data.get("note_name"))
    
            # attributes provided here will bypass validation
            # and will be injected directly to model create method
            serializer.save(
                owner=request.user,
                qr_image=qr_image
            )
            return Response(serializer.data, status=status.HTTP_201_CREATED)
    

    Im not sure that your generate_qr function should save image in the given path because Note should do it for you on create.

    Tip: check the viewsets.GenericViewSet class - it will properly split your view into methods and makes the life easier.