Is possible to write XSS vulnerable flask web app? - security

I'm trying to write a stored xss vulnerable flask web app. My app receive input via this input field <input type="text" name="content" id="content" /> and then show User input into an HTML table.
I tried to insert a script like <script>alert(1)</script> into the input field but when it is shown, the script isn't triggered.
Here is my code:
app.py
from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(200), nullable=False)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return '<Task %r>' % self.id
#app.route('/', methods = ['POST', 'GET'])
def index():
if request.method == 'POST':
task_content = request.form['content']
new_task = Todo(content=task_content)
try:
db.session.add(new_task)
db.session.commit()
return redirect('/')
except:
return "There was an issue adding your task"
else:
tasks = Todo.query.order_by(Todo.date_created).all()
return render_template("index.html", tasks=tasks)
if __name__ == "__main__":
app.run(debug=True)
index.html
{% extends 'base.html' %}
{% block head %}
<title>Task Master</title>
{% endblock %}
{% block body %}
<div class="content">
<h1 style="text-align: center">Task Master</h1>
{% if tasks|length < 1 %}
<h4 style="text-align: center">There are no tasks. Create one below!</h4>
{% else %}
<table>
<tr>
<th>Task</th>
<th>Added</th>
<th>Actions</th>
</tr>
{% for task in tasks %}
<tr>
<td>{{ task.content }}</td>
<td>{{ task.date_created.date() }}</td>
<td>
Delete
<br />
Update
</td>
</tr>
{% endfor %}
</table>
{% endif %}
<div class="form">
<form action="/" method="POST">
<input type="text" name="content" id="content" />
<input type="submit" value="Add Task" />
</form>
</div>
</div>
{% endblock %}
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" >
<meta http-equiv="X-UA-Compatible" content="IE=edge" >
<meta name="viewport" content="width=device-width, initial-scale=1.0" >
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/main.css') }}"
>
{% block head %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
I follow this example.
This is a screen of the form:(https://i.stack.imgur.com/870tY.png)
and this is a screen of the HTML after the script submission:(https://i.stack.imgur.com/U1Jdd.png)
How do I make the script executable when the page is loaded?

Flask uses Jinja2 template engine and Flask enables automatic escaping on Jinja2 by default.
If you really want to allow XSS, change {{ task.content }} to {{ task.content|safe }} on your template.
More information: https://flask.palletsprojects.com/en/1.1.x/templating/#controlling-autoescaping

Related

Error at /basic_app/ Invalid block tag on line 10: 'endblock', expected 'empty' or 'endfor'. Did you forget to register or load this tag?

I am a beginner in HTML templates and Django.
basic_app_base.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Base</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-default navbar-static-top">
<ul class="nav navbar-nav">
<li><a class="navbar-brand" href="{% url 'basic_app:list' %}">School</a></li>
<li><a class="navbar-link" href="{% url 'admin:index' %}"></a></li>
<li></li>
</ul>
</nav>
<div class="container">
{% block body_block %}
{% endblock %}
</div>
</body>
</html>
school_list.html
{% extends "basic_app/basic_app_base.html" %}
<!-- {% load static %} -->
{% block body_block %}
<h1>Here are the list of all the schools!</h1>
<ol>
{% for school in schools %}
<h2><li>{{school.name}}</li></h2>
{% endfor % }
</ol>
{% endblock %}
**Error:**TemplateSyntaxError at /basic_app/
Invalid block tag on line 10: 'endblock', expected 'empty' or 'endfor'. Did you forget to register or load this tag?
Views.py
from django.shortcuts import render
from django.views.generic import (View,TemplateView,
ListView,DetailView)
from . import models
# from django.http import HttpResponse
# Template views with CBV
class IndexView(TemplateView):
template_name='index.html'
# List View
class SchoolListView(ListView):
context_object_name='schools'
model=models.School
template_name='basic_app/school_list.html'
# Detail View
class SchoolDetailView(DetailView):
context_object_name='school_detail'
model=models.School
template_name='basic_app/school_detail.html'
models.py
from django.db import models
from django.urls import reverse
# Create your models here.
class School(models.Model):
name=models.CharField(max_length=256)
principal=models.CharField(max_length=256)
location=models.CharField(max_length=256)
def __str__(self):
return self.name
class Student(models.Model):
name=models.CharField(max_length=256)
age=models.PositiveIntegerField()
school=models.ForeignKey(School,related_name='students',on_delete=models.CASCADE)
def __str__(self):
return self.name
urls.py
from django.urls import include, re_path
# from django.conf.urls import url
from basic_app import views
app_name='basic_app'
urlpatterns = [
re_path(r'^$',views.SchoolListView.as_view(),name='list'),
re_path(r'^(?P<pk>\d+)/$',views.SchoolListView.as_view(),name='detail')
]
I need output like the following image, when clicking on school page :
I found the error, and I was giving too much space after the % when closing the endfor tag.
error on school_list.html file
in line 8 :{% endfor % }
too much space after the % when closing the endfor tag.
solution: {% endfor %}
"After correcting this error on the line 8, it worked for me."
Need remove extra space afrer % in {% endfor % }
after correction
{% extends "basic_app/basic_app_base.html" %}
<!-- {% load static %} -->
{% block body_block %}
<h1>Here are the list of all the schools!</h1>
<ol>
{% for school in schools %}
<h2><li>{{school.name}}</li></h2>
{% endfor %} <---------------------- in this line need correction, put {% endfor %} insted of {% endfor % }
</ol>
{% endblock %}

How to get the table details based on post request django

**views.py**
[if request.method == "POST":
from_date = request.POST.get("from_date")
print(from_date)
to_date = request.POST.get("to_date")
print(to_date)
get_date_from_dates = Scrapper.objects.all().filter(created_at=from_date, updated_at=to_date)
print(len(get_date_from_dates))
page = request.GET.get('page', 1)
paginator = Paginator(get_date_from_dates, 5)
global users
try:
users = paginator.page(page)
except PageNotAnInteger:
users = paginator.page(1)
except EmptyPage:
users = paginator.page(paginator.num_pages)
else:
user_list = Scrapper.objects.all()
page = request.GET.get('page', 1)
paginator = Paginator(user_list, 5)
try:
users = paginator.page(page)
except PageNotAnInteger:
users = paginator.page(1)
except EmptyPage:
users = paginator.page(paginator.num_pages)
return render(request, "home.html", { 'users': users })
return render(request, "home.html", {'users': users})][1]
**home.html**
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<body>
<style>
h2 {text-align: center;}
</style>
<h1>Facilgo Completed Jobs</h1>
<form action="" method="post">
{% csrf_token %}
<label for="from_date">From Date:</label>
<input type="date" id="from_date" name="from_date">
<label for="to_date">To Date:</label>
<input type="date" id="to_date" name="to_date">
<input type="submit"><br>
</form>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Summary Details</h2>
<table id="bootstrapdatatable" class="table table-striped table-bordered" width="100%">
<thead>
<tr>
<th>user_registration_source_id</th>
<th>user_type</th>
<th>user_id</th>
<th>source</th>
<th>source_url</th>
<th>created at</th>
<th>updated at</th>
</tr>
</thead>
<tbody>
{% for stud in users %}
{% csrf_token %}
<tr>
<td>{{stud.user_registration_source_id}}</td>
<td>{{stud.user_type}}</td>
<td>{{stud.user_id}}</td>
<td>{{stud.source}}</td>
<td>{{stud.source_url}}</td>
<td>{{stud.created_at}}</td>
<td>{{stud.updated_at}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if users.has_other_pages %}
<ul class="pagination">
{% if users.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% if user.number|add:'-4' > 1 %}
<li>…</li>
{% endif %}
{% for i in users.paginator.page_range %}
{% if users.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% elif i > users.number|add:'-5' and i < users.number|add:'5' %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if users.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
{% endif %}
</div>
</div>
</div>
</body>
</html>
Webpage
When I post my from date and to date it would check the "created at" and "updated at" field in table and able to view only the datas of particular date. I have written the post request and get the from date and to date from the form. Is there any solution after posting data only get the values of particular dates in table. But at the opening the webpage it should show all datas in table
Output:
Output Image
I think its better approach to use a certain time period if not chosen by the user. for example one month range.
from datetime import date as dt
from dateutil.relativedelta import relativedelta
if request.method == "POST":
if "from_date" and "to_date" in request.POST:
from_date = request.POST.get("from_date")
to_date = request.POST.get("to_date")
else:
from_date = dt.today()
to_date = today + relativedelta(months=+1)
and then filter with this dates.

Django NoReverseMatch Error on Update and Delete Buttons

Thank you for reading my question, and for your help.
I wrote a simple CRUD app, and used django-tables2 module to make my tables look pretty and more robust. I am using django 3.2, python 3.8.5, django_tables2 2.3.4.
I can enter a query in the search bar on the home.html page and lists the returned results from a postgresql on the search_results.html page. On the search_results page, I have buttons next to each returned row with edit and delete options, when I hover over update buttons it points to url for localhost:8955/update/7887 or localhost:8955/delete/7887 for the delete button, of course the last four digits are unique to the returned rows, however when I click on the update or delete button I get a NoReverseMatch error. I am at my wits end what is causing the edit and delete buttons not to work, your help and assistance is very much appreciated it.
Image with the returned results with the update and delete button
tables.py
from .models import EsgDatabase
from django.urls import reverse_lazy
from django.contrib.auth.models import User as user
class EsgListViewTable(tables.Table):
class Meta:
model = EsgDatabase
template_name = "django_tables2/bootstrap-responsive.html"
fields = ('id','role', 'hq','system','market','state','zone','arrisseachangezone','xgsystem','xgzonecode','fto','xocnoc','snowticketassignment',)
if user.is_authenticated:
edit = TemplateColumn(template_name='update.html')
delete = TemplateColumn(template_name='delete.html')
app urls.py
from django.urls import path, include
from .views import HomePageView, SearchResultsView, EsgCreateView, EsgUpdateView, EsgDeleteView, EsgDetailView
urlpatterns = [
path('search/', SearchResultsView.as_view(),name='search_results',),
path('', HomePageView.as_view(), name='home'),
path('createform/', EsgCreateView.as_view(), name='createform'),
path('update/<int:pk>', EsgUpdateView.as_view(), name='update'),
path('delete/<int:pk>', EsgDeleteView.as_view(), name='delete' ),
path('details/<int:pk>', EsgDetailView.as_view(), name='details'),
]
views.py
from .models import EsgDatabase
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.shortcuts import render
from django.views.generic import TemplateView, ListView, CreateView, UpdateView, DeleteView, DetailView
from django_tables2 import SingleTableView
from esgsheet.tables import EsgListViewTable
class EsgDetailView(DetailView):
template_name = 'details.html'
model = EsgDatabase
table_class = EsgListViewTable
context_object_name = 'esgdetail'
def get_success_url(self):
return reverse_lazy('details',kwargs={'pk':self.object.id})
class HomePageView(TemplateView):
template_name = 'home.html'
context_object_name = 'esghome'
#method_decorator(login_required, name='dispatch')
class EsgDeleteView(DeleteView):
template_name = 'delete.html'
model = EsgDatabase
table_class = EsgListViewTable
# success_url = reverse_lazy('home')
context_object_name = 'deleteview'
def get_success_url(self):
return reverse_lazy('home',kwargs={'pk':self.object.id})
#method_decorator(login_required, name='dispatch')
class EsgUpdateView(UpdateView):
model = EsgDatabase
fields = '__all__'
table_class = EsgListViewTable
template_name = 'update.html'
context_object_name = 'esgupdate'
strong textdef get_success_url(self):
return reverse_lazy('details', kwargs={'pk':self.object.id})
#method_decorator(login_required, name='dispatch')
class EsgCreateView(CreateView):
model = EsgDatabase
fields = '__all__'
template_name = 'forms.html'
def get_success_url(self):
return reverse_lazy('details', kwargs={'pk':self.object.id})
class SearchResultsView(SingleTableView):
model = EsgDatabase
table_class = EsgListViewTable
template_name = 'search_results.html'
SingleTableView.table_pagination = False
def get_queryset(self):
query = self.request.GET.get('q')
if query:
object_list = EsgDatabase.objects.filter(
Q(role__icontains=query) | Q(hq__icontains=query) |
Q(system__icontains=query) | Q(market__icontains=query) |Q(state__icontains=query) |
Q(zone__icontains=query) |Q(arrisseachangezone__icontains=query) |Q(xgsystem__icontains=query) |
Q(xgzonecode__icontains=query) | Q(syscode__icontains=query) |Q(fto__icontains=query) |
Q(xocnoc__icontains=query) |Q(snowticketassignment__icontains=query)
)
else:
object_list = self.model.objects.none()
return object_list
base.html
{% load django_tables2 %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<center>
{% block title %}
{% endblock title %}
</center>
</head>
<body>
<!-- {% url 'home' as home %}
{% if request.path != home %}
<center> <br>
<form action="{% url 'search_results' %}" method="GET">
<input name='q' type="text" placeholder="Search ESG Database">
</form> <br>
</center>
{% endif %} -->
{% block content %}
{% endblock content %}
</body>
</html>
home.html
{% extends 'base.html' %}
{% block title %}
<h1>ESG Database</h1>
{% endblock title %}
{% block content %}
<center>
<form action="{% url 'search_results' %}" method="GET">
<input name='q' type="text" placeholder="Search ESG Database">
</form>
</center>
{% endblock content %}
delete.html
{% block title %}
{% endblock title %}
{% block content %}
<form method="POST">{% csrf_token %}
</form>
{% if user.is_authenticated %}
<a type="submit" class="btn btn-danger btn-sm" href="{% url 'delete' record.id %}" >Delete.html</a>
{% endif %}
{% endblock content %}
update.html
{% extends 'base.html' %}
{% block title %}
{% endblock title %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{form.as_p}}
{% if user.is_authenticated %}
<a type="submit" class="btn btn-info btn-sm" href="{% url 'update' record.id %}" >Update.html</a>
{% endif %}
</form>
{% endblock content %}
search_results.html
{% extends 'base.html' %}
{% load render_table from django_tables2 %}
{% block title %}
<h1>ESG Database Results</h1>
{% endblock title %}
{% block content %}
{% render_table table %}
{% endblock content %}
forms.html
{% extends 'base.html' %}
{% block title %}
<h1>Create ESG Entry</h1>
{% endblock title %}
{% block content %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
{% if user.is_authenticated %}
<input type="submit" value="Submit">
{% endif %}
</form>
{% endblock content %}
details.html
{% extends 'base.html' %}
{% block title %}
<h1>Updated Detail View</h1>
{% endblock title %}
{% block content %}
<table class="table table-hover">
<thead>
<th>Role</th>
<th>HQ</th>
<th>System</th>
<th>Market</th>
<th>State</th>
<th>Zone</th>
<th>Arris/SeaChange Zone</th>
<th>xG System</th>
<th>xG Zone Code</th>
<th>Syscode</th>
<th>FTO</th>
<th>XOC/NOC</th>
<th>SNOW Ticket Assignment</th>
</thead>
<tbody>
<tr>
<td>{{esgdetail.role}}</td>
<td>{{esgdetail.hq}} </td>
<td>{{esgdetail.system}} </td>
<td>{{esgdetail.market}} </td>
<td>{{esgdetail.state}} </td>
<td>{{esgdetail.zone}} </td>
<td>{{esgdetail.arrisseachangezone}} </td>
<td>{{esgdetail.xgsystem}} </td>
<td>{{esgdetail.xgzonecode}} </td>
<td> {{esgdetail.syscode}} </td>
<td> {{esgdetail.fto}} </td>
<td>{{esgdetail.xocnoc}} </td>
<td> {{esgdetail.snowticketassignment}} </td>
<!-- <td>
edit
delete
</td> -->
</tr>
</tbody>
</table>
{% endblock content %}
Thanks to #funkybob at #django irc room. First I had to delete comments from my code because it was being parsed throwing NoReverseMatch errors. Second, I had to remove context_object_name from update and delete views.

How to upload bulk images to server with their context using ajax in django

I have been trying add bulk images functionality to the asset uploader.
Here is the model of the images
class ProjectImage(models.Model):
project = models.ForeignKey(Project, on_delete = models.CASCADE)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE, default = None, related_name="create_asset")
alt = models.CharField(max_length=255, default=uuid.uuid4)
title = models.CharField(max_length = 100)
width = models.IntegerField(default=0)
height = models.IntegerField(default=0)
slug = models.SlugField(max_length=200,editable = False, default=uuid.uuid4)
TWODIMENTIONAL = "2D"
THREEDIMENTIONAL = "3D"
DESIGN = "DS"
ASSET_CHOICES = (
(TWODIMENTIONAL, '2D render'),
(THREEDIMENTIONAL, '3D render'),
(DESIGN, 'Designer'),
)
asset_type = models.CharField(default=DESIGN,
max_length=2,
choices = ASSET_CHOICES,
verbose_name='Asset Type',
)
how can I add bulk upload to this model with the asset_type.
upload multiple images with their details like type(check boxes) and many other things.
Thanks for your help.
Have you tried using this article - https://simpleisbetterthancomplex.com/tutorial/2016/11/22/django-multiple-file-upload-using-ajax.html ?
settings.py :
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
urls.py :
from django.conf import settings
from django.conf.urls import url, include
from django.conf.urls.static import static
urlpatterns = [
# ...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
base.html:
{% load static %}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Photos Library - Simple is Better Than Complex{% endblock %}</title>
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
{% include 'includes/header.html' %}
<div class="container">
{% block content %}
{% endblock %}
</div>
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
{% block javascript %}
{% endblock %}
</body>
</html>
models.py:
class Photo(models.Model):
title = models.CharField(max_length=255, blank=True)
file = models.FileField(upload_to='photos/')
uploaded_at = models.DateTimeField(auto_now_add=True)
forms.py:
from .models import Photo
class PhotoForm(forms.ModelForm):
class Meta:
model = Photo
fields = ('file', )
urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^basic-upload/$', views.BasicUploadView.as_view(), name='basic_upload'),
]
views.py:
from django.shortcuts import render
from django.http import JsonResponse
from django.views import View
from .forms import PhotoForm
from .models import Photo
class BasicUploadView(View):
def get(self, request):
photos_list = Photo.objects.all()
return render(self.request, 'photos/basic_upload/index.html', {'photos': photos_list})
def post(self, request):
form = PhotoForm(self.request.POST, self.request.FILES)
if form.is_valid():
photo = form.save()
data = {'is_valid': True, 'name': photo.file.name, 'url': photo.file.url}
else:
data = {'is_valid': False}
return JsonResponse(data)
photos/basic_upload/index.html:
{% block javascript %}
{# JQUERY FILE UPLOAD SCRIPTS #}
<script src="{% static 'js/jquery-file-upload/vendor/jquery.ui.widget.js' %}"></script>
<script src="{% static 'js/jquery-file-upload/jquery.iframe-transport.js' %}"></script>
<script src="{% static 'js/jquery-file-upload/jquery.fileupload.js' %}"></script>
{# PHOTOS PAGE SCRIPTS #}
<script src="{% static 'photos/js/basic-upload.js' %}"></script>
{% endblock %}
photos/basic_upload/index.html:
{# 1. BUTTON TO TRIGGER THE ACTION #}
<button type="button" class="btn btn-primary js-upload-photos">
<span class="glyphicon glyphicon-cloud-upload"></span> Upload photos
</button>
{# 2. FILE INPUT TO BE USED BY THE PLUG-IN #}
<input id="fileupload" type="file" name="file" multiple
style="display: none;"
data-url="{% url 'photos:basic_upload' %}"
data-form-data='{"csrfmiddlewaretoken": "{{ csrf_token }}"}'>
{# 3. TABLE TO DISPLAY THE UPLOADED PHOTOS #}
<table id="gallery" class="table table-bordered">
<thead>
<tr>
<th>Photo</th>
</tr>
</thead>
<tbody>
{% for photo in photos %}
<tr>
<td>{{ photo.file.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
photos/js/basic-upload.js:
$(function () {
/* 1. OPEN THE FILE EXPLORER WINDOW */
$(".js-upload-photos").click(function () {
$("#fileupload").click();
});
/* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
$("#fileupload").fileupload({
dataType: 'json',
done: function (e, data) { /* 3. PROCESS THE RESPONSE FROM THE SERVER */
if (data.result.is_valid) {
$("#gallery tbody").prepend(
"<tr><td><a href='" + data.result.url + "'>" + data.result.name + "</a></td></tr>"
)
}
}
});
});

Passing Flask WTForms validations to Bootstrap Alerts via Flask-Bootstrap

new Flask user here...I am building a pretty file upload button for my flask app. The button essentially makes use of WTForms to validate that only csv and txt files can be uploaded. The upload works, but how can I pass validation errors to the screen as a bootstrap alert? For example:
pressing the upload button will generate an alert "no file selected"
pressing upload with a jpeg will generated "wrong file format"
any suggestions will be appreciated!
My forms.py:
class UploadForm(FlaskForm):
validators = [FileRequired(message='There was no file!'),
FileAllowed(['csv', 'txt'], message='Must be a csv file!')]
input_file = FileField('', validators=validators)
submit = SubmitField(label="Upload")
my route.py:
#app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if request.method == 'POST' and form.validate_on_submit():
input_file = request.files['input_file']
# Do stuff
filename = secure_filename(input_file.filename)
# save file to disk to some folder defined in a separate config file....
data = os.path.join(SOME_UPLOAD_FOLDER, filename)
input_file.save(data)
return redirect(url_for('upload'))
else:
return render_template('upload.html', form=form)
and finally the HTML/CSS/JS:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block html_attribs %} lang="en" charset="utf-8"{% endblock %}
{% block metas %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
{% endblock %}
{% block styles %}
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style>
#browsebutton {
background-color: white;
}
#my-file-selector {
display: none;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="jumbotron">
<h3>File Uploader Example</h3>
<div class="row">
<form class="form-inline center-block" action="/upload" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="input-group">
<label id="browsebutton" class="btn btn-default input-group-addon" for="my-file-selector">
{{ form.input_file(id="my-file-selector") }}
Browse...
</label>
<input type="text" class="form-control" readonly>
</div>
{{ form.submit(class_="btn btn-primary") }}
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<! -- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- pretty upload button -->
<script>
$(function() {
// We can attach the `fileselect` event to all file inputs on the page
$(document).on('change', ':file', function() {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label]);
});
// We can watch for our custom `fileselect` event like this
$(document).ready( function() {
$(':file').on('fileselect', function(event, numFiles, label) {
var input = $(this).parents('.input-group').find(':text'),
log = numFiles > 1 ? numFiles + ' files selected' : label;
if( input.length ) {
input.val(log);
} else {
if( log ) alert(log);
}
});
});
});
</script>
{% endblock %}
If you are using WTForms, I would recommend using macros to render your form fields. It is detailed here, and you can get an example there (you can find some others online to help you customize rather than having to write it from scratch).
Note that in WTForm's documentation, the macro render_field() checks if your field has errors:
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
You can of course decide not to use macros, in that case, you can just use this snippet above directly in your html form with form.input_file.errors instead of field.errors

Resources