I have followed Miguel Grinberg's Flask Mega Tutorial and also his tutorial on uploading files using Flask. I'm trying to combine the two to build my own web app. I have a site that handles user logins and allows files (scans of exam papers) to be uploaded to app/uploads/userID. The file details, including the filename, are stored (successfully) in a SQLite database. I can see the files in File Explorer and VS Code, but I cannot get them to display / download from within the website. I don't want to use the static folder as I want to keep each user's uploads private to them.
Here is the relevant section of routes.py:
#app.route('/user/<username>', methods=['GET', 'POST'])
#login_required
def user(username):
user = User.query.filter_by(username=username).first_or_404()
papers=user.papers.all()
form = PaperForm()
if form.validate_on_submit():
uploaded_file = request.files['file']
filename = secure_filename(uploaded_file.filename)
if filename != '':
file_ext = os.path.splitext(filename)[1]
if file_ext not in app.config['UPLOAD_EXTENSIONS']:
flash('Invalid file type. Only PDF files are accepted.')
abort(400)
myPath = os.path.join(app.config['UPLOAD_PATH'],current_user.get_id())
if os.path.isdir(myPath)==False:
os.mkdir(myPath)
uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'],current_user.get_id(), filename))
paper = Paper(paper_name=form.paper.data, author=current_user, filename = filename)
db.session.add(paper)
db.session.commit()
flash('Your paper has been added to the database.')
return redirect(url_for('user', username=user.username))
return render_template('user.html', user=user, form=form, papers=papers)
#app.route('/uploads/<filename>')
#login_required
def upload(filename):
return send_from_directory(os.path.join(app.config['UPLOAD_PATH'], current_user.get_id()), filename)
And here is the template for user.html, which shows the upload form and beneath it displays a table of all the papers uploaded by that user:
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h2>Add a paper</h2>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
<hr>
<h2>My Papers</h2>
<table class="table">
{% for paper in papers %}
<tr valign="top">
<td>{{ paper.paper_name }}</td>
<td>{{ paper.author.username }}</td>
<td>{{ paper.created_time }}</td>
<td>{{ url_for('upload', filename=paper.filename) }}</td>
<td>{{ paper.filename }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
When clicking on the link to a file I just get a 404 file not found error.
Update:
I found a partial solution elsewhere on Stack Overflow. I'm pretty sure the problem is the path to the files. If I change the upload path to
UPLOAD_PATH = './uploads/' then I can see the files which have been uploaded already. However, when I then try to upload a file it fails with an error that the new directory couldn't be created. If I create a directory parallel to the app folder rather than within it I can upload, but of course downloads then fail! I am using Windows, which I suspect may be part of the problem.
Examscanner/
├─ app/
│ ├─ static/
│ ├─ __pycache__/
│ ├─ templates/
│ ├─ uploads/
│ ├─ __init__
│ ├─ errors.py
│ ├─ forms.py
│ ├─ models.py
│ ├─ routes.py
├─ migrations/
├─ examvenv/
I got this working in the end by changing the config.py file to include:
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
UPLOAD_PATH = os.path.join(APP_ROOT, 'uploads')
The files are stored in the uploads folder alongside the app folder rather than within it. I'm hoping this won't cause other problems further down the line.
Related
I have a RESTful API that is built with flask, and on the home page / I want to build a web GUI into it. In order to do so I'm trying to build a flask app into the Index part of my API calls:
class Index(Resource):
def get(self):
from flask import Response, render_template, Flask
from lib.interface.settings import get_db_folders, get_file_type_count, get_recent_scans, make_keys
log_request(request, "/", "GET")
recent_scans = get_recent_scans(DATABASE_FILES_PATH, amount=10)
all_folders = get_db_folders(DATABASE_FILES_PATH)
file_types = get_file_type_count(DATABASE_FILES_PATH)
website = Flask(__name__)
#website.route("/everything")
def show_all():
return Response(render_template(
"everything.html"
))
#website.route("/upload")
def upload():
return Response(render_template(
"upload.html"
))
#website.route("/api")
def apidocs():
return Response(render_template(
"apidocs.html"
))
return Response(render_template(
"index.html", recent_scans=recent_scans,
all_scans_length=len(all_folders),
file_types=file_types, recent_scan_length=len(recent_scans) + 1
))
Everything works fine when I load into the main screen (/). I can see the index.html page. However when trying to redirect to a page called "everything.html" I get an error (the error depends on how I'm trying it as seen below):
If I use <li><span>All Uploads</span></li> in everything.html the error I get is:
...
<li><span>All Uploads</span></li>
TypeError: url_for() takes exactly 1 argument (2 given)
If I do <li><span>All Uploads</span></li>
I get the following error:
...
raise BuildError(endpoint, values, method, self)
BuildError: Could not build url for endpoint 'everything'. Did you mean 'getstrings' instead?
If I do <li><span>All Uploads</span></li> I get:
...
<li><span>All Uploads</span></li>
TypeError: url_for() takes exactly 1 argument (2 given)
And so on and so forth. I'm pretty sure the problem is the way I'm trying to do this part (putting it into an API, etc) but I'm not sure if there is a work around that might work? As of right now my head.html file looks like this:
<!doctype html>
<head>
<meta charset="UTF-8">
<title>Sandbox - {% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='base.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<script src="{{ url_for('static', filename='base.js') }}"></script>
</head>
<header id="display-port">
<img id="header-image" src="{{ url_for('static', filename='base_image.png') }}"/>
</header>
<body>
<nav class="navigation-pane">
<h3 id="navigation-header">Navigation:</h3>
<ul>
<li><span>All Uploads</span></li>
<li><span>Upload Files</span></li>
<li><span>API Documentation</span></li>
</ul>
</nav>
<section class="content">
<h3>Current File Type Counts:</h3>
<ul>
{% for key in file_types.keys() %}
<li>{{ key.upper() }}: {{ file_types[key] }}</li>
{% endfor %}
</ul>
{% block header %}{% endblock %}
{% block content %}{% endblock %}
</section>
</body>
My index.html file looks like this:
{% extends "head.html" %}
{% block title %}Home{%endblock%}
{% block header %}
<h1>Recent Scans:</h1>
{% endblock %}
{% block content %}
<h5>Total Database Scans: {{ all_scans_length }}</h5>
<ul>
{% for i in range(0, recent_scan_length) %}
<li>Hash: {{ recent_scans[i] }}</li>
{% endfor %}
</ul>
{% endblock %}
Directory structure looks like this:
.
├── __init__.py
├── main.py
├── static
│ ├── base.css
│ ├── base.js
│ ├── favicon.ico
│ └── base_image.png
├── templates
│ ├── apidocs.html
│ ├── everything.html
│ ├── head.html
│ ├── index.html
│ └── upload.html
├── wsgi.py
And my everything.html file has nothing in it. I think it is worth noting that I am able to load from the static folder in head.html without problem. So question being as stated above, what is causing the issue? How can I fix it?
I've written the following filter (in templatetags/custom_errors.py):
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
#register.filter
def custom_errors(bound_field):
return mark_safe(f'<span id="{bound_field.id_for_label}-error" class="error">{", ".join(bound_field.errors)}</span>' if bound_field.errors else '')
which I'm trying to use in a template as follows:
{% load widget_tweaks %}
{% load custom_errors %}
{% load label_with_classes %}
{% csrf_token %}
<div class="row">
<div class="input-field col s12">
{{ form.session_number|add_error_class:"invalid" }}
{{ form.session_number|custom_errors }}
{{ form.session_number|label_with_classes }}
</div>
</div>
However, I'm getting a TemplateSyntaxError: 'custom_errors' is not a registered tag library:
What puzzles me, however, is that some of the tags that are registered, such as label_with_classes, are done so in the same way. Here is a tree view of dashboard/templatetags:
dashboard/templatetags
├── active_page.py
├── custom_errors.py
├── edit_view_family_heading.py
├── google_analytics.py
├── label_with_classes.py
├── order_link.py
├── paginator.py
└── profile_badge.py
showing that label_with_classes.py is in the same directory as custom_errors.py. Here are the contents of label_with_classes.py:
from django import template
from django.forms import ChoiceField, BooleanField, DateField
from titlecase import titlecase
register = template.Library()
def pretty_name(name):
"""Convert 'first_name' to 'First Name'."""
return titlecase(name.replace('_', ' '))
#register.filter(is_safe=True)
def label_with_classes(bound_field, contents=None):
'''Extend Django's label_tag() method to provide the appropriate Materialize CSS classes'''
# The field should have the 'active' class if it has a value (see http://materializecss.com/forms.html),
# except for ChoiceFields (which include ModelChoiceFields and MultipleChoiceFields) and BooleanFields
should_be_active = (bound_field.value() and not isinstance(bound_field.field, (ChoiceField, BooleanField)))\
or isinstance(bound_field.field, DateField)
active = 'active' if should_be_active else ''
invalid = 'invalid' if bound_field.errors else ''
classes = f"{active} {invalid}".strip()
contents = contents or pretty_name(bound_field.name)
return bound_field.label_tag(
attrs={'class': classes},
contents=contents,
label_suffix='')
I don't see any difference in the ways both filters are registered (except that one has is_safe=True and the other doesn't). Why is the custom_errors filter not registering? Is there some kind of caching issue?
The terminal from which I was running python manage.py runserver had somehow become unresponsive and wasn't responding to Cntrl+C. I terminated the process and restarted the development server, and now things work as expected.
I'm trying to get the uploads folder path.
UPLOAD_FOLDER = '/uploads'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
I upload the images in the path:
.
└── uploads
└── img_articles
└── 2017
└── 02
└── image.jpg
I want to use image.jpg in a Jinja Template
{% extends "layout.html" %}
{% block content %}
<div class="container">
<img src="{{image_path}}" alt="">
</div>
{% endblock %}
What should I do?
Have a look at the Uploading Files Pattern in the information for the Pros.
Define a route for your uploads:
from flask import send_from_directory
#app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
In your template you can then use the route to get your filename
<img src="{{ url_for('uploaded_file', filename='yourimage.jpg') }}">
I save uploaded files in a media root called /img/:
MEDIA_ROOT = os.path.join(BASE_DIR, 'img')
MEDIA_URL = '/img/'
And use this template to display every image in that folder:
{% load staticfiles %}
<ul>
{% for post in latest_post %}
<li>{{ post.id }} : {{ post.post_body }} : <img src="{% static "/img/" %}{{ post.post_image }}" alt="{{ post.post_image }}" /> </li>
{% endfor %}
</ul>
And I get the right url:
http://127.0.0.1:8000/img/birthday.jpg
But I get "page not found" error when I open the image. Why is that?
Edit: I just ran manage.py collectstatic but it didn't fix the issue. I still get 404 error.
Create a folder in your base directory by the name static and store all your css, img, js etc files in it in their respective folders, like this:
.
├── css
├── fonts
├── icons
├── img
└── js
Run python manage.py collectstatic, this collects all your static files and saves them in a staticroot folder.
After that change your settings.py to the following:
STATIC_URL = '/static/'
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static')),
STATIC_ROOT = 'staticroot/static'
I have a working model.py like this, I am able to add records to this model using Django admin:
from django.db import models
from django.contrib import admin
from audiofield.fields import AudioField
# Create your models here.
class Music(models.Model):
tittel = models.CharField(max_length=20, default = "titel her")
artist = models.CharField(max_length=20, default = "Artist")
cover = models.FileField("Cover", upload_to="player/cover/")
audio = AudioField(upload_to='player/audio/', blank=True, ext_whitelist=(".mp3",".wav",".ogg"))
is_active = models.BooleanField(("is active"), default = False)
date_created = models.DateField(verbose_name="Created on date", auto_now_add="True")
class MusicAdmin(admin.ModelAdmin):
list_display = ('tittel', 'artist', 'cover', 'audio', 'is_active', 'date_created')
I want to display the records it in my index.html using template tags.
My music-app folder structure:
.
├── admin.py
├── __init__.py
├── models.py
├── templatetags
│ ├── __init__.py
│ ├── musikk_tags.py
├── tests.py
└── views.py
The music_tag.py:
from django import template
from music import models
register = template.Library()
#register.assignment_tag
def get_music_tags():
return models.Music.objects.all()
I am trying to use the template tags like this inside the index.html: (I only want the active records to display)
<ul class="playlist hidden">
{% load musikk_tags %}
{% get_musikk_tags as musikk %}
{% if musikk.isactive %}
<li audiourl="{{ musikk.audio.url }}" cover="{{musikk.cover.url}}" artist="{{musikk.artist}}">{{musikk.tittel}}</li>
{% endif %}
</ul>
I do not get any errors, but still no music is displayed.
When not using the music-app, and hard coding the list like this:
<ul><li audiourl="01.mp3" cover="cover1.jpg" artist="Artist 1">01.mp3</li></ul>
Everything work as expected. There must be some kind of issue with the template tags. thank you in advance!
get_music_tags returns a queryset - a collection of (in this case) all Music objects. The queryset itself doesn't have isactive, audio or cover properties: only the individual items inside the queryset do. You need to iterate through them:
{% get_musikk_tags as all_musikk %}
{% for musikk in all_musikk %}
{% if musikk.isactive %}
<li audiourl="{{ musikk.audio.url }}" cover="{{musikk.cover.url}}" artist="{{musikk.artist}}">{{musikk.tittel}}</li>
{% endif %}
{% endfor %}