Django: redirect after file download - django

I'm working on a view in my Django 1.5 that allow me to download a file. The download process it's triggered by a button in the HTML page like this:
<input type="button" value="Download!" />
The url point to a view that manage the download:
def filedownload(request, filename):
down_file = File.objects.get(name = filename)
file_path = MEDIA_ROOT+str(down_file.file)
file_name = down_file.filecomplete()
if not Transaction.objects.filter(user = request.user, file = down_file):
transaction = Transaction.objects.create(date = datetime.now(), user = request.user, file = down_file, vote = False)
transaction.save()
fp = open(file_path, 'rb')
response = HttpResponse(fp.read())
fp.close()
type, encoding = mimetypes.guess_type(file_name)
if type is None:
type = 'application/octet-stream'
response['Content-Type'] = type
response['Content-Length'] = str(os.stat(file_path).st_size)
if encoding is not None:
response['Content-Encoding'] = encoding
if u'WebKit' in request.META['HTTP_USER_AGENT']:
filename_header = 'filename=%s' % file_name.encode('utf-8')
elif u'MSIE' in request.META['HTTP_USER_AGENT']:
filename_header = ''
else:
filename_header = 'filename*=UTF-8\'\'%s' % urllib.quote(file_name.encode('utf-8'))
response['Content-Disposition'] = 'attachment; ' + filename_header
return response
What I wanted to do it's to redirect the user to a success page right after they hit the downlad button but I can't find a way to do it.
I'm not concerned about interrupted or otherwise unsuccessful downloads since it's a school project.

He are all steps that you have to follow to run your code :
get the jQuery File Download which allows downloads with OnSuccess and OnFailure callbacks.
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.
$.fileDownload('some/file.pdf')
.done(function () { //redirect });
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.

You could set the href for the input to the download confirmation page you want to display, passing along the file name, then within the template for the confirmation page, set the onload event to redirect to actually do the download.
<body onload=window.location='/file/download/{{ file.name }}/'>

You can do it using ajax request waiting until the download fully successful.
in your view :
$.fileDownload('some/file.pdf')
.done(function () { //redirect
window.location = '/link';
})
.fail(function () { alert('File download failed!'); });

How to use the previous code:
first add a name or id or class to your link
download link
next: here i use id to identify the link #a_d*
<script type="text/javascript">
$(document).on("click", "#a_d", function () {
$.fileDownload(.done(function () { //redirect
window.location = '/link';})
});
});
</script>
done !!

Related

Django acting weird on Windows server IIS deployment

I have the following view which allows me to save the information of a multi step application.
def saveNewApplication(request, *args, **kwargs):
educationList = [ val for val in pickle.loads(bytes.fromhex(request.session['education'])).values() ]
basicInfoDict = pickle.loads(bytes.fromhex(request.session['basic_info']))
documentsDict = pickle.loads(bytes.fromhex(request.session['documents']))
applicant, created = ApplicantInfo.objects.update_or_create(
applicantId=request.session['applicantId'],
defaults={**basicInfoDict}
)
if created:
#saving the diplomas
for education in educationList:
Education.objects.create(applicant=applicant, **education)
with open(f"{documentsDict['cv_url']}/{request.session['file_name']}", 'rb') as f:
Documents.objects.create(
applicant=applicant,
cv =File(f, name=os.path.basename(f.name)),
langue_de_travail = documentsDict['langue_de_travail']
)
#remove the temporary folder
shutil.rmtree(f"{documentsDict['cv_url']}")
else:
educationFilter = Education.objects.filter(applicant=applicant.id)
for idx, edu in enumerate(educationFilter):
Education.objects.filter(pk=edu.pk).update(**educationList[idx])
#updating the documents
document = get_object_or_404(Documents, applicant=applicant.id)
if documentsDict['cv_url']:
with open(f"{documentsDict['cv_url']}/{request.session['file_name']}", 'rb') as f:
document.cv = File(f, name=os.path.basename(f.name))
document.save()
document.langue_de_travail = documentsDict['langue_de_travail']
document.save()
languagesDict = pickle.loads(bytes.fromhex(request.session['languages']))
Languages.objects.update_or_create(applicant=applicant, defaults={**languagesDict})
if 'experiences' in request.session and request.session['experiences']:
experiencesList = [ pickle.loads(bytes.fromhex(val)) for val in request.session['experiences'].values() ]
Experience.objects.filter(applicant=applicant.id).delete()
for experience in experiencesList:
Experience.objects.create(applicant=applicant, **experience)
return JsonResponse({'success': True})
In the development it works perfectly but if deployed I am getting a 404 raise by this line get_object_or_404(Documents, applicant=applicant.id) meaning the creating is false. and I can't figure why is that.
The weirdest thing is if I do comment the entire else block it also returns a 500 error but this time it I do click in the link of the console it show the right response not redirected {success:true}
down below is my javascript fonction for handling the view.
applyBtn.addEventListener("click", () => {
var finalUrl = "/api/applications/save-application/";
fetch(finalUrl)
.then(res => res.json())
.then(data => {
if (data.success) {
window.location.href = '/management/dashboard/';
} else {
alert("something went wrong, Please try later");
}
})
});
I am using postgresql as a database I deleted twice but nothing.
the url file is here.
path("api/applications/save-application/", views.saveNewApplication, name="save-new-application"),
path("api/applications/delete-applicant/<slug:applicantId>/", views.deleteApplicant , name="delete-applicant"),
path('api/edit-personal-info/', editPersonalInfo, name="edit-personal-info"),
Any help or explanation would be highly appreciated. Thanks in advance.

Remove uploaded files with filepond

I really enjoy the filepond library and would like to implement it in my flask app. Since I was not able to find any useful examples online, I started to write my own, small, proof of concept web application. I would like to upload multiple images to the server and save the filenames in the database. Furthermore, I would like to edit an entry and add additional files or remove the existing ones.
So far I figured out how to upload and revert files before the form is submitted. I am also able to load existing files inside the edit form. Just when I click the 'x' button on a loaded image inside the edit form the image is removed from the filepond window and a 'removefile' event is fired, but the file still remains on the server. Is it possible to trigger the revert request on a loaded file or is there a better solution altogether?
x-button does not remove the file from the server
Here are the relevant snippets from my js file:
FilePond.registerPlugin(
FilePondPluginFileValidateSize,
FilePondPluginImagePreview,
FilePondPluginFileRename,
FilePondPluginFileValidateType
);
inputElement = document.querySelector(".filepond");
token = document
.querySelector('input[name="csrf_token"]')
.getAttribute("value");
FilePond.setOptions({
server: {
headers: { "X-CSRF-TOKEN": token },
process: "./process",
revert: "./revert",
load: {
url: "../",
}
},
});
const filepond = FilePond.create(inputElement, {
// Here I pass the files to my edit form in the following format:
//
// files: [
// {
// source: 'static/images/some_name.png',
// options: {
// type: 'local'
// }
// }]
});
The relevant code from .py file:
#app.route("/process", methods=["POST"])
#app.route("/edit/process", methods=["POST"])
def process():
upload_dir = "static/images"
file_names = []
for key in request.files:
file = request.files[key]
picture_fn = file.filename
file_names.append(picture_fn)
picture_path = os.path.join(upload_dir, picture_fn)
try:
file.save(picture_path)
except:
print("save fail: " + picture_path)
return json.dumps({"filename": [f for f in file_names]})
#app.route("/revert", methods=["DELETE"])
#app.route("/edit/revert", methods=["DELETE"])
def revert():
upload_dir = "static/images"
parsed = json.loads(request.data)
picture_fn = parsed["filename"][0]
picture_path = os.path.join(upload_dir, picture_fn)
try:
os.remove(picture_path)
except:
print("delete fail: " + picture_path)
return json.dumps({"filename": picture_fn})
Here is the repository to my full flask-filepond app:
https://github.com/AugeJevgenij/flask_filepond
Please excuse me if the question is unclear, does not make sense or the code is written poorly.
I just started programming a few months ago.
Acording to filepond documentation you can remove a file stored locally on the server like this:
FilePond.setOptions({
server: {
remove: (source, load, error) {
// 'source' is the path of the file and should be sent to a server endpoint via http
// call the load method before ending the function
load()
}
}
})
then on your server where you receive the source (path), use it to delete the file. Keep in mind that this is a risky approach to get your website hacked!

Redirect or refresh page after file download Django

i have some code to make zip and make it downloadable via browser
i try code from some reference, like make it refresh after download using jquery file download
Django: redirect after file download
https://jqueryfiledownload.apphb.com/
views.py
def backup(request):
...
...
if request.method == 'POST':
...
...
zipper = shutil.make_archive(base_name =
os.path.join(settings.MEDIA_ROOT,file_download), format = 'zip', root_dir = backup_dir, base_dir = './' )
shutil.rmtree(backup_dir)
resp = HttpResponse(open(zipper, 'rb').read(), content_type = "application/octet-stream")
resp['Content-Disposition'] = 'attachment; filename=%s.zip' % file_download
resp['Set-Cookie'] = 'fileDownload=true; Path=/'
del_dir = os.getcwd()
os.remove(os.path.join(settings.MEDIA_ROOT,file_download+'.zip'))
formm.save()
return resp
the file can be downloaded
and then i try add js to refresh
backup.html
<input id="file_download" type="submit" class="btn btn-primary btn-block" value="Backup" name="backup"/>
<script type="text/javascript">
$("#file_download").click(function() {
$.fileDownload($(this).prop('type'), {
preparingMessageHtml: "The file download will begin shortly, please wait...",
failMessageHtml: "There was a problem generating your report, please try again."
});
return false; //this is critical to stop the click event which will trigger a normal file download!
});
</script>
after add that js code the results is, pop up an box with "There was a problem generating your report, please try again."
error log
[wsgi:error] [pid 8485:tid 139715727099648] [remote 192.168.137.10:34003] Not Found: /submit
Looking at the source of the fileDownload plugin reveals successCallback and failCallback options. To reload the page after successful download, try something like this:
$("#file_download").click(function() {
$.fileDownload($(this).prop('type'), {
preparingMessageHtml: "The file download will begin shortly, please wait...",
failMessageHtml: "There was a problem generating your report, please try again.",
successCallback: function() { window.location.reload(true) }
});
return false; //this is critical to stop the click event which will trigger a normal file download!
});

Instagram ?__a=1 not working anymore

I've been using Instagram's undocumented API https://www.instagram.com/<user>/?__a=1 to get a public user feed on a website. Since a while now, this is not working anymore, probably because Facebook removed it. Is there an other way to get the data of an instagram account in a easy way?
I built a small server which does that transformation. You'll receive the instagram data as before with ?__a=1 (as JSON ) - have fun 😊
https://www.instapi.io/u/<username>
https://www.instapi.io/u/appwithus
EDIT 12/2020: Unfortunately the service is no longer available
Edit 15/03 NOT WORKING ANYMORE Seems like instagram changed again their API, now it gives a CORS error.
As of 2 february 2021, I have found a solution
Instead of using https://www.instagram.com/username/?__a=1 which it asks for a login.
Justing adding a /channel seems to make it work, like so:
https://www.instagram.com/username/channel/?__a=1
There is a JSON data in https://www.instagram.com/<user>/.
You can use regexp to find what you need.
Sample
// This regexp gets widest possible dict around "profile_pic_url"
// but inside tag <script type="text/javascript">...</script>
let r = new RegExp('<script type="text\/javascript">' +
'([^{]+?({.*profile_pic_url.*})[^}]+?)' +
'<\/script>');
let source = document.documentElement.outerHTML;
let jsonStr = source.match(r)[2];
let data = JSON.parse(jsonStr);
console.log('data', data);
let oldVariantOfData = data['entry_data']['ProfilePage'][0];
console.log('oldVariantOfData', oldVariantOfData);
The same response is attached in the html response of the profile url, I perform this temporal solution (when I can't use the API) in python:
url_recent_media = 'https://www.instagram.com/%s/' % instagram_id
response = urllib2.urlopen(url_recent_media)
insta_html = response.read()
insta_html_split = insta_html.split('"ProfilePage":[')
if len(insta_html_split) > 1:
insta_html_split_2 = insta_html_split[1].split(']},"gatekeepers"')
if len(insta_html_split_2) > 1:
json_dict = json.loads(insta_html_split_2[0])
I hope this help you.
you can try without using instagram API.
import json, urllib2
img_dicts = []
url = 'https://www.instagram.com/{}/'.format(instagram_username)
try:
r = urllib2.urlopen(url, timeout=10.0)
instagram_html = r.read()
instagram_html_data = instagram_html.split('"ProfilePage":[')
if len(instagram_html_data) > 1:
instagram_html_final_data = instagram_html_data[1].split(']},"gatekeepers"')
if len(instagram_html_final_data) > 1:
json_dict = json.loads(instagram_html_final_data[0])
media = json_dict['graphql']['user']['edge_owner_to_timeline_media']['edges']
for obj in media:
img_dicts.append({
'id': obj['node']['id'],
'caption': obj['node']['edge_media_to_caption']['edges'][0]['node']['text'],
'imgurl_standard': obj['node']['display_url'],
'imgurl_lower': obj['node']['thumbnail_resources'][4]['src'],
'imgurl_thumb': obj['node']['thumbnail_resources'][3]['src']
})
img_dicts will give you images in different quality and caption of instagram post.

Django - jquery : populate combobox based on selection of another combobox

I am quite new to Django and jquery stuff. I am trying to populate a comboBox (ChoiceField in Django) based ont the choice selected in another comboBox (without reloading the page).
I can't find any simple example of such a basic application of ajax.
For now I'm call the following ajax function when I select an item from the first dropdown list.
function get_asset_from_type(){
var type_asset = $("#id_type").val();
var data = {type_asset:type_asset};
var args = {type:"POST", url:"/asset/etatType/", data:data};
$.ajax(args);
alert(type_asset);
return false;
};
It alerts the right type but gives a 403 error on the given url. Weird thing is this url works the first time I load the page. I don't understand what's going on..
EDIT:
403 error seems to be gone, remains the initial question :)
I think you're running up against a CSRF problem. As Django by default blocks POST requests that do not have a CSRF Token with a 403. There are a couple ways to deal with this in JS. One is to pull the value out of the cookie, the code to do that can be found here: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
or you can do it by passing the CSRF_TOKEN in with the javascript script tag:
<script src='myjavascript.js?CSRF_TOKEN={{ csrf_token }}'></script>
Note that it's using a double braket, instead of {%%}. This gets the value of the token, instead of the form input.
function getOptionsFromScriptSrc() {
// Get last script tag in parsed DOM.
// Due to the way html pages are parsed,
// the last one is always the one being loaded.
var options = {}
var js_src = $('script').last().attr('src');
if(js_src.match(/\?/)) {
var options_list = js_src.split('?')[1].split('&');
for(var i = 0; i < options_list.length; i++) {
var tmp = options_list[i].split('=');
options[$.trim(tmp[0])] = $.trim(tmp[1]);
}
}
return options;
}
function get_asset_from_type(){
var options = getOptionsFromScriptSrc();
var type_asset = $("#id_type").val();
var data = {type_asset: type_asset, csrfmiddlewaretoken: options['CSRF_TOKEN']};
var args = {type:"POST", url:"/asset/etatType/", data:data};
$.ajax(args);
alert(type_asset);
return false;
};
I haven't, of course, tested this code, but I have used this method before and it works pretty well.
To the main problem of populating a select box, you need to specify a callback for your ajax post, and then deal with the data returned from your server:
function get_asset_from_type(){
var options = getOptionsFromScriptSrc();
var type_asset = $("#id_type").val();
var post_data = {type_asset: type_asset, csrfmiddlewaretoken: options['CSRF_TOKEN']};
$.post('/asset/etatType/', post_data, function(data){
// Assuming server is going to respond with the html of the options, eg: <option value="1">One</option><option value="2">Two</option>...
$('#id_ofmyselectbox').append(data);
});
};