Custom django model field to upload a file to an external file/cloud host

50 views Asked by At

I want to have a custom model field for file upload to an external hoster, which I can just add to a model. It is important, that I don`t have to edit the admin form, to be form independent.

File should not be saved in django's media system. It should directly be uploaded to a file hoster. The hoster than returns file id. This id should be saved to the field.

I managed to do this with the following code. But this can`t the best solution. It is too much and to strange code.

Let's not talk about dropbox or the dropbox api, it`s just an example. Could be every other cloud/file hoster. My question is about such fields with file upload in django, not about the hoster. I know that there is django-storages and similar packages, but I do not want to use it for reasons.

What would be a better solution?

class DropboxFileIdWidget(AdminFileWidget):
    def value_from_datadict(self, data, files, name):
        uploaded_file = super().value_from_datadict(data, files, name)
        if uploaded_file:
            # Determine the path for the temporary file
            temp_dir = tempfile.gettempdir()
            temp_file_path = os.path.join(temp_dir, uploaded_file.name)

            # Check if a file with the same name already exists, and delete it
            if os.path.exists(temp_file_path):
                os.remove(temp_file_path)

            # Write the uploaded file content to the temporary file
            with open(temp_file_path, 'wb+') as temp_file:
                for chunk in uploaded_file.chunks():
                    temp_file.write(chunk)

            # Return the path to the temporary file
            return temp_file_path
        return uploaded_file

    
    def render(self, name, value, attrs=None, renderer=None):
        # Call the parent's render method
        input_html = super().render(name, value, attrs, renderer)

        # Show list name
        additional_html = ""

        if value:
            additional_html = value
            dbox_client = KKDropboxApiWrapper.instance()
            
            try:
                metadata = dbox_client.get_file_metadata_from_id(value)
                additional_html = " " + metadata.name                
            except Exception as e:
                additional_html = self.add_dropbox_connection_error_to_django_messages_and_return_message_text()

        # Append custom text next to the input
        return format_html('{} <span style="margin-left: 10px; color: #888;"> {}</span>', input_html, format_html(additional_html))


class DropboxFileIdField(models.CharField):
    def __init__(self, *args, **kwargs):
        if not kwargs: kwargs = {}

        # Set anonymous upload to path
        self.upload_to_path = ""
        if kwargs.get("upload_to_path"):
            self.upload_to_path = kwargs.get("upload_to_path")
            kwargs.pop("upload_to_path")

        # Allow blank
        kwargs["blank"] = True

        return super().__init__(*args, **kwargs)

    def formfield(self, **kwargs):
        defaults = {'widget': DropboxFileIdWidget}
        kwargs.update(defaults)
        return super().formfield(**kwargs)
    
    def pre_save(self, model_instance, add):
        temp_file_path_or_id = getattr(model_instance, self.attname, None)

        if temp_file_path_or_id and not "id:" in temp_file_path_or_id:
            temp_file_path = temp_file_path_or_id
            dbox_client = KKDropboxApiWrapper.instance()
            
            # Get upload path
            if isinstance(self.upload_to_path, str):
                dbox_upload_path = os.path.join(
                    self.upload_to_path, 
                    os.path.basename(temp_file_path)
                )
            else:
                dbox_upload_path = self.upload_to_path(
                    instance=model_instance,
                    filename=os.path.basename(temp_file_path)
                )

            # Do upload
            file_id = dbox_client.upload_file(temp_file_path, dbox_upload_path)
            setattr(model_instance, self.attname, file_id)

            # Remove file
            os.remove(temp_file_path)  
        else:
            try:
                saved_instance = model_instance.__class__.objects.get(id=model_instance.id)
                setattr(
                    model_instance, 
                    self.attname, 
                    getattr(saved_instance, self.attname)
                )
            except model_instance.DoesNotExist:
                pass

        return super().pre_save(model_instance, add)
0

There are 0 answers