Skip to content
Snippets Groups Projects
Commit 66edb46e authored by Felix Chadwick-Smith's avatar Felix Chadwick-Smith
Browse files

Merge branch 'master' into 'main'

Master

See merge request !6
parents f46114b7 130a9582
No related branches found
No related tags found
1 merge request!6Master
Showing
with 8 additions and 819 deletions
...@@ -2,14 +2,11 @@ import os ...@@ -2,14 +2,11 @@ import os
import secrets import secrets
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
app = Flask(__name__, static_folder='static') app = Flask(__name__, static_folder='static')
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SECRET_KEY'] = secrets.token_hex(16) app.config['SECRET_KEY'] = secrets.token_hex(16)
app.config['WTF_CSRF_ENABLED'] = True
db = SQLAlchemy(app) db = SQLAlchemy(app)
...@@ -18,11 +15,6 @@ class Project(db.Model): ...@@ -18,11 +15,6 @@ class Project(db.Model):
title = db.Column(db.String(100), nullable=False) title = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=False) description = db.Column(db.Text, nullable=False)
class AddProjectForm(FlaskForm):
title = StringField('Title')
description = StringField('Description')
submit = SubmitField('Submit')
@app.route('/') @app.route('/')
def home(): def home():
try: try:
...@@ -54,21 +46,23 @@ def portfolio(): ...@@ -54,21 +46,23 @@ def portfolio():
def contact(): def contact():
return render_template('contact.html') return render_template('contact.html')
# Updated route for adding a project with Flask-WTF form # Updated route for adding a project without Flask-WTF form
@app.route('/add_project', methods=['GET', 'POST']) @app.route('/add_project', methods=['GET', 'POST'])
def add_project(): def add_project():
form = AddProjectForm() if request.method == 'POST':
# Retrieve form data directly from request
title = request.form.get('title')
description = request.form.get('description')
if form.validate_on_submit():
# Print or log the form data to check if it's received # Print or log the form data to check if it's received
print(f"Received form data - Title: {form.title.data}, Description: {form.description.data}") print(f"Received form data - Title: {title}, Description: {description}")
new_project = Project(title=form.title.data, description=form.description.data) new_project = Project(title=title, description=description)
db.session.add(new_project) db.session.add(new_project)
db.session.commit() db.session.commit()
return redirect(url_for('home')) return redirect(url_for('home'))
return render_template('add_project.html', form=form) return render_template('add_project.html')
# Updated route for serving the 'my-cv.docx' file # Updated route for serving the 'my-cv.docx' file
@app.route('/download_cv') @app.route('/download_cv')
...@@ -92,5 +86,3 @@ def download_assessment(filename): ...@@ -92,5 +86,3 @@ def download_assessment(filename):
app.logger.exception(f"Error serving assessment file: {str(e)}") app.logger.exception(f"Error serving assessment file: {str(e)}")
abort(500) abort(500)
if __name__ == '__main__':
app.run(debug=True, port=int(os.environ.get('PORT', 5000)))
\ No newline at end of file
pip
Metadata-Version: 2.1
Name: Flask-WTF
Version: 1.2.1
Summary: Form rendering, validation, and CSRF protection for Flask with WTForms.
Project-URL: Documentation, https://flask-wtf.readthedocs.io/
Project-URL: Changes, https://flask-wtf.readthedocs.io/changes/
Project-URL: Source Code, https://github.com/wtforms/flask-wtf/
Project-URL: Issue Tracker, https://github.com/wtforms/flask-wtf/issues/
Project-URL: Chat, https://discord.gg/pallets
Maintainer: WTForms
License: Copyright 2010 WTForms
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License-File: LICENSE.rst
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.8
Requires-Dist: flask
Requires-Dist: itsdangerous
Requires-Dist: wtforms
Provides-Extra: email
Requires-Dist: email-validator; extra == 'email'
Description-Content-Type: text/x-rst
Flask-WTF
=========
Simple integration of Flask and WTForms, including CSRF, file upload,
and reCAPTCHA.
Links
-----
- Documentation: https://flask-wtf.readthedocs.io/
- Changes: https://flask-wtf.readthedocs.io/changes/
- PyPI Releases: https://pypi.org/project/Flask-WTF/
- Source Code: https://github.com/wtforms/flask-wtf/
- Issue Tracker: https://github.com/wtforms/flask-wtf/issues/
- Chat: https://discord.gg/pallets
flask_wtf-1.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
flask_wtf-1.2.1.dist-info/METADATA,sha256=9Y5upDJ7WU2m2l4erWImF3HcVSWIZKH3TdX6klYpq4M,3373
flask_wtf-1.2.1.dist-info/RECORD,,
flask_wtf-1.2.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
flask_wtf-1.2.1.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
flask_wtf-1.2.1.dist-info/licenses/LICENSE.rst,sha256=1fGQNkUVeMs27u8EyZ6_fXyi5w3PBDY2UZvEIOFafGI,1475
flask_wtf/__init__.py,sha256=x6ydw5SJzsXZgz-Y6IM_95Sy8VufRepvZH1DUIlFoTo,214
flask_wtf/__pycache__/__init__.cpython-311.pyc,,
flask_wtf/__pycache__/_compat.cpython-311.pyc,,
flask_wtf/__pycache__/csrf.cpython-311.pyc,,
flask_wtf/__pycache__/file.cpython-311.pyc,,
flask_wtf/__pycache__/form.cpython-311.pyc,,
flask_wtf/__pycache__/i18n.cpython-311.pyc,,
flask_wtf/_compat.py,sha256=N3sqC9yzFWY-3MZ7QazX1sidvkO3d5yy4NR6lkp0s94,248
flask_wtf/csrf.py,sha256=O-fjnWygxxi_FsIU2koua97ZpIhiOJVDHA57dXLpvTA,10171
flask_wtf/file.py,sha256=AsfkYTCgtqGWySimc_NjeAxg-DtpdcthhqMLrXIDAhU,4706
flask_wtf/form.py,sha256=TmR7xCrxin2LHp6thn7fq1OeU8aLB7xsZzvv52nH7Ss,4049
flask_wtf/i18n.py,sha256=TyO8gqt9DocHMSaNhj0KKgxoUrPYs-G1nVW-jns0SOw,1166
flask_wtf/recaptcha/__init__.py,sha256=m4eNGoU3Q0Wnt_wP8VvOlA0mwWuoMtAcK9pYT7sPFp8,106
flask_wtf/recaptcha/__pycache__/__init__.cpython-311.pyc,,
flask_wtf/recaptcha/__pycache__/fields.cpython-311.pyc,,
flask_wtf/recaptcha/__pycache__/validators.cpython-311.pyc,,
flask_wtf/recaptcha/__pycache__/widgets.cpython-311.pyc,,
flask_wtf/recaptcha/fields.py,sha256=M1-RFuUKOsJAzsLm3xaaxuhX2bB9oRqS-HVSN-NpkmI,433
flask_wtf/recaptcha/validators.py,sha256=3sd1mUQT3Y3D_WJeKwecxUGstnhh_QD-A_dEBJfkf6s,2434
flask_wtf/recaptcha/widgets.py,sha256=J_XyxAZt3uB15diIMnkXXGII2dmsWCsVsKV3KQYn4Ns,1512
Wheel-Version: 1.0
Generator: hatchling 1.18.0
Root-Is-Purelib: true
Tag: py3-none-any
Copyright 2010 WTForms
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from .csrf import CSRFProtect
from .form import FlaskForm
from .form import Form
from .recaptcha import Recaptcha
from .recaptcha import RecaptchaField
from .recaptcha import RecaptchaWidget
__version__ = "1.2.1"
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
import warnings
class FlaskWTFDeprecationWarning(DeprecationWarning):
pass
warnings.simplefilter("always", FlaskWTFDeprecationWarning)
warnings.filterwarnings(
"ignore", category=FlaskWTFDeprecationWarning, module="wtforms|flask_wtf"
)
import hashlib
import hmac
import logging
import os
from urllib.parse import urlparse
from flask import Blueprint
from flask import current_app
from flask import g
from flask import request
from flask import session
from itsdangerous import BadData
from itsdangerous import SignatureExpired
from itsdangerous import URLSafeTimedSerializer
from werkzeug.exceptions import BadRequest
from wtforms import ValidationError
from wtforms.csrf.core import CSRF
__all__ = ("generate_csrf", "validate_csrf", "CSRFProtect")
logger = logging.getLogger(__name__)
def generate_csrf(secret_key=None, token_key=None):
"""Generate a CSRF token. The token is cached for a request, so multiple
calls to this function will generate the same token.
During testing, it might be useful to access the signed token in
``g.csrf_token`` and the raw token in ``session['csrf_token']``.
:param secret_key: Used to securely sign the token. Default is
``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``.
:param token_key: Key where token is stored in session for comparison.
Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``.
"""
secret_key = _get_config(
secret_key,
"WTF_CSRF_SECRET_KEY",
current_app.secret_key,
message="A secret key is required to use CSRF.",
)
field_name = _get_config(
token_key,
"WTF_CSRF_FIELD_NAME",
"csrf_token",
message="A field name is required to use CSRF.",
)
if field_name not in g:
s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token")
if field_name not in session:
session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest()
try:
token = s.dumps(session[field_name])
except TypeError:
session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest()
token = s.dumps(session[field_name])
setattr(g, field_name, token)
return g.get(field_name)
def validate_csrf(data, secret_key=None, time_limit=None, token_key=None):
"""Check if the given data is a valid CSRF token. This compares the given
signed token to the one stored in the session.
:param data: The signed CSRF token to be checked.
:param secret_key: Used to securely sign the token. Default is
``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``.
:param time_limit: Number of seconds that the token is valid. Default is
``WTF_CSRF_TIME_LIMIT`` or 3600 seconds (60 minutes).
:param token_key: Key where token is stored in session for comparison.
Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``.
:raises ValidationError: Contains the reason that validation failed.
.. versionchanged:: 0.14
Raises ``ValidationError`` with a specific error message rather than
returning ``True`` or ``False``.
"""
secret_key = _get_config(
secret_key,
"WTF_CSRF_SECRET_KEY",
current_app.secret_key,
message="A secret key is required to use CSRF.",
)
field_name = _get_config(
token_key,
"WTF_CSRF_FIELD_NAME",
"csrf_token",
message="A field name is required to use CSRF.",
)
time_limit = _get_config(time_limit, "WTF_CSRF_TIME_LIMIT", 3600, required=False)
if not data:
raise ValidationError("The CSRF token is missing.")
if field_name not in session:
raise ValidationError("The CSRF session token is missing.")
s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token")
try:
token = s.loads(data, max_age=time_limit)
except SignatureExpired as e:
raise ValidationError("The CSRF token has expired.") from e
except BadData as e:
raise ValidationError("The CSRF token is invalid.") from e
if not hmac.compare_digest(session[field_name], token):
raise ValidationError("The CSRF tokens do not match.")
def _get_config(
value, config_name, default=None, required=True, message="CSRF is not configured."
):
"""Find config value based on provided value, Flask config, and default
value.
:param value: already provided config value
:param config_name: Flask ``config`` key
:param default: default value if not provided or configured
:param required: whether the value must not be ``None``
:param message: error message if required config is not found
:raises KeyError: if required config is not found
"""
if value is None:
value = current_app.config.get(config_name, default)
if required and value is None:
raise RuntimeError(message)
return value
class _FlaskFormCSRF(CSRF):
def setup_form(self, form):
self.meta = form.meta
return super().setup_form(form)
def generate_csrf_token(self, csrf_token_field):
return generate_csrf(
secret_key=self.meta.csrf_secret, token_key=self.meta.csrf_field_name
)
def validate_csrf_token(self, form, field):
if g.get("csrf_valid", False):
# already validated by CSRFProtect
return
try:
validate_csrf(
field.data,
self.meta.csrf_secret,
self.meta.csrf_time_limit,
self.meta.csrf_field_name,
)
except ValidationError as e:
logger.info(e.args[0])
raise
class CSRFProtect:
"""Enable CSRF protection globally for a Flask app.
::
app = Flask(__name__)
csrf = CSRFProtect(app)
Checks the ``csrf_token`` field sent with forms, or the ``X-CSRFToken``
header sent with JavaScript requests. Render the token in templates using
``{{ csrf_token() }}``.
See the :ref:`csrf` documentation.
"""
def __init__(self, app=None):
self._exempt_views = set()
self._exempt_blueprints = set()
if app:
self.init_app(app)
def init_app(self, app):
app.extensions["csrf"] = self
app.config.setdefault("WTF_CSRF_ENABLED", True)
app.config.setdefault("WTF_CSRF_CHECK_DEFAULT", True)
app.config["WTF_CSRF_METHODS"] = set(
app.config.get("WTF_CSRF_METHODS", ["POST", "PUT", "PATCH", "DELETE"])
)
app.config.setdefault("WTF_CSRF_FIELD_NAME", "csrf_token")
app.config.setdefault("WTF_CSRF_HEADERS", ["X-CSRFToken", "X-CSRF-Token"])
app.config.setdefault("WTF_CSRF_TIME_LIMIT", 3600)
app.config.setdefault("WTF_CSRF_SSL_STRICT", True)
app.jinja_env.globals["csrf_token"] = generate_csrf
app.context_processor(lambda: {"csrf_token": generate_csrf})
@app.before_request
def csrf_protect():
if not app.config["WTF_CSRF_ENABLED"]:
return
if not app.config["WTF_CSRF_CHECK_DEFAULT"]:
return
if request.method not in app.config["WTF_CSRF_METHODS"]:
return
if not request.endpoint:
return
if app.blueprints.get(request.blueprint) in self._exempt_blueprints:
return
view = app.view_functions.get(request.endpoint)
dest = f"{view.__module__}.{view.__name__}"
if dest in self._exempt_views:
return
self.protect()
def _get_csrf_token(self):
# find the token in the form data
field_name = current_app.config["WTF_CSRF_FIELD_NAME"]
base_token = request.form.get(field_name)
if base_token:
return base_token
# if the form has a prefix, the name will be {prefix}-csrf_token
for key in request.form:
if key.endswith(field_name):
csrf_token = request.form[key]
if csrf_token:
return csrf_token
# find the token in the headers
for header_name in current_app.config["WTF_CSRF_HEADERS"]:
csrf_token = request.headers.get(header_name)
if csrf_token:
return csrf_token
return None
def protect(self):
if request.method not in current_app.config["WTF_CSRF_METHODS"]:
return
try:
validate_csrf(self._get_csrf_token())
except ValidationError as e:
logger.info(e.args[0])
self._error_response(e.args[0])
if request.is_secure and current_app.config["WTF_CSRF_SSL_STRICT"]:
if not request.referrer:
self._error_response("The referrer header is missing.")
good_referrer = f"https://{request.host}/"
if not same_origin(request.referrer, good_referrer):
self._error_response("The referrer does not match the host.")
g.csrf_valid = True # mark this request as CSRF valid
def exempt(self, view):
"""Mark a view or blueprint to be excluded from CSRF protection.
::
@app.route('/some-view', methods=['POST'])
@csrf.exempt
def some_view():
...
::
bp = Blueprint(...)
csrf.exempt(bp)
"""
if isinstance(view, Blueprint):
self._exempt_blueprints.add(view)
return view
if isinstance(view, str):
view_location = view
else:
view_location = ".".join((view.__module__, view.__name__))
self._exempt_views.add(view_location)
return view
def _error_response(self, reason):
raise CSRFError(reason)
class CSRFError(BadRequest):
"""Raise if the client sends invalid CSRF data with the request.
Generates a 400 Bad Request response with the failure reason by default.
Customize the response by registering a handler with
:meth:`flask.Flask.errorhandler`.
"""
description = "CSRF validation failed."
def same_origin(current_uri, compare_uri):
current = urlparse(current_uri)
compare = urlparse(compare_uri)
return (
current.scheme == compare.scheme
and current.hostname == compare.hostname
and current.port == compare.port
)
from collections import abc
from werkzeug.datastructures import FileStorage
from wtforms import FileField as _FileField
from wtforms import MultipleFileField as _MultipleFileField
from wtforms.validators import DataRequired
from wtforms.validators import StopValidation
from wtforms.validators import ValidationError
class FileField(_FileField):
"""Werkzeug-aware subclass of :class:`wtforms.fields.FileField`."""
def process_formdata(self, valuelist):
valuelist = (x for x in valuelist if isinstance(x, FileStorage) and x)
data = next(valuelist, None)
if data is not None:
self.data = data
else:
self.raw_data = ()
class MultipleFileField(_MultipleFileField):
"""Werkzeug-aware subclass of :class:`wtforms.fields.MultipleFileField`.
.. versionadded:: 1.2.0
"""
def process_formdata(self, valuelist):
valuelist = (x for x in valuelist if isinstance(x, FileStorage) and x)
data = list(valuelist) or None
if data is not None:
self.data = data
else:
self.raw_data = ()
class FileRequired(DataRequired):
"""Validates that the uploaded files(s) is a Werkzeug
:class:`~werkzeug.datastructures.FileStorage` object.
:param message: error message
You can also use the synonym ``file_required``.
"""
def __call__(self, form, field):
field_data = [field.data] if not isinstance(field.data, list) else field.data
if not (
all(isinstance(x, FileStorage) and x for x in field_data) and field_data
):
raise StopValidation(
self.message or field.gettext("This field is required.")
)
file_required = FileRequired
class FileAllowed:
"""Validates that the uploaded file(s) is allowed by a given list of
extensions or a Flask-Uploads :class:`~flaskext.uploads.UploadSet`.
:param upload_set: A list of extensions or an
:class:`~flaskext.uploads.UploadSet`
:param message: error message
You can also use the synonym ``file_allowed``.
"""
def __init__(self, upload_set, message=None):
self.upload_set = upload_set
self.message = message
def __call__(self, form, field):
field_data = [field.data] if not isinstance(field.data, list) else field.data
if not (
all(isinstance(x, FileStorage) and x for x in field_data) and field_data
):
return
filenames = [f.filename.lower() for f in field_data]
for filename in filenames:
if isinstance(self.upload_set, abc.Iterable):
if any(filename.endswith("." + x) for x in self.upload_set):
continue
raise StopValidation(
self.message
or field.gettext(
"File does not have an approved extension: {extensions}"
).format(extensions=", ".join(self.upload_set))
)
if not self.upload_set.file_allowed(field_data, filename):
raise StopValidation(
self.message
or field.gettext("File does not have an approved extension.")
)
file_allowed = FileAllowed
class FileSize:
"""Validates that the uploaded file(s) is within a minimum and maximum
file size (set in bytes).
:param min_size: minimum allowed file size (in bytes). Defaults to 0 bytes.
:param max_size: maximum allowed file size (in bytes).
:param message: error message
You can also use the synonym ``file_size``.
"""
def __init__(self, max_size, min_size=0, message=None):
self.min_size = min_size
self.max_size = max_size
self.message = message
def __call__(self, form, field):
field_data = [field.data] if not isinstance(field.data, list) else field.data
if not (
all(isinstance(x, FileStorage) and x for x in field_data) and field_data
):
return
for f in field_data:
file_size = len(f.read())
f.seek(0) # reset cursor position to beginning of file
if (file_size < self.min_size) or (file_size > self.max_size):
# the file is too small or too big => validation failure
raise ValidationError(
self.message
or field.gettext(
"File must be between {min_size} and {max_size} bytes.".format(
min_size=self.min_size, max_size=self.max_size
)
)
)
file_size = FileSize
from flask import current_app
from flask import request
from flask import session
from markupsafe import Markup
from werkzeug.datastructures import CombinedMultiDict
from werkzeug.datastructures import ImmutableMultiDict
from werkzeug.utils import cached_property
from wtforms import Form
from wtforms.meta import DefaultMeta
from wtforms.widgets import HiddenInput
from .csrf import _FlaskFormCSRF
try:
from .i18n import translations
except ImportError:
translations = None # babel not installed
SUBMIT_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
_Auto = object()
class FlaskForm(Form):
"""Flask-specific subclass of WTForms :class:`~wtforms.form.Form`.
If ``formdata`` is not specified, this will use :attr:`flask.request.form`
and :attr:`flask.request.files`. Explicitly pass ``formdata=None`` to
prevent this.
"""
class Meta(DefaultMeta):
csrf_class = _FlaskFormCSRF
csrf_context = session # not used, provided for custom csrf_class
@cached_property
def csrf(self):
return current_app.config.get("WTF_CSRF_ENABLED", True)
@cached_property
def csrf_secret(self):
return current_app.config.get("WTF_CSRF_SECRET_KEY", current_app.secret_key)
@cached_property
def csrf_field_name(self):
return current_app.config.get("WTF_CSRF_FIELD_NAME", "csrf_token")
@cached_property
def csrf_time_limit(self):
return current_app.config.get("WTF_CSRF_TIME_LIMIT", 3600)
def wrap_formdata(self, form, formdata):
if formdata is _Auto:
if _is_submitted():
if request.files:
return CombinedMultiDict((request.files, request.form))
elif request.form:
return request.form
elif request.is_json:
return ImmutableMultiDict(request.get_json())
return None
return formdata
def get_translations(self, form):
if not current_app.config.get("WTF_I18N_ENABLED", True):
return super().get_translations(form)
return translations
def __init__(self, formdata=_Auto, **kwargs):
super().__init__(formdata=formdata, **kwargs)
def is_submitted(self):
"""Consider the form submitted if there is an active request and
the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
"""
return _is_submitted()
def validate_on_submit(self, extra_validators=None):
"""Call :meth:`validate` only if the form is submitted.
This is a shortcut for ``form.is_submitted() and form.validate()``.
"""
return self.is_submitted() and self.validate(extra_validators=extra_validators)
def hidden_tag(self, *fields):
"""Render the form's hidden fields in one call.
A field is considered hidden if it uses the
:class:`~wtforms.widgets.HiddenInput` widget.
If ``fields`` are given, only render the given fields that
are hidden. If a string is passed, render the field with that
name if it exists.
.. versionchanged:: 0.13
No longer wraps inputs in hidden div.
This is valid HTML 5.
.. versionchanged:: 0.13
Skip passed fields that aren't hidden.
Skip passed names that don't exist.
"""
def hidden_fields(fields):
for f in fields:
if isinstance(f, str):
f = getattr(self, f, None)
if f is None or not isinstance(f.widget, HiddenInput):
continue
yield f
return Markup("\n".join(str(f) for f in hidden_fields(fields or self)))
def _is_submitted():
"""Consider the form submitted if there is an active request and
the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
"""
return bool(request) and request.method in SUBMIT_METHODS
from babel import support
from flask import current_app
from flask import request
from flask_babel import get_locale
from wtforms.i18n import messages_path
__all__ = ("Translations", "translations")
def _get_translations():
"""Returns the correct gettext translations.
Copy from flask-babel with some modifications.
"""
if not request:
return None
# babel should be in extensions for get_locale
if "babel" not in current_app.extensions:
return None
translations = getattr(request, "wtforms_translations", None)
if translations is None:
translations = support.Translations.load(
messages_path(), [get_locale()], domain="wtforms"
)
request.wtforms_translations = translations
return translations
class Translations:
def gettext(self, string):
t = _get_translations()
return string if t is None else t.ugettext(string)
def ngettext(self, singular, plural, n):
t = _get_translations()
if t is None:
return singular if n == 1 else plural
return t.ungettext(singular, plural, n)
translations = Translations()
from .fields import RecaptchaField
from .validators import Recaptcha
from .widgets import RecaptchaWidget
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment