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)