I am trying to download a CSV file from the server to a client machine via a jQuery Ajax call, but for some reason the return response is not triggering the download. I know that there are several other questions which are quite similar, however, none of them are specifically using ajax with Django (that I've found), so I'm not sure if something needs to be done differently. The closest thing I could find was this one: download file using an ajax request which sets the window.location to a download.php file on success, but that doesn't really seem applicable. Everything I've been able to find, including the django documentation, uses some method that is very similar to what I have and yet every variation I've tried produces more or less the same result - nothing gets downloaded.
I've confirmed that each step of the process is executing correctly except for the last one in the views.py.
download_page.html:
{% extends 'base.html' %}
{% load staticfiles %}
<script>
$(function() {
$('#download').on('click', function () {
var pk = $(this).val();
$.ajax({
url: '/ajax/download/',
data: {'pk': pk}
});
});
});
</script>
{% block body %}
...
<button id="download" value="{{ view_id }}">Download</button>
...
{% endblock %}
urls.py:
urlpatterns = [
...
url(r'^ajax/download/', views.download, name='download'),
...
]
views.py:
import pathlib2
from django.http import HttpResponse
from src.apps.main.models import DataModel
def download(request):
pk = request.GET.get('pk', None)
fname, fpath = DataModel.export_to_csv(pk) # does the export and returns fullpath to file
if pathlib2.Path(fpath).exists():
file_download = open(fpath, 'r')
response = HttpResponse(file_download, content_type='application/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(fname)
return response
It gets all the way to the point at which it should return the response and then nothing happens. There are no warnings or errors, it just seems to silently finish without ever triggering the download on the client side. I'm guessing that there's something I need to do differently to return the file, but I've not been able to determine what that is.
maybe could do fname of you are a path
response['Content-Disposition'] = 'attachment; filename="{}"'.format(fname)
you can try:
response['Content-Type'] = 'application/force-download'
response['Content-Disposition'] = 'attachment; filename="donwload.scv"'
note: you can edit the filename accordingly.
Related
I need a protected video file on a page rendered by Django. The file is protected, but it's not serving an html rendered page with the <video src="..."> as I'd expect, like netflix. Instead, all I get is a jumbled mess like this image.
I know the internal redirect is serving the file, therefore it shows up like that, but I need it on a rendered page with the other html like netflix does.... What am I doing wrong??
Nginx conf file:
location /secret_videos/ {
internal;
alias /home/username/path/to/secret/videos/;
}
Url:
path('protected_video/', views.protected_video, name='protected_video'),
View:
def protected_video(request):
....
if request.method =='POST':
if some_var == 'the_correct_value':
protected_uri = '/secret_videos/secret-vid-1.mp4'
response = render(request, 'template.html', {'some_var ': True, 'protected_uri': protected_uri})
response['X-Accel-Redirect'] = protected_uri
return response
return render(request, 'template.html', {})
Template, but it's not rendering html, only the image above:
<video width="75%" height="auto" controls>
<source src="{{ protected_uri }}" type="video/mp4" />
Your browser doesn't support the mp4 video format.
</video>
You're combining what should be two request/responses: rendering the page, and sending the video.
You need to render the template, in which you give the video a URL that calls a Django view. That second view then returns a response with the secret URL as the accel-redirect. So:
path('protected_video/', views.protected_video, name='protected_video'),
path('video_url/<slug: video_slug>/', views.redirect_to_video, name='redirect_to_video'),
...
def protected_video(request):
....
if request.method =='POST':
if some_var == 'the_correct_value':
protected_uri = reverse('redirect_to_video' , kwargs={'video_slug': 'some_slug'})
return render(request, 'template.html', {'some_var ': True, 'protected_uri': protected_uri})
def redirect_to_video(request, slug):
... some logic to get the secret URL from the slug ...
response = HttpResponse()
response['X-Accel-Redirect'] = secret_url
return response
In my Django project I create custom admin page (NOT admin from Django). I have 2 login pages. One for admin, second for other users.
Here below you can see urls.py file for admin. I test it and it works fine. After successful login Django redirect user to url which was next parameter (/administration/dashboard/).
I wrote unit test and it raise error. From error I understand that Django redirect to default url (/accounts/profile/). Why unit test dont use settings which I did in urls.py file (next parameter)?
How to fix this problem?
Right now I notice that problem disappear only if I use this code LOGIN_REDIRECT_URL = '/administration/dashboard/' in settings.py. I cant use it cause in the future I will use LOGIN_REDIRECT_URL to my other login page.
I would be grateful for any help!
urls.py:
from django.contrib.auth import views as authentication_views
urlpatterns = [
# Administration Login
url(r'^login/$',
authentication_views.login,
{
'template_name': 'administration/login.html',
'authentication_form': AdministrationAuthenticationForm,
'extra_context': {
'next': reverse_lazy('administration:dashboard'),
},
'redirect_authenticated_user': True
},
name='administration_login'),
]
tests.py:
class AdministrationViewTestCase(TestCase):
def setUp(self):
self.client = Client()
self.credentials = {'username': 'user', 'password': 'password'}
self.user = User.objects.create_user(self.credentials, is_staff=True)
self.data = dict(
self.credentials,
next=reverse("administration:dashboard")
)
def test_administration_authorization(self):
self.assertTrue(self.user)
# logged_in = self.client.login(**self.credentials)
# self.assertTrue(logged_in)
response = self.client.post(
reverse("administration:administration_login"),
self.data,
follow=True
)
# self.assertEqual(response.status_code, 302)
self.assertRedirects(
response,
reverse("administration:dashboard"),
status_code=302,
target_status_code=200
)
ERROR:
Traceback (most recent call last):
File "/home/nurzhan/CA/administration/tests.py", line 51, in test_administration_authorization
reverse("administration:dashboard"),
File "/srv/envs/py27/lib/python2.7/site-packages/django/test/testcases.py", line 271, in assertRedirects
% (response.status_code, status_code)
AssertionError: Response didn't redirect as expected: Response code was 200 (expected 302)
forms.py:
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext_lazy as _
class AdministrationAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the administration application.
"""
error_messages = {
'invalid_login': (
_("ERROR MESSAGE.")
),
}
required_css_class = 'required'
def confirm_login_allowed(self, user):
if not user.is_active or not user.is_staff:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={
'username': self.username_field.verbose_name
}
)
login.html:
<form action="{% url 'administration:administration_login' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
<input type="hidden" name="next" value="{{ next }}"/>
</form>
Have you tried removing the follow=True in the POST request. You are checking for a redirection but you are telling the requests modules to follow the redirection, so your response will be directly the page and not the 302 redirection HTTP response.
To be more explicit. You are sending a request with requests.post(follow=True), that will follow the 302 redirection to the destination page and your response will be a HTTP 200 with the destination page. Even if the destination page is the one you want, the test assertion will fail because is looking for a HTTP 302 code in your response and you've already followed the redirection.
Django isn't checking next in extra_context but in GET and POST params.
extra_context is used to update your template context. So if you want to pass the values for variables to your template you can set those with extra_context.
However, you can fix your test by either setting LOGIN_REDIRECT_URL in settings.py or passing next as a POST or GET param.
params = dict(**self.credentials, next=reverse("home:index"))
response = self.client.post(
url,
params,
follow=True
)
NOTE:
self.credentials should be unpacked in the dict being applied to self.data.
self.data = dict(
**self.credentials,
next=reverse("administration:dashboard")
)
If you wanted to test without making this change, I can recommend another way to test this.
You need to render your template in a browser and fill out the login form. This way your browser client will make the POST request with next passed as a parameter.
You can get this done with a headless browser using selenium webdriver.
i'm always using the standard Flask-command render_template(), well, to render my templates. The whole template is just static, but there is one form, which is changing after user's input and one image-slider. Now the issue: When the user submits his/her input the wohle template gets re-rendered.
Is it possible that i just update the form-data and leave the rest of the page untouched?
My template looks like that:
<html>
<head>
</head>
<body>
<form action="/" method ="POST" id="Form">
{{msform.ms_1}} is to {{msform.ms_2}} like {{msform.ms_3}} is to {{msform.ms_submit}}
</form>
</body>
</html>
My views.py follows:
#app.route('/', methods=(['GET','POST'])
def index():
if request.method == 'POST':
msform = msForm(request.form, prefix='ms')
msform.ms_submit.label.text= msform.ms_3.data + msform.ms_2.data
return render_template(template, msform=msform)
return render_template(template, msform=msform)
Where should i place the first answer? Thanks, FFoDWindow
If you want the form to send a request with the data without reloading the whole page, then the solution is to send the form data using a XHR request (AJAX request) using JavaScript.
There's no way for the server to refresh just a portion of a template after it's been sent to the browser. HTTP is stateless, so after your browser makes the submit request, the server replies with the template you render, and that's it.
If you need to "replace" or "re-render" just a portion of the page without doing a full page reload, you need JavaScript and and XHR request.
You would need to create a Flask endpoint to receive the POSTed data.
Example using jQuery:
$("#your-form").submit(function(e) {
e.preventDefault(); // Prevent the normal form submit
$.ajax({
type: "POST",
url: '/your-api',
data: $("#your-form").serialize(),
success: function(data) {
// Do something here. For example, the server can return JSON here and you use Js to create the template client-side, or you server can return an HTML fragment and here you append it to a dom node
}
});
});
Normally, these kind of endpoints returns JSON, but if you want to stick with your server side templates as much as possible, you could return the re-rendered form as a response to the AJAX call, and then append with jQuery that response into the container element, effectively replacing the form.
Let's say you're using Jinja2 as a template engine. Then you can have a partial for the form which you normally include in the main template when you render the full page the first time.
You then can render just the same partial when you respond to the AJAX request on submit.
Hope this is helpful.
I'm quite new to using Django and I am trying to develop a website where the user is able to upload a number of excel files, these files are then stored in a media folder Webproject/project/media.
def upload(request):
if request.POST:
form = FileForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return render_to_response('project/upload_successful.html')
else:
form = FileForm()
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('project/create.html', args)
The document is then displayed in a list along with any other document they have uploaded, which you can click into and it will displays basic info about them and the name of the excelfile they have uploaded. From here I want to be able to download the same excel file again using the link:
Download Document
My urls are
urlpatterns = [
url(r'^$', ListView.as_view(queryset=Post.objects.all().order_by("-date")[:25],
template_name="project/project.html")),
url(r'^(?P<pk>\d+)$', DetailView.as_view(model=Post, template_name="project/post.html")),
url(r'^upload/$', upload),
url(r'^download/(?P<path>.*)$', serve, {'document root': settings.MEDIA_ROOT}),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
but I get the error, serve() got an unexpected keyword argument 'document root'. can anyone explain how to fix this?
OR
Explain how I can get the uploaded files to to be selected and served using
def download(request):
file_name = #get the filename of desired excel file
path_to_file = #get the path of desired excel file
response = HttpResponse(mimetype='application/force-download')
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(path_to_file)
return response
You missed underscore in argument document_root. But it's bad idea to use serve in production. Use something like this instead:
import os
from django.conf import settings
from django.http import HttpResponse, Http404
def download(request, path):
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
raise Http404
You can add "download" attribute inside your tag to download files.
<a href="/project/download" download> Download Document </a>
https://www.w3schools.com/tags/att_a_download.asp
Reference:
In view.py Implement function like,
def download(request, id):
obj = your_model_name.objects.get(id=id)
filename = obj.model_attribute_name.path
response = FileResponse(open(filename, 'rb'))
return response
When you upload a file using FileField, the file will have a URL that you can use to point to the file and use HTML download attribute to download that file you can simply do this.
models.py
The model.py looks like this
class CsvFile(models.Model):
csv_file = models.FileField(upload_to='documents')
views.py
#csv upload
class CsvUploadView(generic.CreateView):
model = CsvFile
fields = ['csv_file']
template_name = 'upload.html'
#csv download
class CsvDownloadView(generic.ListView):
model = CsvFile
fields = ['csv_file']
template_name = 'download.html'
Then in your templates.
#Upload template
upload.html
<div class="container">
<form action="#" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button class="btn btn-primary btn-sm" type="submit">Upload</button>
</form>
#download template
download.html
{% for document in object_list %}
Download
{% endfor %}
I did not use forms, just rendered model but either way, FileField is there and it will work the same.
I've found Django's FileField to be really helpful for letting users upload and download files. The Django documentation has a section on managing files. You can store some information about the file in a table, along with a FileField that points to the file itself. Then you can list the available files by searching the table.
#Biswadp's solution worked greatly for me
In your static folder, make sure to have the desired files you would like the user to download
In your HTML template, your code should look like this :
Download
Using the below approach makes everything less secure since any user can access any user's file.
<a href="/project/download" download> Download Document </a>
Using the below approach makes no sense since Django only handles one requests at the time (unless you are using gunicorn or something else), and believe me, the below approach takes a lot of time to complete.
def download(request, path):
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
raise Http404
So what is the optimum solution?
Use Nginx authenticated routes. When requesting a file from Nginx you can make a request to a route and depending on the HTTP response Nginx allows to denies that request. This makes it very secure and also scalable and performant.
You can ready about more here
import mimetypes
from django.http import HttpResponse, Http404
mime_type, _ = mimetypes.guess_type(json_file_path)
if os.path.exists(json_file_path):
with open(json_file_path, 'r') as fh:
response = HttpResponse(fh, content_type=mime_type)
response['Content-Disposition'] = "attachment; filename=%s" % 'config.json'
return response
raise Http404
<a href='/your-download-view/' download>Download</a>
In your view:
from django.http import FileResponse
def download(request):
# pre-processing, authorizations, etc.
# ...
return FileResponse(open(path_to_file, 'rb'), as_attachment=True)
Simple using html like this downloads the file mentioned using static keyword
Download CV
1.settings.py:
MEDIA_DIR = os.path.join(BASE_DIR,'media')
#Media
MEDIA_ROOT = MEDIA_DIR
MEDIA_URL = '/media/'
2.urls.py:
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
3.in template:
<a href="{{ file.url }}" download>Download File.</a>
Work and test in django >=3
for more detail use this link:
https://youtu.be/MpDZ34mEJ5Y
If the file is a FileField in the model, this is the way I do it:
try:
download_file = PrintingFile.objects.get(pk=kwargs.get('pk_file', 0))
return FileResponse(download_file.file.open(), as_attachment=True)
except PrintingFile.DoesNotExist:
raise Http404
More here
I use this method:
{% if quote.myfile %}
<div class="">
<a role="button"
href="{{ quote.myfile.url }}"
download="{{ quote.myfile.url }}"
class="btn btn-light text-dark ml-0">
Download attachment
</a>
</div>
{% endif %}
If you hafe upload your file in media than:
media
example-input-file.txt
views.py
def download_csv(request):
file_path = os.path.join(settings.MEDIA_ROOT, 'example-input-file.txt')
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
urls.py
path('download_csv/', views.download_csv, name='download_csv'),
download.html
a href="{% url 'download_csv' %}" download=""
I'm trying to upload the image file to the Media url specified in my setting.py and
store the path of an image in the database table.
However, I could not achieve this when using Ajax for uploading the image file..
template.html
<div class="profpic" style="background:url(../../../static/app/img/test.png);background-size:cover">
<input type="file" id="picpath" name="picpath" class="uploadpic" value=""/>
</div>
Ajax :
function saveprof() {
$.ajax({
type: "POST",
url: "saveprof",
enctype: 'multipart/form-data',
async: true,
data: {
'picpath_Aj': $('#picpath').val(),
'profemail_Aj': $('#profemail').val(),
'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken]").val()
},
success: function (data, textStatus, jqXHR) {
$('#message').html(data);
}
});
}
Views.py
def saveprof(request):
if request.method == "POST":
picpathV = request.POST['picpath_Aj']
else:
profemailV = ''
response_data = 'Nothing to update!'
return HttpResponse(response_data, content_type="text/plain")
response_data = 'Empty'
try:
res=td_table.objects.filter(id=request.session.get('proid')).update(picpath=picpathV)
except:
response_data = 'Something went wrong!'
return HttpResponse(response_data, content_type="text/plain")
Above code is working fine, but I could save only the file path like ('C:\fakepath\file.jpg').. and file is not getting saved to the media
path provided in the Settings.py.
I could upload the file when I use request.FILES in the view, when used Django form.. but in my case, I need to get this done using Ajax function only.
What could be the wrong in the view code ?
Here is my models.py
class td_Student(models.Model):
firstname = models.CharField(max_length=300,blank=True)
picpath=models.FileField(upload_to=unique_filename)
def unique_filename(instance, filename):
ext = filename.split('.')[-1]
filename = "%s_%s.%s" %(uuid.uuid4(),time.strftime("%Y%m%d_%H%M%S"), ext)
return os.path.join('documents/documents/'+time.strftime("%Y%m%d"), filename)
As per above logic, file name should be like 'documents/documents/20150716/a1f81a80-ce6f-446b-9b49-716b5c67a46e_20150716_222614.jpg' - This value should be stored in my database table.
settings.py
MEDIA_ROOT = 'C:/DJ/'
MEDIA_URL = '/DJ/'
The problem is not with Django, but with your AJAX post, you are just passing the name, hence Django receives and saves the name.
Solution: Once the user selects a file, change event will be emitteed, on this change evet you will have to grab the file instance using event.target.files store it in a local variable and pass it to picpath_Aj'.
// Add events
$('input[type=file]').on('change', prepareUpload);
// Grab the files and set them to our variable
function prepareUpload(event)
{
files = event.target.files;
}
Detailed guide is here http://abandon.ie/notebook/simple-file-uploads-using-jquery-ajax
And alternative Django solution with JS and backend code is https://github.com/skoczen/django-ajax-uploader