Hi I currently have a flask app that shows files and their details, now I want flask to also allow file downloads from the page of the represented file
I tried a couple things including send_from_directory() which didn't work. So my question is how can I add a working download link to the page?
#app.route('/browser/<path:urlFilePath>')
def browser(urlFilePath):
nestedFilePath = os.path.join(FILE_SYSTEM_ROOT, urlFilePath)
if os.path.isdir(nestedFilePath):
itemList = os.listdir(nestedFilePath)
fileProperties = {"filepath": nestedFilePath}
if not urlFilePath.startswith("/"):
urlFilePath = "/" + urlFilePath
return render_template('browse.html', urlFilePath=urlFilePath, itemList=itemList)
if os.path.isfile(nestedFilePath):
fileProperties = {"filepath": nestedFilePath}
sbuf = os.fstat(os.open(nestedFilePath, os.O_RDONLY)) #Opening the file and getting metadata
fileProperties['type'] = stat.S_IFMT(sbuf.st_mode)
fileProperties['mode'] = stat.S_IMODE(sbuf.st_mode)
fileProperties['mtime'] = sbuf.st_mtime
fileProperties['size'] = sbuf.st_size
if not urlFilePath.startswith("/"):
urlFilePath = "/" + urlFilePath
return render_template('file.html', currentFile=nestedFilePath, fileProperties=fileProperties)
return 'something bad happened'
#app.route('/downloads/<path:filename>', methods=['GET', 'POST'])
def download(filename):
uploads = os.path.join(current_app.root_path, app.config['UPLOAD_FOLDER'])
return send_from_directory(directory=uploads, filename=filename)
And with the following HTML
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Filebrowser{% endblock %}</h1>
{% endblock %}
{% block content %}
<p>Current file: {{ currentFile }}</p>
<p>
<table>
{% for key, value in fileProperties.items() %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
<tr>
File
</tr>
{% endfor %}
</table>
</p>
{% endblock %}
I want the download link on that page to work for the {{ currentFile }} can anyone help me out?
Firstly, the link to the file in your file.html template should probably go outside the for loop -- otherwise, the link will appear multiple times which is probably not what you want.
Secondly, based on your /downloads route, your call to url_for for the download link doesn't match up. It should be:
File
You need to supply the filename as an argument so that the flask server can match it to the route & in url_for, you need to supply the name of the function - which in this case is download instead of downloads.
Lastly, your /browser route prepends the subdirectory to the filename - so when you pass currentFile to the HTML template, it will contain the directory prefix -- which you will want to strip, otherwise your link wouldn't work. The file download would then work since in your /downloads route, you prefix the filename with the directory anyway. Hence, when you render the HTML template, use os.path.basename() to obtain the filename without the directory, i.e.
return render_template('file.html', currentFile=os.path.basename(nestedFilePath), fileProperties=fileProperties)
Related
Over the last few days I have been working on building a Django web application for allowing users to make queries on a MLB Statistics database. I have some of the web app working fine, however I tried to implement a search function and ran into problems with the URL mapping. I tried to move on from this and instead work on getting the database to display information based on the link to the player that they click on, but I am running into the same issue with the URL's being dynamic based on the player's player_id. This portion only uses the People model which has player_id as its primary key.
Currently, I have an 'allPlayers.html' that lists every player in the database along with a link sending the player's player_id to the url as so:
allPlayers.html
{% extends "base_template.html" %}
{% block content %}
<h1>MLB Stats All Players</h1>
<p>Currently, there are : {{ num_players }} players listed in the database.</p>
<table style="width:100%">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
{% for player in players %}
<tr>
<td>{{ player.name_first }}</td>
<td>{{ player.name_last }}</td>
<td>More on {{ player.name_first }} {{ player.name_last }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
And the corresponding view for 'allPlayers.html':
def allplayers(request):
"""View function for allPlayers page of site."""
# Generate counts of people object
num_players = People.objects.count()
players = People.objects.all()
context = {
'num_players': num_players,
'players': players,
}
# Render the HTML template allPlayers.html w/ data in context variable
return render(request, 'allPlayers.html', context=context)
With just the above I have successfully created a page on my web app that lists each player in the database along with a link, and notice that I am trying to send the player_id through <a href='players/{{ player.player_id }}>. Currently the allPlayers portion works fine. However, things go south when I add the following playerInfo.html and corresponding view:
playerInfo.html
{% extends "base_template.html" %}
{% block content %}
{% if results %}
{% for player in results %}
<p>{{ player.name_first }} {{ player.name_last }}</p>
{% endfor %}
{% endif %}
{% endblock %}
And the view:
def player(request, pk=None):
if pk is not None:
print('Hello world')
print('pk :', pk)
#instance = get_object_or_404(People, player_id=pk)
results = People.object.filter(player_id=pk)
context = {
"results": results
}
return render(request, "playerInfo.html", context)
else:
print('Hello')
return render(request, 'playerInfo.html')
My idea was that the link noted earlier containing {{ player.player_id }} would match up with the following url and place the player_id value in for pk, as follows using the <int:pk> syntax instead of regex:
polls/urls.py
urlpatterns = [
path('', views.index, name='index'),
path('allPlayers', views.allplayers, name='allplayers'),
path('allTeams', views.allteams, name='allteams'),
path('search/', views.search, name='search'),
path('player/<int:pk>/', views.player, name='player'),
]
However, once I navigate to my 'allPlayers' page and click on one of the links for the player, say Hank Aaron (who has player_id aaronha01), I get the following Page not found (404) error:
Using the URLconf defined in baseballdb.urls, Django tried these URL patterns, in this order:
polls/ [name='index']
polls/ allPlayers [name='allplayers']
polls/ allTeams [name='allteams']
polls/ search/ [name='search']
polls/ player/<int:pk>/ [name='player']
admin/
The current path, polls/player/aaronha01/, didn't match any of these.
I have been having trouble with this for quite a bit now. If anyone has any advice and can point me in the right direction of how I'm thinking about this incorrectly or has a solution for me that would be seriously greatly appreciated! Thank you.
path('player/<int:pk>/' means only valid int pk would match. If your pk is not int and something like a valid slug - use path('player/<slug:pk>/' instead.
docs: https://docs.djangoproject.com/en/2.1/topics/http/urls/#path-converters
And my suggestion is to use {{ player.get_absolute_url }} or {% url 'player' player.id %} instead of building url manually.
Missing leading slash means "from here", not from website root:
https://webmasters.stackexchange.com/questions/56840/what-is-the-purpose-of-leading-slash-in-html-urls
There is a mismatch between <int:pk> in your path(), which expects an integer, and your player_id, which is a string like 'aaronha01'.
You can either use the pk everywhere, and have urls like /player/17/:
path('player/<int:pk>/', views.player, name='player'),
def player(request, pk):
instance = get_object_or_404(People, pk=pk)
context = {
"instance": instance,
}
return render(request, "playerInfo.html", context)
# Use pk in template when constructing URL
<td>More on {{ player.name_first }} {{ player.name_last }}</td>
Or you can use player_id everywhere, and have urls like /player/aaronha01/.
path('player/<slug:player_id>/', views.player, name='player'),
def player(request, player_id):
instance = get_object_or_404(People, player_id=player_id)
context = {
"instance": instance,
}
return render(request, "playerInfo.html", context)
# Use player_id in template when constructing URL
<td>More on {{ player.name_first }} {{ player.name_last }}</td>
As a next improvement, you can start using the {% url %} tag so that you aren't hardcoding the URLs anymore.
<a href="{% url "polls:player" player.player_id %}">
In the above, I've assumed that you have app_name='polls' in your polls/urls.py, and that you have decided to use player_id instead of pk in your url pattern.
As specified in the title, my concern is about how to pass a variable set in a parent Jinja2 template to its child template.
The configuration of the project is the following:
I'm using Tornado to build an API.
In the app.py script, I associated the endpoint /parent to the
class ParentHandler. When a curl GET method is performed, the get()
method of the class ParentHandler is executed and renders its result,
response (which is a dict) to the template parent.html. I would like to use the rendered HTML code as a header for
the child template, so at the end of parent.html, there is a block to
display the tags from the child template.
In app.py, I associated the endpoint '/child' to the class ChildHanlder. When a curl GET method is performed, the get() method of the class ChildHandler is executed and renders its result, child_content (which is a dict) to the template child.html (I'm not getting ParentHandler's response in ChildHandler, so ChildHandler renders only child_content). The template child.html includes parent.html, so the behavior I'm expecting is child.html to display the HTML code from parent.html (with the values from the dict response provided by ParentHandler, not ChildHandler) and to render its own dict, child_content.
Unfortunately, when I try to perform the described process above, child.html doesn't find response from parent.py.
Here's a code snippet:
app.py
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/parent', ParentHandler),
(r'/child', ChildHandler)
]
jinja_load = Jinja2Loader(os.path.join(PATH, '/templates'))
settings = {
'template_path': os.path.join(PATH, '/templates')
'template_loader': jinja_load
}
tornado.web.Application.__init__(self, handlers, **settings)
parent.py
class ParentHandler(tornado.web.RequestHandler):
def get(self):
response = {"status": "200", "val": "some_value"}
try:
self.render("parent.html", response=response)
except:
self.write(response)
child.py
class ChildHandler(tornado.web.RequestHandler):
def get(self):
response = {"status": "200", "data": "some_data"}
try:
self.render("child.html", child_content=response)
except:
self.write(response)
parent.html
<div>
{% if response['status'] == 200 %}
{% set val1 = response.get('val', 0) %}
<i>{{ val1 }}</i>
{% endif %}
</div>
{% block child_content %}{% endblock %}
child.html
{% include 'parent.html' %}
{% from 'parent.html' import val1 %}
{% block child_content %}
<table>
{% for d in data %}
<tr>
<td>{{ d }}</td>
</tr>
{% endfor %}
{% endblock %}
But I end up with this error when I try to render child.html:
UndefinedError: 'response' is undefined
Can anyone help me please?
You just need to add the with keyword to the include statement, like so:
{% include 'parent.html' with var1=value1, var2=value2, ... %}
In your case
{% include 'parent.html' with response=responseValue %}
I ended up giving up my original idea and decided to go with #Strinnityk 's solution.
I changed the output of child.py and updated it with the output of parent.py.
That way, I don't even need to use variables in the child.html template.
Thanks again!
Hat I'm trying to accomplish is to delete file from server ('static' folder, to be specific).
My jinja template:
<table>
<tr>
{% for file in files_.items %}
<td data-title="title" style="text-align: center">{{ file.title }}</td>
<td data-title="download">Download</td>
{% if current_user.username == "admin" %}
<td data-title="delete" style="text-align: center">Delete</td>
{% endif %}
</tr>
{% endfor %}
</table>
and my function:
#app.route('/upload/<path:filename>/', methods=['GET', 'POST'])
#login_required
def delete(filename):
item = db.session.query(File).get(filename)
os.remove(os.path.join(app.static_folder, item.filename))
db.session.query(File).filter_by(file=filename).delete()
db.session.commit()
return render_template('dashboard.html',delete=delete)
What I'm trying to do is to after clicking on delete in html I want to delete record from database and file from the server. Right now I'm not sure if my approach to call this function is correct, since I've tried to use prints as a primitive log system and there was nothing in the terminal, co I would say function was not called. Also my guess is that I would need to pass filename to it, so Ive tried
{{ delete(filename=file.file) }}
but it returned
UndefinedError: 'delete' is undefined
{{ delete(filename=file.file) }} in template tells python "when rendering template, call function delete()". What you want to do is generate link which, when clicked, will call delete endpoint.
So, use {{ url_for('delete', filename=...) }}
I am working on a Python/ Django project, having not really used either much before. I am currently getting a TemplateSyntaxError when clicking a link on one of the pages on my website.
The URL that this link takes you to (the URL for the broken page) is: costing/id/payment-report/overview & the exception value says:
Invalid block tag on line 87: 'date_to_display', expected 'elif', 'else' or 'endif'. Did you forget to register or load this tag?
The template (HTML file) itself for this page, doesn't actually have this variable anywhere in it. The location of the template in the folder structure is: costing/templates/costing/reports_post_deposit.html, however, there is another template file at costing/pdf2_base.html, which does contain this variable within the structure:
<body>
...
{% block content_payment_schedule %}
{% if not webview %}
<div>
<table>
...
<tr>
...
<td> {% date_to_display %} </td>
</tr>
</table>
</div>
{% endif %}
...
{% endblock %}
...
</body>
So I can see that the variable it's complaining about is used within this template. The page at costing/id/payment-report/overview is used to generate a PDF file from information stored in the database, and information provided by the user. The structure of this PDF file is defined in the costing/pdf2_base.html file- and date_to_display is a a variable whose value I wanted to add to the PDF generated...
What do I need to do to register or load this tag, as the error message says I need to do?
when displaying a variable in django templates you use
{{variable}}
so to fix your issue change
{% date_to_display %}
into
{{date_to_display}}
You can check how to display variables in django template in the following link;
https://docs.djangoproject.com/en/1.10/topics/templates/#variables
(Flask novice alert)
Given the following to upload and save a file in Flask:
#app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return render_template_string('''
{% extends "base.html" %}
{% block content %}
<h4>File uploaded</h4>
<p><a href={{ url_for('members_page') }}>Back</a></p>
{% endblock %}
''')
elif not allowed_file(file.filename):
return render_template_string('''
{% extends "base.html" %}
{% block content %}
<h3>Please try again</h3>
<h4>File must be a .csv</h4>
<p><a href={{ url_for('upload_file') }}>Back</a></p>
{% endblock %}
''')
return render_template_string('''
{% extends "base.html" %}
{% block content %}
<h4>Upload CSV of Company/URL data</h2>
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
{% endblock %}
''')
I wish to make filename available within another function:
#app.route('/scrape', methods=['GET', 'POST'])
#login_required # Use of #login_required decorator
def scrape():
parser = ConfigParser()
parser.read('config.ini')
keywords = parser.get('scrape', 'keywords').replace(' ', '').split(',')
jobs = scraper.scrape(os.path.join(app.config['UPLOAD_FOLDER'], filename), keywords)
The above is the desired intent, where filename is known by the scrape fucnction. Obviously that is not yet the case. With upload_file() already having a return value in the positive case (a confirmation page), how can I make filename available? UPLOAD_FOLDER will contain more than just the uploaded file, so I can't just join this path with whatever is in there.
Where this a non-Flask program, I would probably return locals() and then access the appropriate key, but I imagine that's not possible here if I want to maintain the serving up of the confirmation page.
You need to somehow connect two requests. If many users request the first one, a then someone requests a /scrape, how do you know which one is requesting, and which filename does he belong to?
You can use a session (a cookie session for example, see http://pythonhosted.org/Flask-Session/) to keep track of the uploaded file. Store the filename in the session, and the when the same user (with the same cookie) requests /scrape, you can retrieve the filename from the user session.
You can include the filename to use in the second request. This way, the user himself has to keep track of the files that he uploaded.
In either case, but especially in the latter, it's important to think about ownership of files: which user has access to which file on your system?
Pickle the filename in upload_file(), unpickle it in scrape().
PICKLED_CSV_FILENAME = 'pickled_csv_file_name'
def pickle_filename(filename, pickle_file):
with open(os.path.join(UPLOAD_FOLDER, pickle_file),'wb') as p:
pickle.dump(filename, p)
def load_pickled_filename(pickle_file):
return pickle.load(open(os.path.join(UPLOAD_FOLDER, pickle_file), 'rb'))
in upload_file():
pickle_filename(filename, PICKLED_CSV_FILENAME)
then in scrape():
jobs = scraper.scrape(os.path.join(app.config['UPLOAD_FOLDER'], load_pickled_filename(PICKLED_CSV_FILENAME)), keywords)
pickle_filename(filename, PICKLED_CSV_FILENAME)
Obviously not a sustainable solution in the case of many users/files, but it is a single user, single file scenario so it's acceptable.