pax_global_header 0000666 0000000 0000000 00000000064 14716610423 0014516 g ustar 00root root 0000000 0000000 52 comment=3845ac24196d7a34c3ba9a05289bed73008bce8c
wtforms-alchemy-0.19.0/ 0000775 0000000 0000000 00000000000 14716610423 0014726 5 ustar 00root root 0000000 0000000 wtforms-alchemy-0.19.0/.github/ 0000775 0000000 0000000 00000000000 14716610423 0016266 5 ustar 00root root 0000000 0000000 wtforms-alchemy-0.19.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14716610423 0020323 5 ustar 00root root 0000000 0000000 wtforms-alchemy-0.19.0/.github/workflows/lint.yml 0000664 0000000 0000000 00000000702 14716610423 0022013 0 ustar 00root root 0000000 0000000 name: Lint
on:
- push
- pull_request
jobs:
test:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[test]
- name: Run linting
run: |
ruff check .
ruff format --check
wtforms-alchemy-0.19.0/.github/workflows/test.yaml 0000664 0000000 0000000 00000001377 14716610423 0022176 0 ustar 00root root 0000000 0000000 name: Tests
on: [push, pull_request]
jobs:
test:
name: Python ${{ matrix.python-version }} + SQLAlchemy ${{ matrix.sqlalchemy-version }} + WTForms ${{ matrix.wtforms-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
sqlalchemy-version: ["1.4", "2.0"]
wtforms-version: ["3.1", "3.2"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
run: pip install tox
- name: Run tests
run: tox -e sqlalchemy${{ matrix.sqlalchemy-version }}-wtforms${{ matrix.wtforms-version }}
wtforms-alchemy-0.19.0/.gitignore 0000664 0000000 0000000 00000000403 14716610423 0016713 0 ustar 00root root 0000000 0000000 *.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Built docs
docs/_build/
wtforms-alchemy-0.19.0/CHANGES.rst 0000664 0000000 0000000 00000026003 14716610423 0016531 0 ustar 00root root 0000000 0000000 Changelog
=========
Here you can see the full list of changes between each WTForms-Alchemy release.
0.19.0 (2024-11-18)
^^^^^^^^^^^^^^^^^^^
- Dropped support for Python 3.8 and earlier. The minimum supported Python version is now 3.9.
- Dropped support for SQLAlchemy 1.3 and earlier. The minimum supported SQLAlchemy version is now 1.4.
- Added support for Python 3.10–3.13.
- Added support for SQLAlchemy 2.0.
- Added support for WTForms 3.2. The minimum supported WTForms version is now 3.1.
0.18.0 (2021-12-21)
^^^^^^^^^^^^^^^^^^^
- Dropped WTForms 1.0 support
- Added WTForms 3.0 and SA 1.4 support
- Dropped py35 support
0.17.0 (2020-06-02)
^^^^^^^^^^^^^^^^^^^
- Dropped py27, py33 and py34 support
0.16.9 (2019-03-06)
^^^^^^^^^^^^^^^^^^^
- Added support for JSON type in TypeMap (#142, pull request courtesy of fedExpress)
0.16.8 (2018-12-04)
^^^^^^^^^^^^^^^^^^^
- Fixed QuerySelectField.query allowing no results (#136, pull request courtesy of TrilceAC)
0.16.7 (2018-05-07)
^^^^^^^^^^^^^^^^^^^
- Fixed UnknownTypeException being thrown correctly for unsupported types (#131, pull request courtesy of tvuotila)
0.16.6 (2018-01-21)
^^^^^^^^^^^^^^^^^^^
- Added SQLAlchemy 1.2 support
0.16.5 (2017-07-29)
^^^^^^^^^^^^^^^^^^^
- Fixed GroupedQuerySelectMultipleField validator to support empty data (#123, pull request courtesy of superosku)
0.16.4 (2017-07-29)
^^^^^^^^^^^^^^^^^^^
- Fixed GroupedQuerySelectMultipleField validator (#121, pull request courtesy of superosku)
0.16.3 (2017-06-25)
^^^^^^^^^^^^^^^^^^^
- Fixed ChoiceType conversion for Enums (#112, pull request courtesy of fayazkhan)
0.16.2 (2017-02-28)
^^^^^^^^^^^^^^^^^^^
- Added GroupedQueryMultipleSelectField (#113, pull request courtesy of adarshk7)
0.16.1 (2016-05-11)
^^^^^^^^^^^^^^^^^^^
- Updated SQLAlchemy-Utils requirement to 0.32.6
- Fixed PhoneNumberType conversion (#102)
0.16.0 (2016-04-20)
^^^^^^^^^^^^^^^^^^^
- Dropped python 2.6 support
- Made PhoneNumberField work correctly together with DataRequired (#101, pull request courtesy of jmagnusson)
0.15.0 (2016-01-27)
^^^^^^^^^^^^^^^^^^^
- Moved GroupedQuerySelectField from WTForms-Components package to WTForms-Alchemy
- Moved WeekdaysField from WTForms-Components package to WTForms-Alchemy
- Moved PhoneNumberField from WTForms-Components package to WTForms-Alchemy
- Moved Unique validator from WTForms-Components package to WTForms-Alchemy
0.14.0 (2016-01-23)
^^^^^^^^^^^^^^^^^^^
- Added QuerySelectField and QuerySelectMultipleField which were deprecated from
WTForms as of version 2.1
0.13.3 (2015-06-17)
^^^^^^^^^^^^^^^^^^^
- Removed ClassMap's inheritance sorting. This never really worked properly and resulted in weird undeterministic bugs on Python 3.
0.13.2 (2015-05-21)
^^^^^^^^^^^^^^^^^^^
- Added support for callables in type map argument
0.13.1 (2015-04-19)
^^^^^^^^^^^^^^^^^^^
- Added flake8 checks
- Added isort checks
- Fixed country import caused by SQLAlchemy-Utils 0.30.0
- Update SQLAlchemy-Utils dependency to 0.30.0
0.13.0 (2014-10-14)
^^^^^^^^^^^^^^^^^^^
- Made all default validators configurable in model_form_factory
- Added support for disabling default validators
0.12.9 (2014-08-30)
^^^^^^^^^^^^^^^^^^^
- Added support for composite primary keys in ModelFieldList
0.12.8 (2014-07-28)
^^^^^^^^^^^^^^^^^^^
- Added support for URLType of SQLAlchemy-Utils
0.12.7 (2014-07-21)
^^^^^^^^^^^^^^^^^^^
- Fix ModelFieldList handling of simultaneous deletes and updates
0.12.6 (2014-06-12)
^^^^^^^^^^^^^^^^^^^
- Fix various issues with new-style classes
0.12.5 (2014-05-29)
^^^^^^^^^^^^^^^^^^^
- Added CountryField
- Added CountryType to CountryField conversion
- Fixed various issues with column aliases
0.12.4 (2014-03-26)
^^^^^^^^^^^^^^^^^^^
- Added WeekDaysType to WeekDaysField conversion
0.12.3 (2014-03-24)
^^^^^^^^^^^^^^^^^^^
- Fixed ChoiceType coercion for SelectFields
0.12.2 (2014-02-20)
^^^^^^^^^^^^^^^^^^^
- New configuration option: attr_errors
- Min and max info attributes generate NumberRange validator for Numeric, Float, IntRangeType and NumericRangeType columns
0.12.1 (2014-02-13)
^^^^^^^^^^^^^^^^^^^
- Updated SQLAlchemy-i18n optional dependency to 0.8.2
0.12.0 (2013-12-19)
^^^^^^^^^^^^^^^^^^^
- Added support for SQLAlchemy-Utils range types IntRange, NumericRange, DateRange and DateTimeRange
- Deprecated support for NumberRangeField
- Updated SQLAlchemy-Utils dependency to 0.23.1
- Updated WTForms-Components dependency to 0.9.0
0.11.0 (2013-12-19)
^^^^^^^^^^^^^^^^^^^
- Added configurable default validators
- Fixed ModelFieldList processing
0.10.0 (2013-12-16)
^^^^^^^^^^^^^^^^^^^
- Replaced assign_required configuration option with not_null_validator for more fine grained control of not null validation
- Replaced not_null_str_validator with not_null_validator_type_map
0.9.3 (2013-12-12)
^^^^^^^^^^^^^^^^^^
- Support for hybrid properties that return column properties
- Better exception messages for properties that are not of type ColumnProperty
- Support for class level type map customization
0.9.2 (2013-12-11)
^^^^^^^^^^^^^^^^^^
- Smarter object value inspection for ModelFieldList
- Changed ModelFieldList default population strategy to 'update' instead of 'replace'
0.9.1 (2013-12-03)
^^^^^^^^^^^^^^^^^^
- Fixed property alias handling (issue #46)
0.9.0 (2013-11-30)
^^^^^^^^^^^^^^^^^^
- Initial WTForms 2.0 support
- New configuration options: not_null_validator, not_null_str_validator
0.8.6 (2013-11-18)
^^^^^^^^^^^^^^^^^^
- Form fields now generated in class initialization time rather than on form object initialization
0.8.5 (2013-11-13)
^^^^^^^^^^^^^^^^^^
- Added Numeric type scale to DecimalField places conversion
0.8.4 (2013-11-11)
^^^^^^^^^^^^^^^^^^
- Declaration order of model fields now preserved in generated forms
0.8.3 (2013-10-28)
^^^^^^^^^^^^^^^^^^
- Added Python 2.6 support (supported versions now 2.6, 2.7 and 3.3)
- Enhanced coerce func generator
0.8.2 (2013-10-25)
^^^^^^^^^^^^^^^^^^
- TypeDecorator derived type support SelectField coerce callable generator
0.8.1 (2013-10-24)
^^^^^^^^^^^^^^^^^^
- Added support for SQLAlchemy-Utils ChoiceType
- Updated SQLAlchemy-Utils dependency to 0.18.0
0.8.0 (2013-10-11)
^^^^^^^^^^^^^^^^^^
- Fixed None value handling in string stripping when strip_string_fields option is enabled
- Python 3 support
- ModelFormMeta now configurable
0.7.15 (2013-09-06)
^^^^^^^^^^^^^^^^^^^
- Form generation now understands column aliases
0.7.14 (2013-08-27)
^^^^^^^^^^^^^^^^^^^
- Length validators only assigned to string typed columns
0.7.13 (2013-08-22)
^^^^^^^^^^^^^^^^^^^
- Model column_property methods now skipped in model generation process
0.7.12 (2013-08-18)
^^^^^^^^^^^^^^^^^^^
- Updated SQLAlchemy-Utils dependency to 0.16.7
- Updated SQLAlchemy-i18n dependency to 0.6.3
0.7.11 (2013-08-05)
^^^^^^^^^^^^^^^^^^^
- Added configuration skip_unknown_types to silently skip columns with types WTForms-Alchemy does not understand
0.7.10 (2013-08-01)
^^^^^^^^^^^^^^^^^^^
- DecimalField with scales and choices now generate SelectField as expected
0.7.9 (2013-08-01)
^^^^^^^^^^^^^^^^^^
- TSVectorType columns excluded by default
0.7.8 (2013-07-31)
^^^^^^^^^^^^^^^^^^
- String typed columns now convert to WTForms-Components StringFields instead of WTForms TextFields
0.7.7 (2013-07-31)
^^^^^^^^^^^^^^^^^^
- HTML5 step widget param support added
- Updated WTForms-Components dependency to 0.6.6
0.7.6 (2013-07-24)
^^^^^^^^^^^^^^^^^^
- TypeDecorator support added
0.7.5 (2013-05-30)
^^^^^^^^^^^^^^^^^^
- Fixed _obj setting to better cope with wtforms_components unique validator
0.7.4 (2013-05-30)
^^^^^^^^^^^^^^^^^^
- Fixed min and max arg handling when using zero values
0.7.3 (2013-05-24)
^^^^^^^^^^^^^^^^^^
- Fixed ModelFieldList object population when using 'update' population strategy
0.7.2 (2013-05-24)
^^^^^^^^^^^^^^^^^^
- Updated WTForms-Components dependency to 0.6.3
- Made type conversion use WTForms-Components HTML5 fields
0.7.1 (2013-05-23)
^^^^^^^^^^^^^^^^^^
- DataRequired validator now added to not nullable booleans by default
0.7.0 (2013-05-14)
^^^^^^^^^^^^^^^^^^
- SQLAlchemy-i18n support added
0.6.0 (2013-05-07)
^^^^^^^^^^^^^^^^^^
- Updated WTForms dependency to 1.0.4
- Updated WTForms-Components dependency to 0.5.5
- EmailType now converts to HTML5 EmailField
- Integer now converts to HTML5 IntegerField
- Numeric now converts to HTML5 DecimalField
- Date now converts to HTML5 DateField
- DateTime now converts to HTML5 DateTimeField
0.5.7 (2013-05-03)
^^^^^^^^^^^^^^^^^^
- Fixed trim function for None values
0.5.6 (2013-05-02)
^^^^^^^^^^^^^^^^^^
- Column trim option added for fine-grained control of string field trimming
0.5.5 (2013-05-02)
^^^^^^^^^^^^^^^^^^
- Bug fix: strip_string_fields applied only for string fields
0.5.4 (2013-05-02)
^^^^^^^^^^^^^^^^^^
- Possibility to give default configuration for model_form_factory function
- strip_string_fields configuration option
0.5.3 (2013-04-30)
^^^^^^^^^^^^^^^^^^
- Updated SQLAlchemy-Utils dependency to 0.10.0
- Updated WTForms-Components dependency to 0.5.4
- Added support for ColorType
0.5.2 (2013-04-25)
^^^^^^^^^^^^^^^^^^
- Added custom widget support
- Added custom filters support
0.5.1 (2013-04-16)
^^^^^^^^^^^^^^^^^^
- Updated SQLAlchemy-Utils dependency to 0.9.1
- Updated WTForms-Components dependency to 0.5.2
- Fixed Email validator auto-assigning for EmailType
- Smarter type conversion for subclassed types
- Fixed ModelFormField update handling
0.5.0 (2013-04-12)
^^^^^^^^^^^^^^^^^^
- Updated SQLAlchemy dependency to 0.8
- Completely rewritten ModelFieldList implementation
0.4.5 (2013-03-27)
^^^^^^^^^^^^^^^^^^
- Updated WTForms-Components dependencies
- Updated docs
0.4.4 (2013-03-27)
^^^^^^^^^^^^^^^^^^
- Updated WTForms-Components and SQLAlchemy-Utils dependencies
0.4.3 (2013-03-26)
^^^^^^^^^^^^^^^^^^
- Disalbed length validation for PhoneNumberType
0.4.2 (2013-03-26)
^^^^^^^^^^^^^^^^^^
- Added conversion from NumberRangeType to NumberRangeField
0.4.1 (2013-03-21)
^^^^^^^^^^^^^^^^^^
- Added conversion from PhoneNumberType to PhoneNumberField
0.4 (2013-03-15)
^^^^^^^^^^^^^^^^
- Moved custome fields, validators and widgets to WTForms-Components package
0.3.3 (2013-03-14)
^^^^^^^^^^^^^^^^^^
- Added handling of form_field_class = None
0.3.2 (2013-03-14)
^^^^^^^^^^^^^^^^^^
- Added custom field class attribute
0.3.1 (2013-03-01)
^^^^^^^^^^^^^^^^^^
- Better exception messages
0.3.0 (2013-03-01)
^^^^^^^^^^^^^^^^^^
- New unique validator syntax
0.2.5 (2013-02-16)
^^^^^^^^^^^^^^^^^^
- API documentation
0.2.4 (2013-02-08)
^^^^^^^^^^^^^^^^^^
- Enhanced unique validator
- Documented new unique validator
0.2.3 (2012-11-26)
^^^^^^^^^^^^^^^^^^
- Another fix for empty choices handling
0.2.2 (2012-11-26)
^^^^^^^^^^^^^^^^^^
- Fixed empty choices handling for string fields
0.2.1 (2012-11-22)
^^^^^^^^^^^^^^^^^^
- If validator
- Chain validator
0.2 (2012-11-05)
^^^^^^^^^^^^^^^^^^
- DateRange validator
- SelectField with optgroup support
0.1.1
^^^^^
- Added smart one-to-one and one-to-many relationship population
0.1.0
^^^^^
- Initial public release
wtforms-alchemy-0.19.0/LICENSE 0000664 0000000 0000000 00000002635 14716610423 0015741 0 ustar 00root root 0000000 0000000 Copyright (c) 2012, Konsta Vesterinen
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* 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.
* The names of the contributors may not 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 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.
wtforms-alchemy-0.19.0/MANIFEST.in 0000664 0000000 0000000 00000000301 14716610423 0016456 0 ustar 00root root 0000000 0000000 include CHANGES.rst LICENSE README.rst
recursive-include tests *
recursive-exclude tests *.pyc
recursive-include docs *
recursive-exclude docs *.pyc
prune docs/_build
exclude docs/_themes/.git
wtforms-alchemy-0.19.0/README.rst 0000664 0000000 0000000 00000001127 14716610423 0016416 0 ustar 00root root 0000000 0000000 WTForms-Alchemy
===============
|Version Status| |Downloads|
Tools for creating WTForms forms from SQLAlchemy models
Resources
---------
- `Documentation `_
- `Issue Tracker `_
- `Code `_
.. |Version Status| image:: https://img.shields.io/pypi/v/WTForms-Alchemy.svg
:target: https://pypi.python.org/pypi/WTForms-Alchemy/
.. |Downloads| image:: https://img.shields.io/pypi/dm/WTForms-Alchemy.svg
:target: https://pypi.python.org/pypi/WTForms-Alchemy/
wtforms-alchemy-0.19.0/docs/ 0000775 0000000 0000000 00000000000 14716610423 0015656 5 ustar 00root root 0000000 0000000 wtforms-alchemy-0.19.0/docs/Makefile 0000664 0000000 0000000 00000011022 14716610423 0017312 0 ustar 00root root 0000000 0000000 # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WTForms-Alchemy.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WTForms-Alchemy.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/WTForms-Alchemy"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WTForms-Alchemy"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
wtforms-alchemy-0.19.0/docs/advanced.rst 0000664 0000000 0000000 00000003326 14716610423 0020161 0 ustar 00root root 0000000 0000000 Advanced concepts
=================
Using WTForms-Alchemy with SQLAlchemy-Defaults
----------------------------------------------
WTForms-Alchemy works wonderfully with `SQLAlchemy-Defaults`_. When using `SQLAlchemy-Defaults`_ with WTForms-Alchemy you
can define your models and model forms with much more robust syntax. For more information see `SQLAlchemy-Defaults`_ documentation.
.. _SQLAlchemy-Defaults: https://github.com/kvesteri/sqlalchemy-defaults
Example ::
from sqlalchemy_defaults import LazyConfigured
class User(Base, LazyConfigured):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(
sa.Unicode(255),
nullable=False,
label=u'Name'
)
age = sa.Column(
sa.Integer,
nullable=False,
min=18,
max=100,
label=u'Age'
)
class UserForm(ModelForm):
class Meta:
model = User
Using WTForms-Alchemy with Flask-WTF
------------------------------------
In order to make WTForms-Alchemy work with `Flask-WTF`_ you need the following snippet:
.. _Flask-WTF: https://github.com/lepture/flask-wtf/
::
from flask_wtf import FlaskForm
from wtforms_alchemy import model_form_factory
# The variable db here is a SQLAlchemy object instance from
# Flask-SQLAlchemy package
from myproject.extensions import db
BaseModelForm = model_form_factory(FlaskForm)
class ModelForm(BaseModelForm):
@classmethod
def get_session(self):
return db.session
Then you can use the ModelForm just like before:
::
class UserForm(ModelForm):
class Meta:
model = User
wtforms-alchemy-0.19.0/docs/api.rst 0000664 0000000 0000000 00000001626 14716610423 0017166 0 ustar 00root root 0000000 0000000 API Documentation
=================
This part of the documentation covers all the public classes and functions
in WTForms-Alchemy.
:mod:`wtforms_alchemy`
----------------------
.. module:: wtforms_alchemy
.. autoclass:: ModelForm
:members:
.. autofunction:: model_form_factory
.. autoclass:: ModelFormMeta
:members:
.. autofunction:: model_form_meta_factory
:mod:`wtforms_alchemy.generator`
--------------------------------
.. module:: wtforms_alchemy.generator
.. autoclass:: FormGenerator
:members:
:mod:`wtforms_alchemy.fields`
--------------------------------
.. module:: wtforms_alchemy.fields
.. autoclass:: QuerySelectField
:members:
.. autoclass:: QuerySelectMultipleField
:members:
:mod:`wtforms_alchemy.utils`
----------------------------
.. module:: wtforms_alchemy.utils
.. autofunction:: translated_attributes
.. autoclass:: ClassMap
:members:
:special-members:
wtforms-alchemy-0.19.0/docs/column_conversion.rst 0000664 0000000 0000000 00000015474 14716610423 0022165 0 ustar 00root root 0000000 0000000 Column to form field conversion
===============================
Basic type conversion
---------------------
By default WTForms-Alchemy converts SQLAlchemy model columns using the following
type table. So for example if an Unicode column would be converted to TextField.
The reason why so many types here convert to wtforms_components based fields is that
wtforms_components provides better HTML5 compatible type handling than WTForms at the moment.
==================================== =================
**SQAlchemy column type** **Form field**
------------------------------------ -----------------
BigInteger wtforms_components.fields.IntegerField
Boolean BooleanField
Date wtforms_components.fields.DateField
DateTime wtforms_components.fields.DateTimeField
Enum wtforms_components.fields.SelectField
Float FloatField
Integer wtforms_components.fields.IntegerField
Numeric wtforms_components.fields.DecimalField
SmallInteger wtforms_components.fields.IntegerField
String TextField
Text TextAreaField
Time wtforms_components.fields.TimeField
Unicode TextField
UnicodeText TextAreaField
==================================== =================
WTForms-Alchemy also supports many types provided by SQLAlchemy-Utils.
==================================== =================
**SQAlchemy-Utils type** **Form field**
------------------------------------ -----------------
ArrowType wtforms_components.fields.DateTimeField
ChoiceType wtforms_components.fields.SelectField
ColorType wtforms_components.fields.ColorField
CountryType wtforms_alchemy.fields.CountryType
EmailType wtforms_components.fields.EmailField
IPAddressType wtforms_components.fields.IPAddressField
PasswordType wtforms.fields.PasswordField
PhoneNumberType wtforms_components.fields.PhoneNumberField
URLType wtforms_components.fields.StringField + URL validator
UUIDType wtforms.fields.TextField + UUID validator
WeekDaysType wtforms_components.fields.WeekDaysField
==================================== =================
==================================== =================
**SQAlchemy-Utils range type** **Form field**
------------------------------------ -----------------
DateRangeType wtforms_components.fields.DateIntervalField
DateTimeRangeType wtforms_components.fields.DateTimeIntervalField
IntRangeType wtforms_components.fields.IntIntervalField
NumericRangeType wtforms_components.fields.DecimalIntervalField
==================================== =================
Excluded fields
---------------
By default WTForms-Alchemy excludes a column from the ModelForm if one of the following conditions is True:
* Column is primary key
* Column is foreign key
* Column is DateTime field which has default value (usually this is a generated value)
* Column is of TSVectorType type
* Column is set as model inheritance discriminator field
Using include, exclude and only
-------------------------------
If you wish the include some of the excluded fields described in the earlier chapter you can use the 'include' configuration parameter.
In the following example we include the field 'author_id' in the ArticleForm (by default it is excluded since it is a foreign key column).
::
class Article(Base):
__tablename__ = 'article'
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
name = sa.Column(
sa.Unicode(255),
nullable=False
)
author_id = sa.Column(sa.Integer, sa.ForeignKey(User.id))
author = sa.orm.relationship(User)
class ArticleForm(Form):
class Meta:
include = ['author_id']
If you wish the exclude fields you can either use 'exclude' or 'only' configuration parameters. The recommended way is using only, since in most cases it is desirable to explicitly tell which fields the form should contain.
Consider the following model:
::
class Article(Base):
__tablename__ = 'article'
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
name = sa.Column(
sa.Unicode(255),
nullable=False
)
content = sa.Column(
sa.UnicodeText
)
description = sa.Column(
sa.UnicodeText
)
Now let's say we want to exclude 'description' from the form. This can be achieved as follows:
::
class ArticleForm(Form):
class Meta:
exclude = ['description']
Or as follows (the recommended way):
::
class ArticleForm(Form):
class Meta:
only = ['name', 'content']
Adding/overriding fields
------------------------
Example::
from wtforms.fields import TextField, IntegerField
from wtforms.validators import Email
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(
sa.Unicode(255),
nullable=False
)
class UserForm(ModelForm):
class Meta:
model = User
email = TextField(validators=[Optional()])
age = IntegerField()
Now the UserForm would have three fields:
* name, a required TextField
* email, an optional TextField
* age, IntegerField
Type decorators
---------------
WTForms-Alchemy supports SQLAlchemy TypeDecorator based types. When WTForms-Alchemy encounters a TypeDecorator typed column it tries to convert it to underlying type field.
Example::
import sqlalchemy as sa
from wtforms.fields import TextField, IntegerField
from wtforms.validators import Email
class CustomUnicodeType(sa.types.TypeDecorator):
impl = sa.types.Unicode
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
name = sa.Column(CustomUnicodeType(100), primary_key=True)
class UserForm(ModelForm):
class Meta:
model = User
Now the name field of UserForm would be a simple TextField since the underlying type implementation is Unicode.
wtforms-alchemy-0.19.0/docs/conf.py 0000664 0000000 0000000 00000016715 14716610423 0017167 0 ustar 00root root 0000000 0000000 #
# WTForms-Alchemy documentation build configuration file, created by
# sphinx-quickstart on Wed Aug 29 16:20:21 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(".."))
from wtforms_alchemy import __version__
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "WTForms-Alchemy"
copyright = "2012, Konsta Vesterinen"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = "WTForms-Alchemydoc"
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
(
"index",
"WTForms-Alchemy.tex",
"WTForms-Alchemy Documentation",
"Konsta Vesterinen",
"manual",
),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Additional stuff for the LaTeX preamble.
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
"index",
"wtforms-alchemy",
"WTForms-Alchemy Documentation",
["Konsta Vesterinen"],
1,
)
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
"http://docs.python.org/": None,
"https://wtforms.readthedocs.io/en/latest/": None,
"https://sqlalchemy-utils.readthedocs.io/en/latest/": None,
"https://wtforms-components.readthedocs.io/en/latest/": None,
}
wtforms-alchemy-0.19.0/docs/configuration.rst 0000664 0000000 0000000 00000017472 14716610423 0021272 0 ustar 00root root 0000000 0000000 Configuration
=============
ModelForm meta parameters
-------------------------
The following configuration options are available for ModelForm's Meta subclass.
**include_primary_keys** (default: False)
If you wish to include primary keys in the generated form please set this to True.
This is useful when dealing with natural primary keys. In the following example each
user has a natural primary key on its column name.
The UserForm would contain two fields name and email. ::
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(sa.Unicode(255), nullable=False)
class UserForm(ModelForm):
class Meta:
model = User
include_primary_keys = True
**exclude**
.. warning::
Using ``exclude`` might lead to problems in situations where you add columns to your model
and forget to exclude those from the form by using ``exclude``, hence it is recommended to
use ``only`` rather than ``exclude``.
You can exclude certain fields by adding them to the exclude list. ::
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(sa.Unicode(255), nullable=False)
class UserForm(ModelForm):
class Meta:
model = User
include_primary_keys = True
exclude = ['email']
# this form contains only 'name' field
**only**
Generates a form using only the field names provided in ``only``.
::
class UserForm(ModelForm):
class Meta:
model = User
only = ['email']
**field_args** (default: {})
This parameter can be used for overriding field arguments. In the following example we force the email field optional.
::
class UserForm(ModelForm):
class Meta:
model = User
field_args = {'email': {'validators': [Optional()]}}
**include_foreign_keys** (default: False)
Foreign keys can be included in the form by setting include_foreign_keys to True.
**only_indexed_fields** (default: False)
When setting this option to True, only fields that have an index will be included in
the form. This is very useful when creating forms for searching a specific model.
**include_datetimes_with_default** (default: False)
When setting this option to True, datetime with default values will be included in the
form. By default this is False since usually datetime fields that have default values
are generated columns such as "created_at" or "updated_at", which should not be included
in the form.
**validators**
A dict containing additional validators for the generated form field objects.
Example::
from wtforms.validators import Email
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(sa.Unicode(255), nullable=False)
class UserForm(ModelForm):
class Meta:
model = User
include_primary_keys = True
validators = {'email': [Email()]}
**datetime_format** (default: '%Y-%m-%d %H:%M:%S')
Defines the default datetime format, which will be assigned to generated datetime
fields.
**date_format** (default: '%Y-%m-%d')
Defines the default date format, which will be assigned to generated datetime
fields.
**all_fields_optional** (default: False)
Defines all generated fields as optional (useful for update forms).
**assign_required** (default: True)
Whether or not to assign non-nullable fields as required.
**strip_string_fields** (default: False)
Whether or not to add stripping filter to all string fields.
Example ::
from werkzeug.datastructures import MultiDict
class UserForm(ModelForm):
class Meta:
model = User
strip_string_fields = True
form = UserForm(MultiDict([('name', 'someone ')]))
assert form.name.data == 'someone'
You can also fine-grain field stripping by using trim argument for columns. In the example
below the field 'name' would have its values stripped whereas field 'password' would not. ::
from wtforms.validators import Email
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(100))
password = sa.Column(sa.Unicode(100), info={'trim': False})
class UserForm(ModelForm):
class Meta:
model = User
strip_string_fields = True
**form_generator** (default: FormGenerator class)
Change this if you want to use custom form generator class.
Form inheritance
----------------
ModelForm's configuration support inheritance. This means that child classes inherit
parents Meta properties.
Example::
from wtforms.validators import Email
class UserForm(ModelForm):
class Meta:
model = User
validators = {'email': [Email()]}
class UserUpdateForm(UserForm):
class Meta:
all_fields_optional = True
Here UserUpdateForm inherits the configuration properties of UserForm, hence it would
use model User and have additional Email validator on column 'email'. Also it assigns
all fields as optional.
Not nullable column validation
------------------------------
WTForms-Alchemy offers two options for configuring how not nullable columns are validated:
* not_null_validator
The default validator to be used for not nullable columns. Set this to `None`
if you wish to disable it. By default this is `[InputRequired()]`.
* not_null_validator_type_map
Type map which overrides the **not_null_validator** on specific column type. By default this is `ClassMap({sa.String: [InputRequired(), DataRequired()]})`.
In the following example we set `DataRequired` validator for all not nullable Enum typed columns:
::
import sqlalchemy as sa
from wtforms.validators import DataRequired
from wtforms_alchemy import ClassMap
class MyForm(ModelForm):
class Meta:
not_null_validator_type_map = ClassMap({sa.Enum: [DataRequired()]})
Customizing type conversion
---------------------------
You can customize the SQLAlchemy type conversion on class level with type_map Meta property.
Type map accepts dictionary of SQLAlchemy types as keys and WTForms field classes
as values. The key value pairs of this dictionary override the key value pairs of FormGenerator.TYPE_MAP.
Let's say we want to convert all unicode typed properties to TextAreaFields instead of StringFields. We can do this by assigning Unicode, TextAreaField key value pair into type map.
::
from wtforms.fields import TextAreaField
from wtforms_alchemy import ClassMap
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(100))
class UserForm(ModelForm):
class Meta:
type_map = ClassMap({sa.Unicode: TextAreaField})
In case the type_map dictionary values are not inherited from WTForm field class, they are considered callable functions. These functions will be called with the corresponding column as their only parameter.
.. _custom_base:
Custom form base class
----------------------
You can use custom base class for your model forms by using model_form_factory
function. In the following example we have a UserForm which uses Flask-WTF
form as a parent form for ModelForm. ::
from flask.ext.wtf import Form
from wtforms_alchemy import model_form_factory
ModelForm = model_form_factory(Form)
class UserForm(ModelForm):
class Meta:
model = User
You can also pass any form generator option to model_form_factory. ::
ModelForm = model_form_factory(Form, strip_string_fields=True)
class UserForm(ModelForm):
class Meta:
model = User
wtforms-alchemy-0.19.0/docs/customization.rst 0000664 0000000 0000000 00000007031 14716610423 0021321 0 ustar 00root root 0000000 0000000 Form customization
==================
Custom fields
-------------
If you want to use a custom field class, you can pass it by using
form_field_class parameter for the column info dictionary.
Example ::
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
color = sa.Column(
sa.String(7),
info={'form_field_class': ColorField},
nullable=False
)
class UserForm(ModelForm):
class Meta:
model = User
Now the 'color' field of UserForm would be a custom ColorField.
Forcing the use of SelectField
------------------------------
Sometimes you may want to have integer and unicode fields convert to SelectFields.
Probably the easiest way to achieve this is by using choices parameter for the column
info dictionary.
Example ::
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
age = sa.Column(
sa.Integer,
info={'choices': [(i, i) for i in xrange(13, 99)]},
nullable=False
)
class UserForm(ModelForm):
class Meta:
model = User
Here the UserForm would have two fields. One TextField for the name column and one
SelectField for the age column containing range of choices from 13 to 99.
Notice that WTForms-Alchemy is smart enough to use the right coerce function based on
the underlying column type, hence in the previous example the age column would convert
to the following SelectField. ::
SelectField('Age', coerce=int, choices=[(i, i) for i in xrange(13, 99)])
For nullable unicode and string columns WTForms-Alchemy uses special null_or_unicode
coerce function, which converts empty strings to None values.
Field descriptions
------------------
Example::
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(
sa.Unicode(255),
nullable=False,
info={'description': 'This is the description of email.'}
)
class UserForm(ModelForm):
class Meta:
model = User
Now the 'email' field of UserForm would have description 'This is the description of email.'
Field labels
------------
Example::
class User(Base):
__tablename__ = 'user'
name = sa.Column(
sa.Unicode(100), primary_key=True, nullable=False,
info={'label': 'Name'}
)
class UserForm(ModelForm):
class Meta:
model = User
Now the 'name' field of UserForm would have label 'Name'.
Custom widgets
--------------
Example::
from wtforms import widgets
class User(Base):
__tablename__ = 'user'
name = sa.Column(
sa.Unicode(100), primary_key=True, nullable=False,
info={'widget': widgets.HiddenInput()}
)
class UserForm(ModelForm):
class Meta:
model = User
Now the 'name' field of UserForm would use HiddenInput widget instead of TextInput.
Default values
--------------
By default WTForms-Alchemy ModelForm assigns the default values from column definitions.
Example ::
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
level = sa.Column(sa.Integer, default=1)
class UserForm(ModelForm):
class Meta:
model = User
Now the UseForm 'level' field default value would be 1.
wtforms-alchemy-0.19.0/docs/index.rst 0000664 0000000 0000000 00000001107 14716610423 0017516 0 ustar 00root root 0000000 0000000 WTForms-Alchemy
===============
WTForms-Alchemy is a WTForms extension toolkit for easier creation of model
based forms. Strongly influenced by Django ModelForm.
.. toctree::
:maxdepth: 2
introduction
column_conversion
types
customization
validators
configuration
relationships
advanced
api
license
.. _`SQLAlchemy-Utils ChoiceType`: https://sqlalchemy-utils.readthedocs.io/en/latest/#choicetype
.. _`SQLAlchemy-Defaults`: https://sqlalchemy-defaults.readthedocs.io/en/latest/
.. _`Flask-WTF`: https://flask-wtf.readthedocs.io/en/latest/
wtforms-alchemy-0.19.0/docs/introduction.rst 0000664 0000000 0000000 00000006170 14716610423 0021135 0 ustar 00root root 0000000 0000000 Introduction
============
What for?
---------
Many times when building modern web apps with SQLAlchemy you’ll have forms that
map closely to models. For example, you might have a Article model,
and you want to create a form that lets people post new article. In this case,
it would be time-consuming to define the field types and basic validators in
your form, because you’ve already defined the fields in your model.
WTForms-Alchemy provides a helper class that let you create a Form class from a
SQLAlchemy model.
Differences with wtforms.ext.sqlalchemy model_form
--------------------------------------------------
WTForms-Alchemy does not try to replace all the functionality of wtforms.ext.sqlalchemy.
It only tries to replace the model_form function of wtforms.ext.sqlalchemy by a much better solution.
Other functionality of .ext.sqlalchemy such as QuerySelectField and QuerySelectMultipleField can be used
along with WTForms-Alchemy.
Now how is WTForms-Alchemy ModelForm better than wtforms.ext.sqlachemy's model_form?
* Provides explicit declaration of ModelForms (much easier to override certain columns)
* Form generation supports Unique and NumberRange validators
* Form inheritance support (along with form configuration inheritance)
* Automatic SelectField type coercing based on underlying column type
* By default uses wtforms_components SelectField for fields with choices. This field understands None values and renders nested datastructures as optgroups.
* Provides better Unique validator
* Supports custom user defined types as well as type decorators
* Supports SQLAlchemy-Utils datatypes
* Supports ModelForm model relations population
* Smarter field exclusion
* Smarter field conversion
* Understands join table inheritance
* Better configuration
Installation
------------
::
pip install WTForms-Alchemy
The supported Python versions are 3.9–3.13.
QuickStart
----------
Lets say we have a model called User with couple of fields::
import sqlalchemy as sa
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from wtforms_alchemy import ModelForm
engine = create_engine('sqlite:///:memory:')
Base = declarative_base(engine)
Session = sessionmaker(bind=engine)
session = Session()
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.BigInteger, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(100), nullable=False)
email = sa.Column(sa.Unicode(255), nullable=False)
Now we can create our first ModelForm for the User model. ModelForm behaves almost
like your ordinary WTForms Form except it accepts special Meta arguments. Every ModelForm
must define model parameter in the Meta arguments.::
class UserForm(ModelForm):
class Meta:
model = User
Now this ModelForm is essentially the same as ::
class UserForm(Form):
name = TextField(validators=[DataRequired(), Length(max=100)])
email = TextField(validators=[DataRequired(), Length(max=255)])
In the following chapters you'll learn how WTForms-Alchemy converts SQLAlchemy model
columns to form fields.
wtforms-alchemy-0.19.0/docs/license.rst 0000664 0000000 0000000 00000000051 14716610423 0020026 0 ustar 00root root 0000000 0000000 License
=======
.. include:: ../LICENSE
wtforms-alchemy-0.19.0/docs/make.bat 0000664 0000000 0000000 00000010661 14716610423 0017267 0 ustar 00root root 0000000 0000000 @ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^` where ^ is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\WTForms-Alchemy.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\WTForms-Alchemy.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
wtforms-alchemy-0.19.0/docs/relationships.rst 0000664 0000000 0000000 00000005354 14716610423 0021303 0 ustar 00root root 0000000 0000000 Forms with relations
====================
WTForms-Alchemy provides special Field subtypes ModelFormField and ModelFieldList.
When using these types WTForms-Alchemy understands model relations and is smart enough to populate related
objects accordingly.
One-to-one relations
--------------------
Consider the following example. We have Event and Location
classes with each event having one location. ::
from sqlalchemy.orm import declarative_base
from wtforms_alchemy import ModelForm, ModelFormField
Base = declarative_base()
class Location(Base):
__tablename__ = 'location'
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
class Event(Base):
__tablename__ = 'event'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))
location = sa.orm.relationship(Location)
class LocationForm(ModelForm):
class Meta:
model = Location
class EventForm(ModelForm):
class Meta:
model = Event
location = ModelFormField(LocationForm)
Now if we populate the EventForm, WTForms-Alchemy is smart enough to populate related
location too. ::
event = Event()
form = EventForm(request.POST)
form.populate_obj(event)
One-to-many relations
---------------------
Consider the following example. We have Event and Location
classes with each event having many location. Notice we are using FormField along
with ModelFieldList. ::
from sqlalchemy.orm import declarative_base
from wtforms_alchemy import ModelForm, ModelFieldList
from wtforms.fields import FormField
Base = declarative_base()
class Event(Base):
__tablename__ = 'event'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
class Location(Base):
__tablename__ = 'location'
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))
event = sa.orm.relationship(
Location,
backref='locations' # the event needs to have this
)
class LocationForm(ModelForm):
class Meta:
model = Location
class EventForm(ModelForm):
class Meta:
model = Event
locations = ModelFieldList(FormField(LocationForm))
Now if we populate the EventForm, WTForms-Alchemy is smart enough to populate related
locations too. ::
event = Event()
form = EventForm(request.POST)
form.populate_obj(event)
wtforms-alchemy-0.19.0/docs/types.rst 0000664 0000000 0000000 00000014134 14716610423 0017557 0 ustar 00root root 0000000 0000000 Type specific conversion
========================
Numeric type
------------
WTForms-Alchemy automatically converts Numeric columns to DecimalFields. The
converter is also smart enough to convert different decimal scales to
appropriate HTML5 input step args.
::
class Account(Base):
__tablename__ = 'event'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
balance = sa.Column(
sa.Numeric(scale=2),
nullable=False
)
class AccountForm(ModelForm):
class Meta:
model = Account
Now rendering AccountForm.balance would return the following HTML:
Arrow type
----------
WTForms-Alchemy supports the ArrowType of SQLAlchemy-Utils and converts it to
HTML5 compatible DateTimeField of WTForms-Components.
::
from sqlalchemy_utils import ArrowType
class Event(Base):
__tablename__ = 'event'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
start_time = sa.Column(
ArrowType(),
nullable=False
)
class EventForm(ModelForm):
class Meta:
model = Event
Now the EventForm is essentially the same as:
::
class EventForm(Form):
start_time = DateTimeField(validators=[DataRequired()])
Choice type
-----------
WTForms-Alchemy automatically converts
:class:`sqlalchemy_utils.types.choice.ChoiceType` to WTForms-Components
SelectField.
::
from sqlalchemy_utils import ChoiceType
class Event(Base):
__tablename__ = 'event'
TYPES = [
(u'party', u'Party'),
(u'training, u'Training')
]
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
type = sa.Column(ChoiceType(TYPES))
class EventForm(ModelForm):
class Meta:
model = Event
Now the EventForm is essentially the same as:
::
from wtforms_alchemy.utils import choice_type_coerce_factory
class EventForm(Form):
type = SelectField(
choices=Event.TYPES,
coerce=choice_type_coerce_factory(Event.type.type),
validators=[DataRequired()]
)
Color type
----------
::
from sqlalchemy_utils import ColorType
class CustomView(Base):
__tablename__ = 'view'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
background_color = sa.Column(
ColorType(),
nullable=False
)
class CustomViewForm(ModelForm):
class Meta:
model = CustomView
Now the CustomViewForm is essentially the same as:
::
from wtforms_components import ColorField
class CustomViewForm(Form):
color = ColorField(validators=[DataRequired()])
Country type
------------
::
from sqlalchemy_utils import CountryType
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
country = sa.Column(CountryType, nullable=False)
class UserForm(ModelForm):
class Meta:
model = User
The UserForm is essentially the same as:
::
from wtforms_components import CountryField
class UserForm(Form):
country = CountryField(validators=[DataRequired()])
Email type
----------
::
from sqlalchemy_utils import EmailType
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
email = sa.Column(EmailType, nullable=False)
class UserForm(ModelForm):
class Meta:
model = User
The good old wtforms equivalent of this form would be:
::
from wtforms_components import EmailField
class UserForm(Form):
email = EmailField(validators=[DataRequired()])
Password type
-------------
Consider the following model definition:
::
from sqlalchemy_utils import PasswordType
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
name = sa.Column(sa.Unicode(100), nullable=False)
password = sa.Column(
PasswordType(
schemes=['pbkdf2_sha512']
),
nullable=False
)
class UserForm(ModelForm):
class Meta:
model = User
Now the UserForm is essentially the same as:
::
class UserForm(Form):
name = TextField(validators=[DataRequired(), Length(max=100)])
password = PasswordField(validators=[DataRequired()])
Phonenumber type
----------------
WTForms-Alchemy supports the PhoneNumberType of SQLAlchemy-Utils and converts it automatically
to WTForms-Components PhoneNumberField. This field renders itself as HTML5 compatible phonenumber input.
Consider the following model definition:
::
from sqlalchemy_utils import PhoneNumberType
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
name = sa.Column(sa.Unicode(100), nullable=False)
phone_number = sa.Column(PhoneNumberType())
class UserForm(ModelForm):
class Meta:
model = User
Now the UserForm is essentially the same as:
::
from wtforms_components import PhoneNumberField
class UserForm(Form):
name = TextField(validators=[DataRequired(), Length(max=100)])
phone_number = PhoneNumberField(validators=[DataRequired()])
URL type
--------
WTForms-Alchemy automatically converts SQLAlchemy-Utils URLType to StringField and adds URL validator for it.
Consider the following model definition:
::
from sqlalchemy_utils import URLType
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
website = sa.Column(URLType())
class UserForm(ModelForm):
class Meta:
model = User
Now the UserForm is essentially the same as:
::
from wtforms_components import StringField
from wtforms.validators import URL
class UserForm(Form):
website = StringField(validators=[URL()])
wtforms-alchemy-0.19.0/docs/validators.rst 0000664 0000000 0000000 00000014220 14716610423 0020557 0 ustar 00root root 0000000 0000000 Validators
==========
Auto-assigned validators
------------------------
By default WTForms-Alchemy ModelForm assigns the following validators:
* InputRequired validator if column is not nullable and has no default value
* DataRequired validator if column is not nullable, has no default value and is of type `sqlalchemy.types.String`
* NumberRange validator if column if of type Integer, Float or Decimal and column info parameter has min or max arguments defined
* DateRange validator if column is of type Date or DateTime and column info parameter has min or max arguments defined
* TimeRange validator if column is of type Time and info parameter has min or max arguments defined
* Unique validator if column has a unique index
* Length validator for String/Unicode columns with max length
* Optional validator for all nullable columns
Unique validator
----------------
WTForms-Alchemy automatically assigns unique validators for columns which have unique indexes defined. Unique validator raises ValidationError exception whenever a non-unique value for given column is assigned. Consider the following model/form definition. Notice how you need to define get_session() classmethod for your form. Unique validator uses this method for getting the appropriate SQLAlchemy session.
::
engine = create_engine('sqlite:///:memory:')
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(100), nullable=False)
email = sa.Column(
sa.Unicode(255),
nullable=False,
unique=True
)
class UserForm(ModelForm):
class Meta:
model = User
@classmethod
def get_session():
# this method should return sqlalchemy session
return session
Here UserForm would behave the same as the following form:
::
class UserForm(Form):
name = TextField('Name', validators=[DataRequired(), Length(max=100)])
email = TextField(
'Email',
validators=[
DataRequired(),
Length(max=255),
Unique(User.email, get_session=lambda: session)
]
)
If you are using Flask-SQLAlchemy or similar tool, which assigns session-bound query property to your declarative models, you don't need to define the get_session() method. Simply use:
::
Unique(User.email)
Using unique validator with existing objects
--------------------------------------------
When editing an existing object, WTForms-Alchemy must know the object currently edited to avoid raising a ValidationError. Here how to proceed to inform WTForms-Alchemy of this case.
Example::
obj = MyModel.query.get(1)
form = MyForm(obj=obj)
form.populate_obj(obj)
form.validate()
WTForms-Alchemy will then understand to avoid the unique validation of the object with this same object.
Range validators
----------------
WTForms-Alchemy automatically assigns range validators based on column type and assigned column info min and max attributes.
In the following example we create a form for Event model where start_time can't be set in the past.
::
class Event(Base):
__tablename__ = 'event'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255))
start_time = sa.Column(sa.DateTime, info={'min': datetime.now()})
class EventForm(ModelForm):
class Meta:
model = Event
Additional field validators
---------------------------
Example::
from wtforms.validators import Email
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(
sa.Unicode(255),
nullable=False,
info={'validators': Email()}
)
class UserForm(ModelForm):
class Meta:
model = User
Now the 'email' field of UserForm would have Email validator.
Overriding default validators
-----------------------------
Sometimes you may want to override what class WTForms-Alchemy uses for email, number_range, length etc. validations.
For all automatically assigned validators WTForms-Alchemy provides configuration options to override the default validator.
In the following example we set a custom Email validator for User class.
::
from sqlalchemy_utils import EmailType
from wtforms_components import Email
class User(Base):
__tablename__ = 'user'
name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)
email = sa.Column(
EmailType,
nullable=False,
)
class MyEmailValidator(Email):
def __init__(self, message='My custom email error message'):
Email.__init__(self, message=message)
class UserForm(ModelForm):
class Meta:
model = User
email_validator = MyEmailValidator
If you don't wish to subclass you can simply use functions / lambdas:
::
def email():
return Email(message='My custom email error message')
class UserForm(ModelForm):
class Meta:
model = User
email_validator = email
You can also override validators that take multiple arguments this way:
::
def length(min=None, max=None):
return Length(min=min, max=max, message='Wrong length')
class UserForm(ModelForm):
class Meta:
model = User
length_validator = length
Here is the full list of configuration options you can use to override default validators:
* email_validator
* length_validator
* unique_validator
* number_range_validator
* date_range_validator
* time_range_validator
* optional_validator
Disabling validators
--------------------
You can disable certain validators by assigning them as `None`. Let's say you want to disable nullable columns having `Optional` validator. This can be achieved as follows::
class UserForm(ModelForm):
class Meta:
model = User
optional_validator = None
wtforms-alchemy-0.19.0/pyproject.toml 0000664 0000000 0000000 00000000731 14716610423 0017643 0 ustar 00root root 0000000 0000000 [tool.pytest.ini_options]
filterwarnings = [
'error:.*:sqlalchemy.exc.SADeprecationWarning',
'error:.*:sqlalchemy.exc.SAWarning',
'ignore:.*:sqlalchemy.exc.SADeprecationWarning:sqlalchemy_i18n',
]
[tool.ruff]
target-version = "py39"
[tool.ruff.lint]
select = [
"C90", # mccabe
"E", # pycodestyle errors
"F", # Pyflakes
"I", # isort
"UP", # pyupgrade
"W", # pycodestyle warnings
]
[tool.ruff.lint.isort]
order-by-type = false
wtforms-alchemy-0.19.0/setup.py 0000664 0000000 0000000 00000004740 14716610423 0016445 0 ustar 00root root 0000000 0000000 """
WTForms-Alchemy
---------------
Generates WTForms forms from SQLAlchemy models.
"""
import os
import re
import sys
from setuptools import setup
HERE = os.path.dirname(os.path.abspath(__file__))
PY3 = sys.version_info[0] == 3
def get_version():
filename = os.path.join(HERE, "wtforms_alchemy", "__init__.py")
with open(filename) as f:
contents = f.read()
pattern = r'^__version__ = "(.*?)"$'
return re.search(pattern, contents, re.MULTILINE).group(1)
extras_require = {
"test": [
"enum34",
"pytest>=2.3",
"Pygments>=1.2",
"Jinja2>=2.3",
"docutils>=0.10",
"flexmock>=0.9.7",
"ruff==0.7.4",
"WTForms-Test>=0.1.1",
],
"babel": ["Babel>=1.3"],
"arrow": ["arrow>=0.3.4"],
"phone": ["phonenumbers>=5.9.2"],
"intervals": ["intervals>=0.2.0"],
"password": ["passlib >= 1.6, < 2.0"],
"color": ["colour>=0.0.4"],
"i18n": ["SQLAlchemy-i18n >= 0.8.2"],
"ipaddress": ["ipaddr"] if not PY3 else [],
"timezone": ["python-dateutil"],
}
# Add all optional dependencies to testing requirements.
for name, requirements in extras_require.items():
if name != "test":
extras_require["test"] += requirements
setup(
name="WTForms-Alchemy",
version=get_version(),
url="https://github.com/kvesteri/wtforms-alchemy",
license="BSD",
author="Konsta Vesterinen",
author_email="konsta@fastmonkeys.com",
description="Generates WTForms forms from SQLAlchemy models.",
long_description=__doc__,
packages=["wtforms_alchemy"],
zip_safe=False,
include_package_data=True,
platforms="any",
install_requires=[
"SQLAlchemy>=1.4",
"WTForms>=3.1.0",
"WTForms-Components>=0.11.0",
"SQLAlchemy-Utils>=0.40.0",
],
extras_require=extras_require,
classifiers=[
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
],
)
wtforms-alchemy-0.19.0/tests/ 0000775 0000000 0000000 00000000000 14716610423 0016070 5 ustar 00root root 0000000 0000000 wtforms-alchemy-0.19.0/tests/__init__.py 0000664 0000000 0000000 00000003626 14716610423 0020210 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy.orm.session import close_all_sessions
from sqlalchemy_utils import force_auto_coercion
from wtforms_test import FormTestCase
from wtforms_alchemy import ModelForm
force_auto_coercion()
class MultiDict(dict):
def getlist(self, key):
return [self[key]]
class ModelFormTestCase(FormTestCase):
dns = "sqlite:///:memory:"
def setup_method(self, method):
self.engine = create_engine(self.dns)
self.base = declarative_base()
def teardown_method(self, method):
self.engine.dispose()
self.ModelTest = None
self.form_class = None
def init_model(self, type_=sa.Unicode(255), **kwargs):
kwargs.setdefault("nullable", False)
class ModelTest(self.base):
__tablename__ = "model_test"
query = None
id = sa.Column(sa.Integer, primary_key=True)
test_column = sa.Column(type_, **kwargs)
some_property = "something"
self.ModelTest = ModelTest
def init(self, type_=sa.Unicode(255), **kwargs):
self.init_model(type_=type_, **kwargs)
self.init_form()
def init_form(self):
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
self.form_class = ModelTestForm
class FormRelationsTestCase:
dns = "sqlite:///:memory:"
def setup_method(self, method):
self.engine = create_engine(self.dns)
self.base = declarative_base()
self.create_models()
self.create_forms()
self.base.metadata.create_all(self.engine)
Session = sessionmaker(bind=self.engine)
self.session = Session()
def teardown_method(self, method):
close_all_sessions()
self.base.metadata.drop_all(self.engine)
self.engine.dispose()
wtforms-alchemy-0.19.0/tests/conftest.py 0000664 0000000 0000000 00000001743 14716610423 0020274 0 ustar 00root root 0000000 0000000 import pytest
from wtforms.form import FormMeta
from wtforms_alchemy import (
model_form_factory,
model_form_meta_factory,
ModelFormMeta,
)
class _MetaWithInit(FormMeta):
def __init__(cls, *args, **kwargs):
cls.test_attr = "SomeVal"
FormMeta.__init__(cls, *args, **kwargs)
MetaWithInit = model_form_meta_factory(_MetaWithInit)
class _MetaWithoutInit(FormMeta):
test_attr = "SomeVal"
MetaWithoutInit = model_form_meta_factory(_MetaWithoutInit)
@pytest.fixture(params=[MetaWithInit, MetaWithoutInit, ModelFormMeta])
def model_form_all(request):
"""Returns one of each possible model form classes with custom and the
original metaclass."""
ModelForm = model_form_factory(meta=request.param)
return ModelForm
@pytest.fixture(params=[MetaWithInit, MetaWithoutInit])
def model_form_custom(request):
"""Returns one of each possible model form classes with custom
metaclasses."""
return model_form_factory(meta=request.param)
wtforms-alchemy-0.19.0/tests/test_class_map.py 0000664 0000000 0000000 00000002134 14716610423 0021443 0 ustar 00root root 0000000 0000000 from pytest import mark, raises
from wtforms_alchemy.utils import ClassMap
class A:
pass
class B:
pass
class A2(A):
pass
class A3(A2):
pass
class A4(A3):
pass
class A5(A4):
pass
class B2(B):
pass
class C:
pass
@mark.parametrize("key", [B2, B, A, A2])
def test_contains_with_subclass_check(key):
class_map = ClassMap({A: 3, B: 6})
assert key in class_map
@mark.parametrize("key", [B2(), B(), A(), A2()])
def test_contains_with_isinstance_check(key):
class_map = ClassMap({A: 3, B: 6})
assert key in class_map
@mark.parametrize(("key", "value"), [(B2, 6), (B, 6), (A, 3), (A2, 3)])
def test_getitem_with_classes(key, value):
class_map = ClassMap({A: 3, B: 6})
assert class_map[key] == value
@mark.parametrize(("key", "value"), [(B2(), 6), (B(), 6), (A(), 3), (A2(), 3)])
def test_getitem_with_objects(key, value):
class_map = ClassMap({A: 3, B: 6})
assert class_map[key] == value
def test_getitem_throws_keyerror_for_unknown_key():
class_map = ClassMap({A: 3, B: 6})
with raises(KeyError):
class_map["unknown"]
wtforms-alchemy-0.19.0/tests/test_column_aliases.py 0000664 0000000 0000000 00000005345 14716610423 0022506 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from wtforms.validators import NumberRange
from tests import ModelFormTestCase
from wtforms_alchemy import ModelForm
class TestColumnAliases(ModelFormTestCase):
def test_supports_column_aliases(self):
class TestModel(self.base):
__tablename__ = "TestTable"
id = sa.Column(sa.Integer, primary_key=True)
some_alias = sa.Column("some_name", sa.Integer)
class TestForm(ModelForm):
class Meta:
model = TestModel
form = TestForm()
assert hasattr(form, "some_alias")
assert not hasattr(form, "some_name")
def test_labels(self):
class TestModel(self.base):
__tablename__ = "TestTable"
id = sa.Column(sa.Integer, primary_key=True)
some_alias = sa.Column(
"some_name",
sa.Integer,
)
class TestForm(ModelForm):
class Meta:
model = TestModel
form = TestForm()
assert form.some_alias.label.text == "some_alias"
def test_unique_indexes(self):
class TestModel(self.base):
__tablename__ = "TestTable"
id = sa.Column(sa.Integer, primary_key=True)
some_alias = sa.Column("some_name", sa.Integer, unique=True)
class TestForm(ModelForm):
class Meta:
model = TestModel
@staticmethod
def get_session():
return None
form = TestForm()
assert hasattr(form, "some_alias")
assert not hasattr(form, "some_name")
def test_meta_field_args(self):
class TestModel(self.base):
__tablename__ = "TestTable"
id = sa.Column(sa.Integer, primary_key=True)
some_alias = sa.Column("some_name", sa.Integer)
validators = [NumberRange(max=4)]
class TestForm(ModelForm):
class Meta:
model = TestModel
field_args = {"some_alias": {"validators": validators}}
form = TestForm()
assert hasattr(form, "some_alias")
assert not hasattr(form, "some_name")
assert form.some_alias.validators == validators
def test_additional_validators(self):
class TestModel(self.base):
__tablename__ = "TestTable"
id = sa.Column(sa.Integer, primary_key=True)
some_alias = sa.Column("some_name", sa.Integer)
number_range = NumberRange(max=4)
validator_list = [number_range]
class TestForm(ModelForm):
class Meta:
model = TestModel
validators = {"some_alias": validator_list}
form = TestForm()
assert number_range in form.some_alias.validators
wtforms-alchemy-0.19.0/tests/test_configuration.py 0000664 0000000 0000000 00000015334 14716610423 0022356 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from pytest import raises
from wtforms.fields import IntegerField
from wtforms.validators import Email
from tests import ModelFormTestCase, MultiDict
from wtforms_alchemy import (
AttributeTypeException,
InvalidAttributeException,
ModelForm,
)
class UnknownType(sa.types.UserDefinedType):
def get_col_spec(self):
return "UNKNOWN()"
class TestModelFormConfiguration(ModelFormTestCase):
def test_skip_unknown_types(self):
class ModelTest(self.base):
__tablename__ = "model_test"
query = None
id = sa.Column(sa.Integer, primary_key=True)
test_column = sa.Column(UnknownType)
some_property = "something"
self.ModelTest = ModelTest
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
skip_unknown_types = True
self.form_class = ModelTestForm
assert not self.has_field("test_column")
def test_supports_field_exclusion(self):
self.init_model()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
exclude = ["test_column"]
self.form_class = ModelTestForm
assert not self.has_field("test_column")
def test_throws_exception_for_unknown_excluded_column(self):
self.init_model()
with raises(InvalidAttributeException):
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
exclude = ["some_unknown_column"]
def test_invalid_exclude_with_attr_errors_as_false(self):
self.init_model()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
attr_errors = False
exclude = ["some_unknown_column"]
def test_throws_exception_for_unknown_included_column(self):
self.init_model()
with raises(InvalidAttributeException):
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
include = ["some_unknown_column"]
def test_invalid_include_with_attr_errors_as_false(self):
self.init_model()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
attr_errors = False
include = ["some_unknown_column"]
def test_throws_exception_for_non_column_fields(self):
self.init_model()
with raises(AttributeTypeException):
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
include = ["some_property"]
def test_supports_field_inclusion(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
include = ["id"]
self.form_class = ModelTestForm
assert self.has_field("id")
def test_supports_only_attribute(self):
class ModelTest(self.base):
__tablename__ = "model_test"
query = None
id = sa.Column(sa.Integer, primary_key=True)
test_column = sa.Column(sa.UnicodeText)
test_column2 = sa.Column(sa.UnicodeText)
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
only = ["test_column"]
form = ModelTestForm()
assert len(form._fields) == 1
def test_supports_field_overriding(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
test_column = IntegerField()
self.form_class = ModelTestForm
self.assert_type("test_column", IntegerField)
def test_supports_assigning_all_fields_as_optional(self):
self.init(nullable=False)
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
all_fields_optional = True
self.form_class = ModelTestForm
self.assert_not_required("test_column")
self.assert_optional("test_column")
def test_supports_custom_datetime_format(self):
self.init(sa.DateTime, nullable=False)
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
datetime_format = "%Y-%m-%dT%H:%M:%S"
form = ModelTestForm()
assert form.test_column.format == ["%Y-%m-%dT%H:%M:%S"]
def test_supports_additional_validators(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
validators = {"test_column": Email()}
self.form_class = ModelTestForm
self.assert_has_validator("test_column", Email)
def test_inherits_config_params_from_parent_meta(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
only = ["test_column"]
class AnotherModelTestForm(ModelTestForm):
class Meta:
pass
assert AnotherModelTestForm.Meta.only == ["test_column"]
def test_child_classes_override_parents_config_params(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
only = ["test_column"]
class AnotherModelTestForm(ModelTestForm):
class Meta:
only = []
assert AnotherModelTestForm.Meta.only == []
def test_strip_strings_fields(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
only = ["test_column"]
strip_string_fields = True
form = ModelTestForm(MultiDict(test_column=" something "))
assert form.test_column.data == "something"
def test_strip_strings_fields_with_empty_values(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
only = ["test_column"]
strip_string_fields = True
ModelTestForm()
def test_class_meta_regression(self):
self.init()
class SomeForm(ModelForm):
class Meta:
model = self.ModelTest
foo = 9
class OtherForm(SomeForm):
class Meta:
pass
assert issubclass(OtherForm.Meta, SomeForm.Meta)
form = OtherForm()
# Create a side effect on the base meta.
assert form.Meta.foo == 9
SomeForm.Meta.foo = 12
assert form.Meta.foo == 12
wtforms-alchemy-0.19.0/tests/test_country_field.py 0000664 0000000 0000000 00000002134 14716610423 0022347 0 ustar 00root root 0000000 0000000 import sqlalchemy_utils
from babel import Locale
from wtforms import Form
from tests import MultiDict
from wtforms_alchemy import CountryField
sqlalchemy_utils.i18n.get_locale = lambda: Locale("en")
class TestCountryField:
field_class = CountryField
def init_form(self, **kwargs):
class TestForm(Form):
test_field = self.field_class(**kwargs)
self.form_class = TestForm
return self.form_class
def setup_method(self, method):
self.valid_countries = ["US", "SA", "FI"]
self.invalid_countries = [
"unknown",
]
def test_valid_countries(self):
form_class = self.init_form()
for country in self.valid_countries:
form = form_class(MultiDict(test_field=country))
form.validate()
assert len(form.errors) == 0
def test_invalid_countries(self):
form_class = self.init_form()
for country in self.invalid_countries:
form = form_class(MultiDict(test_field=country))
form.validate()
assert len(form.errors["test_field"]) == 2
wtforms-alchemy-0.19.0/tests/test_custom_fields.py 0000664 0000000 0000000 00000001010 14716610423 0022331 0 ustar 00root root 0000000 0000000 from wtforms import Form
from wtforms_components import SelectField
from tests import MultiDict
from wtforms_alchemy import null_or_unicode
class TestSelectField:
def test_understands_none_values(self):
class MyForm(Form):
choice_field = SelectField(
choices=[("", "-- Choose --"), ("choice 1", "Something")],
coerce=null_or_unicode,
)
form = MyForm(MultiDict({"choice_field": ""}))
form.validate()
assert form.errors == {}
wtforms-alchemy-0.19.0/tests/test_deep_form_relations.py 0000664 0000000 0000000 00000012165 14716610423 0023526 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from wtforms.fields import FormField
from tests import FormRelationsTestCase, MultiDict
from wtforms_alchemy import ModelFieldList, ModelForm, ModelFormField
class TestDeepFormRelationsOneToManyToOne(FormRelationsTestCase):
def create_models(self):
class Event(self.base):
__tablename__ = "event"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
class Address(self.base):
__tablename__ = "address"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
street = sa.Column(sa.Unicode(255), nullable=True)
class Location(self.base):
__tablename__ = "location"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
address_id = sa.Column(sa.Integer, sa.ForeignKey(Address.id))
address = sa.orm.relationship(Address)
event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))
event = sa.orm.relationship(Event, backref="locations")
self.Event = Event
self.Location = Location
self.Address = Address
def create_forms(self):
class AddressForm(ModelForm):
class Meta:
model = self.Address
class LocationForm(ModelForm):
class Meta:
model = self.Location
address = ModelFormField(AddressForm)
class EventForm(ModelForm):
class Meta:
model = self.Event
locations = ModelFieldList(FormField(LocationForm))
self.LocationForm = LocationForm
self.EventForm = EventForm
self.AddressForm = AddressForm
def save(self):
data = {
"name": "Some event",
"locations-0-name": "Some location",
"locations-0-address-street": "Some address",
}
event = self.Event()
self.session.add(event)
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
def test_assigment_and_deletion(self):
self.save()
event = self.session.query(self.Event).first()
assert event.locations[0].name == "Some location"
assert event.locations[0].address.street == "Some address"
data = {"name": "Some event"}
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
event = self.session.query(self.Event).first()
assert event.locations == []
class TestDeepFormRelationsOneToOneToMany(FormRelationsTestCase):
def create_models(self):
class Location(self.base):
__tablename__ = "location"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
class Address(self.base):
__tablename__ = "address"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
street = sa.Column(sa.Unicode(255), nullable=True)
location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))
location = sa.orm.relationship(Location, backref="addresses")
class Event(self.base):
__tablename__ = "event"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))
location = sa.orm.relationship(Location)
self.Event = Event
self.Location = Location
self.Address = Address
def create_forms(self):
class AddressForm(ModelForm):
class Meta:
model = self.Address
class LocationForm(ModelForm):
class Meta:
model = self.Location
addresses = ModelFieldList(FormField(AddressForm))
class EventForm(ModelForm):
class Meta:
model = self.Event
location = ModelFormField(LocationForm)
self.LocationForm = LocationForm
self.EventForm = EventForm
self.AddressForm = AddressForm
def save(self):
data = {
"name": "Some event",
"location-name": "Some location",
"location-addresses-0-street": "Some address",
}
event = self.Event()
self.session.add(event)
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
def test_assigment_and_deletion(self):
self.save()
event = self.session.query(self.Event).first()
assert event.location.name == "Some location"
assert event.location.addresses[0].street == "Some address"
data = {"name": "Some event"}
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
event = self.session.query(self.Event).first()
assert event.location.addresses == []
wtforms-alchemy-0.19.0/tests/test_descriptions.py 0000664 0000000 0000000 00000001223 14716610423 0022205 0 ustar 00root root 0000000 0000000 from tests import ModelFormTestCase
from wtforms_alchemy import ModelForm
class TestFieldParameters(ModelFormTestCase):
def test_assigns_description_from_column_info(self):
self.init(info={"description": "Description"})
self.assert_description("test_column", "Description")
def test_assigns_descriptions_from_form_configuration(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
field_args = {"test_column": {"description": "TESTING"}}
self.form_class = ModelTestForm
self.assert_description("test_column", "TESTING")
wtforms-alchemy-0.19.0/tests/test_field_exclusion.py 0000664 0000000 0000000 00000001634 14716610423 0022661 0 ustar 00root root 0000000 0000000 from datetime import datetime
import sqlalchemy as sa
from sqlalchemy_utils import TSVectorType
from tests import ModelFormTestCase
class TestFieldExclusion(ModelFormTestCase):
def test_does_not_include_datetime_columns_with_default(self):
self.init(sa.DateTime, default=datetime.now())
assert not self.has_field("test_column")
def test_excludes_surrogate_primary_keys_by_default(self):
self.init()
assert not self.has_field("id")
def test_excludes_column_properties(self):
self.init()
self.ModelTest.calculated_value = sa.orm.column_property(
sa.func.lower(self.ModelTest.test_column)
)
self.init_form()
self.form_class()
class TestTSVectorType(ModelFormTestCase):
def test_does_not_include_tsvector_typed_columns_with_default(self):
self.init(TSVectorType)
assert not self.has_field("test_column")
wtforms-alchemy-0.19.0/tests/test_field_order.py 0000664 0000000 0000000 00000001747 14716610423 0021770 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from tests import ModelFormTestCase
class TestFieldOrder(ModelFormTestCase):
def setup_method(self, method):
ModelFormTestCase.setup_method(self, method)
class ModelTest(self.base):
__tablename__ = "model_test"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
full_description = sa.Column(sa.UnicodeText)
description = sa.Column(sa.UnicodeText)
start_time = sa.Column(sa.DateTime)
end_time = sa.Column(sa.DateTime)
category = sa.Column(sa.Unicode(255))
entry_fee = sa.Column(sa.Numeric)
type = sa.Column(sa.Unicode(255))
self.ModelTest = ModelTest
self.init_form()
def test_field_definition_order(self):
field_names = [field.name for field in self.form_class()]
assert field_names == sa.inspect(self.ModelTest).attrs.keys()[1:]
wtforms-alchemy-0.19.0/tests/test_field_parameters.py 0000664 0000000 0000000 00000010107 14716610423 0023006 0 ustar 00root root 0000000 0000000 from datetime import date, time
import sqlalchemy as sa
from sqlalchemy_utils import IntRangeType
from wtforms import widgets
from wtforms.fields import StringField
from wtforms.validators import NumberRange
from wtforms_components import DateRange, TimeRange
from tests import ModelFormTestCase
from wtforms_alchemy import ModelForm
class TestFieldParameters(ModelFormTestCase):
def test_accepts_custom_widgets(self):
self.init(info={"widget": widgets.HiddenInput()})
form = self.form_class()
assert isinstance(form.test_column.widget, widgets.HiddenInput)
def test_accepts_custom_filters(self):
def test_filter(a):
return a
self.init(info={"filters": [test_filter]})
form = self.form_class()
assert test_filter in form.test_column.filters
def test_assigns_description_from_column_info(self):
self.init(info={"description": "Description"})
self.assert_description("test_column", "Description")
def test_does_not_add_default_value_if_default_is_callable(self):
self.init(default=lambda: "test")
self.assert_default("test_column", None)
def test_assigns_scalar_defaults(self):
self.init(default="test")
self.assert_default("test_column", "test")
def test_min_and_max_info_attributes_with_integer_field(self):
self.init(type_=sa.Integer, info={"min": 1, "max": 100})
validator = self.get_validator("test_column", NumberRange)
assert validator.min == 1
assert validator.max == 100
def test_min_and_max_info_attributes_with_numeric_field(self):
self.init(type_=sa.Numeric, info={"min": 1, "max": 100})
validator = self.get_validator("test_column", NumberRange)
assert validator.min == 1
assert validator.max == 100
def test_min_and_max_info_attributes_with_float_field(self):
self.init(type_=sa.Float, info={"min": 1, "max": 100})
validator = self.get_validator("test_column", NumberRange)
assert validator.min == 1
assert validator.max == 100
def test_min_and_max_info_attributes_with_int_range_field(self):
self.init(type_=IntRangeType, info={"min": 1, "max": 100})
validator = self.get_validator("test_column", NumberRange)
assert validator.min == 1
assert validator.max == 100
def test_min_and_max_info_attributes_generate_time_range_validator(self):
self.init(type_=sa.types.Time, info={"min": time(12, 30), "max": time(14, 30)})
validator = self.get_validator("test_column", TimeRange)
assert validator.min == time(12, 30)
assert validator.max == time(14, 30)
def test_min_and_max_info_attributes_generate_date_range_validator(self):
self.init(
type_=sa.Date, info={"min": date(1990, 1, 1), "max": date(2000, 1, 1)}
)
validator = self.get_validator("test_column", DateRange)
assert validator.min == date(1990, 1, 1)
assert validator.max == date(2000, 1, 1)
def test_uses_custom_field_class(self):
class InputTest(widgets.Input):
input_type = "color"
validation_attrs = []
class FieldTest(StringField):
widget = InputTest()
class ModelTest(self.base):
__tablename__ = "model_test"
query = None
id = sa.Column(sa.Integer, primary_key=True)
test_column = sa.Column(
sa.UnicodeText, info={"form_field_class": FieldTest}
)
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
form = ModelTestForm()
assert 'type="color"' in str(form.test_column)
def test_accepts_none_as_custom_field_class(self):
class ModelTest(self.base):
__tablename__ = "model_test"
query = None
id = sa.Column(sa.Integer, primary_key=True)
test_column = sa.Column(sa.UnicodeText, info={"form_field_class": None})
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
assert ModelTestForm()
wtforms-alchemy-0.19.0/tests/test_field_trimming.py 0000664 0000000 0000000 00000001546 14716610423 0022500 0 ustar 00root root 0000000 0000000 from tests import ModelFormTestCase, MultiDict
from wtforms_alchemy import ModelForm
class TestStringFieldTrimming(ModelFormTestCase):
def test_strip_string_fields_set_for_string_field(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
strip_string_fields = True
f = ModelTestForm(MultiDict([("test_column", "strip this ")]))
assert f.test_column.data == "strip this"
def test_does_not_trim_fields_when_trim_param_is_false(self):
self.init(info={"trim": False})
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
strip_string_fields = True
f = ModelTestForm(MultiDict([("test_column", "strip this ")]))
assert f.test_column.data == "strip this "
wtforms-alchemy-0.19.0/tests/test_form_meta.py 0000664 0000000 0000000 00000002173 14716610423 0021455 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from tests import ModelFormTestCase
class TestModelFormMetaWithInheritance(ModelFormTestCase):
def test_skip_unknown_types(self, model_form_all):
self.init(type_=sa.Integer)
class ModelTestForm(model_form_all):
class Meta:
skip_unknown_types = True
class ModelTestForm2(ModelTestForm):
class Meta:
model = self.ModelTest
self.form_class = ModelTestForm2
assert self.form_class.Meta.skip_unknown_types is True
def test_inheritance_attributes(self, model_form_custom):
self.init(type_=sa.Integer)
class ModelTestForm(model_form_custom):
class Meta:
model = self.ModelTest
assert ModelTestForm.test_attr == "SomeVal"
class TestUnboundFieldsInitialization(ModelFormTestCase):
def test_skip_unknown_types(self, model_form_all):
self.init(type_=sa.Integer)
class ModelTestForm(model_form_all):
class Meta:
model = self.ModelTest
skip_unknown_types = True
assert ModelTestForm.test_column
wtforms-alchemy-0.19.0/tests/test_hybrid_properties.py 0000664 0000000 0000000 00000003772 14716610423 0023247 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from pytest import raises
from sqlalchemy.ext.hybrid import hybrid_property
from tests import ModelFormTestCase
from wtforms_alchemy import ModelForm
from wtforms_alchemy.exc import AttributeTypeException
class TestHybridProperties(ModelFormTestCase):
def test_hybrid_property_returning_column_property(self):
class ModelTest(self.base):
__tablename__ = "model_test"
id = sa.Column(sa.Integer, primary_key=True)
_test_column = sa.Column("test_column", sa.Boolean, nullable=False)
@hybrid_property
def test_column_hybrid(self):
return self._test_column
@test_column_hybrid.setter
def test_column_hybrid(self, value):
self._test_column = value
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
not_null_str_validator = None
not_null_validator = None
include = ("test_column_hybrid",)
exclude = ("_test_column",)
form = ModelTestForm()
assert form.test_column_hybrid
def test_hybrid_property_returning_expression(self):
class ModelTest(self.base):
__tablename__ = "model_test"
id = sa.Column(sa.Integer, primary_key=True)
_test_column = sa.Column("test_column", sa.Boolean, nullable=False)
@hybrid_property
def test_column_hybrid(self):
return self._test_column + self._test_column
@test_column_hybrid.setter
def test_column_hybrid(self, value):
self._test_column = value
with raises(AttributeTypeException):
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
not_null_str_validator = None
not_null_validator = None
include = ("test_column_hybrid",)
exclude = ("_test_column",)
wtforms-alchemy-0.19.0/tests/test_i18n_extension.py 0000664 0000000 0000000 00000004000 14716610423 0022346 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from packaging.version import Version
from pytest import raises, skip
from sqlalchemy_i18n import make_translatable, Translatable, translation_base
from tests import ModelFormTestCase, MultiDict
from wtforms_alchemy import ModelForm
sqlalchemy_version = sa.__version__
if Version(sqlalchemy_version) >= Version("2.0"):
skip("sqlalchemy_i18n does not support SQLAlchemy 2.0", allow_module_level=True)
make_translatable()
class TestInternationalizationExtension(ModelFormTestCase):
def init(self):
class ModelTest(self.base, Translatable):
__tablename__ = "model_test"
__translatable__ = {"locales": ["fi", "en"]}
id = sa.Column(sa.Integer, primary_key=True)
some_property = "something"
locale = "en"
class ModelTranslation(translation_base(ModelTest)):
__tablename__ = "model_translation"
name = sa.Column(sa.Unicode(255))
content = sa.Column(sa.Unicode(255))
self.ModelTest = ModelTest
sa.orm.configure_mappers()
Session = sa.orm.sessionmaker(bind=self.engine)
self.session = Session()
self.init_form()
def test_supports_translated_columns(self):
self.init()
form = self.form_class()
assert form.name
assert form.content
def test_supports_field_exclusion(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
exclude = ["name"]
with raises(AttributeError):
ModelTestForm().name
def test_model_population(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
form = ModelTestForm(
MultiDict([("name", "something"), ("content", "something")])
)
obj = self.ModelTest()
form.populate_obj(obj)
assert obj.name == "something"
assert obj.content == "something"
wtforms-alchemy-0.19.0/tests/test_inheritance.py 0000664 0000000 0000000 00000002055 14716610423 0021774 0 ustar 00root root 0000000 0000000 from wtforms import Form
from wtforms_test import FormTestCase
from wtforms_alchemy import model_form_factory, ModelForm
class TestInheritance(FormTestCase):
class Base(Form):
@classmethod
def get_session(self):
return "TestSession"
def test_default_base(self):
assert ModelForm.get_session is None
def test_custom_base_without_session(self):
cls = model_form_factory(Form)
assert cls.get_session is None
def test_custom_base_with_session(self):
cls = model_form_factory(self.Base)
assert cls.get_session() == "TestSession"
def test_inherit_with_new_session(self):
cls = model_form_factory(self.Base)
class Sub(cls):
@classmethod
def get_session(self):
return "SubTestSession"
assert Sub.get_session() == "SubTestSession"
def test_inherit_without_new_session(self):
cls = model_form_factory(self.Base)
class Sub(cls):
pass
assert Sub.get_session() == "TestSession"
wtforms-alchemy-0.19.0/tests/test_labels.py 0000664 0000000 0000000 00000001154 14716610423 0020744 0 ustar 00root root 0000000 0000000 from tests import ModelFormTestCase
from wtforms_alchemy import ModelForm
class TestFieldLabels(ModelFormTestCase):
def test_assigns_labels_from_column_info(self):
self.init(info={"label": "Test Column"})
self.assert_label("test_column", "Test Column")
def test_assigns_labels_from_form_configuration(self):
self.init()
class ModelTestForm(ModelForm):
class Meta:
model = self.ModelTest
field_args = {"test_column": {"label": "TESTING"}}
self.form_class = ModelTestForm
self.assert_label("test_column", "TESTING")
wtforms-alchemy-0.19.0/tests/test_model_field_list.py 0000664 0000000 0000000 00000017770 14716610423 0023013 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from wtforms.fields import FormField
from wtforms_components import PassiveHiddenField
from tests import FormRelationsTestCase, MultiDict
from wtforms_alchemy import ModelFieldList, ModelForm
class ModelFieldListTestCase(FormRelationsTestCase):
def create_models(self):
class Event(self.base):
__tablename__ = "event"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
class Location(self.base):
__tablename__ = "location"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))
event = sa.orm.relationship(Event, backref="locations")
self.Event = Event
self.Location = Location
def save(self, event=None, data=None):
if data is None:
data = {
"name": "Some event",
"locations-0-name": "Some location",
"locations-0-description": "Some description",
}
if not event:
event = self.Event()
self.session.add(event)
form = self.EventForm(MultiDict(data))
else:
form = self.EventForm(MultiDict(data), obj=event)
form.validate()
form.populate_obj(event)
self.session.commit()
return event
class TestReplaceStrategy(ModelFieldListTestCase):
def create_forms(self):
class LocationForm(ModelForm):
class Meta:
model = self.Location
class EventForm(ModelForm):
class Meta:
model = self.Event
locations = ModelFieldList(FormField(LocationForm))
self.LocationForm = LocationForm
self.EventForm = EventForm
def test_assigment_and_deletion(self):
self.save()
event = self.session.query(self.Event).first()
assert event.locations[0].name == "Some location"
data = {"name": "Some event"}
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
event = self.session.query(self.Event).first()
assert event.locations == []
class TestUpdateStrategy(ModelFieldListTestCase):
def create_models(self):
class Event(self.base):
__tablename__ = "event"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
class Location(self.base):
__tablename__ = "location"
TYPES = ("", "football field", "restaurant")
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
description = sa.Column(sa.Unicode(255), default="")
type = sa.Column(
sa.Unicode(255), info={"choices": zip(TYPES, TYPES)}, default=""
)
event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))
event = sa.orm.relationship(Event, backref="locations")
def __repr__(self):
return f"Location(id={self.id!r}, name={self.name!r})"
self.Event = Event
self.Location = Location
def create_forms(self):
class LocationForm(ModelForm):
class Meta:
model = self.Location
only = ["name", "description", "type"]
id = PassiveHiddenField()
class EventForm(ModelForm):
class Meta:
model = self.Event
locations = ModelFieldList(
FormField(LocationForm), population_strategy="update"
)
self.LocationForm = LocationForm
self.EventForm = EventForm
def test_single_entry_update(self):
event = self.save()
location_id = event.locations[0].id
data = {
"name": "Some event",
"locations-0-id": location_id,
"locations-0-name": "Some other location",
}
self.save(event, data)
assert len(event.locations) == 1
assert event.locations[0].id == location_id
assert event.locations[0].name == "Some other location"
def test_creates_new_objects_for_entries_with_unknown_identifiers(self):
event = self.save()
location_id = event.locations[0].id
data = {
"name": "Some event",
"locations-0-id": 12,
"locations-0-name": "Some other location",
}
self.save(event, data)
assert event.locations
assert event.locations[0].id != location_id
def test_replace_entry(self):
data = {
"name": "Some event",
"locations-0-name": "Some location",
"locations-0-description": "Some description",
"locations-0-type": "restaurant",
}
event = self.save(data=data)
location_id = event.locations[0].id
self.session.commit()
data = {
"name": "Some event",
"locations-0-name": "Some other location",
}
self.save(event, data)
location = event.locations[0]
assert location.id != location_id
assert location.name == "Some other location"
assert location.description == ""
assert location.type == ""
assert len(event.locations) == 1
def test_replace_and_update(self):
data = {
"name": "Some event",
"locations-0-name": "Location 1",
"locations-0-description": "Location 1 description",
"locations-1-name": "Location 2",
"locations-1-description": "Location 2 description",
}
event = self.save(data=data)
self.session.commit()
data = {
"name": "Some event",
"locations-0-id": event.locations[1].id,
"locations-0-name": "Location 2 updated",
"locations-0-description": "Location 2 description updated",
"locations-1-name": "Location 3",
}
self.save(event, data)
self.session.commit()
location = event.locations[0]
location2 = event.locations[1]
assert location.name == "Location 2 updated"
assert location.description == "Location 2 description updated"
assert len(event.locations) == 2
assert location2.name == "Location 3"
assert location2.description == ""
def test_multiple_entries(self):
event = self.save()
location_id = event.locations[0].id
data = {
"name": "Some event",
"locations-0-name": "Some location",
"locations-1-id": str(location_id), # test coercing works
"locations-1-name": "Some other location",
"locations-2-name": "Third location",
"locations-3-id": 123,
"locations-3-name": "Fourth location",
}
self.save(event, data)
assert len(event.locations) == 4
assert event.locations[0].id == location_id
assert event.locations[0].name == "Some other location"
assert event.locations[1].name == "Some location"
assert event.locations[2].name == "Third location"
assert event.locations[3].name == "Fourth location"
def test_delete_all_field_list_entries(self):
event = self.save()
data = {"name": "Some event"}
self.save(event, data)
assert not event.locations
def test_update_and_remove(self):
location = self.Location(name="Location #2")
event = self.Event(
name="Some event", locations=[self.Location(name="Location #1"), location]
)
self.session.add(event)
self.session.commit()
data = {
"locations-0-id": location.id,
"locations-0-name": "Location",
}
self.save(event, data)
self.session.refresh(event)
assert len(event.locations) == 1
assert event.locations[0] == location
wtforms-alchemy-0.19.0/tests/test_model_form_factory.py 0000664 0000000 0000000 00000007014 14716610423 0023355 0 ustar 00root root 0000000 0000000 import wtforms
from pytest import raises
from wtforms import Form
from tests import ModelFormTestCase
from wtforms_alchemy import (
FormGenerator,
model_form_factory,
UnknownConfigurationOption,
)
class TestModelFormFactory(ModelFormTestCase):
def test_supports_parameter_overriding(self):
self.init()
class MyFormGenerator(FormGenerator):
pass
defaults = {
"all_fields_optional": True,
"only_indexed_fields": True,
"include_primary_keys": True,
"include_foreign_keys": True,
"strip_string_fields": True,
"include_datetimes_with_default": True,
"form_generator": True,
"date_format": "%d-%m-%Y",
"datetime_format": "%Y-%m-%dT%H:%M:%S",
}
ModelForm = model_form_factory(Form, **defaults)
for key, value in defaults.items():
assert getattr(ModelForm.Meta, key) == value
def test_throws_exception_for_unknown_configuration_option(self):
self.init()
class SomeForm(Form):
pass
defaults = {"unknown": "something"}
with raises(UnknownConfigurationOption):
model_form_factory(SomeForm, **defaults)
def test_supports_custom_base_class_with_model_form_factory(self):
self.init()
class SomeForm(Form):
pass
class TestCustomBase(model_form_factory(SomeForm)):
class Meta:
model = self.ModelTest
assert isinstance(TestCustomBase(), SomeForm)
def test_url_validator(self):
form = model_form_factory(url_validator=None)
assert form.Meta.url_validator is None
def test_email_validator(self):
form = model_form_factory(email_validator=None)
assert form.Meta.email_validator is None
def test_length_validator(self):
form = model_form_factory(length_validator=None)
assert form.Meta.length_validator is None
def test_number_range_validator(self):
form = model_form_factory(number_range_validator=None)
assert form.Meta.number_range_validator is None
def test_date_range_validator(self):
form = model_form_factory(date_range_validator=None)
assert form.Meta.date_range_validator is None
def test_time_range_validator(self):
form = model_form_factory(time_range_validator=None)
assert form.Meta.time_range_validator is None
def test_optional_validator(self):
form = model_form_factory(optional_validator=None)
assert form.Meta.optional_validator is None
def test_unique_validator(self):
form = model_form_factory(unique_validator=None)
assert form.Meta.unique_validator is None
def test_class_meta_wtforms2(self):
self.init()
class SomeForm(Form):
class Meta:
locales = ["fr"]
foo = 9
class OtherForm(SomeForm):
class Meta:
pass
class TestCustomBase(model_form_factory(SomeForm)):
class Meta:
model = self.ModelTest
form = TestCustomBase()
other_form = OtherForm()
assert isinstance(form.meta, wtforms.meta.DefaultMeta)
assert form.meta.locales == ["fr"]
assert hasattr(form.meta, "model")
assert hasattr(form.meta, "csrf")
assert form.meta.foo == 9
# Create a side effect on the base meta.
SomeForm.Meta.foo = 12
assert other_form.meta.foo == 12
assert form.meta.foo == 12
wtforms-alchemy-0.19.0/tests/test_model_form_field.py 0000664 0000000 0000000 00000005414 14716610423 0022773 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from pytest import raises
from tests import FormRelationsTestCase, MultiDict
from wtforms_alchemy import ModelForm, ModelFormField
class TestOneToOneModelFormRelations(FormRelationsTestCase):
def create_models(self):
class Location(self.base):
__tablename__ = "location"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=True)
class Event(self.base):
__tablename__ = "event"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(255), nullable=False)
location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))
location = sa.orm.relationship(Location)
self.Event = Event
self.Location = Location
def create_forms(self):
class LocationForm(ModelForm):
class Meta:
model = self.Location
class EventForm(ModelForm):
class Meta:
model = self.Event
location = ModelFormField(LocationForm)
self.LocationForm = LocationForm
self.EventForm = EventForm
def save(self, event=None, data={}):
if not data:
data = {
"name": "Some event",
"location-name": "Some location",
}
if not event:
event = self.Event()
self.session.add(event)
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
return event
def test_assigment_and_deletion(self):
self.save()
event = self.session.query(self.Event).first()
assert event.location.name == "Some location"
data = {"name": "Some event"}
form = self.EventForm(MultiDict(data))
form.validate()
form.populate_obj(event)
self.session.commit()
event = self.session.query(self.Event).first()
assert not event.location.name
def test_only_populates_related_if_they_are_obj_attributes(self):
class EventForm(ModelForm):
class Meta:
model = self.Event
unknown_field = ModelFormField(self.LocationForm)
self.EventForm = EventForm
with raises(TypeError):
self.save(
data={
"name": "Some event",
"unknown_field-name": "Some location",
}
)
def test_updating_related_object(self):
event = self.save()
location_id = event.location.id
self.save(event, {"name": "some name", "location-name": "Some other location"})
assert event.name == "some name"
assert event.location.id == location_id
wtforms-alchemy-0.19.0/tests/test_phone_number.py 0000664 0000000 0000000 00000005451 14716610423 0022167 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from pytest import mark
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy.orm.session import close_all_sessions
from sqlalchemy_utils.types import phone_number
from tests import MultiDict
from wtforms_alchemy import ModelForm
class TestCase:
def setup_method(self, method):
self.engine = create_engine("sqlite:///:memory:")
self.Base = declarative_base()
self.create_models()
self.Base.metadata.create_all(self.engine)
Session = sessionmaker(bind=self.engine)
self.session = Session()
def teardown_method(self, method):
close_all_sessions()
self.Base.metadata.drop_all(self.engine)
self.engine.dispose()
def create_models(self):
class User(self.Base):
__tablename__ = "user"
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255))
phone_number = sa.Column(phone_number.PhoneNumberType(country_code="FI"))
self.User = User
@mark.xfail("phone_number.phonenumbers is None")
class TestPhoneNumbers(TestCase):
"""
Simple tests to ensure that sqlalchemy_utils.PhoneNumber,
wtforms_alchemy.PhoneNumberType and sqlalchemy_utils.PhoneNumberField work
nicely together.
"""
def setup_method(self, method):
super().setup_method(method)
class UserForm(ModelForm):
class Meta:
model = self.User
self.UserForm = UserForm
super().setup_method(method)
self.phone_number = phone_number.PhoneNumber("040 1234567", "FI")
self.user = self.User()
self.user.name = "Someone"
self.user.phone_number = self.phone_number
self.session.add(self.user)
self.session.commit()
def test_query_returns_phone_number_object(self):
queried_user = self.session.query(self.User).first()
assert queried_user.phone_number == self.phone_number
def test_phone_number_is_stored_as_string(self):
result = self.session.execute(
sa.text("SELECT phone_number FROM user WHERE id=:param"),
{"param": self.user.id},
)
assert result.first()[0] == "+358401234567"
def test_phone_number_in_form(self):
form = self.UserForm(
MultiDict(name="Matti Meikalainen", phone_number="+358401231233")
)
form.validate()
assert len(form.errors) == 0
assert form.data["phone_number"] == (phone_number.PhoneNumber("+358401231233"))
def test_empty_phone_number_in_form(self):
form = self.UserForm(MultiDict(name="Matti Meikalainen", phone_number=""))
form.validate()
assert len(form.errors) == 0
assert form.data["phone_number"] is None
wtforms-alchemy-0.19.0/tests/test_phone_number_field.py 0000664 0000000 0000000 00000007024 14716610423 0023330 0 ustar 00root root 0000000 0000000 import pytest
from wtforms import Form
from tests import MultiDict
from wtforms_alchemy import DataRequired, PhoneNumberField
class TestPhoneNumberField:
def setup_method(self, method):
self.valid_phone_numbers = [
"040 1234567",
"+358 401234567",
"09 2501234",
"+358 92501234",
"0800 939393",
"09 4243 0456",
"0600 900 500",
]
self.invalid_phone_numbers = ["abc", "+040 1234567", "0111234567", "358"]
def init_form(self, **kwargs):
class TestForm(Form):
phone_number = PhoneNumberField(**kwargs)
return TestForm
def test_valid_phone_numbers(self):
form_class = self.init_form(region="FI")
for phone_number in self.valid_phone_numbers:
form = form_class(MultiDict(phone_number=phone_number))
form.validate()
assert len(form.errors) == 0
def test_invalid_phone_numbers(self):
form_class = self.init_form(region="FI")
for phone_number in self.invalid_phone_numbers:
form = form_class(MultiDict(phone_number=phone_number))
form.validate()
assert len(form.errors["phone_number"]) == 1
def test_render_empty_phone_number_value(self):
form_class = self.init_form(region="FI")
form = form_class(MultiDict(phone_number=""))
assert 'value=""' in form.phone_number()
def test_empty_phone_number_value_passed_as_none(self):
form_class = self.init_form(region="FI")
form = form_class(MultiDict(phone_number=""))
form.validate()
assert len(form.errors) == 0
assert form.data["phone_number"] is None
def test_default_display_format(self):
form_class = self.init_form(region="FI")
form = form_class(MultiDict(phone_number="+358401234567"))
assert 'value="040 1234567"' in form.phone_number()
def test_international_display_format(self):
form_class = self.init_form(region="FI", display_format="international")
form = form_class(MultiDict(phone_number="0401234567"))
assert 'value="+358 40 1234567"' in form.phone_number()
def test_e164_display_format(self):
form_class = self.init_form(region="FI", display_format="e164")
form = form_class(MultiDict(phone_number="0401234567"))
assert 'value="+358401234567"' in form.phone_number()
def test_field_rendering_when_invalid_phone_number(self):
form_class = self.init_form()
form = form_class(MultiDict(phone_number="invalid"))
form.validate()
assert 'value="invalid"' in form.phone_number()
@pytest.mark.parametrize(
"number,error_msg,check_value",
(
("", "This field is required.", lambda v, orig: v is None),
("1", "Not a valid phone number value", lambda v, orig: v is not None),
("123", "Not a valid phone number value", lambda v, orig: v is not None),
("+46123456789", None, lambda v, orig: v.e164 == orig),
),
)
def test_required_phone_number_form(self, number, error_msg, check_value):
class PhoneNumberForm(Form):
phone = PhoneNumberField("Phone number", validators=[DataRequired()])
form = PhoneNumberForm(MultiDict(phone=number))
form.validate()
if error_msg:
assert len(form.errors) == 1
assert form.errors["phone"][0] == error_msg
else:
assert len(form.errors) == 0
assert check_value(form.phone.data, number) is True
wtforms-alchemy-0.19.0/tests/test_query_select_field.py 0000664 0000000 0000000 00000032702 14716610423 0023354 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm.session import close_all_sessions
from wtforms import Form
from wtforms_alchemy import (
GroupedQuerySelectField,
GroupedQuerySelectMultipleField,
ModelForm,
QuerySelectField,
QuerySelectMultipleField,
)
class DummyPostData(dict):
def getlist(self, key):
v = self[key]
if not isinstance(v, (list, tuple)):
v = [v]
return v
class LazySelect:
def __call__(self, field, **kwargs):
return list(
(val, str(label), selected)
for val, label, selected, _ in field.iter_choices()
)
class Base:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
class TestBase:
def create_models(self):
class Test(self.base):
__tablename__ = "test"
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
name = sa.Column(sa.String, nullable=False)
self.Test = Test
class PKTest(self.base):
__tablename__ = "pk_test"
foobar = sa.Column(sa.String, primary_key=True, nullable=False)
baz = sa.Column(sa.String, nullable=False)
def __str__(self):
return self.baz
self.PKTest = PKTest
def _fill(self, sess):
for i, n in [(1, "apple"), (2, "banana")]:
s = self.Test(id=i, name=n)
p = self.PKTest(foobar=f"hello{i}", baz=n)
sess.add(s)
sess.add(p)
sess.flush()
sess.commit()
class TestQuerySelectField(TestBase):
def setup_method(self):
self.engine = sa.create_engine("sqlite:///:memory:", echo=False)
self.base = declarative_base()
self.create_models()
self.base.metadata.create_all(self.engine)
Session = sa.orm.session.sessionmaker(bind=self.engine)
self.session = Session()
def teardown_method(self):
close_all_sessions()
self.base.metadata.drop_all(self.engine)
self.engine.dispose()
def test_without_factory(self):
self._fill(self.session)
class F(Form):
a = QuerySelectField(
get_label="name", widget=LazySelect(), get_pk=lambda x: x.id
)
form = F(DummyPostData(a=["1"]))
form.a.query = self.session.query(self.Test)
assert form.a.data is not None
assert form.a.data.id, 1
assert form.a(), [("1", "apple", True), ("2", "banana", False)]
assert form.validate()
form = F(a=self.session.query(self.Test).filter_by(name="banana").first())
form.a.query = self.session.query(self.Test).filter(self.Test.name != "banana")
assert not form.validate()
assert form.a.errors, ["Not a valid choice"]
# Test query with no results
form = F()
form.a.query = (
self.session.query(self.Test)
.filter(self.Test.id == 1, self.Test.id != 1)
.all()
)
assert form.a() == []
def test_with_query_factory(self):
self._fill(self.session)
class F(Form):
a = QuerySelectField(
get_label=(lambda model: model.name),
query_factory=lambda: self.session.query(self.Test),
widget=LazySelect(),
)
b = QuerySelectField(
allow_blank=True,
query_factory=lambda: self.session.query(self.PKTest),
widget=LazySelect(),
)
form = F()
assert form.a.data is None
assert form.a() == [("1", "apple", False), ("2", "banana", False)]
assert form.b.data is None
assert form.b() == [
("__None", "", True),
("hello1", "apple", False),
("hello2", "banana", False),
]
assert not form.validate()
# Test query with no results
form = F()
form.a.query = (
self.session.query(self.Test)
.filter(self.Test.id == 1, self.Test.id != 1)
.all()
)
assert form.a() == []
form = F(DummyPostData(a=["1"], b=["hello2"]))
assert form.a.data.id == 1
assert form.a() == [("1", "apple", True), ("2", "banana", False)]
assert form.b.data.baz == "banana"
assert form.b() == [
("__None", "", False),
("hello1", "apple", False),
("hello2", "banana", True),
]
assert form.validate()
# Make sure the query is cached
self.session.add(self.Test(id=3, name="meh"))
self.session.flush()
self.session.commit()
assert form.a() == [("1", "apple", True), ("2", "banana", False)]
form.a._object_list = None
assert form.a() == [
("1", "apple", True),
("2", "banana", False),
("3", "meh", False),
]
# Test bad data
form = F(DummyPostData(b=["__None"], a=["fail"]))
assert not form.validate()
assert form.a.errors == ["Not a valid choice"]
assert form.b.errors == []
assert form.b.data is None
class TestQuerySelectMultipleField(TestBase):
def setup_method(self):
self.engine = sa.create_engine("sqlite:///:memory:", echo=False)
self.base = declarative_base()
self.create_models()
self.base.metadata.create_all(self.engine)
Session = sa.orm.session.sessionmaker(bind=self.engine)
self.session = Session()
self._fill(self.session)
def teardown_method(self):
close_all_sessions()
self.base.metadata.drop_all(self.engine)
self.engine.dispose()
class F(Form):
a = QuerySelectMultipleField(get_label="name", widget=LazySelect())
def test_unpopulated_default(self):
form = self.F()
assert [] == form.a.data
def test_single_value_without_factory(self):
form = self.F(DummyPostData(a=["1"]))
form.a.query = self.session.query(self.Test)
assert [1] == [v.id for v in form.a.data]
assert form.a() == [("1", "apple", True), ("2", "banana", False)]
assert form.validate()
def test_multiple_values_without_query_factory(self):
form = self.F(DummyPostData(a=["1", "2"]))
form.a.query = self.session.query(self.Test)
assert [1, 2] == [v.id for v in form.a.data]
assert form.a() == [("1", "apple", True), ("2", "banana", True)]
assert form.validate()
form = self.F(DummyPostData(a=["1", "3"]))
form.a.query = self.session.query(self.Test)
assert [x.id for x in form.a.data], [1]
assert not form.validate()
def test_single_default_value(self):
first_test = self.session.get(self.Test, 2)
class F(Form):
a = QuerySelectMultipleField(
get_label="name",
default=[first_test],
widget=LazySelect(),
query_factory=lambda: self.session.query(self.Test),
)
form = F()
assert [v.id for v in form.a.data], [2]
assert form.a(), [("1", "apple", False), ("2", "banana", True)]
assert form.validate()
def test_empty_query(self):
# Test query with no results
form = self.F()
form.a.query = (
self.session.query(self.Test)
.filter(self.Test.id == 1, self.Test.id != 1)
.all()
)
assert form.a() == []
class DatabaseTestCase:
def setup_method(self, method):
self.engine = sa.create_engine("sqlite:///:memory:")
self.base = declarative_base()
self.create_models()
self.base.metadata.create_all(self.engine)
Session = sa.orm.session.sessionmaker(bind=self.engine)
self.session = Session()
def teardown_method(self, method):
close_all_sessions()
self.base.metadata.drop_all(self.engine)
self.engine.dispose()
def create_models(self):
class City(self.base):
__tablename__ = "city"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
country = sa.Column(sa.String)
state_id = sa.Column(sa.Integer, sa.ForeignKey("state.id"))
self.City = City
class State(self.base):
__tablename__ = "state"
id = sa.Column(sa.Integer, primary_key=True)
cities = sa.orm.relationship("City")
self.State = State
def create_cities(self):
self.session.add_all(
[
self.City(name="Helsinki", country="Finland"),
self.City(name="Vantaa", country="Finland"),
self.City(name="New York", country="USA"),
self.City(name="Washington", country="USA"),
self.City(name="Stockholm", country="Sweden"),
]
)
class TestGroupedQuerySelectField(DatabaseTestCase):
def create_form(self, **kwargs):
query = self.session.query(self.City).order_by("name", "country")
class MyForm(Form):
city = GroupedQuerySelectField(
label=kwargs.get("label", "City"),
query_factory=kwargs.get("query_factory", lambda: query),
get_label=kwargs.get("get_label", lambda c: c.name),
get_group=kwargs.get("get_group", lambda c: c.country),
allow_blank=kwargs.get("allow_blank", False),
blank_text=kwargs.get("blank_text", ""),
blank_value=kwargs.get("blank_value", "__None"),
)
return MyForm
def test_custom_none_value(self):
self.create_cities()
MyForm = self.create_form(
allow_blank=True, blank_text="Choose city...", blank_value=""
)
form = MyForm(DummyPostData({"city": ""}))
assert form.validate(), form.errors
assert '' in (str(form.city))
def test_rendering(self):
MyForm = self.create_form()
self.create_cities()
assert str(MyForm().city).replace("\n", "") == (
'"
)
class TestGroupedQuerySelectMultipleField(DatabaseTestCase):
def create_form(self, **kwargs):
query = self.session.query(self.City).order_by("name", "country")
class MyForm(ModelForm):
class Meta:
model = self.State
cities = GroupedQuerySelectMultipleField(
label=kwargs.get("label", "City"),
query_factory=kwargs.get("query_factory", lambda: query),
get_label=kwargs.get("get_label", lambda c: c.name),
get_group=kwargs.get("get_group", lambda c: c.country),
blank_text=kwargs.get("blank_text", ""),
)
return MyForm
def test_unpopulated_default(self):
MyForm = self.create_form()
self.create_cities()
assert MyForm().cities.data == []
def test_single_value_without_factory(self):
obj = self.State()
MyForm = self.create_form()
self.create_cities()
form = MyForm(DummyPostData(cities=["1"]), obj=obj)
assert [1] == [v.id for v in form.cities.data]
assert form.validate()
form.populate_obj(obj)
assert [city.id for city in obj.cities] == [1]
def test_multiple_values_without_query_factory(self):
obj = self.State()
MyForm = self.create_form()
self.create_cities()
form = MyForm(DummyPostData(cities=["1", "2"]), obj=obj)
form.cities.query = self.session.query(self.City)
assert [1, 2] == [v.id for v in form.cities.data]
assert form.validate()
form.populate_obj(obj)
assert [city.id for city in obj.cities] == [1, 2]
form = MyForm(DummyPostData(cities=["1", "666"]))
form.cities.query = self.session.query(self.City)
assert not form.validate()
assert [x.id for x in form.cities.data] == [1]
assert not form.validate()
form.populate_obj(obj)
assert [city.id for city in obj.cities] == [1]
form = MyForm(DummyPostData(cities=["666"]))
form.cities.query = self.session.query(self.City)
assert not form.validate()
assert [x.id for x in form.cities.data] == []
assert not form.validate()
form.populate_obj(obj)
assert [city.id for city in obj.cities] == []
def test_rendering(self):
MyForm = self.create_form()
self.create_cities()
assert str(MyForm().cities).replace("\n", "") == (
'"
)
wtforms-alchemy-0.19.0/tests/test_select_field.py 0000664 0000000 0000000 00000006461 14716610423 0022132 0 ustar 00root root 0000000 0000000 from decimal import Decimal
import six
import sqlalchemy as sa
from wtforms_components import SelectField
from tests import ModelFormTestCase
class MultiDict(dict):
def getlist(self, key):
return [self[key]]
class TestSelectFieldDefaultValue(ModelFormTestCase):
def test_option_selected_by_field_default_value(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.Integer, default="1", info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2"}))
assert '' in str(form.test_column)
class TestSelectFieldCoerce(ModelFormTestCase):
def test_integer_coerces_values_to_integers(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.Integer, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2"}))
assert form.test_column.data == 2
def test_nullable_integer_coerces_values_to_integers(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.Integer, nullable=True, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2"}))
assert form.test_column.data == 2
def test_integer_coerces_empty_strings_to_nulls(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.Integer, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": ""}))
assert form.test_column.data is None
def test_big_integer_coerces_values_to_integers(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.BigInteger, info={"choices": choices})
self.assert_type("test_column", SelectField)
form = self.form_class(MultiDict({"test_column": "2"}))
assert form.test_column.data == 2
def test_small_integer_coerces_values_to_integers(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.SmallInteger, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2"}))
assert form.test_column.data == 2
def test_numeric_coerces_values_to_decimals(self):
choices = [("1.0", "1.0"), ("2.0", "2.0")]
self.init(type_=sa.Numeric, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2.0"}))
assert form.test_column.data == Decimal("2.0")
def test_float_coerces_values_to_floats(self):
choices = [("1.0", "1.0"), ("2.0", "2.0")]
self.init(type_=sa.Float, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2.0"}))
assert form.test_column.data == 2.0
def test_unicode_coerces_values_to_unicode_strings(self):
choices = [("1.0", "1.0"), ("2.0", "2.0")]
self.init(type_=sa.Unicode(255), info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2.0"}))
assert form.test_column.data == "2.0"
assert isinstance(form.test_column.data, six.text_type)
def test_unicode_text_coerces_values_to_unicode_strings(self):
choices = [("1.0", "1.0"), ("2.0", "2.0")]
self.init(type_=sa.UnicodeText, info={"choices": choices})
form = self.form_class(MultiDict({"test_column": "2.0"}))
assert form.test_column.data == "2.0"
assert isinstance(form.test_column.data, six.text_type)
wtforms-alchemy-0.19.0/tests/test_synonym.py 0000664 0000000 0000000 00000003704 14716610423 0021221 0 ustar 00root root 0000000 0000000 import sqlalchemy as sa
from sqlalchemy.ext.hybrid import hybrid_property
from tests import ModelFormTestCase
from wtforms_alchemy import ModelForm
class TestSynonym(ModelFormTestCase):
def test_synonym_returning_column_property_with_include(self):
class ModelTest(self.base):
__tablename__ = "model_test"
id = sa.Column(sa.Integer, primary_key=True)
_test_column = sa.Column("test_column", sa.Integer, nullable=False)
@hybrid_property
def test_column_hybrid(self):
return self.test_column * 2
@test_column_hybrid.setter
def test_column_hybrid(self, value):
self._test_column = value
test_column = sa.orm.synonym("_test_column")
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
not_null_str_validator = None
not_null_validator = None
include = ("test_column",)
exclude = ("_test_column",)
form = ModelTestForm()
assert form.test_column
def test_synonym_returning_column_property_with_only(self):
class ModelTest(self.base):
__tablename__ = "model_test"
id = sa.Column(sa.Integer, primary_key=True)
_test_column = sa.Column("test_column", sa.Integer, nullable=False)
@hybrid_property
def test_column_hybrid(self):
return self.test_column * 2
@test_column_hybrid.setter
def test_column_hybrid(self, value):
self._test_column = value
test_column = sa.orm.synonym("_test_column")
class ModelTestForm(ModelForm):
class Meta:
model = ModelTest
not_null_str_validator = None
not_null_validator = None
only = ("test_column",)
form = ModelTestForm()
assert form.test_column
wtforms-alchemy-0.19.0/tests/test_types.py 0000664 0000000 0000000 00000026325 14716610423 0020655 0 ustar 00root root 0000000 0000000 from enum import Enum
import sqlalchemy as sa
from pytest import mark, raises
from sqlalchemy_utils import (
ChoiceType,
ColorType,
CountryType,
EmailType,
IntRangeType,
PasswordType,
PhoneNumberType,
URLType,
UUIDType,
)
from sqlalchemy_utils.types import arrow, phone_number, WeekDaysType # noqa
from wtforms.fields import (
BooleanField,
FloatField,
PasswordField,
TextAreaField,
)
from wtforms.validators import Length, URL
from wtforms_components import Email
from wtforms_components.fields import (
ColorField,
DateField,
DateTimeField,
DecimalField,
EmailField,
IntegerField,
IntIntervalField,
SelectField,
StringField,
TimeField,
)
from tests import ModelFormTestCase
from wtforms_alchemy import (
CountryField,
ModelForm,
null_or_unicode,
PhoneNumberField,
UnknownTypeException,
WeekDaysField,
)
from wtforms_alchemy.utils import ClassMap
try:
import passlib # noqa
except ImportError:
passlib = None
class UnknownType(sa.types.UserDefinedType):
def get_col_spec(self):
return "UNKNOWN()"
class CustomUnicodeTextType(sa.types.TypeDecorator):
impl = sa.types.UnicodeText
class CustomUnicodeType(sa.types.TypeDecorator):
impl = sa.types.Unicode
class CustomNumericType(sa.types.TypeDecorator):
impl = sa.types.Numeric
class TestModelColumnToFormFieldTypeConversion(ModelFormTestCase):
def test_raises_exception_for_unknown_type(self):
with raises(UnknownTypeException):
self.init(type_=UnknownType)
self.form_class()
def test_raises_exception_for_array_type(self):
with raises(UnknownTypeException):
self.init(type_=sa.ARRAY(sa.Integer))
self.form_class()
def test_unicode_converts_to_text_field(self):
self.init()
self.assert_type("test_column", StringField)
def test_custom_unicode_converts_to_text_field(self):
self.init(type_=CustomUnicodeType)
self.assert_type("test_column", StringField)
def test_string_converts_to_text_field(self):
self.init(type_=sa.String)
self.assert_type("test_column", StringField)
def test_integer_converts_to_integer_field(self):
self.init(type_=sa.Integer)
self.assert_type("test_column", IntegerField)
def test_unicode_text_converts_to_text_area_field(self):
self.init(type_=sa.UnicodeText)
self.assert_type("test_column", TextAreaField)
def test_custom_unicode_text_converts_to_text_area_field(self):
self.init(type_=CustomUnicodeTextType)
self.assert_type("test_column", TextAreaField)
def test_boolean_converts_to_boolean_field(self):
self.init(type_=sa.Boolean)
self.assert_type("test_column", BooleanField)
def test_datetime_converts_to_datetime_field(self):
self.init(type_=sa.DateTime)
self.assert_type("test_column", DateTimeField)
def test_date_converts_to_date_field(self):
self.init(type_=sa.Date)
self.assert_type("test_column", DateField)
def test_float_converts_to_float_field(self):
self.init(type_=sa.Float)
self.assert_type("test_column", FloatField)
def test_numeric_converts_to_decimal_field(self):
self.init(type_=sa.Numeric)
self.assert_type("test_column", DecimalField)
def test_numeric_scale_converts_to_decimal_field_scale(self):
self.init(type_=sa.Numeric(scale=4))
form = self.form_class()
assert form.test_column.places == 4
def test_custom_numeric_converts_to_decimal_field(self):
self.init(type_=CustomNumericType)
self.assert_type("test_column", DecimalField)
def test_enum_field_converts_to_select_field(self):
choices = ["1", "2"]
self.init(type_=sa.Enum(*choices))
self.assert_type("test_column", SelectField)
form = self.form_class()
assert form.test_column.choices == [(s, s) for s in choices]
def test_nullable_enum_uses_null_or_unicode_coerce_func_by_default(self):
choices = ["1", "2"]
self.init(type_=sa.Enum(*choices), nullable=True)
field = self._get_field("test_column")
assert field.coerce == null_or_unicode
def test_custom_choices_override_enum_choices(self):
choices = ["1", "2"]
custom_choices = [("2", "2"), ("3", "3")]
self.init(type_=sa.Enum(*choices), info={"choices": custom_choices})
form = self.form_class()
assert form.test_column.choices == custom_choices
def test_column_with_choices_converts_to_select_field(self):
choices = [("1", "1"), ("2", "2")]
self.init(type_=sa.Integer, info={"choices": choices})
self.assert_type("test_column", SelectField)
form = self.form_class()
assert form.test_column.choices == choices
def test_assigns_email_validator_for_email_type(self):
self.init(type_=EmailType)
self.assert_has_validator("test_column", Email)
def test_assigns_url_validator_for_url_type(self):
self.init(type_=URLType)
self.assert_has_validator("test_column", URL)
def test_time_converts_to_time_field(self):
self.init(type_=sa.types.Time)
self.assert_type("test_column", TimeField)
def test_varchar_converts_to_text_field(self):
self.init(type_=sa.types.VARCHAR)
self.assert_type("test_column", StringField)
def test_text_converts_to_textarea_field(self):
self.init(type_=sa.types.TEXT)
self.assert_type("test_column", TextAreaField)
def test_char_converts_to_text_field(self):
self.init(type_=sa.types.CHAR)
self.assert_type("test_column", StringField)
def test_real_converts_to_float_field(self):
self.init(type_=sa.types.REAL)
self.assert_type("test_column", FloatField)
def test_json_converts_to_textarea_field(self):
self.init(type_=sa.types.JSON)
self.assert_type("test_column", TextAreaField)
@mark.xfail("phone_number.phonenumbers is None")
def test_phone_number_converts_to_phone_number_field(self):
self.init(type_=PhoneNumberType)
self.assert_type("test_column", PhoneNumberField)
@mark.xfail("phone_number.phonenumbers is None")
def test_phone_number_country_code_passed_to_field(self):
self.init(type_=PhoneNumberType(region="SE"))
form = self.form_class()
assert form.test_column.region == "SE"
@mark.xfail("phone_number.phonenumbers is None")
def test_phone_number_type_has_no_length_validation(self):
self.init(type_=PhoneNumberType(country_code="FI"))
field = self._get_field("test_column")
for validator in field.validators:
assert validator.__class__ != Length
@mark.parametrize(("type", "field"), ((IntRangeType, IntIntervalField),))
def test_range_type_conversion(self, type, field):
self.init(type_=type)
self.assert_type("test_column", field)
@mark.xfail("passlib is None")
def test_password_type_converts_to_password_field(self):
self.init(type_=PasswordType)
self.assert_type("test_column", PasswordField)
@mark.xfail("arrow.arrow is None")
def test_arrow_type_converts_to_datetime_field(self):
self.init(type_=arrow.ArrowType)
self.assert_type("test_column", DateTimeField)
def test_url_type_converts_to_string_field(self):
self.init(type_=URLType)
self.assert_type("test_column", StringField)
def test_uuid_type_converst_to_uuid_type(self):
self.init(type_=UUIDType)
self.assert_type("test_column", StringField)
def test_color_type_converts_to_color_field(self):
self.init(type_=ColorType)
self.assert_type("test_column", ColorField)
def test_email_type_converts_to_email_field(self):
self.init(type_=EmailType)
self.assert_type("test_column", EmailField)
def test_country_type_converts_to_country_field(self):
self.init(type_=CountryType)
self.assert_type("test_column", CountryField)
def test_choice_type_converts_to_select_field(self):
choices = [("1", "choice 1"), ("2", "choice 2")]
self.init(type_=ChoiceType(choices))
self.assert_type("test_column", SelectField)
assert list(self.form_class().test_column.choices) == choices
def test_choice_type_uses_custom_coerce_func(self):
choices = [("1", "choice 1"), ("2", "choice 2")]
self.init(type_=ChoiceType(choices))
self.assert_type("test_column", SelectField)
model = self.ModelTest(test_column="2")
form = self.form_class(obj=model)
assert '