ImperialCollegeLondon / django-drf-filepond

A Django app providing a server implemention for the Filepond file upload library
BSD 3-Clause "New" or "Revised" License
103 stars 39 forks source link

save image to ImageField #78

Closed darbre closed 1 year ago

darbre commented 2 years ago

Hi I use FilePond Vue.js + django-drf-firepond. I want to save images in ImageField. I am sending a request with upload_id via axios. But nothing works. How to implement it? Here is part of the code views.py. Thank You

@api_view(['PATCH'])
def change_instance(request, instanceID):
        instance = generics.get_object_or_404(Instance, id=instanceID)
        avatar_uploudID = request.data.get('avatar')

        if avatar_uploudID and avatar_uploudID != None:
            try:
                tu = TemporaryUpload.objects.get(upload_id=avatar_uploudID)
            except TemporaryUpload.DoesNotExist:
                raise Response("The specified file does not exist.", status=status.HTTP_404_NOT_FOUND
            avatar_path = 'user_{0}/'.format(request.user.id)+tu.upload_name
            su = store_upload(avatar_uploudID,avatar_path)
            my_avatar_image= open(os.path.join(settings.MEDIA_ROOT,avatar_path))
            myfile = File(my_avatar_image)
            request.data['avatar'] = myfile

        serializer =InstanceSerializer(instance,data=request.data, partial=True)

    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_200_OK)

    return Response(serializer._errors, status=status.HTTP_400_BAD_REQUEST)
jcohen02 commented 2 years ago

Hi @darbre, sorry to hear that you're having problems getting uploading of image files to work. In order to provide some assistance, I'll need some more information about the problems you're experiencing.

Can you explain the process you're following and what you've done so far to try and identify the problem?

For example, I presume you have a frontend where a user selects an image file on their system which you need to upload and store on the server? When you select the image in the filepond frontend in a browser, do you see that the image is being uploaded? The image should be uploaded straight away and stored by django-drf-filepond as a temporary upload on the server if you have the front-end client configured correctly. (unless you have disabled instantUpload in the filepond client).

If instantUpload is set to true (the default setting), if you look in the network tab of your browser debug console, you should see the file being uploaded and handled by django-drf-filepond on the server side, once the upload is complete, a 22-character upload-ID is returned to the client. This gets stored in the client-side form. When you submit your form that contains the file upload element, the ID is sent back to the server. At this point, I'm assuming you are handling the the request with the change_instance function you have shown above?

Before change_instance has even been called, you should have the file uploaded and you should be able to see the temporary upload stored on the server in the directory you have specified in your django-drf-filepond configuration.

If you are using a different workflow, for example, you have disabled instantUpload on the filepond client and you are trying to upload the image data alongside the submission of the form itself, things will be different and this may explain the problems you're experiencing. If you can explain the process you're trying to follow, that will help me to offer some advice. Thanks.

darbre commented 2 years ago

Yes, i have a front end where a user selects an image file on their system which is successfully uploaded and saved on the server. I have the frontend client configured correctly (hopefully).

Yes, i'm handling the request with the change_instance function.

Yes, the temporary upload stored on the server.

darbre commented 2 years ago

Hi! Do you have any ideas how to save the image in ImageField?

Now I'm moving the image to the main folder store_upload(avatar_uploudID,avatar_path) and additionally changing the value in the object itself:

instance.avatar = avatar_path
instance.save()

But I don't think this is a good and universal solution. I will have several such ImageFields.

jcohen02 commented 2 years ago

Hi @darbre, apologies for the delay in responding.

Since your upload is working successfully and you can see that the temporary upload is being stored correctly on the server, I wonder if this is perhaps something that is better handled as an implementation detail within your application rather than an issue with django-drf-filepond directly?

I may still be misunderstanding what you're trying to achieve. Just to check, you're trying to make use of the Django Rest Framework ImageField object to wrap some uploaded file data, presumably so that you can allow ImageField to verify the image and check that it's of the correct format, and presumably also to use this as the basis for storing metadata about the image within your application database?

I think where I'm perhaps a little confused here is that one would, presumably, normally upload the image on form submission along with any other form content and the DRF view handling the incoming request would use a serializer to process the incoming data? In this case, I guess you would have a multi-part request including the image data and other form content and the DRF serializer would process this data, creating the ImageField object in the process. If I understand correctly, in your case, you're trying to create the object instance from the data submitted with the form, then instantiate the ImageField, wrapping the previously uploaded image that was sent by the filepond client and handled by django-drf-filepond, and then add this to the instance? Apologies if I'm misunderstanding.

As I say, I think this is probably more a DRF-related issue than something specific to django-drf-filepond. However, I'm keen to see if there's anything that could be done with django-drf-filepond to help you. If you could prepare a minimal demo example of what you're trying to achieve - i.e. a simplistic example that is completely separate from your application and that you can share publicly on GitHub, that would make it much easier to investigate.

darbre commented 2 years ago

I thought django-drf-filepond, in addition to temporary loading on the server, could save a file in the Django field. Yes, the images are uploaded to the server successfully, but how to save them in the field and, accordingly, in the database?

I only use Django field: models.ImageField.

darbre commented 2 years ago

models.py

class Instance(models.Model):
    avatar = models.ImageField(upload_to = user_directory_path)

Vue JS view:

<template>
  <div>
    <MyFilePond
      :fieldValue="this.myAvatar"
      fieldName="avatar"
      imageWidth="200px"
      imageHeight="200px"
      imageCrop="1:1"
      stylePanel="compact circle"
    />
    <div class="btn" @click="sendData(this.myAvatar)">Done</div>
  </div>
</template>
<script>
import MyFilePond from "@/components/MyFilePond.vue";
export default {
  name: "AvatarChange",
  components: {
    MyFilePond,
  },
  data() {
    return {
      myAvatar: null,
    };
  },
  methods: {
    sendData(data) {
      let endpoint = `/api/changeinstance/`;
      await axios
        .post(endpoint, data)
        .then((response) => {
          if (response.status == 201) {
            alert("done");
          } else {
            console.log(response);
          }
        })
        .catch((error) => {
          console.log(error);
        });
    },
  },
};
</script>

views.py

def change_instance(request, instanceID):
        instance = generics.get_object_or_404(Instance, id=instanceID)
        avatar_uploudID = request.data.get('avatar')

        if avatar_uploudID and avatar_uploudID != None:
            try:
                tu = TemporaryUpload.objects.get(upload_id=avatar_uploudID)
            except TemporaryUpload.DoesNotExist:
                raise Response("The specified file does not exist.", status=status.HTTP_404_NOT_FOUND
            avatar_path = 'user_{0}/'.format(request.user.id)+tu.upload_name
            store_upload(avatar_uploudID,avatar_path)
            del request.data['avatar']
            instance.avatar = avatar_path
            instance.save()

        serializer =InstanceSerializer(instance,data=request.data, partial=True)

    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_200_OK)

    return Response(serializer._errors, status=status.HTTP_400_BAD_REQUEST)

Сan you tell me please: how do I link the uploaded image via Vue JS FilePond and ImageField of Django Model using django-drf-filepond?

jcohen02 commented 2 years ago

Hi @darbre, thanks for providing this example code.

It's my understanding that the issue you are experiencing is not directly related to django-drf-filepond, it's an issue with how django rest framework is being used to handle image uploads. django-drf-filepond is correctly uploading the data and then you are correctly using the api function store_upload to store the temporary upload to a file.

With rest_framework's ImageField my understanding is that you'd normally use this in a view that directly receives the raw uploaded file data from the client. In your case, uploading the file data with django-drf-filepond bypasses the intended workflow of using ImageField. As such, I'm not sure that trying to connect up an ImageField within a serialiser after you've already uploaded and stored the data using the approach shown in your example views.py above is the optimal approach.

I've spent a significant amount of time trying to convert your example code above into a working example so I can try and demonstrate an alternative approach but I've not worked with vue.js before and the code is missing imports and, for the client side, the structure and loading of dependency scripts to make this deployable as a single HTML file so that I can use it as a test client. Due to other commitments, I'm unfortunately unable to spend any more time on this at present.

If you're still having issues with this and you're able to provide a simple, working, standalone example of what you're trying to do, for example something that can be cloned from a test repository and run, then I'm happy to try and offer some further assistance.

At this stage, my recommendation would be that you use the store_upload function, as you're already doing, to store the uploaded file data to the location set as upload_to in your ImageField definition in your models.py. Then, look at how the file link is stored in the database - I think it may just be a path to the stored file - then maybe you can update this directly. If you have a separate serialiser that you're using when accessing the data from the client, this should then still work.

I hope this helps and I'm sorry I'm not able to contribute any further time to trying to offer advice on this issue at present.

derek-adair commented 1 year ago

A couple things about this thread. Honestly, @jcohen02 you are a saint for trying to help this individual, but this individual clearly does not understand this project nor how to wield django. This entire thread is out of the scope of this project and for me... when people submit this stuff to my projects... I typically direct them to stack overflow and/or the documentation and immediately close the thread.

Anyways... If you are having issue's like this needs to look more closely at this project and recognize it's purpose... Which is to take filepond API requests and store and/or interact with them from filepond. Nothing more, nothing less.

This is not a one stop shop for file upload.

In general, if you are having problems wielding this project I'd suggest you look more closely at how one might handle uploading images WITHOUT DRF-filepond. It's almost no different. Basically;

  1. Do filepond file things in the front end.
  2. Submit filepond form
  3. Process the form in any of the numerous ways outlined in the documentation.

@darbre I use something like this for my app:

       if avatar_uploudID and avatar_uploudID != None:
            avatar_path = 'user_{0}/'.format(request.user+avatar_uploudID)
            store_upload(avatar_uploudID,avatar_path)
            del request.data['avatar']
            instance.avatar = avatar_path

If this isn't working for you, i'd suggest you browse the django manual or some basic tutorials on how to wield django properly.

jcohen02 commented 1 year ago

Just to close the discussion on this issue, I hope you managed to find a solution to your problem @darbre. As @derek-adair has pointed out, it looks like the issue that you're trying to solve is somewhat out of scope of the aims and design principles of django-drf-filepond and that the problem you were facing is likely to be best handled using Django's built in file upload support or, indeed, other server-side libraries for handling uploads.

I appreciate that we all have to start somewhere with these tools and are often learning "on the job" as we go along. The tooling in the Python ecosystem is generally fantastic but depending on what technical background or prior experience people have, I also recognise that it can sometimes be really challenging to understand the models and approaches required to use these frameworks, especially if there aren't experienced colleagues or collaborators to ask for advice. On this basis, I tried to better understand the issue being reported here and to offer some thoughts and advice on how this could be addressed - I hope this was some help.

More generally, I think it's really important that this remains an open, friendly and accessible open source project and from that perspective, I don't want to discourage the posting of issues such as this, it's great that the wider community finds this tool, wants to use it and engages with the project in the event of any questions or anything that isn't clear. I can't always guarantee that I'll have time to invest in digging in to such issues and trying to offer help but happy to try when possible.

Thanks @darbre and @derek-adair for your input on this issue, I'm going to close this now.