How can I add a link to download a file in a Django admin detail page?
Question:
I want to create a file containing a string and allow the user to download the file when they click a button in the admin detail page. Any ideas?
Probably add html to the form? But how can I do that? I am new to Django.
Answers:
In your models.py
field for that application, add the following piece of code
from django.utils.safestring import mark_safe
def fieldname_download(self):
return mark_safe('<a href="/media/{0}" download>{1}</a>'.format(
self.fieldname, self.fieldname))
fieldname_download.short_description = 'Download Fieldname'
Then in your admin.py
, add this field to your readonly_fields for that model
readonly_fields = ('fieldname_download', )
In your settings.py
file you need to specify a root path to a directory where to serve the files from and a base url for accessing them:
MEDIA_ROOT=(str, 'path/to/your/media/directory/'),
MEDIA_URL=(str,'/media/'),
You can go along the following lines:
class YourAdmin(ModelAdmin):
# add the link to the various fields attributes (fieldsets if necessary)
readonly_fields = ('download_link',)
fields = (..., 'download_link', ...)
# add custom view to urls
def get_urls(self):
urls = super(YourAdmin, self).get_urls()
urls += [
url(r'^download-file/(?P<pk>d+)$', self.download_file,
name='applabel_modelname_download-file'),
]
return urls
# custom "field" that returns a link to the custom function
def download_link(self, obj):
return format_html(
'<a href="{}">Download file</a>',
reverse('admin:applabel_modelname_download-file', args=[obj.pk])
)
download_link.short_description = "Download file"
# add custom view function that downloads the file
def download_file(self, request, pk):
response = HttpResponse(content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="whatever.txt"')
# generate dynamic file content using object pk
response.write('whatever content')
return response
There are two answer about add download link as new field to details page which is easyer than add download link inside AdminFileWidget
. I write this answer in case someone need add download link inside AdminFileWidget
.
The final result like this:
The way to achieve this is:
1 models.py
:
class Attachment(models.Model):
name = models.CharField(max_length=100,
verbose_name='name')
file = models.FileField(upload_to=attachment_file,
null=True,
verbose_name='file ')
2 views.py:
class AttachmentView(BaseContextMixin, DetailView):
queryset = Attachment.objects.all()
slug_field = 'id'
def get(self, request, *args, **kwargs):
instance = self.get_object()
if settings.DEBUG:
response = HttpResponse(instance.file, content_type='application/force-download')
else:
# x-sendfile is a module of apache,you can replace it with something else
response = HttpResponse(content_type='application/force-download')
response['X-Sendfile'] = instance.file.path
response['Content-Disposition'] = 'attachment; filename={}'.format(urlquote(instance.filename))
return response
3 urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('attachment/<int:pk>/', AttachmentView.as_view(), name='attachment'),
]
4 admin.py
from django.urls import reverse
from django.contrib import admin
from django.utils.html import format_html
from django.contrib.admin import widgets
class DownloadFileWidget(widgets.AdminFileWidget):
id = None
template_name = 'widgets/download_file_input.html'
def __init__(self, id, attrs=None):
self.id = id
super().__init__(attrs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
print(self, name, value, attrs, self.id)
context['download_url'] = reverse('attachment', kwargs={'pk': self.id})
return context
class AttachmentAdmin(admin.ModelAdmin):
list_display = ['id', 'name', '_get_download_url']
search_fields = ('name',)
my_id_for_formfield = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.my_id_for_formfield = obj.id
return super(AttachmentAdmin, self).get_form(request, obj=obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
if self.my_id_for_formfield:
if db_field.name == 'file':
kwargs['widget'] = DownloadFileWidget(id=self.my_id_for_formfield)
return super(AttachmentAdmin, self).formfield_for_dbfield(db_field, **kwargs)
def _get_download_url(self, instance):
return format_html('<a href="{}">{}</a>', reverse('attachment', kwargs={'pk': instance.id}), instance.filename)
_get_download_url.short_description = 'download'
admin.site.register(Attachment, AttachmentAdmin)
5 download_file_input.html
{% include "admin/widgets/clearable_file_input.html" %}
<a href="{{ download_url }}">Download {{ widget.value }}</a>
That’s all!
I want to create a file containing a string and allow the user to download the file when they click a button in the admin detail page. Any ideas?
Probably add html to the form? But how can I do that? I am new to Django.
In your models.py
field for that application, add the following piece of code
from django.utils.safestring import mark_safe
def fieldname_download(self):
return mark_safe('<a href="/media/{0}" download>{1}</a>'.format(
self.fieldname, self.fieldname))
fieldname_download.short_description = 'Download Fieldname'
Then in your admin.py
, add this field to your readonly_fields for that model
readonly_fields = ('fieldname_download', )
In your settings.py
file you need to specify a root path to a directory where to serve the files from and a base url for accessing them:
MEDIA_ROOT=(str, 'path/to/your/media/directory/'),
MEDIA_URL=(str,'/media/'),
You can go along the following lines:
class YourAdmin(ModelAdmin):
# add the link to the various fields attributes (fieldsets if necessary)
readonly_fields = ('download_link',)
fields = (..., 'download_link', ...)
# add custom view to urls
def get_urls(self):
urls = super(YourAdmin, self).get_urls()
urls += [
url(r'^download-file/(?P<pk>d+)$', self.download_file,
name='applabel_modelname_download-file'),
]
return urls
# custom "field" that returns a link to the custom function
def download_link(self, obj):
return format_html(
'<a href="{}">Download file</a>',
reverse('admin:applabel_modelname_download-file', args=[obj.pk])
)
download_link.short_description = "Download file"
# add custom view function that downloads the file
def download_file(self, request, pk):
response = HttpResponse(content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="whatever.txt"')
# generate dynamic file content using object pk
response.write('whatever content')
return response
There are two answer about add download link as new field to details page which is easyer than add download link inside AdminFileWidget
. I write this answer in case someone need add download link inside AdminFileWidget
.
The final result like this:
The way to achieve this is:
1 models.py
:
class Attachment(models.Model):
name = models.CharField(max_length=100,
verbose_name='name')
file = models.FileField(upload_to=attachment_file,
null=True,
verbose_name='file ')
2 views.py:
class AttachmentView(BaseContextMixin, DetailView):
queryset = Attachment.objects.all()
slug_field = 'id'
def get(self, request, *args, **kwargs):
instance = self.get_object()
if settings.DEBUG:
response = HttpResponse(instance.file, content_type='application/force-download')
else:
# x-sendfile is a module of apache,you can replace it with something else
response = HttpResponse(content_type='application/force-download')
response['X-Sendfile'] = instance.file.path
response['Content-Disposition'] = 'attachment; filename={}'.format(urlquote(instance.filename))
return response
3 urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('attachment/<int:pk>/', AttachmentView.as_view(), name='attachment'),
]
4 admin.py
from django.urls import reverse
from django.contrib import admin
from django.utils.html import format_html
from django.contrib.admin import widgets
class DownloadFileWidget(widgets.AdminFileWidget):
id = None
template_name = 'widgets/download_file_input.html'
def __init__(self, id, attrs=None):
self.id = id
super().__init__(attrs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
print(self, name, value, attrs, self.id)
context['download_url'] = reverse('attachment', kwargs={'pk': self.id})
return context
class AttachmentAdmin(admin.ModelAdmin):
list_display = ['id', 'name', '_get_download_url']
search_fields = ('name',)
my_id_for_formfield = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.my_id_for_formfield = obj.id
return super(AttachmentAdmin, self).get_form(request, obj=obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
if self.my_id_for_formfield:
if db_field.name == 'file':
kwargs['widget'] = DownloadFileWidget(id=self.my_id_for_formfield)
return super(AttachmentAdmin, self).formfield_for_dbfield(db_field, **kwargs)
def _get_download_url(self, instance):
return format_html('<a href="{}">{}</a>', reverse('attachment', kwargs={'pk': instance.id}), instance.filename)
_get_download_url.short_description = 'download'
admin.site.register(Attachment, AttachmentAdmin)
5 download_file_input.html
{% include "admin/widgets/clearable_file_input.html" %}
<a href="{{ download_url }}">Download {{ widget.value }}</a>
That’s all!