[
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  - push\n  - pull_request\n\njobs:\n  test:\n    name: Lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.12\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install .[test]\n\n      - name: Run linting\n        run: |\n          ruff check .\n          ruff format --check\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Tests\non: [push, pull_request]\njobs:\n  test:\n    name: Python ${{ matrix.python-version }} + SQLAlchemy ${{ matrix.sqlalchemy-version }} + WTForms ${{ matrix.wtforms-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n        sqlalchemy-version: [\"1.4\", \"2.0\"]\n        wtforms-version: [\"3.1\", \"3.2\"]\n    steps:\n      - uses: actions/checkout@v2\n      - name: Set up Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Install tox\n        run: pip install tox\n      - name: Run tests\n        run: tox -e sqlalchemy${{ matrix.sqlalchemy-version }}-wtforms${{ matrix.wtforms-version }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[co]\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\n\n#Translations\n*.mo\n\n#Mr Developer\n.mr.developer.cfg\n\n# Built docs\ndocs/_build/\n"
  },
  {
    "path": "CHANGES.rst",
    "content": "Changelog\n=========\n\nHere you can see the full list of changes between each WTForms-Alchemy release.\n\n0.19.1 (2025-08-11)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed issue where empty list was ignored for the ``only`` meta parameter of ``ModelForm``.\n\n0.19.0 (2024-11-18)\n^^^^^^^^^^^^^^^^^^^\n\n- Dropped support for Python 3.8 and earlier. The minimum supported Python version is now 3.9.\n- Dropped support for SQLAlchemy 1.3 and earlier. The minimum supported SQLAlchemy version is now 1.4.\n- Added support for Python 3.10–3.13.\n- Added support for SQLAlchemy 2.0.\n- Added support for WTForms 3.2. The minimum supported WTForms version is now 3.1.\n\n0.18.0 (2021-12-21)\n^^^^^^^^^^^^^^^^^^^\n\n- Dropped WTForms 1.0 support\n- Added WTForms 3.0 and SA 1.4 support\n- Dropped py35 support\n\n\n0.17.0 (2020-06-02)\n^^^^^^^^^^^^^^^^^^^\n\n- Dropped py27, py33 and py34 support\n\n\n0.16.9 (2019-03-06)\n^^^^^^^^^^^^^^^^^^^\n\n- Added support for JSON type in TypeMap (#142, pull request courtesy of fedExpress)\n\n\n0.16.8 (2018-12-04)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed QuerySelectField.query allowing no results (#136, pull request courtesy of TrilceAC)\n\n\n0.16.7 (2018-05-07)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed UnknownTypeException being thrown correctly for unsupported types (#131, pull request courtesy of tvuotila)\n\n\n0.16.6 (2018-01-21)\n^^^^^^^^^^^^^^^^^^^\n\n- Added SQLAlchemy 1.2 support\n\n\n0.16.5 (2017-07-29)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed GroupedQuerySelectMultipleField validator to support empty data (#123, pull request courtesy of superosku)\n\n\n0.16.4 (2017-07-29)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed GroupedQuerySelectMultipleField validator (#121, pull request courtesy of superosku)\n\n\n0.16.3 (2017-06-25)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed ChoiceType conversion for Enums (#112, pull request courtesy of fayazkhan)\n\n\n0.16.2 (2017-02-28)\n^^^^^^^^^^^^^^^^^^^\n\n- Added GroupedQueryMultipleSelectField (#113, pull request courtesy of adarshk7)\n\n\n0.16.1 (2016-05-11)\n^^^^^^^^^^^^^^^^^^^\n\n- Updated SQLAlchemy-Utils requirement to 0.32.6\n- Fixed PhoneNumberType conversion (#102)\n\n\n0.16.0 (2016-04-20)\n^^^^^^^^^^^^^^^^^^^\n\n- Dropped python 2.6 support\n- Made PhoneNumberField work correctly together with DataRequired (#101, pull request courtesy of jmagnusson)\n\n\n0.15.0 (2016-01-27)\n^^^^^^^^^^^^^^^^^^^\n\n- Moved GroupedQuerySelectField from WTForms-Components package to WTForms-Alchemy\n- Moved WeekdaysField from WTForms-Components package to WTForms-Alchemy\n- Moved PhoneNumberField from WTForms-Components package to WTForms-Alchemy\n- Moved Unique validator from WTForms-Components package to WTForms-Alchemy\n\n\n0.14.0 (2016-01-23)\n^^^^^^^^^^^^^^^^^^^\n\n- Added QuerySelectField and QuerySelectMultipleField which were deprecated from\nWTForms as of version 2.1\n\n\n0.13.3 (2015-06-17)\n^^^^^^^^^^^^^^^^^^^\n\n- Removed ClassMap's inheritance sorting. This never really worked properly and resulted in weird undeterministic bugs on Python 3.\n\n\n0.13.2 (2015-05-21)\n^^^^^^^^^^^^^^^^^^^\n\n- Added support for callables in type map argument\n\n\n0.13.1 (2015-04-19)\n^^^^^^^^^^^^^^^^^^^\n\n- Added flake8 checks\n- Added isort checks\n- Fixed country import caused by SQLAlchemy-Utils 0.30.0\n- Update SQLAlchemy-Utils dependency to 0.30.0\n\n\n0.13.0 (2014-10-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Made all default validators configurable in model_form_factory\n- Added support for disabling default validators\n\n\n0.12.9 (2014-08-30)\n^^^^^^^^^^^^^^^^^^^\n\n- Added support for composite primary keys in ModelFieldList\n\n\n0.12.8 (2014-07-28)\n^^^^^^^^^^^^^^^^^^^\n\n- Added support for URLType of SQLAlchemy-Utils\n\n\n0.12.7 (2014-07-21)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix ModelFieldList handling of simultaneous deletes and updates\n\n\n0.12.6 (2014-06-12)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix various issues with new-style classes\n\n\n0.12.5 (2014-05-29)\n^^^^^^^^^^^^^^^^^^^\n\n- Added CountryField\n- Added CountryType to CountryField conversion\n- Fixed various issues with column aliases\n\n\n0.12.4 (2014-03-26)\n^^^^^^^^^^^^^^^^^^^\n\n- Added WeekDaysType to WeekDaysField conversion\n\n\n0.12.3 (2014-03-24)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed ChoiceType coercion for SelectFields\n\n\n0.12.2 (2014-02-20)\n^^^^^^^^^^^^^^^^^^^\n\n- New configuration option: attr_errors\n- Min and max info attributes generate NumberRange validator for Numeric, Float, IntRangeType and NumericRangeType columns\n\n\n0.12.1 (2014-02-13)\n^^^^^^^^^^^^^^^^^^^\n\n- Updated SQLAlchemy-i18n optional dependency to 0.8.2\n\n\n0.12.0 (2013-12-19)\n^^^^^^^^^^^^^^^^^^^\n\n- Added support for SQLAlchemy-Utils range types IntRange, NumericRange, DateRange and DateTimeRange\n- Deprecated support for NumberRangeField\n- Updated SQLAlchemy-Utils dependency to 0.23.1\n- Updated WTForms-Components dependency to 0.9.0\n\n\n0.11.0 (2013-12-19)\n^^^^^^^^^^^^^^^^^^^\n\n- Added configurable default validators\n- Fixed ModelFieldList processing\n\n\n0.10.0 (2013-12-16)\n^^^^^^^^^^^^^^^^^^^\n\n- Replaced assign_required configuration option with not_null_validator for more fine grained control of not null validation\n- Replaced not_null_str_validator with not_null_validator_type_map\n\n\n0.9.3 (2013-12-12)\n^^^^^^^^^^^^^^^^^^\n\n- Support for hybrid properties that return column properties\n- Better exception messages for properties that are not of type ColumnProperty\n- Support for class level type map customization\n\n\n0.9.2 (2013-12-11)\n^^^^^^^^^^^^^^^^^^\n\n- Smarter object value inspection for ModelFieldList\n- Changed ModelFieldList default population strategy to 'update' instead of 'replace'\n\n\n0.9.1 (2013-12-03)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed property alias handling (issue #46)\n\n\n0.9.0 (2013-11-30)\n^^^^^^^^^^^^^^^^^^\n\n- Initial WTForms 2.0 support\n- New configuration options: not_null_validator, not_null_str_validator\n\n\n0.8.6 (2013-11-18)\n^^^^^^^^^^^^^^^^^^\n\n- Form fields now generated in class initialization time rather than on form object initialization\n\n\n0.8.5 (2013-11-13)\n^^^^^^^^^^^^^^^^^^\n\n- Added Numeric type scale to DecimalField places conversion\n\n\n0.8.4 (2013-11-11)\n^^^^^^^^^^^^^^^^^^\n\n- Declaration order of model fields now preserved in generated forms\n\n\n0.8.3 (2013-10-28)\n^^^^^^^^^^^^^^^^^^\n\n- Added Python 2.6 support (supported versions now 2.6, 2.7 and 3.3)\n- Enhanced coerce func generator\n\n\n0.8.2 (2013-10-25)\n^^^^^^^^^^^^^^^^^^\n\n- TypeDecorator derived type support SelectField coerce callable generator\n\n\n0.8.1 (2013-10-24)\n^^^^^^^^^^^^^^^^^^\n\n- Added support for SQLAlchemy-Utils ChoiceType\n- Updated SQLAlchemy-Utils dependency to 0.18.0\n\n\n0.8.0 (2013-10-11)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed None value handling in string stripping when strip_string_fields option is enabled\n- Python 3 support\n- ModelFormMeta now configurable\n\n\n0.7.15 (2013-09-06)\n^^^^^^^^^^^^^^^^^^^\n\n- Form generation now understands column aliases\n\n\n0.7.14 (2013-08-27)\n^^^^^^^^^^^^^^^^^^^\n\n- Length validators only assigned to string typed columns\n\n\n0.7.13 (2013-08-22)\n^^^^^^^^^^^^^^^^^^^\n\n- Model column_property methods now skipped in model generation process\n\n\n0.7.12 (2013-08-18)\n^^^^^^^^^^^^^^^^^^^\n\n- Updated SQLAlchemy-Utils dependency to 0.16.7\n- Updated SQLAlchemy-i18n dependency to 0.6.3\n\n\n0.7.11 (2013-08-05)\n^^^^^^^^^^^^^^^^^^^\n\n- Added configuration skip_unknown_types to silently skip columns with types WTForms-Alchemy does not understand\n\n\n0.7.10 (2013-08-01)\n^^^^^^^^^^^^^^^^^^^\n\n- DecimalField with scales and choices now generate SelectField as expected\n\n\n0.7.9 (2013-08-01)\n^^^^^^^^^^^^^^^^^^\n\n- TSVectorType columns excluded by default\n\n\n0.7.8 (2013-07-31)\n^^^^^^^^^^^^^^^^^^\n\n- String typed columns now convert to WTForms-Components StringFields instead of WTForms TextFields\n\n\n0.7.7 (2013-07-31)\n^^^^^^^^^^^^^^^^^^\n\n- HTML5 step widget param support added\n- Updated WTForms-Components dependency to 0.6.6\n\n\n0.7.6 (2013-07-24)\n^^^^^^^^^^^^^^^^^^\n\n- TypeDecorator support added\n\n\n0.7.5 (2013-05-30)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed _obj setting to better cope with wtforms_components unique validator\n\n\n0.7.4 (2013-05-30)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed min and max arg handling when using zero values\n\n\n0.7.3 (2013-05-24)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed ModelFieldList object population when using 'update' population strategy\n\n\n0.7.2 (2013-05-24)\n^^^^^^^^^^^^^^^^^^\n\n- Updated WTForms-Components dependency to 0.6.3\n- Made type conversion use WTForms-Components HTML5 fields\n\n\n0.7.1 (2013-05-23)\n^^^^^^^^^^^^^^^^^^\n\n- DataRequired validator now added to not nullable booleans by default\n\n\n0.7.0 (2013-05-14)\n^^^^^^^^^^^^^^^^^^\n\n- SQLAlchemy-i18n support added\n\n\n0.6.0 (2013-05-07)\n^^^^^^^^^^^^^^^^^^\n\n- Updated WTForms dependency to 1.0.4\n- Updated WTForms-Components dependency to 0.5.5\n- EmailType now converts to HTML5 EmailField\n- Integer now converts to HTML5 IntegerField\n- Numeric now converts to HTML5 DecimalField\n- Date now converts to HTML5 DateField\n- DateTime now converts to HTML5 DateTimeField\n\n\n0.5.7 (2013-05-03)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed trim function for None values\n\n\n0.5.6 (2013-05-02)\n^^^^^^^^^^^^^^^^^^\n\n- Column trim option added for fine-grained control of string field trimming\n\n\n0.5.5 (2013-05-02)\n^^^^^^^^^^^^^^^^^^\n\n- Bug fix: strip_string_fields applied only for string fields\n\n\n0.5.4 (2013-05-02)\n^^^^^^^^^^^^^^^^^^\n\n- Possibility to give default configuration for model_form_factory function\n- strip_string_fields configuration option\n\n\n0.5.3 (2013-04-30)\n^^^^^^^^^^^^^^^^^^\n\n- Updated SQLAlchemy-Utils dependency to 0.10.0\n- Updated WTForms-Components dependency to 0.5.4\n- Added support for ColorType\n\n\n0.5.2 (2013-04-25)\n^^^^^^^^^^^^^^^^^^\n\n- Added custom widget support\n- Added custom filters support\n\n\n0.5.1 (2013-04-16)\n^^^^^^^^^^^^^^^^^^\n\n- Updated SQLAlchemy-Utils dependency to 0.9.1\n- Updated WTForms-Components dependency to 0.5.2\n- Fixed Email validator auto-assigning for EmailType\n- Smarter type conversion for subclassed types\n- Fixed ModelFormField update handling\n\n\n0.5.0 (2013-04-12)\n^^^^^^^^^^^^^^^^^^\n\n- Updated SQLAlchemy dependency to 0.8\n- Completely rewritten ModelFieldList implementation\n\n\n0.4.5 (2013-03-27)\n^^^^^^^^^^^^^^^^^^\n\n- Updated WTForms-Components dependencies\n- Updated docs\n\n\n0.4.4 (2013-03-27)\n^^^^^^^^^^^^^^^^^^\n\n- Updated WTForms-Components and SQLAlchemy-Utils dependencies\n\n\n0.4.3 (2013-03-26)\n^^^^^^^^^^^^^^^^^^\n\n- Disalbed length validation for PhoneNumberType\n\n\n0.4.2 (2013-03-26)\n^^^^^^^^^^^^^^^^^^\n\n- Added conversion from NumberRangeType to NumberRangeField\n\n\n0.4.1 (2013-03-21)\n^^^^^^^^^^^^^^^^^^\n\n- Added conversion from PhoneNumberType to PhoneNumberField\n\n\n0.4 (2013-03-15)\n^^^^^^^^^^^^^^^^\n\n- Moved custome fields, validators and widgets to WTForms-Components package\n\n\n0.3.3 (2013-03-14)\n^^^^^^^^^^^^^^^^^^\n\n- Added handling of form_field_class = None\n\n\n0.3.2 (2013-03-14)\n^^^^^^^^^^^^^^^^^^\n\n- Added custom field class attribute\n\n\n0.3.1 (2013-03-01)\n^^^^^^^^^^^^^^^^^^\n\n- Better exception messages\n\n\n0.3.0 (2013-03-01)\n^^^^^^^^^^^^^^^^^^\n\n- New unique validator syntax\n\n\n0.2.5 (2013-02-16)\n^^^^^^^^^^^^^^^^^^\n\n- API documentation\n\n\n0.2.4 (2013-02-08)\n^^^^^^^^^^^^^^^^^^\n\n- Enhanced unique validator\n- Documented new unique validator\n\n\n0.2.3 (2012-11-26)\n^^^^^^^^^^^^^^^^^^\n\n- Another fix for empty choices handling\n\n\n0.2.2 (2012-11-26)\n^^^^^^^^^^^^^^^^^^\n\n- Fixed empty choices handling for string fields\n\n\n0.2.1 (2012-11-22)\n^^^^^^^^^^^^^^^^^^\n\n- If validator\n- Chain validator\n\n\n0.2 (2012-11-05)\n^^^^^^^^^^^^^^^^^^\n\n- DateRange validator\n- SelectField with optgroup support\n\n\n0.1.1\n^^^^^\n\n- Added smart one-to-one and one-to-many relationship population\n\n0.1.0\n^^^^^\n\n- Initial public release\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012, Konsta Vesterinen\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* The names of the contributors may not be used to endorse or promote products\n  derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT,\nINDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\nOR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include CHANGES.rst LICENSE README.rst\nrecursive-include tests *\nrecursive-exclude tests *.pyc\nrecursive-include docs *\nrecursive-exclude docs *.pyc\nprune docs/_build\nexclude docs/_themes/.git\n"
  },
  {
    "path": "README.rst",
    "content": "WTForms-Alchemy\n===============\n\n|Version Status| |Downloads|\n\nTools for creating WTForms forms from SQLAlchemy models\n\n\nResources\n---------\n\n- `Documentation <https://wtforms-alchemy.readthedocs.io/>`_\n- `Issue Tracker <http://github.com/kvesteri/wtforms-alchemy/issues>`_\n- `Code <http://github.com/kvesteri/wtforms-alchemy/>`_\n\n.. |Version Status| image:: https://img.shields.io/pypi/v/WTForms-Alchemy.svg\n   :target: https://pypi.python.org/pypi/WTForms-Alchemy/\n.. |Downloads| image:: https://img.shields.io/pypi/dm/WTForms-Alchemy.svg\n   :target: https://pypi.python.org/pypi/WTForms-Alchemy/\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/WTForms-Alchemy.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/WTForms-Alchemy.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/WTForms-Alchemy\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WTForms-Alchemy\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\tmake -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/advanced.rst",
    "content": "Advanced concepts\n=================\n\nUsing WTForms-Alchemy with SQLAlchemy-Defaults\n----------------------------------------------\n\nWTForms-Alchemy works wonderfully with `SQLAlchemy-Defaults`_. When using `SQLAlchemy-Defaults`_ with WTForms-Alchemy you\ncan define your models and model forms with much more robust syntax. For more information see `SQLAlchemy-Defaults`_ documentation.\n\n\n.. _SQLAlchemy-Defaults: https://github.com/kvesteri/sqlalchemy-defaults\n\n\nExample ::\n\n    from sqlalchemy_defaults import LazyConfigured\n\n\n    class User(Base, LazyConfigured):\n        __tablename__ = 'user'\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(\n            sa.Unicode(255),\n            nullable=False,\n            label=u'Name'\n        )\n        age = sa.Column(\n            sa.Integer,\n            nullable=False,\n            min=18,\n            max=100,\n            label=u'Age'\n        )\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nUsing WTForms-Alchemy with Flask-WTF\n------------------------------------\n\nIn order to make WTForms-Alchemy work with `Flask-WTF`_ you need the following snippet:\n\n.. _Flask-WTF: https://github.com/lepture/flask-wtf/\n\n::\n\n\n    from flask_wtf import FlaskForm\n    from wtforms_alchemy import model_form_factory\n    # The variable db here is a SQLAlchemy object instance from\n    # Flask-SQLAlchemy package\n    from myproject.extensions import db\n\n    BaseModelForm = model_form_factory(FlaskForm)\n\n    class ModelForm(BaseModelForm):\n        @classmethod\n        def get_session(self):\n            return db.session\n\nThen you can use the ModelForm just like before:\n\n\n::\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n"
  },
  {
    "path": "docs/api.rst",
    "content": "API Documentation\n=================\n\nThis part of the documentation covers all the public classes and functions\nin WTForms-Alchemy.\n\n:mod:`wtforms_alchemy`\n----------------------\n\n.. module:: wtforms_alchemy\n.. autoclass:: ModelForm\n    :members:\n.. autofunction:: model_form_factory\n.. autoclass:: ModelFormMeta\n    :members:\n.. autofunction:: model_form_meta_factory\n\n:mod:`wtforms_alchemy.generator`\n--------------------------------\n\n.. module:: wtforms_alchemy.generator\n\n.. autoclass:: FormGenerator\n    :members:\n\n:mod:`wtforms_alchemy.fields`\n--------------------------------\n\n.. module:: wtforms_alchemy.fields\n\n.. autoclass:: QuerySelectField\n    :members:\n\n.. autoclass:: QuerySelectMultipleField\n    :members:\n\n\n:mod:`wtforms_alchemy.utils`\n----------------------------\n\n.. module:: wtforms_alchemy.utils\n\n.. autofunction:: translated_attributes\n\n.. autoclass:: ClassMap\n    :members:\n    :special-members:\n"
  },
  {
    "path": "docs/column_conversion.rst",
    "content": "Column to form field conversion\n===============================\n\nBasic type conversion\n---------------------\n\nBy default WTForms-Alchemy converts SQLAlchemy model columns using the following\ntype table. So for example if an Unicode column would be converted to TextField.\n\nThe reason why so many types here convert to wtforms_components based fields is that\nwtforms_components provides better HTML5 compatible type handling than WTForms at the moment.\n\n\n====================================    =================\n **SQAlchemy column type**              **Form field**\n------------------------------------    -----------------\n    BigInteger                          wtforms_components.fields.IntegerField\n    Boolean                             BooleanField\n    Date                                wtforms_components.fields.DateField\n    DateTime                            wtforms_components.fields.DateTimeField\n    Enum                                wtforms_components.fields.SelectField\n    Float                               FloatField\n    Integer                             wtforms_components.fields.IntegerField\n    Numeric                             wtforms_components.fields.DecimalField\n    SmallInteger                        wtforms_components.fields.IntegerField\n    String                              TextField\n    Text                                TextAreaField\n    Time                                wtforms_components.fields.TimeField\n    Unicode                             TextField\n    UnicodeText                         TextAreaField\n====================================    =================\n\n\nWTForms-Alchemy also supports many types provided by SQLAlchemy-Utils.\n\n\n====================================    =================\n **SQAlchemy-Utils type**               **Form field**\n------------------------------------    -----------------\n    ArrowType                           wtforms_components.fields.DateTimeField\n    ChoiceType                          wtforms_components.fields.SelectField\n    ColorType                           wtforms_components.fields.ColorField\n    CountryType                         wtforms_alchemy.fields.CountryType\n    EmailType                           wtforms_components.fields.EmailField\n    IPAddressType                       wtforms_components.fields.IPAddressField\n    PasswordType                        wtforms.fields.PasswordField\n    PhoneNumberType                     wtforms_components.fields.PhoneNumberField\n    URLType                             wtforms_components.fields.StringField + URL validator\n    UUIDType                            wtforms.fields.TextField + UUID validator\n    WeekDaysType                        wtforms_components.fields.WeekDaysField\n====================================    =================\n\n\n====================================    =================\n **SQAlchemy-Utils range type**         **Form field**\n------------------------------------    -----------------\n    DateRangeType                       wtforms_components.fields.DateIntervalField\n    DateTimeRangeType                   wtforms_components.fields.DateTimeIntervalField\n    IntRangeType                        wtforms_components.fields.IntIntervalField\n    NumericRangeType                    wtforms_components.fields.DecimalIntervalField\n====================================    =================\n\n\n\n\nExcluded fields\n---------------\nBy default WTForms-Alchemy excludes a column from the ModelForm if one of the following conditions is True:\n    * Column is primary key\n    * Column is foreign key\n    * Column is DateTime field which has default value (usually this is a generated value)\n    * Column is of TSVectorType type\n    * Column is set as model inheritance discriminator field\n\n\nUsing include, exclude and only\n-------------------------------\n\nIf you wish the include some of the excluded fields described in the earlier chapter you can use the 'include' configuration parameter.\n\n\nIn the following example we include the field 'author_id' in the ArticleForm (by default it is excluded since it is a foreign key column).\n\n::\n\n\n    class Article(Base):\n        __tablename__ = 'article'\n\n        id = sa.Column(sa.Integer, primary_key=True, nullable=False)\n        name = sa.Column(\n            sa.Unicode(255),\n            nullable=False\n        )\n        author_id = sa.Column(sa.Integer, sa.ForeignKey(User.id))\n        author = sa.orm.relationship(User)\n\n\n    class ArticleForm(Form):\n        class Meta:\n            include = ['author_id']\n\n\nIf 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.\n\nConsider the following model:\n\n::\n\n\n    class Article(Base):\n        __tablename__ = 'article'\n\n        id = sa.Column(sa.Integer, primary_key=True, nullable=False)\n        name = sa.Column(\n            sa.Unicode(255),\n            nullable=False\n        )\n        content = sa.Column(\n            sa.UnicodeText\n        )\n        description = sa.Column(\n            sa.UnicodeText\n        )\n\n\nNow let's say we want to exclude 'description' from the form. This can be achieved as follows:\n\n::\n\n\n    class ArticleForm(Form):\n        class Meta:\n            exclude = ['description']\n\n\nOr as follows (the recommended way):\n\n\n::\n\n\n    class ArticleForm(Form):\n        class Meta:\n            only = ['name', 'content']\n\n\n\n\nAdding/overriding fields\n------------------------\n\nExample::\n\n    from wtforms.fields import TextField, IntegerField\n    from wtforms.validators import Email\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(\n            sa.Unicode(255),\n            nullable=False\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n        email = TextField(validators=[Optional()])\n        age = IntegerField()\n\nNow the UserForm would have three fields:\n    * name, a required TextField\n    * email, an optional TextField\n    * age, IntegerField\n\n\nType decorators\n---------------\n\nWTForms-Alchemy supports SQLAlchemy TypeDecorator based types. When WTForms-Alchemy encounters a TypeDecorator typed column it tries to convert it to underlying type field.\n\nExample::\n\n\n    import sqlalchemy as sa\n    from wtforms.fields import TextField, IntegerField\n    from wtforms.validators import Email\n\n\n    class CustomUnicodeType(sa.types.TypeDecorator):\n        impl = sa.types.Unicode\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        name = sa.Column(CustomUnicodeType(100), primary_key=True)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nNow the name field of UserForm would be a simple TextField since the underlying type implementation is Unicode.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#\n# WTForms-Alchemy documentation build configuration file, created by\n# sphinx-quickstart on Wed Aug 29 16:20:21 2012.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport os\nimport sys\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(\"..\"))\nfrom wtforms_alchemy import __version__\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.todo\",\n    \"sphinx.ext.viewcode\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"WTForms-Alchemy\"\ncopyright = \"2012, Konsta Vesterinen\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = __version__\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = [\"_build\"]\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"default\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"WTForms-Alchemydoc\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# The paper size ('letter' or 'a4').\n# latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n# latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\n        \"index\",\n        \"WTForms-Alchemy.tex\",\n        \"WTForms-Alchemy Documentation\",\n        \"Konsta Vesterinen\",\n        \"manual\",\n    ),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Additional stuff for the LaTeX preamble.\n# latex_preamble = ''\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (\n        \"index\",\n        \"wtforms-alchemy\",\n        \"WTForms-Alchemy Documentation\",\n        [\"Konsta Vesterinen\"],\n        1,\n    )\n]\n\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {\n    \"http://docs.python.org/\": None,\n    \"https://wtforms.readthedocs.io/en/latest/\": None,\n    \"https://sqlalchemy-utils.readthedocs.io/en/latest/\": None,\n    \"https://wtforms-components.readthedocs.io/en/latest/\": None,\n}\n"
  },
  {
    "path": "docs/configuration.rst",
    "content": "Configuration\n=============\n\nModelForm meta parameters\n-------------------------\n\nThe following configuration options are available for ModelForm's Meta subclass.\n\n**include_primary_keys** (default: False)\n\nIf you wish to include primary keys in the generated form please set this to True.\nThis is useful when dealing with natural primary keys. In the following example each\nuser has a natural primary key on its column name.\n\nThe UserForm would contain two fields name and email. ::\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(sa.Unicode(255), nullable=False)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            include_primary_keys = True\n\n\n**exclude**\n\n.. warning::\n\n    Using ``exclude`` might lead to problems in situations where you add columns to your model\n    and forget to exclude those from the form by using ``exclude``, hence it is recommended to\n    use ``only`` rather than ``exclude``.\n\n\nYou can exclude certain fields by adding them to the exclude list. ::\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(sa.Unicode(255), nullable=False)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            include_primary_keys = True\n            exclude = ['email']\n            # this form contains only 'name' field\n\n\n**only**\n\nGenerates a form using only the field names provided in ``only``.\n\n::\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            only = ['email']\n\n\n**field_args** (default: {})\n\nThis parameter can be used for overriding field arguments. In the following example we force the email field optional.\n\n::\n\n\n     class UserForm(ModelForm):\n        class Meta:\n            model = User\n            field_args = {'email': {'validators': [Optional()]}}\n\n\n**include_foreign_keys** (default: False)\n\nForeign keys can be included in the form by setting include_foreign_keys to True.\n\n**only_indexed_fields** (default: False)\n\nWhen setting this option to True, only fields that have an index will be included in\nthe form. This is very useful when creating forms for searching a specific model.\n\n\n**include_datetimes_with_default** (default: False)\n\nWhen setting this option to True, datetime with default values will be included in the\nform. By default this is False since usually datetime fields that have default values\nare generated columns such as \"created_at\" or \"updated_at\", which should not be included\nin the form.\n\n\n**validators**\n\nA dict containing additional validators for the generated form field objects.\n\nExample::\n\n    from wtforms.validators import Email\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(sa.Unicode(255), nullable=False)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            include_primary_keys = True\n            validators = {'email': [Email()]}\n\n**datetime_format** (default: '%Y-%m-%d %H:%M:%S')\n\nDefines the default datetime format, which will be assigned to generated datetime\nfields.\n\n**date_format** (default: '%Y-%m-%d')\n\nDefines the default date format, which will be assigned to generated datetime\nfields.\n\n\n**all_fields_optional** (default: False)\n\nDefines all generated fields as optional (useful for update forms).\n\n**assign_required** (default: True)\n\nWhether or not to assign non-nullable fields as required.\n\n**strip_string_fields** (default: False)\n\nWhether or not to add stripping filter to all string fields.\n\nExample ::\n\n\n    from werkzeug.datastructures import MultiDict\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            strip_string_fields = True\n\n\n    form = UserForm(MultiDict([('name', 'someone     ')]))\n\n    assert form.name.data == 'someone'\n\n\nYou can also fine-grain field stripping by using trim argument for columns. In the example\nbelow the field 'name' would have its values stripped whereas field 'password' would not. ::\n\n\n    from wtforms.validators import Email\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(sa.Unicode(100))\n        password = sa.Column(sa.Unicode(100), info={'trim': False})\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            strip_string_fields = True\n\n\n**form_generator** (default: FormGenerator class)\n\nChange this if you want to use custom form generator class.\n\n\nForm inheritance\n----------------\n\nModelForm's configuration support inheritance. This means that child classes inherit\nparents Meta properties.\n\nExample::\n\n    from wtforms.validators import Email\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            validators = {'email': [Email()]}\n\n\n    class UserUpdateForm(UserForm):\n        class Meta:\n            all_fields_optional = True\n\n\nHere UserUpdateForm inherits the configuration properties of UserForm, hence it would\nuse model User and have additional Email validator on column 'email'. Also it assigns\nall fields as optional.\n\n\nNot nullable column validation\n------------------------------\n\nWTForms-Alchemy offers two options for configuring how not nullable columns are validated:\n\n* not_null_validator\n\n    The default validator to be used for not nullable columns. Set this to `None`\n    if you wish to disable it. By default this is `[InputRequired()]`.\n\n\n* not_null_validator_type_map\n\n    Type map which overrides the **not_null_validator** on specific column type. By default this is `ClassMap({sa.String: [InputRequired(), DataRequired()]})`.\n\n\nIn the following example we set `DataRequired` validator for all not nullable Enum typed columns:\n\n\n::\n\n    import sqlalchemy as sa\n    from wtforms.validators import DataRequired\n    from wtforms_alchemy import ClassMap\n\n\n    class MyForm(ModelForm):\n        class Meta:\n            not_null_validator_type_map = ClassMap({sa.Enum: [DataRequired()]})\n\n\n\nCustomizing type conversion\n---------------------------\n\nYou can customize the SQLAlchemy type conversion on class level with type_map Meta property.\n\nType map accepts dictionary of SQLAlchemy types as keys and WTForms field classes\nas values. The key value pairs of this dictionary override the key value pairs of FormGenerator.TYPE_MAP.\n\nLet'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.\n\n::\n\n\n    from wtforms.fields import TextAreaField\n    from wtforms_alchemy import ClassMap\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(sa.Unicode(100))\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            type_map = ClassMap({sa.Unicode: TextAreaField})\n\n\nIn 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.\n\n\n.. _custom_base:\n\nCustom form base class\n----------------------\n\nYou can use custom base class for your model forms by using model_form_factory\nfunction. In the following example we have a UserForm which uses Flask-WTF\nform as a parent form for ModelForm. ::\n\n\n    from flask.ext.wtf import Form\n    from wtforms_alchemy import model_form_factory\n\n\n    ModelForm = model_form_factory(Form)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nYou can also pass any form generator option to model_form_factory. ::\n\n\n    ModelForm = model_form_factory(Form, strip_string_fields=True)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n"
  },
  {
    "path": "docs/customization.rst",
    "content": "Form customization\n==================\n\n\nCustom fields\n-------------\n\nIf you want to use a custom field class, you can pass it by using\nform_field_class parameter for the column info dictionary.\n\nExample ::\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        color = sa.Column(\n            sa.String(7),\n            info={'form_field_class': ColorField},\n            nullable=False\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\nNow the 'color' field of UserForm would be a custom ColorField.\n\n\nForcing the use of SelectField\n------------------------------\n\nSometimes you may want to have integer and unicode fields convert to SelectFields.\nProbably the easiest way to achieve this is by using choices parameter for the column\ninfo dictionary.\n\nExample ::\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        age = sa.Column(\n            sa.Integer,\n            info={'choices': [(i, i) for i in xrange(13, 99)]},\n            nullable=False\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nHere the UserForm would have two fields. One TextField for the name column and one\nSelectField for the age column containing range of choices from 13 to 99.\n\nNotice that WTForms-Alchemy is smart enough to use the right coerce function based on\nthe underlying column type, hence in the previous example the age column would convert\nto the following SelectField. ::\n\n\n    SelectField('Age', coerce=int, choices=[(i, i) for i in xrange(13, 99)])\n\n\nFor nullable unicode and string columns WTForms-Alchemy uses special null_or_unicode\ncoerce function, which converts empty strings to None values.\n\n\nField descriptions\n------------------\n\nExample::\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(\n            sa.Unicode(255),\n            nullable=False,\n            info={'description': 'This is the description of email.'}\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\nNow the 'email' field of UserForm would have description 'This is the description of email.'\n\n\nField labels\n------------\n\nExample::\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(\n            sa.Unicode(100), primary_key=True, nullable=False,\n            info={'label': 'Name'}\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\nNow the 'name' field of UserForm would have label 'Name'.\n\n\nCustom widgets\n--------------\n\nExample::\n\n    from wtforms import widgets\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(\n            sa.Unicode(100), primary_key=True, nullable=False,\n            info={'widget': widgets.HiddenInput()}\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\nNow the 'name' field of UserForm would use HiddenInput widget instead of TextInput.\n\n\nDefault values\n--------------\n\nBy default WTForms-Alchemy ModelForm assigns the default values from column definitions.\nExample ::\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        level = sa.Column(sa.Integer, default=1)\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\nNow the UseForm 'level' field default value would be 1.\n"
  },
  {
    "path": "docs/index.rst",
    "content": "WTForms-Alchemy\n===============\n\nWTForms-Alchemy is a WTForms extension toolkit for easier creation of model\nbased forms. Strongly influenced by Django ModelForm.\n\n\n\n\n.. toctree::\n   :maxdepth: 2\n\n   introduction\n   column_conversion\n   types\n   customization\n   validators\n   configuration\n   relationships\n   advanced\n   api\n   license\n\n\n\n\n\n.. _`SQLAlchemy-Utils ChoiceType`: https://sqlalchemy-utils.readthedocs.io/en/latest/#choicetype\n.. _`SQLAlchemy-Defaults`: https://sqlalchemy-defaults.readthedocs.io/en/latest/\n.. _`Flask-WTF`: https://flask-wtf.readthedocs.io/en/latest/\n\n"
  },
  {
    "path": "docs/introduction.rst",
    "content": "Introduction\n============\n\nWhat for?\n---------\nMany times when building modern web apps with SQLAlchemy you’ll have forms that\nmap closely to models. For example, you might have a Article model,\nand you want to create a form that lets people post new article. In this case,\nit would be time-consuming to define the field types and basic validators in\nyour form, because you’ve already defined the fields in your model.\n\nWTForms-Alchemy provides a helper class that let you create a Form class from a\nSQLAlchemy model.\n\nDifferences with wtforms.ext.sqlalchemy model_form\n--------------------------------------------------\n\nWTForms-Alchemy does not try to replace all the functionality of wtforms.ext.sqlalchemy.\nIt only tries to replace the model_form function of wtforms.ext.sqlalchemy by a much better solution.\nOther functionality of .ext.sqlalchemy such as QuerySelectField and QuerySelectMultipleField can be used\nalong with WTForms-Alchemy.\n\nNow how is WTForms-Alchemy ModelForm better than wtforms.ext.sqlachemy's model_form?\n\n* Provides explicit declaration of ModelForms (much easier to override certain columns)\n* Form generation supports Unique and NumberRange validators\n* Form inheritance support (along with form configuration inheritance)\n* Automatic SelectField type coercing based on underlying column type\n* By default uses wtforms_components SelectField for fields with choices. This field understands None values and renders nested datastructures as optgroups.\n* Provides better Unique validator\n* Supports custom user defined types as well as type decorators\n* Supports SQLAlchemy-Utils datatypes\n* Supports ModelForm model relations population\n* Smarter field exclusion\n* Smarter field conversion\n* Understands join table inheritance\n* Better configuration\n\n\nInstallation\n------------\n\n::\n\n\n    pip install WTForms-Alchemy\n\n\n\nThe supported Python versions are 3.9–3.13.\n\n\n\nQuickStart\n----------\n\nLets say we have a model called User with couple of fields::\n\n    import sqlalchemy as sa\n    from sqlalchemy import create_engine\n    from sqlalchemy.orm import declarative_base, sessionmaker\n    from wtforms_alchemy import ModelForm\n\n    engine = create_engine('sqlite:///:memory:')\n    Base = declarative_base(engine)\n    Session = sessionmaker(bind=engine)\n    session = Session()\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.BigInteger, autoincrement=True, primary_key=True)\n        name = sa.Column(sa.Unicode(100), nullable=False)\n        email = sa.Column(sa.Unicode(255), nullable=False)\n\n\nNow we can create our first ModelForm for the User model. ModelForm behaves almost\nlike your ordinary WTForms Form except it accepts special Meta arguments. Every ModelForm\nmust define model parameter in the Meta arguments.::\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nNow this ModelForm is essentially the same as ::\n\n    class UserForm(Form):\n        name = TextField(validators=[DataRequired(), Length(max=100)])\n        email = TextField(validators=[DataRequired(), Length(max=255)])\n\nIn the following chapters you'll learn how WTForms-Alchemy converts SQLAlchemy model\ncolumns to form fields.\n"
  },
  {
    "path": "docs/license.rst",
    "content": "License\n=======\n\n.. include:: ../LICENSE\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\WTForms-Alchemy.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\WTForms-Alchemy.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/relationships.rst",
    "content": "Forms with relations\n====================\n\nWTForms-Alchemy provides special Field subtypes ModelFormField and ModelFieldList.\nWhen using these types WTForms-Alchemy understands model relations and is smart enough to populate related\nobjects accordingly.\n\nOne-to-one relations\n--------------------\n\nConsider the following example. We have Event and Location\nclasses with each event having one location. ::\n\n    from sqlalchemy.orm import declarative_base\n    from wtforms_alchemy import ModelForm, ModelFormField\n\n    Base = declarative_base()\n\n\n    class Location(Base):\n        __tablename__ = 'location'\n        id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n        name = sa.Column(sa.Unicode(255), nullable=True)\n\n    class Event(Base):\n        __tablename__ = 'event'\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(sa.Unicode(255), nullable=False)\n        location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))\n        location = sa.orm.relationship(Location)\n\n    class LocationForm(ModelForm):\n        class Meta:\n            model = Location\n\n    class EventForm(ModelForm):\n        class Meta:\n            model = Event\n\n        location = ModelFormField(LocationForm)\n\nNow if we populate the EventForm, WTForms-Alchemy is smart enough to populate related\nlocation too. ::\n\n    event = Event()\n    form = EventForm(request.POST)\n    form.populate_obj(event)\n\n\n\nOne-to-many relations\n---------------------\n\nConsider the following example. We have Event and Location\nclasses with each event having many location. Notice we are using FormField along\nwith ModelFieldList. ::\n\n    from sqlalchemy.orm import declarative_base\n    from wtforms_alchemy import ModelForm, ModelFieldList\n    from wtforms.fields import FormField\n\n    Base = declarative_base()\n\n\n    class Event(Base):\n        __tablename__ = 'event'\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(sa.Unicode(255), nullable=False)\n\n\n    class Location(Base):\n        __tablename__ = 'location'\n        id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n        name = sa.Column(sa.Unicode(255), nullable=True)\n\n        event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))\n        event = sa.orm.relationship(\n            Location,\n            backref='locations'  # the event needs to have this\n        )\n\n\n    class LocationForm(ModelForm):\n        class Meta:\n            model = Location\n\n\n    class EventForm(ModelForm):\n        class Meta:\n            model = Event\n\n        locations = ModelFieldList(FormField(LocationForm))\n\nNow if we populate the EventForm, WTForms-Alchemy is smart enough to populate related\nlocations too. ::\n\n    event = Event()\n    form = EventForm(request.POST)\n    form.populate_obj(event)\n"
  },
  {
    "path": "docs/types.rst",
    "content": "Type specific conversion\n========================\n\n\nNumeric type\n------------\n\nWTForms-Alchemy automatically converts Numeric columns to DecimalFields. The\nconverter is also smart enough to convert different decimal scales to\nappropriate HTML5 input step args.\n\n\n::\n\n\n    class Account(Base):\n        __tablename__ = 'event'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        balance = sa.Column(\n            sa.Numeric(scale=2),\n            nullable=False\n        )\n\n    class AccountForm(ModelForm):\n        class Meta:\n            model = Account\n\n\nNow rendering AccountForm.balance would return the following HTML:\n\n<input type='decimal' required step=\"0.01\">\n\n\nArrow type\n----------\n\nWTForms-Alchemy supports the ArrowType of SQLAlchemy-Utils and converts it to\nHTML5 compatible DateTimeField of WTForms-Components.\n\n::\n\n\n    from sqlalchemy_utils import ArrowType\n\n\n    class Event(Base):\n        __tablename__ = 'event'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        start_time = sa.Column(\n            ArrowType(),\n            nullable=False\n        )\n\n    class EventForm(ModelForm):\n        class Meta:\n            model = Event\n\n\nNow the EventForm is essentially the same as:\n\n::\n\n\n    class EventForm(Form):\n        start_time = DateTimeField(validators=[DataRequired()])\n\n\nChoice type\n-----------\n\nWTForms-Alchemy automatically converts\n:class:`sqlalchemy_utils.types.choice.ChoiceType` to WTForms-Components\nSelectField.\n\n\n::\n\n\n    from sqlalchemy_utils import ChoiceType\n\n\n    class Event(Base):\n        __tablename__ = 'event'\n        TYPES = [\n            (u'party', u'Party'),\n            (u'training, u'Training')\n        ]\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        type = sa.Column(ChoiceType(TYPES))\n\n\n    class EventForm(ModelForm):\n        class Meta:\n            model = Event\n\n\nNow the EventForm is essentially the same as:\n\n::\n\n    from wtforms_alchemy.utils import choice_type_coerce_factory\n\n\n    class EventForm(Form):\n        type = SelectField(\n            choices=Event.TYPES,\n            coerce=choice_type_coerce_factory(Event.type.type),\n            validators=[DataRequired()]\n        )\n\n\n\nColor type\n----------\n\n::\n\n\n    from sqlalchemy_utils import ColorType\n\n\n    class CustomView(Base):\n        __tablename__ = 'view'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        background_color = sa.Column(\n            ColorType(),\n            nullable=False\n        )\n\n    class CustomViewForm(ModelForm):\n        class Meta:\n            model = CustomView\n\n\nNow the CustomViewForm is essentially the same as:\n\n::\n\n\n    from wtforms_components import ColorField\n\n\n    class CustomViewForm(Form):\n        color = ColorField(validators=[DataRequired()])\n\n\n\nCountry type\n------------\n\n::\n\n\n    from sqlalchemy_utils import CountryType\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        country = sa.Column(CountryType, nullable=False)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nThe UserForm is essentially the same as:\n\n::\n\n\n    from wtforms_components import CountryField\n\n\n    class UserForm(Form):\n        country = CountryField(validators=[DataRequired()])\n\n\n\nEmail type\n----------\n\n::\n\n\n    from sqlalchemy_utils import EmailType\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        email = sa.Column(EmailType, nullable=False)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nThe good old wtforms equivalent of this form would be:\n\n::\n\n\n    from wtforms_components import EmailField\n\n\n    class UserForm(Form):\n        email = EmailField(validators=[DataRequired()])\n\n\n\nPassword type\n-------------\n\nConsider the following model definition:\n\n::\n\n\n    from sqlalchemy_utils import PasswordType\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        name = sa.Column(sa.Unicode(100), nullable=False)\n        password = sa.Column(\n            PasswordType(\n                schemes=['pbkdf2_sha512']\n            ),\n            nullable=False\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nNow the UserForm is essentially the same as:\n\n::\n\n    class UserForm(Form):\n        name = TextField(validators=[DataRequired(), Length(max=100)])\n        password = PasswordField(validators=[DataRequired()])\n\n\n\n\nPhonenumber type\n----------------\n\nWTForms-Alchemy supports the PhoneNumberType of SQLAlchemy-Utils and converts it automatically\nto WTForms-Components PhoneNumberField. This field renders itself as HTML5 compatible phonenumber input.\n\n\nConsider the following model definition:\n\n::\n\n\n    from sqlalchemy_utils import PhoneNumberType\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        name = sa.Column(sa.Unicode(100), nullable=False)\n        phone_number = sa.Column(PhoneNumberType())\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nNow the UserForm is essentially the same as:\n\n::\n\n    from wtforms_components import PhoneNumberField\n\n\n    class UserForm(Form):\n        name = TextField(validators=[DataRequired(), Length(max=100)])\n        phone_number = PhoneNumberField(validators=[DataRequired()])\n\n\nURL type\n--------\n\nWTForms-Alchemy automatically converts SQLAlchemy-Utils URLType to StringField and adds URL validator for it.\n\nConsider the following model definition:\n\n::\n\n\n    from sqlalchemy_utils import URLType\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)\n        website = sa.Column(URLType())\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n\nNow the UserForm is essentially the same as:\n\n::\n\n    from wtforms_components import StringField\n    from wtforms.validators import URL\n\n\n    class UserForm(Form):\n        website = StringField(validators=[URL()])\n"
  },
  {
    "path": "docs/validators.rst",
    "content": "Validators\n==========\n\n\nAuto-assigned validators\n------------------------\n\nBy default WTForms-Alchemy ModelForm assigns the following validators:\n    * InputRequired validator if column is not nullable and has no default value\n    * DataRequired validator if column is not nullable, has no default value and is of type `sqlalchemy.types.String`\n    * NumberRange validator if column if of type Integer, Float or Decimal and column info parameter has min or max arguments defined\n    * DateRange validator if column is of type Date or DateTime and column info parameter has min or max arguments defined\n    * TimeRange validator if column is of type Time and info parameter has min or max arguments defined\n    * Unique validator if column has a unique index\n    * Length validator for String/Unicode columns with max length\n    * Optional validator for all nullable columns\n\n\nUnique validator\n----------------\n\nWTForms-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.\n::\n\n\n    engine = create_engine('sqlite:///:memory:')\n\n    Base = declarative_base()\n\n    Session = sessionmaker(bind=engine)\n    session = Session()\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(sa.Unicode(100), nullable=False)\n        email = sa.Column(\n            sa.Unicode(255),\n            nullable=False,\n            unique=True\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\n        @classmethod\n        def get_session():\n            # this method should return sqlalchemy session\n            return session\n\n\nHere UserForm would behave the same as the following form:\n::\n\n\n    class UserForm(Form):\n        name = TextField('Name', validators=[DataRequired(), Length(max=100)])\n        email = TextField(\n            'Email',\n            validators=[\n                DataRequired(),\n                Length(max=255),\n                Unique(User.email, get_session=lambda: session)\n            ]\n        )\n\n\nIf 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:\n\n::\n\n    Unique(User.email)\n\n\nUsing unique validator with existing objects\n--------------------------------------------\n\nWhen 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.\nExample::\n\n    obj = MyModel.query.get(1)\n    form = MyForm(obj=obj)\n    form.populate_obj(obj)\n    form.validate()\n\nWTForms-Alchemy will then understand to avoid the unique validation of the object with this same object.\n\n\nRange validators\n----------------\n\nWTForms-Alchemy automatically assigns range validators based on column type and assigned column info min and max attributes.\n\nIn the following example we create a form for Event model where start_time can't be set in the past.\n\n::\n\n    class Event(Base):\n        __tablename__ = 'event'\n\n        id = sa.Column(sa.Integer, primary_key=True)\n        name = sa.Column(sa.Unicode(255))\n        start_time = sa.Column(sa.DateTime, info={'min': datetime.now()})\n\n\n    class EventForm(ModelForm):\n        class Meta:\n            model = Event\n\n\n\nAdditional field validators\n---------------------------\n\nExample::\n\n    from wtforms.validators import Email\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(\n            sa.Unicode(255),\n            nullable=False,\n            info={'validators': Email()}\n        )\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n\nNow the 'email' field of UserForm would have Email validator.\n\n\nOverriding default validators\n-----------------------------\n\nSometimes you may want to override what class WTForms-Alchemy uses for email, number_range, length etc. validations.\nFor all automatically assigned validators WTForms-Alchemy provides configuration options to override the default validator.\n\nIn the following example we set a custom Email validator for User class.\n\n::\n\n\n    from sqlalchemy_utils import EmailType\n    from wtforms_components import Email\n\n\n    class User(Base):\n        __tablename__ = 'user'\n\n        name = sa.Column(sa.Unicode(100), primary_key=True, nullable=False)\n        email = sa.Column(\n            EmailType,\n            nullable=False,\n        )\n\n    class MyEmailValidator(Email):\n        def __init__(self, message='My custom email error message'):\n            Email.__init__(self, message=message)\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            email_validator = MyEmailValidator\n\n\nIf you don't wish to subclass you can simply use functions / lambdas:\n\n::\n\n\n    def email():\n        return Email(message='My custom email error message')\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            email_validator = email\n\n\nYou can also override validators that take multiple arguments this way:\n\n::\n\n\n    def length(min=None, max=None):\n        return Length(min=min, max=max, message='Wrong length')\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            length_validator = length\n\n\nHere is the full list of configuration options you can use to override default validators:\n\n* email_validator\n\n* length_validator\n\n* unique_validator\n\n* number_range_validator\n\n* date_range_validator\n\n* time_range_validator\n\n* optional_validator\n\n\nDisabling validators\n--------------------\n\nYou 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::\n\n\n    class UserForm(ModelForm):\n        class Meta:\n            model = User\n            optional_validator = None\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.pytest.ini_options]\nfilterwarnings = [\n    'error:.*:sqlalchemy.exc.SADeprecationWarning',\n    'error:.*:sqlalchemy.exc.SAWarning',\n    'ignore:.*:sqlalchemy.exc.SADeprecationWarning:sqlalchemy_i18n',\n]\n\n[tool.ruff]\ntarget-version = \"py39\"\n\n[tool.ruff.lint]\nselect = [\n    \"C90\", # mccabe\n    \"E\",   # pycodestyle errors\n    \"F\",   # Pyflakes\n    \"I\",   # isort\n    \"UP\",  # pyupgrade\n    \"W\",   # pycodestyle warnings\n]\n\n[tool.ruff.lint.isort]\norder-by-type = false\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"\nWTForms-Alchemy\n---------------\n\nGenerates WTForms forms from SQLAlchemy models.\n\"\"\"\n\nimport os\nimport re\n\nfrom setuptools import setup\n\nHERE = os.path.dirname(os.path.abspath(__file__))\n\n\ndef get_version():\n    filename = os.path.join(HERE, \"wtforms_alchemy\", \"__init__.py\")\n    with open(filename) as f:\n        contents = f.read()\n    pattern = r'^__version__ = \"(.*?)\"$'\n    return re.search(pattern, contents, re.MULTILINE).group(1)\n\n\nextras_require = {\n    \"test\": [\n        \"enum34\",\n        \"pytest>=2.3\",\n        \"Pygments>=1.2\",\n        \"Jinja2>=2.3\",\n        \"docutils>=0.10\",\n        \"flexmock>=0.9.7\",\n        \"ruff==0.7.4\",\n        \"WTForms-Test>=0.1.1\",\n    ],\n    \"babel\": [\"Babel>=1.3\"],\n    \"arrow\": [\"arrow>=0.3.4\"],\n    \"phone\": [\"phonenumbers>=5.9.2\"],\n    \"intervals\": [\"intervals>=0.2.0\"],\n    \"password\": [\"passlib >= 1.6, < 2.0\"],\n    \"color\": [\"colour>=0.0.4\"],\n    \"i18n\": [\"SQLAlchemy-i18n >= 0.8.2\"],\n    \"timezone\": [\"python-dateutil\"],\n}\n\n\n# Add all optional dependencies to testing requirements.\nfor name, requirements in extras_require.items():\n    if name != \"test\":\n        extras_require[\"test\"] += requirements\n\n\nsetup(\n    name=\"WTForms-Alchemy\",\n    version=get_version(),\n    url=\"https://github.com/kvesteri/wtforms-alchemy\",\n    license=\"BSD\",\n    author=\"Konsta Vesterinen\",\n    author_email=\"konsta@fastmonkeys.com\",\n    description=\"Generates WTForms forms from SQLAlchemy models.\",\n    long_description=__doc__,\n    packages=[\"wtforms_alchemy\"],\n    zip_safe=False,\n    include_package_data=True,\n    platforms=\"any\",\n    install_requires=[\n        \"SQLAlchemy>=1.4\",\n        \"WTForms>=3.1.0\",\n        \"WTForms-Components>=0.11.0\",\n        \"SQLAlchemy-Utils>=0.40.0\",\n    ],\n    extras_require=extras_require,\n    classifiers=[\n        \"Environment :: Web Environment\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: BSD License\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: 3.12\",\n        \"Programming Language :: Python :: 3.13\",\n        \"Topic :: Internet :: WWW/HTTP :: Dynamic Content\",\n        \"Topic :: Software Development :: Libraries :: Python Modules\",\n    ],\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "import sqlalchemy as sa\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import declarative_base, sessionmaker\nfrom sqlalchemy.orm.session import close_all_sessions\nfrom sqlalchemy_utils import force_auto_coercion\nfrom wtforms_test import FormTestCase\n\nfrom wtforms_alchemy import ModelForm\n\nforce_auto_coercion()\n\n\nclass MultiDict(dict):\n    def getlist(self, key):\n        return [self[key]]\n\n\nclass ModelFormTestCase(FormTestCase):\n    dns = \"sqlite:///:memory:\"\n\n    def setup_method(self, method):\n        self.engine = create_engine(self.dns)\n        self.base = declarative_base()\n\n    def teardown_method(self, method):\n        self.engine.dispose()\n        self.ModelTest = None\n        self.form_class = None\n\n    def init_model(self, type_=sa.Unicode(255), **kwargs):\n        kwargs.setdefault(\"nullable\", False)\n\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            query = None\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(type_, **kwargs)\n            some_property = \"something\"\n\n        self.ModelTest = ModelTest\n\n    def init(self, type_=sa.Unicode(255), **kwargs):\n        self.init_model(type_=type_, **kwargs)\n        self.init_form()\n\n    def init_form(self):\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n\n        self.form_class = ModelTestForm\n\n\nclass FormRelationsTestCase:\n    dns = \"sqlite:///:memory:\"\n\n    def setup_method(self, method):\n        self.engine = create_engine(self.dns)\n\n        self.base = declarative_base()\n        self.create_models()\n        self.create_forms()\n\n        self.base.metadata.create_all(self.engine)\n\n        Session = sessionmaker(bind=self.engine)\n        self.session = Session()\n\n    def teardown_method(self, method):\n        close_all_sessions()\n        self.base.metadata.drop_all(self.engine)\n        self.engine.dispose()\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import pytest\nfrom wtforms.form import FormMeta\n\nfrom wtforms_alchemy import (\n    model_form_factory,\n    model_form_meta_factory,\n    ModelFormMeta,\n)\n\n\nclass _MetaWithInit(FormMeta):\n    def __init__(cls, *args, **kwargs):\n        cls.test_attr = \"SomeVal\"\n        FormMeta.__init__(cls, *args, **kwargs)\n\n\nMetaWithInit = model_form_meta_factory(_MetaWithInit)\n\n\nclass _MetaWithoutInit(FormMeta):\n    test_attr = \"SomeVal\"\n\n\nMetaWithoutInit = model_form_meta_factory(_MetaWithoutInit)\n\n\n@pytest.fixture(params=[MetaWithInit, MetaWithoutInit, ModelFormMeta])\ndef model_form_all(request):\n    \"\"\"Returns one of each possible model form classes with custom and the\n    original metaclass.\"\"\"\n    ModelForm = model_form_factory(meta=request.param)\n    return ModelForm\n\n\n@pytest.fixture(params=[MetaWithInit, MetaWithoutInit])\ndef model_form_custom(request):\n    \"\"\"Returns one of each possible model form classes with custom\n    metaclasses.\"\"\"\n    return model_form_factory(meta=request.param)\n"
  },
  {
    "path": "tests/test_class_map.py",
    "content": "from pytest import mark, raises\n\nfrom wtforms_alchemy.utils import ClassMap\n\n\nclass A:\n    pass\n\n\nclass B:\n    pass\n\n\nclass A2(A):\n    pass\n\n\nclass A3(A2):\n    pass\n\n\nclass A4(A3):\n    pass\n\n\nclass A5(A4):\n    pass\n\n\nclass B2(B):\n    pass\n\n\nclass C:\n    pass\n\n\n@mark.parametrize(\"key\", [B2, B, A, A2])\ndef test_contains_with_subclass_check(key):\n    class_map = ClassMap({A: 3, B: 6})\n    assert key in class_map\n\n\n@mark.parametrize(\"key\", [B2(), B(), A(), A2()])\ndef test_contains_with_isinstance_check(key):\n    class_map = ClassMap({A: 3, B: 6})\n    assert key in class_map\n\n\n@mark.parametrize((\"key\", \"value\"), [(B2, 6), (B, 6), (A, 3), (A2, 3)])\ndef test_getitem_with_classes(key, value):\n    class_map = ClassMap({A: 3, B: 6})\n    assert class_map[key] == value\n\n\n@mark.parametrize((\"key\", \"value\"), [(B2(), 6), (B(), 6), (A(), 3), (A2(), 3)])\ndef test_getitem_with_objects(key, value):\n    class_map = ClassMap({A: 3, B: 6})\n    assert class_map[key] == value\n\n\ndef test_getitem_throws_keyerror_for_unknown_key():\n    class_map = ClassMap({A: 3, B: 6})\n    with raises(KeyError):\n        class_map[\"unknown\"]\n"
  },
  {
    "path": "tests/test_column_aliases.py",
    "content": "import sqlalchemy as sa\nfrom wtforms.validators import NumberRange\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestColumnAliases(ModelFormTestCase):\n    def test_supports_column_aliases(self):\n        class TestModel(self.base):\n            __tablename__ = \"TestTable\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            some_alias = sa.Column(\"some_name\", sa.Integer)\n\n        class TestForm(ModelForm):\n            class Meta:\n                model = TestModel\n\n        form = TestForm()\n        assert hasattr(form, \"some_alias\")\n        assert not hasattr(form, \"some_name\")\n\n    def test_labels(self):\n        class TestModel(self.base):\n            __tablename__ = \"TestTable\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            some_alias = sa.Column(\n                \"some_name\",\n                sa.Integer,\n            )\n\n        class TestForm(ModelForm):\n            class Meta:\n                model = TestModel\n\n        form = TestForm()\n        assert form.some_alias.label.text == \"some_alias\"\n\n    def test_unique_indexes(self):\n        class TestModel(self.base):\n            __tablename__ = \"TestTable\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            some_alias = sa.Column(\"some_name\", sa.Integer, unique=True)\n\n        class TestForm(ModelForm):\n            class Meta:\n                model = TestModel\n\n            @staticmethod\n            def get_session():\n                return None\n\n        form = TestForm()\n        assert hasattr(form, \"some_alias\")\n        assert not hasattr(form, \"some_name\")\n\n    def test_meta_field_args(self):\n        class TestModel(self.base):\n            __tablename__ = \"TestTable\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            some_alias = sa.Column(\"some_name\", sa.Integer)\n\n        validators = [NumberRange(max=4)]\n\n        class TestForm(ModelForm):\n            class Meta:\n                model = TestModel\n                field_args = {\"some_alias\": {\"validators\": validators}}\n\n        form = TestForm()\n        assert hasattr(form, \"some_alias\")\n        assert not hasattr(form, \"some_name\")\n        assert form.some_alias.validators == validators\n\n    def test_additional_validators(self):\n        class TestModel(self.base):\n            __tablename__ = \"TestTable\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            some_alias = sa.Column(\"some_name\", sa.Integer)\n\n        number_range = NumberRange(max=4)\n        validator_list = [number_range]\n\n        class TestForm(ModelForm):\n            class Meta:\n                model = TestModel\n                validators = {\"some_alias\": validator_list}\n\n        form = TestForm()\n        assert number_range in form.some_alias.validators\n"
  },
  {
    "path": "tests/test_configuration.py",
    "content": "import sqlalchemy as sa\nfrom pytest import raises\nfrom wtforms.fields import IntegerField\nfrom wtforms.validators import Email\n\nfrom tests import ModelFormTestCase, MultiDict\nfrom wtforms_alchemy import (\n    AttributeTypeException,\n    InvalidAttributeException,\n    ModelForm,\n)\n\n\nclass UnknownType(sa.types.UserDefinedType):\n    def get_col_spec(self):\n        return \"UNKNOWN()\"\n\n\nclass TestModelFormConfiguration(ModelFormTestCase):\n    def test_skip_unknown_types(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            query = None\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(UnknownType)\n            some_property = \"something\"\n\n        self.ModelTest = ModelTest\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                skip_unknown_types = True\n\n        self.form_class = ModelTestForm\n        assert not self.has_field(\"test_column\")\n\n    def test_supports_field_exclusion(self):\n        self.init_model()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                exclude = [\"test_column\"]\n\n        self.form_class = ModelTestForm\n        assert not self.has_field(\"test_column\")\n\n    def test_throws_exception_for_unknown_excluded_column(self):\n        self.init_model()\n\n        with raises(InvalidAttributeException):\n\n            class ModelTestForm(ModelForm):\n                class Meta:\n                    model = self.ModelTest\n                    exclude = [\"some_unknown_column\"]\n\n    def test_invalid_exclude_with_attr_errors_as_false(self):\n        self.init_model()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                attr_errors = False\n                exclude = [\"some_unknown_column\"]\n\n    def test_throws_exception_for_unknown_included_column(self):\n        self.init_model()\n\n        with raises(InvalidAttributeException):\n\n            class ModelTestForm(ModelForm):\n                class Meta:\n                    model = self.ModelTest\n                    include = [\"some_unknown_column\"]\n\n    def test_invalid_include_with_attr_errors_as_false(self):\n        self.init_model()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                attr_errors = False\n                include = [\"some_unknown_column\"]\n\n    def test_throws_exception_for_non_column_fields(self):\n        self.init_model()\n\n        with raises(AttributeTypeException):\n\n            class ModelTestForm(ModelForm):\n                class Meta:\n                    model = self.ModelTest\n                    include = [\"some_property\"]\n\n    def test_supports_field_inclusion(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                include = [\"id\"]\n\n        self.form_class = ModelTestForm\n        assert self.has_field(\"id\")\n\n    def test_supports_only_attribute(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            query = None\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.UnicodeText)\n            test_column2 = sa.Column(sa.UnicodeText)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                only = [\"test_column\"]\n\n        form = ModelTestForm()\n        assert len(form._fields) == 1\n\n    def test_empty_only_attribute(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                only = []\n\n        form = ModelTestForm()\n        assert len(form._fields) == 0\n\n    def test_supports_field_overriding(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n\n            test_column = IntegerField()\n\n        self.form_class = ModelTestForm\n        self.assert_type(\"test_column\", IntegerField)\n\n    def test_supports_assigning_all_fields_as_optional(self):\n        self.init(nullable=False)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                all_fields_optional = True\n\n        self.form_class = ModelTestForm\n        self.assert_not_required(\"test_column\")\n        self.assert_optional(\"test_column\")\n\n    def test_supports_custom_datetime_format(self):\n        self.init(sa.DateTime, nullable=False)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                datetime_format = \"%Y-%m-%dT%H:%M:%S\"\n\n        form = ModelTestForm()\n        assert form.test_column.format == [\"%Y-%m-%dT%H:%M:%S\"]\n\n    def test_supports_additional_validators(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                validators = {\"test_column\": Email()}\n\n        self.form_class = ModelTestForm\n        self.assert_has_validator(\"test_column\", Email)\n\n    def test_inherits_config_params_from_parent_meta(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                only = [\"test_column\"]\n\n        class AnotherModelTestForm(ModelTestForm):\n            class Meta:\n                pass\n\n        assert AnotherModelTestForm.Meta.only == [\"test_column\"]\n\n    def test_child_classes_override_parents_config_params(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                only = [\"test_column\"]\n\n        class AnotherModelTestForm(ModelTestForm):\n            class Meta:\n                only = []\n\n        assert AnotherModelTestForm.Meta.only == []\n\n    def test_strip_strings_fields(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                only = [\"test_column\"]\n                strip_string_fields = True\n\n        form = ModelTestForm(MultiDict(test_column=\" something \"))\n        assert form.test_column.data == \"something\"\n\n    def test_strip_strings_fields_with_empty_values(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                only = [\"test_column\"]\n                strip_string_fields = True\n\n        ModelTestForm()\n\n    def test_class_meta_regression(self):\n        self.init()\n\n        class SomeForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                foo = 9\n\n        class OtherForm(SomeForm):\n            class Meta:\n                pass\n\n        assert issubclass(OtherForm.Meta, SomeForm.Meta)\n        form = OtherForm()\n\n        # Create a side effect on the base meta.\n        assert form.Meta.foo == 9\n        SomeForm.Meta.foo = 12\n        assert form.Meta.foo == 12\n"
  },
  {
    "path": "tests/test_country_field.py",
    "content": "import sqlalchemy_utils\nfrom babel import Locale\nfrom wtforms import Form\n\nfrom tests import MultiDict\nfrom wtforms_alchemy import CountryField\n\nsqlalchemy_utils.i18n.get_locale = lambda: Locale(\"en\")\n\n\nclass TestCountryField:\n    field_class = CountryField\n\n    def init_form(self, **kwargs):\n        class TestForm(Form):\n            test_field = self.field_class(**kwargs)\n\n        self.form_class = TestForm\n        return self.form_class\n\n    def setup_method(self, method):\n        self.valid_countries = [\"US\", \"SA\", \"FI\"]\n        self.invalid_countries = [\n            \"unknown\",\n        ]\n\n    def test_valid_countries(self):\n        form_class = self.init_form()\n        for country in self.valid_countries:\n            form = form_class(MultiDict(test_field=country))\n            form.validate()\n            assert len(form.errors) == 0\n\n    def test_invalid_countries(self):\n        form_class = self.init_form()\n        for country in self.invalid_countries:\n            form = form_class(MultiDict(test_field=country))\n            form.validate()\n            assert len(form.errors[\"test_field\"]) == 2\n"
  },
  {
    "path": "tests/test_custom_fields.py",
    "content": "from wtforms import Form\nfrom wtforms_components import SelectField\n\nfrom tests import MultiDict\nfrom wtforms_alchemy import null_or_unicode\n\n\nclass TestSelectField:\n    def test_understands_none_values(self):\n        class MyForm(Form):\n            choice_field = SelectField(\n                choices=[(\"\", \"-- Choose --\"), (\"choice 1\", \"Something\")],\n                coerce=null_or_unicode,\n            )\n\n        form = MyForm(MultiDict({\"choice_field\": \"\"}))\n        form.validate()\n        assert form.errors == {}\n"
  },
  {
    "path": "tests/test_deep_form_relations.py",
    "content": "import sqlalchemy as sa\nfrom wtforms.fields import FormField\n\nfrom tests import FormRelationsTestCase, MultiDict\nfrom wtforms_alchemy import ModelFieldList, ModelForm, ModelFormField\n\n\nclass TestDeepFormRelationsOneToManyToOne(FormRelationsTestCase):\n    def create_models(self):\n        class Event(self.base):\n            __tablename__ = \"event\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n\n        class Address(self.base):\n            __tablename__ = \"address\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            street = sa.Column(sa.Unicode(255), nullable=True)\n\n        class Location(self.base):\n            __tablename__ = \"location\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=True)\n\n            address_id = sa.Column(sa.Integer, sa.ForeignKey(Address.id))\n            address = sa.orm.relationship(Address)\n\n            event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))\n            event = sa.orm.relationship(Event, backref=\"locations\")\n\n        self.Event = Event\n        self.Location = Location\n        self.Address = Address\n\n    def create_forms(self):\n        class AddressForm(ModelForm):\n            class Meta:\n                model = self.Address\n\n        class LocationForm(ModelForm):\n            class Meta:\n                model = self.Location\n\n            address = ModelFormField(AddressForm)\n\n        class EventForm(ModelForm):\n            class Meta:\n                model = self.Event\n\n            locations = ModelFieldList(FormField(LocationForm))\n\n        self.LocationForm = LocationForm\n        self.EventForm = EventForm\n        self.AddressForm = AddressForm\n\n    def save(self):\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-name\": \"Some location\",\n            \"locations-0-address-street\": \"Some address\",\n        }\n        event = self.Event()\n        self.session.add(event)\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n\n    def test_assigment_and_deletion(self):\n        self.save()\n        event = self.session.query(self.Event).first()\n        assert event.locations[0].name == \"Some location\"\n        assert event.locations[0].address.street == \"Some address\"\n        data = {\"name\": \"Some event\"}\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n        event = self.session.query(self.Event).first()\n        assert event.locations == []\n\n\nclass TestDeepFormRelationsOneToOneToMany(FormRelationsTestCase):\n    def create_models(self):\n        class Location(self.base):\n            __tablename__ = \"location\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=True)\n\n        class Address(self.base):\n            __tablename__ = \"address\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            street = sa.Column(sa.Unicode(255), nullable=True)\n\n            location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))\n            location = sa.orm.relationship(Location, backref=\"addresses\")\n\n        class Event(self.base):\n            __tablename__ = \"event\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n            location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))\n            location = sa.orm.relationship(Location)\n\n        self.Event = Event\n        self.Location = Location\n        self.Address = Address\n\n    def create_forms(self):\n        class AddressForm(ModelForm):\n            class Meta:\n                model = self.Address\n\n        class LocationForm(ModelForm):\n            class Meta:\n                model = self.Location\n\n            addresses = ModelFieldList(FormField(AddressForm))\n\n        class EventForm(ModelForm):\n            class Meta:\n                model = self.Event\n\n            location = ModelFormField(LocationForm)\n\n        self.LocationForm = LocationForm\n        self.EventForm = EventForm\n        self.AddressForm = AddressForm\n\n    def save(self):\n        data = {\n            \"name\": \"Some event\",\n            \"location-name\": \"Some location\",\n            \"location-addresses-0-street\": \"Some address\",\n        }\n        event = self.Event()\n        self.session.add(event)\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n\n    def test_assigment_and_deletion(self):\n        self.save()\n        event = self.session.query(self.Event).first()\n        assert event.location.name == \"Some location\"\n        assert event.location.addresses[0].street == \"Some address\"\n        data = {\"name\": \"Some event\"}\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n        event = self.session.query(self.Event).first()\n        assert event.location.addresses == []\n"
  },
  {
    "path": "tests/test_descriptions.py",
    "content": "from tests import ModelFormTestCase\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestFieldParameters(ModelFormTestCase):\n    def test_assigns_description_from_column_info(self):\n        self.init(info={\"description\": \"Description\"})\n        self.assert_description(\"test_column\", \"Description\")\n\n    def test_assigns_descriptions_from_form_configuration(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n\n                field_args = {\"test_column\": {\"description\": \"TESTING\"}}\n\n        self.form_class = ModelTestForm\n        self.assert_description(\"test_column\", \"TESTING\")\n"
  },
  {
    "path": "tests/test_field_exclusion.py",
    "content": "from datetime import datetime\n\nimport sqlalchemy as sa\nfrom sqlalchemy_utils import TSVectorType\n\nfrom tests import ModelFormTestCase\n\n\nclass TestFieldExclusion(ModelFormTestCase):\n    def test_does_not_include_datetime_columns_with_default(self):\n        self.init(sa.DateTime, default=datetime.now())\n        assert not self.has_field(\"test_column\")\n\n    def test_excludes_surrogate_primary_keys_by_default(self):\n        self.init()\n        assert not self.has_field(\"id\")\n\n    def test_excludes_column_properties(self):\n        self.init()\n        self.ModelTest.calculated_value = sa.orm.column_property(\n            sa.func.lower(self.ModelTest.test_column)\n        )\n        self.init_form()\n        self.form_class()\n\n\nclass TestTSVectorType(ModelFormTestCase):\n    def test_does_not_include_tsvector_typed_columns_with_default(self):\n        self.init(TSVectorType)\n        assert not self.has_field(\"test_column\")\n"
  },
  {
    "path": "tests/test_field_order.py",
    "content": "import sqlalchemy as sa\n\nfrom tests import ModelFormTestCase\n\n\nclass TestFieldOrder(ModelFormTestCase):\n    def setup_method(self, method):\n        ModelFormTestCase.setup_method(self, method)\n\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=True)\n            full_description = sa.Column(sa.UnicodeText)\n            description = sa.Column(sa.UnicodeText)\n            start_time = sa.Column(sa.DateTime)\n            end_time = sa.Column(sa.DateTime)\n            category = sa.Column(sa.Unicode(255))\n            entry_fee = sa.Column(sa.Numeric)\n            type = sa.Column(sa.Unicode(255))\n\n        self.ModelTest = ModelTest\n        self.init_form()\n\n    def test_field_definition_order(self):\n        field_names = [field.name for field in self.form_class()]\n        assert field_names == sa.inspect(self.ModelTest).attrs.keys()[1:]\n"
  },
  {
    "path": "tests/test_field_parameters.py",
    "content": "from datetime import date, time\n\nimport sqlalchemy as sa\nfrom sqlalchemy_utils import IntRangeType\nfrom wtforms import widgets\nfrom wtforms.fields import StringField\nfrom wtforms.validators import NumberRange\nfrom wtforms_components import DateRange, TimeRange\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestFieldParameters(ModelFormTestCase):\n    def test_accepts_custom_widgets(self):\n        self.init(info={\"widget\": widgets.HiddenInput()})\n        form = self.form_class()\n        assert isinstance(form.test_column.widget, widgets.HiddenInput)\n\n    def test_accepts_custom_filters(self):\n        def test_filter(a):\n            return a\n\n        self.init(info={\"filters\": [test_filter]})\n        form = self.form_class()\n        assert test_filter in form.test_column.filters\n\n    def test_assigns_description_from_column_info(self):\n        self.init(info={\"description\": \"Description\"})\n        self.assert_description(\"test_column\", \"Description\")\n\n    def test_does_not_add_default_value_if_default_is_callable(self):\n        self.init(default=lambda: \"test\")\n        self.assert_default(\"test_column\", None)\n\n    def test_assigns_scalar_defaults(self):\n        self.init(default=\"test\")\n        self.assert_default(\"test_column\", \"test\")\n\n    def test_min_and_max_info_attributes_with_integer_field(self):\n        self.init(type_=sa.Integer, info={\"min\": 1, \"max\": 100})\n        validator = self.get_validator(\"test_column\", NumberRange)\n        assert validator.min == 1\n        assert validator.max == 100\n\n    def test_min_and_max_info_attributes_with_numeric_field(self):\n        self.init(type_=sa.Numeric, info={\"min\": 1, \"max\": 100})\n        validator = self.get_validator(\"test_column\", NumberRange)\n        assert validator.min == 1\n        assert validator.max == 100\n\n    def test_min_and_max_info_attributes_with_float_field(self):\n        self.init(type_=sa.Float, info={\"min\": 1, \"max\": 100})\n        validator = self.get_validator(\"test_column\", NumberRange)\n        assert validator.min == 1\n        assert validator.max == 100\n\n    def test_min_and_max_info_attributes_with_int_range_field(self):\n        self.init(type_=IntRangeType, info={\"min\": 1, \"max\": 100})\n        validator = self.get_validator(\"test_column\", NumberRange)\n        assert validator.min == 1\n        assert validator.max == 100\n\n    def test_min_and_max_info_attributes_generate_time_range_validator(self):\n        self.init(type_=sa.types.Time, info={\"min\": time(12, 30), \"max\": time(14, 30)})\n        validator = self.get_validator(\"test_column\", TimeRange)\n        assert validator.min == time(12, 30)\n        assert validator.max == time(14, 30)\n\n    def test_min_and_max_info_attributes_generate_date_range_validator(self):\n        self.init(\n            type_=sa.Date, info={\"min\": date(1990, 1, 1), \"max\": date(2000, 1, 1)}\n        )\n        validator = self.get_validator(\"test_column\", DateRange)\n        assert validator.min == date(1990, 1, 1)\n        assert validator.max == date(2000, 1, 1)\n\n    def test_uses_custom_field_class(self):\n        class InputTest(widgets.Input):\n            input_type = \"color\"\n            validation_attrs = []\n\n        class FieldTest(StringField):\n            widget = InputTest()\n\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            query = None\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(\n                sa.UnicodeText, info={\"form_field_class\": FieldTest}\n            )\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n\n        form = ModelTestForm()\n        assert 'type=\"color\"' in str(form.test_column)\n\n    def test_accepts_none_as_custom_field_class(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            query = None\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.UnicodeText, info={\"form_field_class\": None})\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n\n        assert ModelTestForm()\n"
  },
  {
    "path": "tests/test_field_trimming.py",
    "content": "from tests import ModelFormTestCase, MultiDict\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestStringFieldTrimming(ModelFormTestCase):\n    def test_strip_string_fields_set_for_string_field(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                strip_string_fields = True\n\n        f = ModelTestForm(MultiDict([(\"test_column\", \"strip this   \")]))\n        assert f.test_column.data == \"strip this\"\n\n    def test_does_not_trim_fields_when_trim_param_is_false(self):\n        self.init(info={\"trim\": False})\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                strip_string_fields = True\n\n        f = ModelTestForm(MultiDict([(\"test_column\", \"strip this   \")]))\n        assert f.test_column.data == \"strip this   \"\n"
  },
  {
    "path": "tests/test_form_meta.py",
    "content": "import sqlalchemy as sa\n\nfrom tests import ModelFormTestCase\n\n\nclass TestModelFormMetaWithInheritance(ModelFormTestCase):\n    def test_skip_unknown_types(self, model_form_all):\n        self.init(type_=sa.Integer)\n\n        class ModelTestForm(model_form_all):\n            class Meta:\n                skip_unknown_types = True\n\n        class ModelTestForm2(ModelTestForm):\n            class Meta:\n                model = self.ModelTest\n\n        self.form_class = ModelTestForm2\n        assert self.form_class.Meta.skip_unknown_types is True\n\n    def test_inheritance_attributes(self, model_form_custom):\n        self.init(type_=sa.Integer)\n\n        class ModelTestForm(model_form_custom):\n            class Meta:\n                model = self.ModelTest\n\n        assert ModelTestForm.test_attr == \"SomeVal\"\n\n\nclass TestUnboundFieldsInitialization(ModelFormTestCase):\n    def test_skip_unknown_types(self, model_form_all):\n        self.init(type_=sa.Integer)\n\n        class ModelTestForm(model_form_all):\n            class Meta:\n                model = self.ModelTest\n                skip_unknown_types = True\n\n        assert ModelTestForm.test_column\n"
  },
  {
    "path": "tests/test_hybrid_properties.py",
    "content": "import sqlalchemy as sa\nfrom pytest import raises\nfrom sqlalchemy.ext.hybrid import hybrid_property\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import ModelForm\nfrom wtforms_alchemy.exc import AttributeTypeException\n\n\nclass TestHybridProperties(ModelFormTestCase):\n    def test_hybrid_property_returning_column_property(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            _test_column = sa.Column(\"test_column\", sa.Boolean, nullable=False)\n\n            @hybrid_property\n            def test_column_hybrid(self):\n                return self._test_column\n\n            @test_column_hybrid.setter\n            def test_column_hybrid(self, value):\n                self._test_column = value\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_str_validator = None\n                not_null_validator = None\n                include = (\"test_column_hybrid\",)\n                exclude = (\"_test_column\",)\n\n        form = ModelTestForm()\n        assert form.test_column_hybrid\n\n    def test_hybrid_property_returning_expression(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            _test_column = sa.Column(\"test_column\", sa.Boolean, nullable=False)\n\n            @hybrid_property\n            def test_column_hybrid(self):\n                return self._test_column + self._test_column\n\n            @test_column_hybrid.setter\n            def test_column_hybrid(self, value):\n                self._test_column = value\n\n        with raises(AttributeTypeException):\n\n            class ModelTestForm(ModelForm):\n                class Meta:\n                    model = ModelTest\n                    not_null_str_validator = None\n                    not_null_validator = None\n                    include = (\"test_column_hybrid\",)\n                    exclude = (\"_test_column\",)\n"
  },
  {
    "path": "tests/test_i18n_extension.py",
    "content": "import sqlalchemy as sa\nfrom packaging.version import Version\nfrom pytest import raises, skip\nfrom sqlalchemy_i18n import make_translatable, Translatable, translation_base\n\nfrom tests import ModelFormTestCase, MultiDict\nfrom wtforms_alchemy import ModelForm\n\nsqlalchemy_version = sa.__version__\nif Version(sqlalchemy_version) >= Version(\"2.0\"):\n    skip(\"sqlalchemy_i18n does not support SQLAlchemy 2.0\", allow_module_level=True)\n\nmake_translatable()\n\n\nclass TestInternationalizationExtension(ModelFormTestCase):\n    def init(self):\n        class ModelTest(self.base, Translatable):\n            __tablename__ = \"model_test\"\n            __translatable__ = {\"locales\": [\"fi\", \"en\"]}\n\n            id = sa.Column(sa.Integer, primary_key=True)\n            some_property = \"something\"\n\n            locale = \"en\"\n\n        class ModelTranslation(translation_base(ModelTest)):\n            __tablename__ = \"model_translation\"\n\n            name = sa.Column(sa.Unicode(255))\n            content = sa.Column(sa.Unicode(255))\n\n        self.ModelTest = ModelTest\n        sa.orm.configure_mappers()\n        Session = sa.orm.sessionmaker(bind=self.engine)\n        self.session = Session()\n\n        self.init_form()\n\n    def test_supports_translated_columns(self):\n        self.init()\n        form = self.form_class()\n        assert form.name\n        assert form.content\n\n    def test_supports_field_exclusion(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n                exclude = [\"name\"]\n\n        with raises(AttributeError):\n            ModelTestForm().name\n\n    def test_model_population(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n\n        form = ModelTestForm(\n            MultiDict([(\"name\", \"something\"), (\"content\", \"something\")])\n        )\n        obj = self.ModelTest()\n        form.populate_obj(obj)\n        assert obj.name == \"something\"\n        assert obj.content == \"something\"\n"
  },
  {
    "path": "tests/test_inheritance.py",
    "content": "from wtforms import Form\nfrom wtforms_test import FormTestCase\n\nfrom wtforms_alchemy import model_form_factory, ModelForm\n\n\nclass TestInheritance(FormTestCase):\n    class Base(Form):\n        @classmethod\n        def get_session(self):\n            return \"TestSession\"\n\n    def test_default_base(self):\n        assert ModelForm.get_session is None\n\n    def test_custom_base_without_session(self):\n        cls = model_form_factory(Form)\n        assert cls.get_session is None\n\n    def test_custom_base_with_session(self):\n        cls = model_form_factory(self.Base)\n        assert cls.get_session() == \"TestSession\"\n\n    def test_inherit_with_new_session(self):\n        cls = model_form_factory(self.Base)\n\n        class Sub(cls):\n            @classmethod\n            def get_session(self):\n                return \"SubTestSession\"\n\n        assert Sub.get_session() == \"SubTestSession\"\n\n    def test_inherit_without_new_session(self):\n        cls = model_form_factory(self.Base)\n\n        class Sub(cls):\n            pass\n\n        assert Sub.get_session() == \"TestSession\"\n"
  },
  {
    "path": "tests/test_labels.py",
    "content": "from tests import ModelFormTestCase\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestFieldLabels(ModelFormTestCase):\n    def test_assigns_labels_from_column_info(self):\n        self.init(info={\"label\": \"Test Column\"})\n        self.assert_label(\"test_column\", \"Test Column\")\n\n    def test_assigns_labels_from_form_configuration(self):\n        self.init()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = self.ModelTest\n\n                field_args = {\"test_column\": {\"label\": \"TESTING\"}}\n\n        self.form_class = ModelTestForm\n        self.assert_label(\"test_column\", \"TESTING\")\n"
  },
  {
    "path": "tests/test_model_field_list.py",
    "content": "import sqlalchemy as sa\nfrom wtforms.fields import FormField\nfrom wtforms_components import PassiveHiddenField\n\nfrom tests import FormRelationsTestCase, MultiDict\nfrom wtforms_alchemy import ModelFieldList, ModelForm\n\n\nclass ModelFieldListTestCase(FormRelationsTestCase):\n    def create_models(self):\n        class Event(self.base):\n            __tablename__ = \"event\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n\n        class Location(self.base):\n            __tablename__ = \"location\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=True)\n\n            event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))\n            event = sa.orm.relationship(Event, backref=\"locations\")\n\n        self.Event = Event\n        self.Location = Location\n\n    def save(self, event=None, data=None):\n        if data is None:\n            data = {\n                \"name\": \"Some event\",\n                \"locations-0-name\": \"Some location\",\n                \"locations-0-description\": \"Some description\",\n            }\n        if not event:\n            event = self.Event()\n            self.session.add(event)\n            form = self.EventForm(MultiDict(data))\n        else:\n            form = self.EventForm(MultiDict(data), obj=event)\n\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n        return event\n\n\nclass TestReplaceStrategy(ModelFieldListTestCase):\n    def create_forms(self):\n        class LocationForm(ModelForm):\n            class Meta:\n                model = self.Location\n\n        class EventForm(ModelForm):\n            class Meta:\n                model = self.Event\n\n            locations = ModelFieldList(FormField(LocationForm))\n\n        self.LocationForm = LocationForm\n        self.EventForm = EventForm\n\n    def test_assigment_and_deletion(self):\n        self.save()\n        event = self.session.query(self.Event).first()\n        assert event.locations[0].name == \"Some location\"\n        data = {\"name\": \"Some event\"}\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n        event = self.session.query(self.Event).first()\n        assert event.locations == []\n\n\nclass TestUpdateStrategy(ModelFieldListTestCase):\n    def create_models(self):\n        class Event(self.base):\n            __tablename__ = \"event\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n\n        class Location(self.base):\n            __tablename__ = \"location\"\n            TYPES = (\"\", \"football field\", \"restaurant\")\n\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=True)\n            description = sa.Column(sa.Unicode(255), default=\"\")\n            type = sa.Column(\n                sa.Unicode(255), info={\"choices\": zip(TYPES, TYPES)}, default=\"\"\n            )\n\n            event_id = sa.Column(sa.Integer, sa.ForeignKey(Event.id))\n            event = sa.orm.relationship(Event, backref=\"locations\")\n\n            def __repr__(self):\n                return f\"Location(id={self.id!r}, name={self.name!r})\"\n\n        self.Event = Event\n        self.Location = Location\n\n    def create_forms(self):\n        class LocationForm(ModelForm):\n            class Meta:\n                model = self.Location\n                only = [\"name\", \"description\", \"type\"]\n\n            id = PassiveHiddenField()\n\n        class EventForm(ModelForm):\n            class Meta:\n                model = self.Event\n\n            locations = ModelFieldList(\n                FormField(LocationForm), population_strategy=\"update\"\n            )\n\n        self.LocationForm = LocationForm\n        self.EventForm = EventForm\n\n    def test_single_entry_update(self):\n        event = self.save()\n        location_id = event.locations[0].id\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-id\": location_id,\n            \"locations-0-name\": \"Some other location\",\n        }\n        self.save(event, data)\n\n        assert len(event.locations) == 1\n        assert event.locations[0].id == location_id\n        assert event.locations[0].name == \"Some other location\"\n\n    def test_creates_new_objects_for_entries_with_unknown_identifiers(self):\n        event = self.save()\n        location_id = event.locations[0].id\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-id\": 12,\n            \"locations-0-name\": \"Some other location\",\n        }\n        self.save(event, data)\n        assert event.locations\n        assert event.locations[0].id != location_id\n\n    def test_replace_entry(self):\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-name\": \"Some location\",\n            \"locations-0-description\": \"Some description\",\n            \"locations-0-type\": \"restaurant\",\n        }\n        event = self.save(data=data)\n        location_id = event.locations[0].id\n        self.session.commit()\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-name\": \"Some other location\",\n        }\n        self.save(event, data)\n        location = event.locations[0]\n        assert location.id != location_id\n        assert location.name == \"Some other location\"\n        assert location.description == \"\"\n        assert location.type == \"\"\n        assert len(event.locations) == 1\n\n    def test_replace_and_update(self):\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-name\": \"Location 1\",\n            \"locations-0-description\": \"Location 1 description\",\n            \"locations-1-name\": \"Location 2\",\n            \"locations-1-description\": \"Location 2 description\",\n        }\n        event = self.save(data=data)\n        self.session.commit()\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-id\": event.locations[1].id,\n            \"locations-0-name\": \"Location 2 updated\",\n            \"locations-0-description\": \"Location 2 description updated\",\n            \"locations-1-name\": \"Location 3\",\n        }\n        self.save(event, data)\n        self.session.commit()\n        location = event.locations[0]\n        location2 = event.locations[1]\n        assert location.name == \"Location 2 updated\"\n        assert location.description == \"Location 2 description updated\"\n        assert len(event.locations) == 2\n        assert location2.name == \"Location 3\"\n        assert location2.description == \"\"\n\n    def test_multiple_entries(self):\n        event = self.save()\n        location_id = event.locations[0].id\n        data = {\n            \"name\": \"Some event\",\n            \"locations-0-name\": \"Some location\",\n            \"locations-1-id\": str(location_id),  # test coercing works\n            \"locations-1-name\": \"Some other location\",\n            \"locations-2-name\": \"Third location\",\n            \"locations-3-id\": 123,\n            \"locations-3-name\": \"Fourth location\",\n        }\n        self.save(event, data)\n        assert len(event.locations) == 4\n        assert event.locations[0].id == location_id\n        assert event.locations[0].name == \"Some other location\"\n        assert event.locations[1].name == \"Some location\"\n        assert event.locations[2].name == \"Third location\"\n        assert event.locations[3].name == \"Fourth location\"\n\n    def test_delete_all_field_list_entries(self):\n        event = self.save()\n        data = {\"name\": \"Some event\"}\n        self.save(event, data)\n        assert not event.locations\n\n    def test_update_and_remove(self):\n        location = self.Location(name=\"Location #2\")\n        event = self.Event(\n            name=\"Some event\", locations=[self.Location(name=\"Location #1\"), location]\n        )\n        self.session.add(event)\n        self.session.commit()\n        data = {\n            \"locations-0-id\": location.id,\n            \"locations-0-name\": \"Location\",\n        }\n        self.save(event, data)\n        self.session.refresh(event)\n        assert len(event.locations) == 1\n        assert event.locations[0] == location\n"
  },
  {
    "path": "tests/test_model_form_factory.py",
    "content": "import wtforms\nfrom pytest import raises\nfrom wtforms import Form\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import (\n    FormGenerator,\n    model_form_factory,\n    UnknownConfigurationOption,\n)\n\n\nclass TestModelFormFactory(ModelFormTestCase):\n    def test_supports_parameter_overriding(self):\n        self.init()\n\n        class MyFormGenerator(FormGenerator):\n            pass\n\n        defaults = {\n            \"all_fields_optional\": True,\n            \"only_indexed_fields\": True,\n            \"include_primary_keys\": True,\n            \"include_foreign_keys\": True,\n            \"strip_string_fields\": True,\n            \"include_datetimes_with_default\": True,\n            \"form_generator\": True,\n            \"date_format\": \"%d-%m-%Y\",\n            \"datetime_format\": \"%Y-%m-%dT%H:%M:%S\",\n        }\n        ModelForm = model_form_factory(Form, **defaults)\n        for key, value in defaults.items():\n            assert getattr(ModelForm.Meta, key) == value\n\n    def test_throws_exception_for_unknown_configuration_option(self):\n        self.init()\n\n        class SomeForm(Form):\n            pass\n\n        defaults = {\"unknown\": \"something\"}\n        with raises(UnknownConfigurationOption):\n            model_form_factory(SomeForm, **defaults)\n\n    def test_supports_custom_base_class_with_model_form_factory(self):\n        self.init()\n\n        class SomeForm(Form):\n            pass\n\n        class TestCustomBase(model_form_factory(SomeForm)):\n            class Meta:\n                model = self.ModelTest\n\n        assert isinstance(TestCustomBase(), SomeForm)\n\n    def test_url_validator(self):\n        form = model_form_factory(url_validator=None)\n        assert form.Meta.url_validator is None\n\n    def test_email_validator(self):\n        form = model_form_factory(email_validator=None)\n        assert form.Meta.email_validator is None\n\n    def test_length_validator(self):\n        form = model_form_factory(length_validator=None)\n        assert form.Meta.length_validator is None\n\n    def test_number_range_validator(self):\n        form = model_form_factory(number_range_validator=None)\n        assert form.Meta.number_range_validator is None\n\n    def test_date_range_validator(self):\n        form = model_form_factory(date_range_validator=None)\n        assert form.Meta.date_range_validator is None\n\n    def test_time_range_validator(self):\n        form = model_form_factory(time_range_validator=None)\n        assert form.Meta.time_range_validator is None\n\n    def test_optional_validator(self):\n        form = model_form_factory(optional_validator=None)\n        assert form.Meta.optional_validator is None\n\n    def test_unique_validator(self):\n        form = model_form_factory(unique_validator=None)\n        assert form.Meta.unique_validator is None\n\n    def test_class_meta_wtforms2(self):\n        self.init()\n\n        class SomeForm(Form):\n            class Meta:\n                locales = [\"fr\"]\n                foo = 9\n\n        class OtherForm(SomeForm):\n            class Meta:\n                pass\n\n        class TestCustomBase(model_form_factory(SomeForm)):\n            class Meta:\n                model = self.ModelTest\n\n        form = TestCustomBase()\n        other_form = OtherForm()\n        assert isinstance(form.meta, wtforms.meta.DefaultMeta)\n        assert form.meta.locales == [\"fr\"]\n        assert hasattr(form.meta, \"model\")\n        assert hasattr(form.meta, \"csrf\")\n\n        assert form.meta.foo == 9\n        # Create a side effect on the base meta.\n        SomeForm.Meta.foo = 12\n        assert other_form.meta.foo == 12\n        assert form.meta.foo == 12\n"
  },
  {
    "path": "tests/test_model_form_field.py",
    "content": "import sqlalchemy as sa\nfrom pytest import raises\n\nfrom tests import FormRelationsTestCase, MultiDict\nfrom wtforms_alchemy import ModelForm, ModelFormField\n\n\nclass TestOneToOneModelFormRelations(FormRelationsTestCase):\n    def create_models(self):\n        class Location(self.base):\n            __tablename__ = \"location\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=True)\n\n        class Event(self.base):\n            __tablename__ = \"event\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n            location_id = sa.Column(sa.Integer, sa.ForeignKey(Location.id))\n            location = sa.orm.relationship(Location)\n\n        self.Event = Event\n        self.Location = Location\n\n    def create_forms(self):\n        class LocationForm(ModelForm):\n            class Meta:\n                model = self.Location\n\n        class EventForm(ModelForm):\n            class Meta:\n                model = self.Event\n\n            location = ModelFormField(LocationForm)\n\n        self.LocationForm = LocationForm\n        self.EventForm = EventForm\n\n    def save(self, event=None, data={}):\n        if not data:\n            data = {\n                \"name\": \"Some event\",\n                \"location-name\": \"Some location\",\n            }\n        if not event:\n            event = self.Event()\n        self.session.add(event)\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n        return event\n\n    def test_assigment_and_deletion(self):\n        self.save()\n        event = self.session.query(self.Event).first()\n        assert event.location.name == \"Some location\"\n        data = {\"name\": \"Some event\"}\n        form = self.EventForm(MultiDict(data))\n        form.validate()\n        form.populate_obj(event)\n        self.session.commit()\n        event = self.session.query(self.Event).first()\n        assert not event.location.name\n\n    def test_only_populates_related_if_they_are_obj_attributes(self):\n        class EventForm(ModelForm):\n            class Meta:\n                model = self.Event\n\n            unknown_field = ModelFormField(self.LocationForm)\n\n        self.EventForm = EventForm\n\n        with raises(TypeError):\n            self.save(\n                data={\n                    \"name\": \"Some event\",\n                    \"unknown_field-name\": \"Some location\",\n                }\n            )\n\n    def test_updating_related_object(self):\n        event = self.save()\n        location_id = event.location.id\n        self.save(event, {\"name\": \"some name\", \"location-name\": \"Some other location\"})\n        assert event.name == \"some name\"\n        assert event.location.id == location_id\n"
  },
  {
    "path": "tests/test_phone_number.py",
    "content": "import sqlalchemy as sa\nfrom pytest import mark\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import declarative_base, sessionmaker\nfrom sqlalchemy.orm.session import close_all_sessions\nfrom sqlalchemy_utils.types import phone_number\n\nfrom tests import MultiDict\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestCase:\n    def setup_method(self, method):\n        self.engine = create_engine(\"sqlite:///:memory:\")\n        self.Base = declarative_base()\n\n        self.create_models()\n        self.Base.metadata.create_all(self.engine)\n\n        Session = sessionmaker(bind=self.engine)\n        self.session = Session()\n\n    def teardown_method(self, method):\n        close_all_sessions()\n        self.Base.metadata.drop_all(self.engine)\n        self.engine.dispose()\n\n    def create_models(self):\n        class User(self.Base):\n            __tablename__ = \"user\"\n            id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n            name = sa.Column(sa.Unicode(255))\n            phone_number = sa.Column(phone_number.PhoneNumberType(country_code=\"FI\"))\n\n        self.User = User\n\n\n@mark.xfail(\"phone_number.phonenumbers is None\")\nclass TestPhoneNumbers(TestCase):\n    \"\"\"\n    Simple tests to ensure that sqlalchemy_utils.PhoneNumber,\n    wtforms_alchemy.PhoneNumberType and sqlalchemy_utils.PhoneNumberField work\n    nicely together.\n    \"\"\"\n\n    def setup_method(self, method):\n        super().setup_method(method)\n\n        class UserForm(ModelForm):\n            class Meta:\n                model = self.User\n\n        self.UserForm = UserForm\n\n        super().setup_method(method)\n        self.phone_number = phone_number.PhoneNumber(\"040 1234567\", \"FI\")\n        self.user = self.User()\n        self.user.name = \"Someone\"\n        self.user.phone_number = self.phone_number\n        self.session.add(self.user)\n        self.session.commit()\n\n    def test_query_returns_phone_number_object(self):\n        queried_user = self.session.query(self.User).first()\n        assert queried_user.phone_number == self.phone_number\n\n    def test_phone_number_is_stored_as_string(self):\n        result = self.session.execute(\n            sa.text(\"SELECT phone_number FROM user WHERE id=:param\"),\n            {\"param\": self.user.id},\n        )\n        assert result.first()[0] == \"+358401234567\"\n\n    def test_phone_number_in_form(self):\n        form = self.UserForm(\n            MultiDict(name=\"Matti Meikalainen\", phone_number=\"+358401231233\")\n        )\n        form.validate()\n        assert len(form.errors) == 0\n        assert form.data[\"phone_number\"] == (phone_number.PhoneNumber(\"+358401231233\"))\n\n    def test_empty_phone_number_in_form(self):\n        form = self.UserForm(MultiDict(name=\"Matti Meikalainen\", phone_number=\"\"))\n        form.validate()\n        assert len(form.errors) == 0\n        assert form.data[\"phone_number\"] is None\n"
  },
  {
    "path": "tests/test_phone_number_field.py",
    "content": "import pytest\nfrom wtforms import Form\n\nfrom tests import MultiDict\nfrom wtforms_alchemy import DataRequired, PhoneNumberField\n\n\nclass TestPhoneNumberField:\n    def setup_method(self, method):\n        self.valid_phone_numbers = [\n            \"040 1234567\",\n            \"+358 401234567\",\n            \"09 2501234\",\n            \"+358 92501234\",\n            \"0800 939393\",\n            \"09 4243 0456\",\n            \"0600 900 500\",\n        ]\n        self.invalid_phone_numbers = [\"abc\", \"+040 1234567\", \"0111234567\", \"358\"]\n\n    def init_form(self, **kwargs):\n        class TestForm(Form):\n            phone_number = PhoneNumberField(**kwargs)\n\n        return TestForm\n\n    def test_valid_phone_numbers(self):\n        form_class = self.init_form(region=\"FI\")\n        for phone_number in self.valid_phone_numbers:\n            form = form_class(MultiDict(phone_number=phone_number))\n            form.validate()\n            assert len(form.errors) == 0\n\n    def test_invalid_phone_numbers(self):\n        form_class = self.init_form(region=\"FI\")\n        for phone_number in self.invalid_phone_numbers:\n            form = form_class(MultiDict(phone_number=phone_number))\n            form.validate()\n            assert len(form.errors[\"phone_number\"]) == 1\n\n    def test_render_empty_phone_number_value(self):\n        form_class = self.init_form(region=\"FI\")\n        form = form_class(MultiDict(phone_number=\"\"))\n        assert 'value=\"\"' in form.phone_number()\n\n    def test_empty_phone_number_value_passed_as_none(self):\n        form_class = self.init_form(region=\"FI\")\n        form = form_class(MultiDict(phone_number=\"\"))\n        form.validate()\n        assert len(form.errors) == 0\n        assert form.data[\"phone_number\"] is None\n\n    def test_default_display_format(self):\n        form_class = self.init_form(region=\"FI\")\n        form = form_class(MultiDict(phone_number=\"+358401234567\"))\n        assert 'value=\"040 1234567\"' in form.phone_number()\n\n    def test_international_display_format(self):\n        form_class = self.init_form(region=\"FI\", display_format=\"international\")\n        form = form_class(MultiDict(phone_number=\"0401234567\"))\n        assert 'value=\"+358 40 1234567\"' in form.phone_number()\n\n    def test_e164_display_format(self):\n        form_class = self.init_form(region=\"FI\", display_format=\"e164\")\n        form = form_class(MultiDict(phone_number=\"0401234567\"))\n        assert 'value=\"+358401234567\"' in form.phone_number()\n\n    def test_field_rendering_when_invalid_phone_number(self):\n        form_class = self.init_form()\n        form = form_class(MultiDict(phone_number=\"invalid\"))\n        form.validate()\n        assert 'value=\"invalid\"' in form.phone_number()\n\n    @pytest.mark.parametrize(\n        \"number,error_msg,check_value\",\n        (\n            (\"\", \"This field is required.\", lambda v, orig: v is None),\n            (\"1\", \"Not a valid phone number value\", lambda v, orig: v is not None),\n            (\"123\", \"Not a valid phone number value\", lambda v, orig: v is not None),\n            (\"+46123456789\", None, lambda v, orig: v.e164 == orig),\n        ),\n    )\n    def test_required_phone_number_form(self, number, error_msg, check_value):\n        class PhoneNumberForm(Form):\n            phone = PhoneNumberField(\"Phone number\", validators=[DataRequired()])\n\n        form = PhoneNumberForm(MultiDict(phone=number))\n        form.validate()\n        if error_msg:\n            assert len(form.errors) == 1\n            assert form.errors[\"phone\"][0] == error_msg\n        else:\n            assert len(form.errors) == 0\n        assert check_value(form.phone.data, number) is True\n"
  },
  {
    "path": "tests/test_query_select_field.py",
    "content": "import sqlalchemy as sa\nfrom sqlalchemy.orm import declarative_base\nfrom sqlalchemy.orm.session import close_all_sessions\nfrom wtforms import Form\n\nfrom wtforms_alchemy import (\n    GroupedQuerySelectField,\n    GroupedQuerySelectMultipleField,\n    ModelForm,\n    QuerySelectField,\n    QuerySelectMultipleField,\n)\n\n\nclass DummyPostData(dict):\n    def getlist(self, key):\n        v = self[key]\n        if not isinstance(v, (list, tuple)):\n            v = [v]\n        return v\n\n\nclass LazySelect:\n    def __call__(self, field, **kwargs):\n        return list(\n            (val, str(label), selected)\n            for val, label, selected, _ in field.iter_choices()\n        )\n\n\nclass Base:\n    def __init__(self, **kwargs):\n        for k, v in kwargs.items():\n            setattr(self, k, v)\n\n\nclass TestBase:\n    def create_models(self):\n        class Test(self.base):\n            __tablename__ = \"test\"\n            id = sa.Column(sa.Integer, primary_key=True, nullable=False)\n            name = sa.Column(sa.String, nullable=False)\n\n        self.Test = Test\n\n        class PKTest(self.base):\n            __tablename__ = \"pk_test\"\n            foobar = sa.Column(sa.String, primary_key=True, nullable=False)\n            baz = sa.Column(sa.String, nullable=False)\n\n            def __str__(self):\n                return self.baz\n\n        self.PKTest = PKTest\n\n    def _fill(self, sess):\n        for i, n in [(1, \"apple\"), (2, \"banana\")]:\n            s = self.Test(id=i, name=n)\n            p = self.PKTest(foobar=f\"hello{i}\", baz=n)\n            sess.add(s)\n            sess.add(p)\n        sess.flush()\n        sess.commit()\n\n\nclass TestQuerySelectField(TestBase):\n    def setup_method(self):\n        self.engine = sa.create_engine(\"sqlite:///:memory:\", echo=False)\n\n        self.base = declarative_base()\n        self.create_models()\n\n        self.base.metadata.create_all(self.engine)\n\n        Session = sa.orm.session.sessionmaker(bind=self.engine)\n        self.session = Session()\n\n    def teardown_method(self):\n        close_all_sessions()\n        self.base.metadata.drop_all(self.engine)\n        self.engine.dispose()\n\n    def test_without_factory(self):\n        self._fill(self.session)\n\n        class F(Form):\n            a = QuerySelectField(\n                get_label=\"name\", widget=LazySelect(), get_pk=lambda x: x.id\n            )\n\n        form = F(DummyPostData(a=[\"1\"]))\n        form.a.query = self.session.query(self.Test)\n        assert form.a.data is not None\n        assert form.a.data.id, 1\n        assert form.a(), [(\"1\", \"apple\", True), (\"2\", \"banana\", False)]\n        assert form.validate()\n\n        form = F(a=self.session.query(self.Test).filter_by(name=\"banana\").first())\n        form.a.query = self.session.query(self.Test).filter(self.Test.name != \"banana\")\n        assert not form.validate()\n        assert form.a.errors, [\"Not a valid choice\"]\n\n        # Test query with no results\n        form = F()\n        form.a.query = (\n            self.session.query(self.Test)\n            .filter(self.Test.id == 1, self.Test.id != 1)\n            .all()\n        )\n        assert form.a() == []\n\n    def test_with_query_factory(self):\n        self._fill(self.session)\n\n        class F(Form):\n            a = QuerySelectField(\n                get_label=(lambda model: model.name),\n                query_factory=lambda: self.session.query(self.Test),\n                widget=LazySelect(),\n            )\n            b = QuerySelectField(\n                allow_blank=True,\n                query_factory=lambda: self.session.query(self.PKTest),\n                widget=LazySelect(),\n            )\n\n        form = F()\n        assert form.a.data is None\n        assert form.a() == [(\"1\", \"apple\", False), (\"2\", \"banana\", False)]\n        assert form.b.data is None\n        assert form.b() == [\n            (\"__None\", \"\", True),\n            (\"hello1\", \"apple\", False),\n            (\"hello2\", \"banana\", False),\n        ]\n        assert not form.validate()\n\n        # Test query with no results\n        form = F()\n        form.a.query = (\n            self.session.query(self.Test)\n            .filter(self.Test.id == 1, self.Test.id != 1)\n            .all()\n        )\n        assert form.a() == []\n\n        form = F(DummyPostData(a=[\"1\"], b=[\"hello2\"]))\n        assert form.a.data.id == 1\n        assert form.a() == [(\"1\", \"apple\", True), (\"2\", \"banana\", False)]\n        assert form.b.data.baz == \"banana\"\n        assert form.b() == [\n            (\"__None\", \"\", False),\n            (\"hello1\", \"apple\", False),\n            (\"hello2\", \"banana\", True),\n        ]\n        assert form.validate()\n\n        # Make sure the query is cached\n        self.session.add(self.Test(id=3, name=\"meh\"))\n        self.session.flush()\n        self.session.commit()\n        assert form.a() == [(\"1\", \"apple\", True), (\"2\", \"banana\", False)]\n        form.a._object_list = None\n        assert form.a() == [\n            (\"1\", \"apple\", True),\n            (\"2\", \"banana\", False),\n            (\"3\", \"meh\", False),\n        ]\n\n        # Test bad data\n        form = F(DummyPostData(b=[\"__None\"], a=[\"fail\"]))\n        assert not form.validate()\n        assert form.a.errors == [\"Not a valid choice\"]\n        assert form.b.errors == []\n        assert form.b.data is None\n\n\nclass TestQuerySelectMultipleField(TestBase):\n    def setup_method(self):\n        self.engine = sa.create_engine(\"sqlite:///:memory:\", echo=False)\n\n        self.base = declarative_base()\n        self.create_models()\n\n        self.base.metadata.create_all(self.engine)\n\n        Session = sa.orm.session.sessionmaker(bind=self.engine)\n        self.session = Session()\n\n        self._fill(self.session)\n\n    def teardown_method(self):\n        close_all_sessions()\n        self.base.metadata.drop_all(self.engine)\n        self.engine.dispose()\n\n    class F(Form):\n        a = QuerySelectMultipleField(get_label=\"name\", widget=LazySelect())\n\n    def test_unpopulated_default(self):\n        form = self.F()\n        assert [] == form.a.data\n\n    def test_single_value_without_factory(self):\n        form = self.F(DummyPostData(a=[\"1\"]))\n        form.a.query = self.session.query(self.Test)\n        assert [1] == [v.id for v in form.a.data]\n        assert form.a() == [(\"1\", \"apple\", True), (\"2\", \"banana\", False)]\n        assert form.validate()\n\n    def test_multiple_values_without_query_factory(self):\n        form = self.F(DummyPostData(a=[\"1\", \"2\"]))\n        form.a.query = self.session.query(self.Test)\n        assert [1, 2] == [v.id for v in form.a.data]\n        assert form.a() == [(\"1\", \"apple\", True), (\"2\", \"banana\", True)]\n        assert form.validate()\n\n        form = self.F(DummyPostData(a=[\"1\", \"3\"]))\n        form.a.query = self.session.query(self.Test)\n        assert [x.id for x in form.a.data], [1]\n        assert not form.validate()\n\n    def test_single_default_value(self):\n        first_test = self.session.get(self.Test, 2)\n\n        class F(Form):\n            a = QuerySelectMultipleField(\n                get_label=\"name\",\n                default=[first_test],\n                widget=LazySelect(),\n                query_factory=lambda: self.session.query(self.Test),\n            )\n\n        form = F()\n        assert [v.id for v in form.a.data], [2]\n        assert form.a(), [(\"1\", \"apple\", False), (\"2\", \"banana\", True)]\n        assert form.validate()\n\n    def test_empty_query(self):\n        # Test query with no results\n        form = self.F()\n        form.a.query = (\n            self.session.query(self.Test)\n            .filter(self.Test.id == 1, self.Test.id != 1)\n            .all()\n        )\n        assert form.a() == []\n\n\nclass DatabaseTestCase:\n    def setup_method(self, method):\n        self.engine = sa.create_engine(\"sqlite:///:memory:\")\n\n        self.base = declarative_base()\n        self.create_models()\n\n        self.base.metadata.create_all(self.engine)\n\n        Session = sa.orm.session.sessionmaker(bind=self.engine)\n        self.session = Session()\n\n    def teardown_method(self, method):\n        close_all_sessions()\n        self.base.metadata.drop_all(self.engine)\n        self.engine.dispose()\n\n    def create_models(self):\n        class City(self.base):\n            __tablename__ = \"city\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.String)\n            country = sa.Column(sa.String)\n            state_id = sa.Column(sa.Integer, sa.ForeignKey(\"state.id\"))\n\n        self.City = City\n\n        class State(self.base):\n            __tablename__ = \"state\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            cities = sa.orm.relationship(\"City\")\n\n        self.State = State\n\n    def create_cities(self):\n        self.session.add_all(\n            [\n                self.City(name=\"Helsinki\", country=\"Finland\"),\n                self.City(name=\"Vantaa\", country=\"Finland\"),\n                self.City(name=\"New York\", country=\"USA\"),\n                self.City(name=\"Washington\", country=\"USA\"),\n                self.City(name=\"Stockholm\", country=\"Sweden\"),\n            ]\n        )\n\n\nclass TestGroupedQuerySelectField(DatabaseTestCase):\n    def create_form(self, **kwargs):\n        query = self.session.query(self.City).order_by(\"name\", \"country\")\n\n        class MyForm(Form):\n            city = GroupedQuerySelectField(\n                label=kwargs.get(\"label\", \"City\"),\n                query_factory=kwargs.get(\"query_factory\", lambda: query),\n                get_label=kwargs.get(\"get_label\", lambda c: c.name),\n                get_group=kwargs.get(\"get_group\", lambda c: c.country),\n                allow_blank=kwargs.get(\"allow_blank\", False),\n                blank_text=kwargs.get(\"blank_text\", \"\"),\n                blank_value=kwargs.get(\"blank_value\", \"__None\"),\n            )\n\n        return MyForm\n\n    def test_custom_none_value(self):\n        self.create_cities()\n        MyForm = self.create_form(\n            allow_blank=True, blank_text=\"Choose city...\", blank_value=\"\"\n        )\n        form = MyForm(DummyPostData({\"city\": \"\"}))\n        assert form.validate(), form.errors\n        assert '<option selected value=\"\">Choose city...</option>' in (str(form.city))\n\n    def test_rendering(self):\n        MyForm = self.create_form()\n        self.create_cities()\n        assert str(MyForm().city).replace(\"\\n\", \"\") == (\n            '<select id=\"city\" name=\"city\">'\n            '<optgroup label=\"Finland\">'\n            '<option value=\"1\">Helsinki</option>'\n            '<option value=\"2\">Vantaa</option>'\n            '</optgroup><optgroup label=\"Sweden\">'\n            '<option value=\"5\">Stockholm</option>'\n            \"</optgroup>\"\n            '<optgroup label=\"USA\">'\n            '<option value=\"3\">New York</option>'\n            '<option value=\"4\">Washington</option>'\n            \"</optgroup>\"\n            \"</select>\"\n        )\n\n\nclass TestGroupedQuerySelectMultipleField(DatabaseTestCase):\n    def create_form(self, **kwargs):\n        query = self.session.query(self.City).order_by(\"name\", \"country\")\n\n        class MyForm(ModelForm):\n            class Meta:\n                model = self.State\n\n            cities = GroupedQuerySelectMultipleField(\n                label=kwargs.get(\"label\", \"City\"),\n                query_factory=kwargs.get(\"query_factory\", lambda: query),\n                get_label=kwargs.get(\"get_label\", lambda c: c.name),\n                get_group=kwargs.get(\"get_group\", lambda c: c.country),\n                blank_text=kwargs.get(\"blank_text\", \"\"),\n            )\n\n        return MyForm\n\n    def test_unpopulated_default(self):\n        MyForm = self.create_form()\n        self.create_cities()\n        assert MyForm().cities.data == []\n\n    def test_single_value_without_factory(self):\n        obj = self.State()\n        MyForm = self.create_form()\n        self.create_cities()\n        form = MyForm(DummyPostData(cities=[\"1\"]), obj=obj)\n        assert [1] == [v.id for v in form.cities.data]\n        assert form.validate()\n        form.populate_obj(obj)\n        assert [city.id for city in obj.cities] == [1]\n\n    def test_multiple_values_without_query_factory(self):\n        obj = self.State()\n        MyForm = self.create_form()\n        self.create_cities()\n        form = MyForm(DummyPostData(cities=[\"1\", \"2\"]), obj=obj)\n        form.cities.query = self.session.query(self.City)\n\n        assert [1, 2] == [v.id for v in form.cities.data]\n        assert form.validate()\n        form.populate_obj(obj)\n        assert [city.id for city in obj.cities] == [1, 2]\n\n        form = MyForm(DummyPostData(cities=[\"1\", \"666\"]))\n        form.cities.query = self.session.query(self.City)\n        assert not form.validate()\n        assert [x.id for x in form.cities.data] == [1]\n        assert not form.validate()\n        form.populate_obj(obj)\n        assert [city.id for city in obj.cities] == [1]\n\n        form = MyForm(DummyPostData(cities=[\"666\"]))\n        form.cities.query = self.session.query(self.City)\n        assert not form.validate()\n        assert [x.id for x in form.cities.data] == []\n        assert not form.validate()\n        form.populate_obj(obj)\n        assert [city.id for city in obj.cities] == []\n\n    def test_rendering(self):\n        MyForm = self.create_form()\n        self.create_cities()\n        assert str(MyForm().cities).replace(\"\\n\", \"\") == (\n            '<select id=\"cities\" multiple name=\"cities\">'\n            '<optgroup label=\"Finland\">'\n            '<option value=\"1\">Helsinki</option>'\n            '<option value=\"2\">Vantaa</option>'\n            '</optgroup><optgroup label=\"Sweden\">'\n            '<option value=\"5\">Stockholm</option>'\n            \"</optgroup>\"\n            '<optgroup label=\"USA\">'\n            '<option value=\"3\">New York</option>'\n            '<option value=\"4\">Washington</option>'\n            \"</optgroup>\"\n            \"</select>\"\n        )\n"
  },
  {
    "path": "tests/test_select_field.py",
    "content": "from decimal import Decimal\n\nimport sqlalchemy as sa\nfrom wtforms_components import SelectField\n\nfrom tests import ModelFormTestCase\n\n\nclass MultiDict(dict):\n    def getlist(self, key):\n        return [self[key]]\n\n\nclass TestSelectFieldDefaultValue(ModelFormTestCase):\n    def test_option_selected_by_field_default_value(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.Integer, default=\"1\", info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2\"}))\n        assert '<option selected value=\"2\">2</option>' in str(form.test_column)\n\n\nclass TestSelectFieldCoerce(ModelFormTestCase):\n    def test_integer_coerces_values_to_integers(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.Integer, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2\"}))\n        assert form.test_column.data == 2\n\n    def test_nullable_integer_coerces_values_to_integers(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.Integer, nullable=True, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2\"}))\n        assert form.test_column.data == 2\n\n    def test_integer_coerces_empty_strings_to_nulls(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.Integer, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"\"}))\n        assert form.test_column.data is None\n\n    def test_big_integer_coerces_values_to_integers(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.BigInteger, info={\"choices\": choices})\n        self.assert_type(\"test_column\", SelectField)\n        form = self.form_class(MultiDict({\"test_column\": \"2\"}))\n        assert form.test_column.data == 2\n\n    def test_small_integer_coerces_values_to_integers(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.SmallInteger, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2\"}))\n        assert form.test_column.data == 2\n\n    def test_numeric_coerces_values_to_decimals(self):\n        choices = [(\"1.0\", \"1.0\"), (\"2.0\", \"2.0\")]\n        self.init(type_=sa.Numeric, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2.0\"}))\n        assert form.test_column.data == Decimal(\"2.0\")\n\n    def test_float_coerces_values_to_floats(self):\n        choices = [(\"1.0\", \"1.0\"), (\"2.0\", \"2.0\")]\n        self.init(type_=sa.Float, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2.0\"}))\n        assert form.test_column.data == 2.0\n\n    def test_unicode_coerces_values_to_unicode_strings(self):\n        choices = [(\"1.0\", \"1.0\"), (\"2.0\", \"2.0\")]\n        self.init(type_=sa.Unicode(255), info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2.0\"}))\n        assert form.test_column.data == \"2.0\"\n        assert isinstance(form.test_column.data, str)\n\n    def test_unicode_text_coerces_values_to_unicode_strings(self):\n        choices = [(\"1.0\", \"1.0\"), (\"2.0\", \"2.0\")]\n        self.init(type_=sa.UnicodeText, info={\"choices\": choices})\n        form = self.form_class(MultiDict({\"test_column\": \"2.0\"}))\n        assert form.test_column.data == \"2.0\"\n        assert isinstance(form.test_column.data, str)\n"
  },
  {
    "path": "tests/test_synonym.py",
    "content": "import sqlalchemy as sa\nfrom sqlalchemy.ext.hybrid import hybrid_property\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import ModelForm\n\n\nclass TestSynonym(ModelFormTestCase):\n    def test_synonym_returning_column_property_with_include(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            _test_column = sa.Column(\"test_column\", sa.Integer, nullable=False)\n\n            @hybrid_property\n            def test_column_hybrid(self):\n                return self.test_column * 2\n\n            @test_column_hybrid.setter\n            def test_column_hybrid(self, value):\n                self._test_column = value\n\n            test_column = sa.orm.synonym(\"_test_column\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_str_validator = None\n                not_null_validator = None\n                include = (\"test_column\",)\n                exclude = (\"_test_column\",)\n\n        form = ModelTestForm()\n        assert form.test_column\n\n    def test_synonym_returning_column_property_with_only(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            _test_column = sa.Column(\"test_column\", sa.Integer, nullable=False)\n\n            @hybrid_property\n            def test_column_hybrid(self):\n                return self.test_column * 2\n\n            @test_column_hybrid.setter\n            def test_column_hybrid(self, value):\n                self._test_column = value\n\n            test_column = sa.orm.synonym(\"_test_column\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_str_validator = None\n                not_null_validator = None\n                only = (\"test_column\",)\n\n        form = ModelTestForm()\n        assert form.test_column\n"
  },
  {
    "path": "tests/test_types.py",
    "content": "from enum import Enum\n\nimport sqlalchemy as sa\nfrom pytest import mark, raises\nfrom sqlalchemy_utils import (\n    ChoiceType,\n    ColorType,\n    CountryType,\n    EmailType,\n    IntRangeType,\n    PasswordType,\n    PhoneNumberType,\n    URLType,\n    UUIDType,\n)\nfrom sqlalchemy_utils.types import arrow, phone_number, WeekDaysType  # noqa\nfrom wtforms.fields import (\n    BooleanField,\n    FloatField,\n    PasswordField,\n    TextAreaField,\n)\nfrom wtforms.validators import Length, URL\nfrom wtforms_components import Email\nfrom wtforms_components.fields import (\n    ColorField,\n    DateField,\n    DateTimeField,\n    DecimalField,\n    EmailField,\n    IntegerField,\n    IntIntervalField,\n    SelectField,\n    StringField,\n    TimeField,\n)\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import (\n    CountryField,\n    ModelForm,\n    null_or_unicode,\n    PhoneNumberField,\n    UnknownTypeException,\n    WeekDaysField,\n)\nfrom wtforms_alchemy.utils import ClassMap\n\ntry:\n    import passlib  # noqa\nexcept ImportError:\n    passlib = None\n\n\nclass UnknownType(sa.types.UserDefinedType):\n    def get_col_spec(self):\n        return \"UNKNOWN()\"\n\n\nclass CustomUnicodeTextType(sa.types.TypeDecorator):\n    impl = sa.types.UnicodeText\n\n\nclass CustomUnicodeType(sa.types.TypeDecorator):\n    impl = sa.types.Unicode\n\n\nclass CustomNumericType(sa.types.TypeDecorator):\n    impl = sa.types.Numeric\n\n\nclass TestModelColumnToFormFieldTypeConversion(ModelFormTestCase):\n    def test_raises_exception_for_unknown_type(self):\n        with raises(UnknownTypeException):\n            self.init(type_=UnknownType)\n            self.form_class()\n\n    def test_raises_exception_for_array_type(self):\n        with raises(UnknownTypeException):\n            self.init(type_=sa.ARRAY(sa.Integer))\n            self.form_class()\n\n    def test_unicode_converts_to_text_field(self):\n        self.init()\n        self.assert_type(\"test_column\", StringField)\n\n    def test_custom_unicode_converts_to_text_field(self):\n        self.init(type_=CustomUnicodeType)\n        self.assert_type(\"test_column\", StringField)\n\n    def test_string_converts_to_text_field(self):\n        self.init(type_=sa.String)\n        self.assert_type(\"test_column\", StringField)\n\n    def test_integer_converts_to_integer_field(self):\n        self.init(type_=sa.Integer)\n        self.assert_type(\"test_column\", IntegerField)\n\n    def test_unicode_text_converts_to_text_area_field(self):\n        self.init(type_=sa.UnicodeText)\n        self.assert_type(\"test_column\", TextAreaField)\n\n    def test_custom_unicode_text_converts_to_text_area_field(self):\n        self.init(type_=CustomUnicodeTextType)\n        self.assert_type(\"test_column\", TextAreaField)\n\n    def test_boolean_converts_to_boolean_field(self):\n        self.init(type_=sa.Boolean)\n        self.assert_type(\"test_column\", BooleanField)\n\n    def test_datetime_converts_to_datetime_field(self):\n        self.init(type_=sa.DateTime)\n        self.assert_type(\"test_column\", DateTimeField)\n\n    def test_date_converts_to_date_field(self):\n        self.init(type_=sa.Date)\n        self.assert_type(\"test_column\", DateField)\n\n    def test_float_converts_to_float_field(self):\n        self.init(type_=sa.Float)\n        self.assert_type(\"test_column\", FloatField)\n\n    def test_numeric_converts_to_decimal_field(self):\n        self.init(type_=sa.Numeric)\n        self.assert_type(\"test_column\", DecimalField)\n\n    def test_numeric_scale_converts_to_decimal_field_scale(self):\n        self.init(type_=sa.Numeric(scale=4))\n        form = self.form_class()\n        assert form.test_column.places == 4\n\n    def test_custom_numeric_converts_to_decimal_field(self):\n        self.init(type_=CustomNumericType)\n        self.assert_type(\"test_column\", DecimalField)\n\n    def test_enum_field_converts_to_select_field(self):\n        choices = [\"1\", \"2\"]\n        self.init(type_=sa.Enum(*choices))\n        self.assert_type(\"test_column\", SelectField)\n        form = self.form_class()\n        assert form.test_column.choices == [(s, s) for s in choices]\n\n    def test_nullable_enum_uses_null_or_unicode_coerce_func_by_default(self):\n        choices = [\"1\", \"2\"]\n        self.init(type_=sa.Enum(*choices), nullable=True)\n        field = self._get_field(\"test_column\")\n        assert field.coerce == null_or_unicode\n\n    def test_custom_choices_override_enum_choices(self):\n        choices = [\"1\", \"2\"]\n        custom_choices = [(\"2\", \"2\"), (\"3\", \"3\")]\n        self.init(type_=sa.Enum(*choices), info={\"choices\": custom_choices})\n        form = self.form_class()\n        assert form.test_column.choices == custom_choices\n\n    def test_column_with_choices_converts_to_select_field(self):\n        choices = [(\"1\", \"1\"), (\"2\", \"2\")]\n        self.init(type_=sa.Integer, info={\"choices\": choices})\n        self.assert_type(\"test_column\", SelectField)\n        form = self.form_class()\n        assert form.test_column.choices == choices\n\n    def test_assigns_email_validator_for_email_type(self):\n        self.init(type_=EmailType)\n        self.assert_has_validator(\"test_column\", Email)\n\n    def test_assigns_url_validator_for_url_type(self):\n        self.init(type_=URLType)\n        self.assert_has_validator(\"test_column\", URL)\n\n    def test_time_converts_to_time_field(self):\n        self.init(type_=sa.types.Time)\n        self.assert_type(\"test_column\", TimeField)\n\n    def test_varchar_converts_to_text_field(self):\n        self.init(type_=sa.types.VARCHAR)\n        self.assert_type(\"test_column\", StringField)\n\n    def test_text_converts_to_textarea_field(self):\n        self.init(type_=sa.types.TEXT)\n        self.assert_type(\"test_column\", TextAreaField)\n\n    def test_char_converts_to_text_field(self):\n        self.init(type_=sa.types.CHAR)\n        self.assert_type(\"test_column\", StringField)\n\n    def test_real_converts_to_float_field(self):\n        self.init(type_=sa.types.REAL)\n        self.assert_type(\"test_column\", FloatField)\n\n    def test_json_converts_to_textarea_field(self):\n        self.init(type_=sa.types.JSON)\n        self.assert_type(\"test_column\", TextAreaField)\n\n    @mark.xfail(\"phone_number.phonenumbers is None\")\n    def test_phone_number_converts_to_phone_number_field(self):\n        self.init(type_=PhoneNumberType)\n        self.assert_type(\"test_column\", PhoneNumberField)\n\n    @mark.xfail(\"phone_number.phonenumbers is None\")\n    def test_phone_number_country_code_passed_to_field(self):\n        self.init(type_=PhoneNumberType(region=\"SE\"))\n        form = self.form_class()\n        assert form.test_column.region == \"SE\"\n\n    @mark.xfail(\"phone_number.phonenumbers is None\")\n    def test_phone_number_type_has_no_length_validation(self):\n        self.init(type_=PhoneNumberType(country_code=\"FI\"))\n        field = self._get_field(\"test_column\")\n        for validator in field.validators:\n            assert validator.__class__ != Length\n\n    @mark.parametrize((\"type\", \"field\"), ((IntRangeType, IntIntervalField),))\n    def test_range_type_conversion(self, type, field):\n        self.init(type_=type)\n        self.assert_type(\"test_column\", field)\n\n    @mark.xfail(\"passlib is None\")\n    def test_password_type_converts_to_password_field(self):\n        self.init(type_=PasswordType)\n        self.assert_type(\"test_column\", PasswordField)\n\n    @mark.xfail(\"arrow.arrow is None\")\n    def test_arrow_type_converts_to_datetime_field(self):\n        self.init(type_=arrow.ArrowType)\n        self.assert_type(\"test_column\", DateTimeField)\n\n    def test_url_type_converts_to_string_field(self):\n        self.init(type_=URLType)\n        self.assert_type(\"test_column\", StringField)\n\n    def test_uuid_type_converst_to_uuid_type(self):\n        self.init(type_=UUIDType)\n        self.assert_type(\"test_column\", StringField)\n\n    def test_color_type_converts_to_color_field(self):\n        self.init(type_=ColorType)\n        self.assert_type(\"test_column\", ColorField)\n\n    def test_email_type_converts_to_email_field(self):\n        self.init(type_=EmailType)\n        self.assert_type(\"test_column\", EmailField)\n\n    def test_country_type_converts_to_country_field(self):\n        self.init(type_=CountryType)\n        self.assert_type(\"test_column\", CountryField)\n\n    def test_choice_type_converts_to_select_field(self):\n        choices = [(\"1\", \"choice 1\"), (\"2\", \"choice 2\")]\n        self.init(type_=ChoiceType(choices))\n        self.assert_type(\"test_column\", SelectField)\n        assert list(self.form_class().test_column.choices) == choices\n\n    def test_choice_type_uses_custom_coerce_func(self):\n        choices = [(\"1\", \"choice 1\"), (\"2\", \"choice 2\")]\n        self.init(type_=ChoiceType(choices))\n        self.assert_type(\"test_column\", SelectField)\n        model = self.ModelTest(test_column=\"2\")\n        form = self.form_class(obj=model)\n        assert '<option selected value=\"2\">' in str(form.test_column)\n\n    def test_choice_type_with_enum(self):\n        class Choice(Enum):\n            choice1 = 1\n            choice2 = 2\n\n            def __str__(self):\n                return self.name\n\n        self.init(type_=ChoiceType(Choice))\n        self.assert_type(\"test_column\", SelectField)\n        assert self.form_class().test_column.choices == [(1, \"choice1\"), (2, \"choice2\")]\n\n    @mark.parametrize([\"type_\", \"impl\"], [(int, sa.Integer()), (str, sa.String())])\n    def test_choice_type_with_enum_uses_custom_coerce_func(self, type_, impl):\n        class Choice(Enum):\n            choice1 = type_(1)\n            choice2 = type_(2)\n\n            def __str__(self):\n                return self.name\n\n        self.init(type_=ChoiceType(Choice, impl=impl))\n        self.assert_type(\"test_column\", SelectField)\n        model = self.ModelTest(test_column=type_(2))\n        form = self.form_class(obj=model)\n        assert '<option selected value=\"2\">' in str(form.test_column)\n\n\nclass TestWeekDaysTypeConversion(ModelFormTestCase):\n    def test_weekdays_type_converts_to_weekdays_field(self):\n        self.init(type_=WeekDaysType)\n        self.assert_type(\"test_column\", WeekDaysField)\n\n\nclass TestCustomTypeMap(ModelFormTestCase):\n    def test_override_type_map_on_class_level(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Unicode(255), nullable=False)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_validator = None\n                type_map = ClassMap({sa.Unicode: TextAreaField})\n\n        form = ModelTestForm()\n        assert isinstance(form.test_column, TextAreaField)\n\n    def test_override_type_map_with_callable(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column_short = sa.Column(sa.Unicode(255), nullable=False)\n            test_column_long = sa.Column(sa.Unicode(), nullable=False)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_validator = None\n                type_map = ClassMap(\n                    {\n                        sa.Unicode: lambda column: (\n                            StringField if column.type.length else TextAreaField\n                        )\n                    }\n                )\n\n        form = ModelTestForm()\n        assert isinstance(form.test_column_short, StringField)\n        assert isinstance(form.test_column_long, TextAreaField)\n"
  },
  {
    "path": "tests/test_unique_validator.py",
    "content": "import sqlalchemy as sa\nfrom pytest import mark, raises\nfrom sqlalchemy.orm import declarative_base, relationship\nfrom sqlalchemy.orm.session import close_all_sessions\nfrom wtforms import Form\nfrom wtforms.fields import StringField\n\nfrom tests import MultiDict\nfrom wtforms_alchemy import ModelForm, QuerySelectField, Unique\n\nbase = declarative_base()\n\n\nclass Color(base):\n    __tablename__ = \"color\"\n    id = sa.Column(sa.Integer, primary_key=True)\n    name = sa.Column(sa.Unicode(255), unique=True)\n\n\nclass User(base):\n    __tablename__ = \"user\"\n    id = sa.Column(sa.Integer, primary_key=True)\n    name = sa.Column(sa.Unicode(255), unique=True)\n    email = sa.Column(sa.Unicode(255))\n    favorite_color_id = sa.Column(sa.Integer, sa.ForeignKey(Color.id))\n    favorite_color = relationship(Color)\n\n\nclass TestUniqueValidator:\n    def create_models(self):\n        # This is a hack so we can use our classes\n        # without initializing self first\n        self.base = base\n\n    def setup_method(self, method):\n        self.engine = sa.create_engine(\"sqlite:///:memory:\")\n\n        self.base = declarative_base()\n        self.create_models()\n\n        self.base.metadata.create_all(self.engine)\n\n        Session = sa.orm.session.sessionmaker(bind=self.engine)\n        self.session = Session()\n\n    def teardown_method(self, method):\n        close_all_sessions()\n        self.base.metadata.drop_all(self.engine)\n        self.engine.dispose()\n\n    def _test_syntax(self, column, expected_dict):\n        class MyForm(ModelForm):\n            name = StringField()\n            email = StringField()\n\n        validator = Unique(column, get_session=lambda: self.session)\n        form = MyForm()\n        if not hasattr(form, \"Meta\"):\n            form.Meta = lambda: None\n        form.Meta.model = User\n        result = validator._syntaxes_as_tuples(form, form.name, column)\n        assert result == expected_dict\n\n    def test_with_form_obj_unavailable(self):\n        class MyForm(Form):\n            name = StringField(\n                validators=[Unique(User.name, get_session=lambda: self.session)]\n            )\n\n        form = MyForm()\n        with raises(Exception) as e:\n            form.validate()\n        assert \"Couldn't access Form._obj attribute\" in str(e)\n\n    @mark.parametrize(\n        [\"column\", \"expected_dict\"],\n        (\n            (User.name, ((\"name\", User.name),)),\n            (\"name\", ((\"name\", User.name),)),\n            ((\"name\", \"email\"), ((\"name\", User.name), (\"email\", User.email))),\n            ({\"exampleName\": User.name}, ((\"exampleName\", User.name),)),\n            ((User.name, User.email), ((\"name\", User.name), (\"email\", User.email))),\n            (\n                (User.name, User.favorite_color),\n                ((\"name\", User.name), (\"favorite_color\", User.favorite_color)),\n            ),\n        ),\n    )\n    def test_columns_as_tuples(self, column, expected_dict):\n        self._test_syntax(column, expected_dict)\n\n    def test_columns_as_tuples_classical_mapping(self):\n        users = sa.Table(\"users\", sa.MetaData(None), sa.Column(\"name\", sa.Unicode(255)))\n        self._test_syntax(users.c.name, ((\"name\", users.c.name),))\n\n    @mark.parametrize(\n        \"column\", (User.name, {\"name\": User.name}, ((\"name\", User.name),))\n    )\n    def test_raises_exception_if_improperly_configured(self, column):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique(\n                        column,\n                    )\n                ]\n            )\n\n        with raises(Exception):\n            MyForm().validate()\n\n    def test_raises_exception_string_if_improperly_configured(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique(\n                        (\"name\", \"email\"),\n                    )\n                ]\n            )\n\n        with raises(Exception):\n            MyForm().validate()\n\n    def test_existing_name_collision(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[Unique(User.name, get_session=lambda: self.session)]\n            )\n\n        self.session.add(User(name=\"someone\"))\n        self.session.commit()\n\n        form = MyForm(MultiDict({\"name\": \"someone\"}))\n        form.validate()\n        assert form.errors == {\"name\": [\"Already exists.\"]}\n\n    def test_existing_name_collision_multiple(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique([User.name, User.email], get_session=lambda: self.session)\n                ]\n            )\n            email = StringField()\n\n        self.session.add(User(name=\"someone\", email=\"someone@example.com\"))\n        self.session.commit()\n\n        form = MyForm(MultiDict({\"name\": \"someone\", \"email\": \"someone@example.com\"}))\n        form.validate()\n        assert form.errors == {\"name\": [\"Already exists.\"]}\n\n    def test_works_with_flask_sqlalchemy_syntax(self, monkeypatch):\n        monkeypatch.setattr(User, \"query\", self.session.query(User), False)\n\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique([User.name, User.email], get_session=lambda: self.session)\n                ]\n            )\n            email = StringField()\n\n        self.session.add(User(name=\"someone\", email=\"someone@example.com\"))\n        self.session.commit()\n\n        form = MyForm(MultiDict({\"name\": \"someone\", \"email\": \"someone@example.com\"}))\n        form.validate()\n        assert form.errors == {\"name\": [\"Already exists.\"]}\n\n    def test_existing_name_collision_classical_mapping(self):\n        sa.Table(\n            \"user\",\n            sa.MetaData(None),\n            sa.Column(\"name\", sa.String(255)),\n            sa.Column(\"email\", sa.String(255)),\n        )\n\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique([User.name, User.email], get_session=lambda: self.session)\n                ]\n            )\n            email = StringField()\n\n        self.session.add(User(name=\"someone\", email=\"someone@example.com\"))\n        self.session.commit()\n\n        form = MyForm(MultiDict({\"name\": \"someone\", \"email\": \"someone@example.com\"}))\n        form.validate()\n        assert form.errors == {\"name\": [\"Already exists.\"]}\n\n    def test_relationship_multiple_collision(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique(\n                        [User.name, User.favorite_color],\n                        get_session=lambda: self.session,\n                    )\n                ]\n            )\n            email = StringField()\n            favorite_color = QuerySelectField(\n                query_factory=lambda: self.session.query(Color).all(), allow_blank=True\n            )\n\n        red_color = Color(name=\"red\")\n        blue_color = Color(name=\"blue\")\n        self.session.add(red_color)\n        self.session.add(blue_color)\n        self.session.add(\n            User(\n                name=\"someone\",\n                email=\"first.email@example.com\",\n                favorite_color=red_color,\n            )\n        )\n        self.session.commit()\n\n        form = MyForm(\n            MultiDict(\n                {\n                    \"name\": \"someone\",\n                    \"email\": \"second.email@example.com\",\n                    \"favorite_color\": str(red_color.id),\n                }\n            )\n        )\n        form.validate()\n        assert form.errors == {\"name\": [\"Already exists.\"]}\n\n    def test_relationship_multiple_no_collision(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique(\n                        [User.name, User.favorite_color],\n                        get_session=lambda: self.session,\n                    )\n                ]\n            )\n            email = StringField()\n            favorite_color = QuerySelectField(\n                query_factory=lambda: self.session.query(Color).all(), allow_blank=True\n            )\n\n        red_color = Color(name=\"red\")\n        blue_color = Color(name=\"blue\")\n        self.session.add(red_color)\n        self.session.add(blue_color)\n        self.session.add(\n            User(\n                name=\"someone\",\n                email=\"first.email@example.com\",\n                favorite_color=red_color,\n            )\n        )\n        self.session.commit()\n\n        form = MyForm(\n            MultiDict(\n                {\n                    \"name\": \"someone\",\n                    \"email\": \"second.email@example.com\",\n                    \"favorite_color\": str(blue_color.id),\n                }\n            )\n        )\n        form.validate()\n        assert form.errors == {}\n\n    def test_without_obj_without_collision(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[Unique(User.name, get_session=lambda: self.session)]\n            )\n\n        self.session.add(User(name=\"someone else\"))\n        self.session.commit()\n\n        form = MyForm(MultiDict({\"name\": \"someone\"}))\n        assert form.validate()\n\n    def test_without_obj_without_collision_multiple(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique([User.name, User.email], get_session=lambda: self.session)\n                ]\n            )\n            email = StringField()\n\n        self.session.add(User(name=\"someone\", email=\"someone@example.com\"))\n        self.session.commit()\n\n        form = MyForm(MultiDict({\"name\": \"someone\", \"email\": \"else@example.com\"}))\n        assert form.validate()\n\n    def test_existing_name_no_collision(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[Unique(User.name, get_session=lambda: self.session)]\n            )\n\n        obj = User(name=\"someone\")\n        self.session.add(obj)\n\n        form = MyForm(MultiDict({\"name\": \"someone\"}), obj=obj)\n        assert form.validate()\n\n    def test_existing_name_no_collision_multiple(self):\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique((User.name, User.email), get_session=lambda: self.session)\n                ]\n            )\n            email = StringField()\n\n        obj = User(name=\"someone\", email=\"hello@world.com\")\n        self.session.add(obj)\n\n        form = MyForm(\n            MultiDict({\"name\": \"someone\", \"email\": \"hello@world.com\"}), obj=obj\n        )\n        assert form.validate()\n\n    def test_supports_model_query_parameter(self):\n        User.query = self.session.query(User)\n\n        class MyForm(ModelForm):\n            name = StringField(\n                validators=[\n                    Unique(\n                        User.name,\n                    )\n                ]\n            )\n\n        form = MyForm(MultiDict({\"name\": \"someone\"}))\n        assert form.validate()\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import sqlalchemy as sa\n\nfrom tests import FormRelationsTestCase\nfrom wtforms_alchemy import utils\n\n\nclass TestUtils(FormRelationsTestCase):\n    def create_models(self):\n        class Band(self.base):\n            __tablename__ = \"band\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n\n        class Person(self.base):\n            __tablename__ = \"person\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255), nullable=False)\n\n        class BandMember(self.base):\n            __tablename__ = \"band_members\"\n            band_id = sa.Column(sa.Integer, sa.ForeignKey(Band.id), primary_key=True)\n            band = sa.orm.relationship(Band, backref=\"members\")\n            person_id = sa.Column(\n                sa.Integer, sa.ForeignKey(Person.id), primary_key=True\n            )\n            person = sa.orm.relationship(\n                Person, backref=sa.orm.backref(\"band_role\", uselist=False)\n            )\n            role = sa.Column(sa.Unicode(255), nullable=False)\n\n            def __repr__(self):\n                fmt = \"<BandMember band_id={} person_id={} at {:x}>\"\n                return fmt.format(repr(self.band_id), repr(self.person_id), id(self))\n\n        self.Band = Band\n        self.Person = Person\n        self.BandMember = BandMember\n\n    def create_forms(self):\n        pass\n\n    def test_find_entity(self):\n        band = self.Band(name=\"The Furious\")\n        self.session.add(band)\n        singer = self.Person(name=\"Paul Insane\")\n        self.session.add(self.BandMember(band=band, person=singer, role=\"singer\"))\n        guitarist = self.Person(name=\"John Crazy\")\n        self.session.add(self.BandMember(band=band, person=guitarist, role=\"guitar\"))\n        self.session.commit()\n\n        sing_data = dict(band_id=band.id, person_id=singer.id, role=\"singer\")\n        guitar_data = dict(band_id=band.id, person_id=guitarist.id, role=\"guitar\")\n\n        assert (\n            utils.find_entity(band.members, self.BandMember, sing_data)\n            is singer.band_role\n        )\n        assert (\n            utils.find_entity(band.members, self.BandMember, guitar_data)\n            is guitarist.band_role\n        )\n"
  },
  {
    "path": "tests/test_validators.py",
    "content": "from datetime import datetime, time\n\nimport sqlalchemy as sa\nfrom sqlalchemy_utils import EmailType\nfrom wtforms.validators import (\n    DataRequired,\n    Email,\n    InputRequired,\n    Length,\n    NumberRange,\n    Optional,\n)\nfrom wtforms_components import DateRange, TimeRange\n\nfrom tests import ModelFormTestCase\nfrom wtforms_alchemy import ClassMap, ModelForm, Unique\n\n\nclass TestAutoAssignedValidators(ModelFormTestCase):\n    def test_auto_assigns_length_validators(self):\n        self.init()\n        self.assert_max_length(\"test_column\", 255)\n\n    def test_assigns_validators_from_info_field(self):\n        self.init(info={\"validators\": Email()})\n        self.assert_has_validator(\"test_column\", Email)\n\n    def test_assigns_unique_validator_for_unique_fields(self):\n        self.init(unique=True)\n        self.assert_has_validator(\"test_column\", Unique)\n\n    def test_assigns_non_nullable_fields_as_required(self):\n        self.init(nullable=False)\n        self.assert_has_validator(\"test_column\", DataRequired)\n        self.assert_has_validator(\"test_column\", InputRequired)\n\n    def test_type_level_not_nullable_validators(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Unicode(255), nullable=False)\n\n        validator = DataRequired()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_validator_type_map = ClassMap()\n                not_null_validator = validator\n\n        form = ModelTestForm()\n        assert validator in form.test_column.validators\n\n    def test_not_nullable_validator_with_type_decorator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(EmailType, nullable=False)\n\n        validator = DataRequired()\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_validator_type_map = ClassMap([(sa.String, validator)])\n                not_null_validator = []\n\n        form = ModelTestForm()\n        assert validator in form.test_column.validators\n\n    def test_not_null_validator_as_empty_list(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Boolean, nullable=False)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_validator_type_map = ClassMap()\n                not_null_validator = []\n\n        form = ModelTestForm()\n        assert list(form.test_column.validators) == []\n\n    def test_not_null_validator_as_none(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Boolean, nullable=False)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                not_null_validator_type_map = ClassMap()\n                not_null_validator = None\n\n        form = ModelTestForm()\n        assert len(form.test_column.validators) == 1\n        assert isinstance(form.test_column.validators[0], Optional)\n\n    def test_not_nullable_booleans_are_required(self):\n        self.init(sa.Boolean, nullable=False)\n        self.assert_has_validator(\"test_column\", InputRequired)\n\n    def test_not_nullable_fields_with_defaults_are_not_required(self):\n        self.init(nullable=False, default=\"default\")\n        self.assert_not_required(\"test_column\")\n\n    def test_assigns_nullable_integers_as_optional(self):\n        self.init(sa.Integer, nullable=True)\n        self.assert_optional(\"test_column\")\n\n    def test_override_email_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(EmailType, nullable=True)\n\n        def validator():\n            return Email(\"Wrong email\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                email_validator = validator\n\n        form = ModelTestForm()\n        assert form.test_column.validators[1].message == \"Wrong email\"\n\n    def test_override_optional_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(EmailType, nullable=True)\n\n        class MyOptionalValidator:\n            def __init__(self, *args, **kwargs):\n                pass\n\n            def __call__(self, form, field):\n                pass\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                optional_validator = MyOptionalValidator\n\n        form = ModelTestForm()\n        assert isinstance(form.test_column.validators[0], MyOptionalValidator)\n\n    def test_override_number_range_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Integer, info={\"min\": 3}, nullable=True)\n\n        def number_range(min=-1, max=-1):\n            return NumberRange(min=min, max=max, message=\"Wrong number range\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                number_range_validator = number_range\n\n        form = ModelTestForm()\n        assert form.test_column.validators[1].message == \"Wrong number range\"\n\n    def test_override_date_range_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(\n                sa.DateTime, info={\"min\": datetime(2000, 1, 1)}, nullable=True\n            )\n\n        def date_range(min=None, max=None):\n            return DateRange(min=min, max=max, message=\"Wrong date range\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                date_range_validator = date_range\n\n        form = ModelTestForm()\n        assert form.test_column.validators[1].message == \"Wrong date range\"\n\n    def test_override_time_range_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Time, info={\"min\": time(14, 30)}, nullable=True)\n\n        def time_range(min=None, max=None):\n            return TimeRange(min=min, max=max, message=\"Wrong time\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                time_range_validator = time_range\n\n        form = ModelTestForm()\n        assert form.test_column.validators[1].message == \"Wrong time\"\n\n    def test_override_length_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Unicode(255), nullable=True)\n\n        def length(min=-1, max=-1):\n            return Length(min=min, max=max, message=\"Wrong length\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                length_validator = length\n\n        form = ModelTestForm()\n        assert form.test_column.validators[1].message == \"Wrong length\"\n\n    def test_override_optional_validator_as_none(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Boolean, nullable=True)\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                optional_validator = None\n\n        form = ModelTestForm()\n        assert list(form.test_column.validators) == []\n\n    def test_override_unique_validator(self):\n        class ModelTest(self.base):\n            __tablename__ = \"model_test\"\n            id = sa.Column(sa.Integer, primary_key=True)\n            test_column = sa.Column(sa.Unicode(255), unique=True, nullable=True)\n\n        def unique(column, get_session):\n            return Unique(column, get_session=get_session, message=\"Not unique\")\n\n        class ModelTestForm(ModelForm):\n            class Meta:\n                model = ModelTest\n                unique_validator = unique\n\n            @staticmethod\n            def get_session():\n                return None\n\n        form = ModelTestForm()\n        assert form.test_column.validators[2].message == \"Not unique\"\n"
  },
  {
    "path": "tests/test_weekdays_field.py",
    "content": "from wtforms import Form\n\nfrom tests import MultiDict\nfrom wtforms_alchemy import WeekDaysField\n\n\nclass TestWeekDaysField:\n    def init_form(self, **kwargs):\n        class TestForm(Form):\n            test_field = WeekDaysField(**kwargs)\n\n        return TestForm\n\n    def test_valid_weekdays(self):\n        form_class = self.init_form()\n        form = form_class(MultiDict(test_field=0))\n        form.validate()\n        assert len(form.errors) == 0\n\n    def test_invalid_weekdays(self):\n        form_class = self.init_form()\n        form = form_class(\n            MultiDict(\n                [\n                    (\"test_field\", \"8\"),\n                ]\n            )\n        )\n        form.validate()\n        assert len(form.errors[\"test_field\"]) == 1\n"
  },
  {
    "path": "tests/test_widgets.py",
    "content": "from decimal import Decimal\n\nimport sqlalchemy as sa\n\nfrom tests import ModelFormTestCase\n\n\nclass TestNumericFieldWidgets(ModelFormTestCase):\n    def test_converts_numeric_scale_to_steps(self):\n        self.init(\n            type_=sa.Numeric(scale=2),\n        )\n        form = self.form_class()\n        assert 'step=\"0.01\"' in str(form.test_column)\n\n    def test_supports_numeric_column_without_scale(self):\n        self.init(\n            type_=sa.Numeric(),\n        )\n        form = self.form_class()\n        assert 'step=\"any\"' in str(form.test_column)\n\n    def test_supports_step_as_info_arg(self):\n        self.init(\n            type_=sa.Numeric(),\n            info={\"step\": \"0.1\"},\n        )\n        form = self.form_class()\n        assert 'step=\"0.1\"' in str(form.test_column)\n\n    def test_numeric_field_with_scale_and_choices(self):\n        self.init(\n            type_=sa.Numeric(scale=2),\n            info={\n                \"choices\": [(Decimal(\"1.1\"), \"choice1\"), (Decimal(\"1.2\"), \"choice2\")]\n            },\n        )\n        form = self.form_class()\n        assert 'step=\"0.1\"' not in str(form.test_column)\n\n\nclass TestIntegerFieldWidgets(ModelFormTestCase):\n    def test_supports_step_as_info_arg(self):\n        self.init(\n            type_=sa.Integer,\n            info={\"step\": \"3\"},\n        )\n        form = self.form_class()\n        assert 'step=\"3\"' in str(form.test_column)\n\n\nclass TestFloatFieldWidgets(ModelFormTestCase):\n    def test_supports_step_as_info_arg(self):\n        self.init(\n            type_=sa.Float,\n            info={\"step\": \"0.2\"},\n        )\n        form = self.form_class()\n        assert 'step=\"0.2\"' in str(form.test_column)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py{39,310,311,312,313}-sqlalchemy{1.4,2.0}-wtforms{3.1,3.2}\n\n[testenv]\ndeps =\n    .[test_all]\n    pytest-cov\n    sqlalchemy1.4: SQLAlchemy~=1.4\n    sqlalchemy2.0: SQLAlchemy~=2.0\n    wtforms3.1: WTForms~=3.1\n    wtforms3.2: WTForms~=3.2\ncommands = pip install -e \".[test]\"\n           py.test\ninstall_command = pip install {packages}\nrecreate = True\n"
  },
  {
    "path": "wtforms_alchemy/__init__.py",
    "content": "import sqlalchemy as sa\nfrom wtforms import Form\nfrom wtforms.form import FormMeta\nfrom wtforms.validators import (\n    DataRequired,\n    InputRequired,\n    Length,\n    NumberRange,\n    Optional,\n    URL,\n)\nfrom wtforms_components import DateRange, Email, TimeRange\n\nfrom .exc import (\n    AttributeTypeException,\n    InvalidAttributeException,\n    UnknownConfigurationOption,\n    UnknownTypeException,\n)\nfrom .fields import (  # noqa\n    CountryField,\n    GroupedQuerySelectField,\n    GroupedQuerySelectMultipleField,\n    ModelFieldList,\n    ModelFormField,\n    PhoneNumberField,\n    QuerySelectField,\n    QuerySelectMultipleField,\n    WeekDaysField,\n)\nfrom .generator import FormGenerator\nfrom .utils import (\n    ClassMap,\n    is_date_column,\n    is_scalar,\n    null_or_int,\n    null_or_unicode,\n)\nfrom .validators import Unique  # noqa\n\n__all__ = (\n    AttributeTypeException,\n    CountryField,\n    DateRange,\n    InvalidAttributeException,\n    ModelFieldList,\n    ModelFormField,\n    PhoneNumberField,\n    Unique,\n    UnknownTypeException,\n    is_date_column,\n    is_scalar,\n    null_or_int,\n    null_or_unicode,\n)\n\n\n__version__ = \"0.19.1\"\n\n\ndef model_form_meta_factory(base=FormMeta):\n    \"\"\"\n    Create a new class usable as a metaclass for the\n    :func:`model_form_factory`. You only need to concern yourself with this if\n    you desire to have a custom metclass. Otherwise, a default class is\n    created and is used as a metaclass on :func:`model_form_factory`.\n\n    :param base: The base class to use for the meta class. This is an optional\n                 parameter that defaults to :class:`.FormMeta`. If you want to\n                 provide your own, your class must derive from this class and\n                 not directly from ``type``.\n\n    :return: A new class suitable as a metaclass for the actual model form.\n             Therefore, it should be passed as the ``meta`` argument to\n             :func:`model_form_factory`.\n\n    Example usage:\n\n    .. code-block:: python\n\n        from wtforms.form import FormMeta\n\n\n        class MyModelFormMeta(FormMeta):\n            # do some metaclass magic here\n            pass\n\n        ModelFormMeta = model_form_meta_factory(MyModelFormMeta)\n        ModelForm = model_form_factory(meta=ModelFormMeta)\n    \"\"\"\n\n    class ModelFormMeta(base):\n        \"\"\"\n        Meta class that overrides WTForms base meta class. The primary purpose\n        of this class is allowing ModelForms use special configuration params\n        under the 'Meta' class namespace.\n\n        ModelForm classes inherit parent's Meta class properties.\n        \"\"\"\n\n        def __init__(cls, *args, **kwargs):\n            bases = []\n            for class_ in cls.__mro__:\n                if \"Meta\" in class_.__dict__:\n                    bases.append(getattr(class_, \"Meta\"))\n\n            if object not in bases:\n                bases.append(object)\n\n            cls.Meta = type(\"Meta\", tuple(bases), {})\n\n            base.__init__(cls, *args, **kwargs)\n\n            if hasattr(cls.Meta, \"model\") and cls.Meta.model:\n                generator = cls.Meta.form_generator(cls)\n                generator.create_form(cls)\n\n    return ModelFormMeta\n\n\nModelFormMeta = model_form_meta_factory()\n\n\ndef model_form_factory(base=Form, meta=ModelFormMeta, **defaults):\n    \"\"\"\n    Create a base class for all model forms to derive from.\n\n    :param base: Class that should be used as a base for the returned class.\n                 By default, this is WTForms's base class\n                 :class:`wtforms.Form`.\n\n    :param meta: A metaclass to use on this class. Normally, you do not need to\n                 provide this value, but if you want, you should check out\n                 :func:`model_form_meta_factory`.\n\n    :return: A class to be used as the base class for all forms that should be\n             connected to a SQLAlchemy model class.\n\n    Additional arguments provided to the form override the default\n    configuration as described in :ref:`custom_base`.\n    \"\"\"\n\n    class ModelForm(base, metaclass=meta):\n        \"\"\"\n        Standard base-class for all forms to be combined with a model. Use\n        :func:`model_form_factory` in case you wish to change its behavior.\n\n        ``get_session``: If you want to use the Unique validator, you should\n        define this method. If you are using Flask-SQLAlchemy along with\n        WTForms-Alchemy you don't need to set this. If you define this in the\n        superclass, it will not be overriden.\n        \"\"\"\n\n        if not hasattr(base, \"get_session\"):\n            get_session = None\n\n        class Meta:\n            model = None\n\n            default = None\n\n            #: Whether or not to skip unknown types. If this is set to True,\n            #: fields with types that are not present in FormGenerator type map\n            #: will be silently excluded from the generated form.\n            #:\n            #: By default this is set to False, meaning unknown types throw\n            #: exceptions when encountered.\n            skip_unknown_types = defaults.pop(\"skip_unknown_types\", False)\n\n            #: Whether or not to assign all fields as optional, useful when\n            #: creating update forms for patch requests\n            all_fields_optional = defaults.pop(\"all_fields_optional\", False)\n\n            validators = defaults.pop(\"validators\", {})\n\n            #: A dict with keys as field names and values as field arguments.\n            field_args = defaults.pop(\"field_args\", {})\n\n            #: A dict with keys as field names and values as widget options.\n            widget_options = defaults.pop(\"widget_options\", {})\n\n            #: Whether or not to include only indexed fields.\n            only_indexed_fields = defaults.pop(\"only_indexed_fields\", False)\n\n            #: Whether or not to include primary keys.\n            include_primary_keys = defaults.pop(\"include_primary_keys\", False)\n\n            #: Whether or not to include foreign keys. By default this is False\n            #: indicating that foreign keys are not included in the generated\n            #: form.\n            include_foreign_keys = defaults.pop(\"include_foreign_keys\", False)\n\n            #: Whether or not to strip string fields\n            strip_string_fields = defaults.pop(\"strip_string_fields\", False)\n\n            #: Whether or not to include datetime columns that have a default\n            #: value. A good example is created_at column which has a default\n            #: value of datetime.utcnow.\n            include_datetimes_with_default = defaults.pop(\n                \"include_datetimes_with_default\", False\n            )\n\n            #: The default validator to be used for not nullable columns. Set\n            #: this to `None` if you wish to disable it.\n            not_null_validator = defaults.pop(\"not_null_validator\", InputRequired())\n\n            #: A dictionary that overrides not null validation on type level.\n            #: Keys should be valid SQLAlchemy types and values should be valid\n            #: WTForms validators.\n            not_null_validator_type_map = defaults.pop(\n                \"not_null_validator_type_map\",\n                ClassMap([(sa.String, [InputRequired(), DataRequired()])]),\n            )\n\n            #: Default email validator\n            email_validator = defaults.pop(\"email_validator\", Email)\n\n            #: Default length validator\n            length_validator = defaults.pop(\"length_validator\", Length)\n\n            #: Default unique validator\n            unique_validator = defaults.pop(\"unique_validator\", Unique)\n\n            #: Default number range validator\n            number_range_validator = defaults.pop(\"number_range_validator\", NumberRange)\n\n            #: Default date range validator\n            date_range_validator = defaults.pop(\"date_range_validator\", DateRange)\n\n            #: Default time range validator\n            time_range_validator = defaults.pop(\"time_range_validator\", TimeRange)\n\n            #: Default optional validator\n            optional_validator = defaults.pop(\"optional_validator\", Optional)\n\n            #: Default URL validator\n            url_validator = defaults.pop(\"url_validator\", URL)\n\n            #: Which form generator to use. Only override this if you have a\n            #: valid form generator which you want to use instead of the\n            #: default one.\n            form_generator = defaults.pop(\"form_generator\", FormGenerator)\n\n            #: Default date format\n            date_format = defaults.pop(\"date_format\", \"%Y-%m-%d\")\n\n            #: Default datetime format\n            datetime_format = defaults.pop(\"datetime_format\", \"%Y-%m-%d %H:%M:%S\")\n\n            #: Dictionary of SQLAlchemy types as keys and WTForms field classes\n            #: as values. The key value pairs of this dictionary override\n            #: the key value pairs of FormGenerator.TYPE_MAP.\n            #:\n            #: Using this configuration option one can easily configure the\n            #: type conversion in class level.\n            type_map = defaults.pop(\"type_map\", ClassMap())\n\n            #: Whether or not to raise InvalidAttributExceptions when invalid\n            #: attribute names are given for include / exclude or only\n            attr_errors = defaults.pop(\"attr_errors\", True)\n\n            #: Additional fields to include in the generated form.\n            include = defaults.pop(\"include\", [])\n\n            #: List of fields to exclude from the generated form.\n            exclude = defaults.pop(\"exclude\", [])\n\n            #: List of fields to only include in the generated form.\n            only = defaults.pop(\"only\", None)\n\n        def __init__(self, *args, **kwargs):\n            \"\"\"Sets object as form attribute.\"\"\"\n\n            self._obj = kwargs.get(\"obj\", None)\n            super().__init__(*args, **kwargs)\n\n    if defaults:\n        raise UnknownConfigurationOption(list(defaults.keys())[0])\n\n    return ModelForm\n\n\nModelForm = model_form_factory(Form)\n\n\nclass ModelCreateForm(ModelForm):\n    pass\n\n\nclass ModelUpdateForm(ModelForm):\n    class Meta:\n        all_fields_optional = True\n        assign_required = False\n\n\nclass ModelSearchForm(ModelForm):\n    class Meta:\n        all_fields_optional = True\n        only_indexed_fields = True\n        include_primary_keys = True\n"
  },
  {
    "path": "wtforms_alchemy/exc.py",
    "content": "class UnknownTypeException(Exception):\n    def __init__(self, column):\n        Exception.__init__(\n            self, f\"Unknown type '{column.type!r}' for column '{column.name}'\"\n        )\n\n\nclass InvalidAttributeException(Exception):\n    def __init__(self, attr_name):\n        Exception.__init__(\n            self, f\"Model does not contain attribute named '{attr_name}'.\"\n        )\n\n\nclass AttributeTypeException(Exception):\n    def __init__(self, attr_name):\n        Exception.__init__(\n            self, f\"Model attribute '{attr_name}' is not of type ColumnProperty.\"\n        )\n\n\nclass UnknownConfigurationOption(Exception):\n    def __init__(self, option):\n        Exception.__init__(self, f\"Unknown configuration option '{option}' given.\")\n"
  },
  {
    "path": "wtforms_alchemy/fields.py",
    "content": "import operator\nfrom itertools import groupby\n\nimport sqlalchemy as sa\nfrom sqlalchemy.orm.util import identity_key\nfrom sqlalchemy_utils import Country, i18n, PhoneNumber\nfrom sqlalchemy_utils.primitives import WeekDay, WeekDays\nfrom wtforms import widgets\nfrom wtforms.fields import FieldList, FormField, SelectFieldBase\nfrom wtforms.utils import unset_value\nfrom wtforms.validators import ValidationError\nfrom wtforms.widgets import CheckboxInput, ListWidget\nfrom wtforms_components import SelectField, SelectMultipleField\nfrom wtforms_components.fields.html5 import StringField\nfrom wtforms_components.widgets import SelectWidget, TelInput\n\nfrom .utils import find_entity\n\n\nclass SkipOperation(Exception):\n    pass\n\n\nclass ModelFormField(FormField):\n    def populate_obj(self, obj, name):\n        if self.data:\n            try:\n                if getattr(obj, name) is None:\n                    setattr(obj, name, self.form.Meta.model())\n            except AttributeError:\n                pass\n        FormField.populate_obj(self, obj, name)\n\n\nclass ModelFieldList(FieldList):\n    def __init__(self, unbound_field, population_strategy=\"update\", **kwargs):\n        self.population_strategy = population_strategy\n        super().__init__(unbound_field, **kwargs)\n\n    @property\n    def model(self):\n        return self.unbound_field.args[0].Meta.model\n\n    def _get_bound_field_for_entry(self, formdata, data, index):\n        assert (\n            not self.max_entries or len(self.entries) < self.max_entries\n        ), \"You cannot have more than max_entries entries in this FieldList\"\n        new_index = self.last_index = index or (self.last_index + 1)\n        name = \"%s-%d\" % (self.short_name, new_index)\n        id = \"%s-%d\" % (self.id, new_index)\n        return self.unbound_field.bind(\n            form=None, name=name, prefix=self._prefix, id=id, _meta=self.meta\n        )\n\n    def _add_entry(self, formdata=None, data=unset_value, index=None):\n        field = self._get_bound_field_for_entry(\n            formdata=formdata, data=data, index=index\n        )\n        if data != unset_value and data:\n            if formdata:\n                field.process(formdata)\n            else:\n                field.process(formdata, data=data)\n\n            entity = find_entity(self.object_data, self.model, field.data)\n            if entity is not None:\n                field.process(formdata, entity)\n        else:\n            field.process(formdata)\n\n        self.entries.append(field)\n        return field\n\n    def populate_obj(self, obj, name):\n        state = sa.inspect(obj)\n\n        if not state.identity or self.population_strategy == \"replace\":\n            setattr(obj, name, [])\n            for counter in range(len(self.entries)):\n                try:\n                    getattr(obj, name).append(self.model())\n                except AttributeError:\n                    pass\n        else:\n            coll = getattr(obj, name)\n            entities = []\n            for index, entry in enumerate(self.entries):\n                data = entry.data\n                entity = find_entity(coll, self.model, data)\n                if entity is None:\n                    entities.insert(index, self.model())\n                else:\n                    entities.append(entity)\n            setattr(obj, name, entities)\n        FieldList.populate_obj(self, obj, name)\n\n\nclass CountryField(SelectField):\n    def __init__(self, *args, **kwargs):\n        kwargs[\"coerce\"] = Country\n        super().__init__(*args, **kwargs)\n        self.choices = self._get_choices\n\n    def _get_choices(self):\n        # Get all territories and filter out continents (3-digit code)\n        # and some odd territories such as \"Unknown or Invalid Region\"\n        # (\"ZZ\"), \"European Union\" (\"QU\") and \"Outlying Oceania\" (\"QO\").\n        territories = [\n            (code, name)\n            for code, name in i18n.get_locale().territories.items()\n            if len(code) == 2 and code not in (\"QO\", \"QU\", \"ZZ\")\n        ]\n        return sorted(territories, key=operator.itemgetter(1))\n\n\nclass QuerySelectField(SelectFieldBase):\n    \"\"\"\n    Will display a select drop-down field to choose between ORM results in a\n    sqlalchemy `Query`.  The `data` property actually will store/keep an ORM\n    model instance, not the ID. Submitting a choice which is not in the query\n    will result in a validation error.\n    This field only works for queries on models whose primary key column(s)\n    have a consistent string representation. This means it mostly only works\n    for those composed of string, unicode, and integer types. For the most\n    part, the primary keys will be auto-detected from the model, alternately\n    pass a one-argument callable to `get_pk` which can return a unique\n    comparable key.\n    The `query` property on the field can be set from within a view to assign\n    a query per-instance to the field. If the property is not set, the\n    `query_factory` callable passed to the field constructor will be called to\n    obtain a query.\n    Specify `get_label` to customize the label associated with each option. If\n    a string, this is the name of an attribute on the model object to use as\n    the label text. If a one-argument callable, this callable will be passed\n    model instance and expected to return the label text. Otherwise, the model\n    object's `__str__` or `__unicode__` will be used.\n    If `allow_blank` is set to `True`, then a blank choice will be added to the\n    top of the list. Selecting this choice will result in the `data` property\n    being `None`. The label for this blank choice can be set by specifying the\n    `blank_text` parameter.\n    \"\"\"\n\n    widget = widgets.Select()\n\n    def __init__(\n        self,\n        label=None,\n        validators=None,\n        query_factory=None,\n        get_pk=None,\n        get_label=None,\n        allow_blank=False,\n        blank_text=\"\",\n        **kwargs,\n    ):\n        super().__init__(label, validators, **kwargs)\n        self.query_factory = query_factory\n\n        if get_pk is None:\n            self.get_pk = get_pk_from_identity\n        else:\n            self.get_pk = get_pk\n\n        if get_label is None:\n            self.get_label = lambda x: x\n        elif isinstance(get_label, str):\n            self.get_label = operator.attrgetter(get_label)\n        else:\n            self.get_label = get_label\n\n        self.allow_blank = allow_blank\n        self.blank_text = blank_text\n        self.query = None\n        self._object_list = None\n\n    def _get_data(self):\n        if self._formdata is not None:\n            for pk, obj in self._get_object_list():\n                if pk == self._formdata:\n                    self._set_data(obj)\n                    break\n        return self._data\n\n    def _set_data(self, data):\n        self._data = data\n        self._formdata = None\n\n    data = property(_get_data, _set_data)\n\n    def _get_object_list(self):\n        if self._object_list is None:\n            query = self.query if self.query is not None else self.query_factory()\n            get_pk = self.get_pk\n            self._object_list = list((str(get_pk(obj)), obj) for obj in query)\n        return self._object_list\n\n    def iter_choices(self):\n        if self.allow_blank:\n            yield (\"__None\", self.blank_text, self.data is None, {})\n\n        for pk, obj in self._get_object_list():\n            yield (pk, self.get_label(obj), obj == self.data, {})\n\n    def process_formdata(self, valuelist):\n        if valuelist:\n            if self.allow_blank and valuelist[0] == \"__None\":\n                self.data = None\n            else:\n                self._data = None\n                self._formdata = valuelist[0]\n\n    def pre_validate(self, form):\n        data = self.data\n        if data is not None:\n            for pk, obj in self._get_object_list():\n                if data == obj:\n                    break\n            else:\n                raise ValidationError(self.gettext(\"Not a valid choice\"))\n        elif self._formdata or not self.allow_blank:\n            raise ValidationError(self.gettext(\"Not a valid choice\"))\n\n\nclass QuerySelectMultipleField(QuerySelectField):\n    \"\"\"\n    Very similar to QuerySelectField with the difference that this will\n    display a multiple select. The data property will hold a list with ORM\n    model instances and will be an empty list when no value is selected.\n    If any of the items in the data list or submitted form data cannot be\n    found in the query, this will result in a validation error.\n    \"\"\"\n\n    widget = widgets.Select(multiple=True)\n\n    def __init__(self, label=None, validators=None, default=None, **kwargs):\n        if default is None:\n            default = []\n        super().__init__(label, validators, default=default, **kwargs)\n        if kwargs.get(\"allow_blank\", False):\n            import warnings\n\n            warnings.warn(\n                \"allow_blank=True does not do anything for \" \"QuerySelectMultipleField.\"\n            )\n        self._invalid_formdata = False\n\n    def _get_data(self):\n        formdata = self._formdata\n        if formdata is not None:\n            data = []\n            for pk, obj in self._get_object_list():\n                if not formdata:\n                    break\n                elif pk in formdata:\n                    formdata.remove(pk)\n                    data.append(obj)\n            if formdata:\n                self._invalid_formdata = True\n            self._set_data(data)\n        return self._data\n\n    def _set_data(self, data):\n        self._data = data\n        self._formdata = None\n\n    data = property(_get_data, _set_data)\n\n    def iter_choices(self):\n        for pk, obj in self._get_object_list():\n            yield (pk, self.get_label(obj), obj in self.data, {})\n\n    def process_formdata(self, valuelist):\n        self._formdata = set(valuelist)\n\n    def pre_validate(self, form):\n        if self._invalid_formdata:\n            raise ValidationError(self.gettext(\"Not a valid choice\"))\n        elif self.data:\n            obj_list = list(x[1] for x in self._get_object_list())\n            for v in self.data:\n                if v not in obj_list:\n                    raise ValidationError(self.gettext(\"Not a valid choice\"))\n\n\ndef get_pk_from_identity(obj):\n    cls, key = identity_key(instance=obj)[0:2]\n    return \":\".join(str(x) for x in key)\n\n\nclass GroupedQuerySelectField(SelectField):\n    widget = SelectWidget()\n\n    def __init__(\n        self,\n        label=None,\n        validators=None,\n        query_factory=None,\n        get_pk=None,\n        get_label=None,\n        get_group=None,\n        allow_blank=False,\n        blank_text=\"\",\n        blank_value=\"__None\",\n        **kwargs,\n    ):\n        super().__init__(label, validators, coerce=lambda x: x, **kwargs)\n\n        self.query = None\n        self.query_factory = query_factory\n\n        if get_pk is None:\n            self.get_pk = get_pk_from_identity\n        else:\n            self.get_pk = get_pk\n\n        self.get_label = get_label\n        self.get_group = get_group\n\n        self.allow_blank = allow_blank\n        self.blank_text = blank_text\n        self.blank_value = blank_value\n\n        self._choices = None\n\n    def _get_object_list(self):\n        query = self.query if self.query is not None else self.query_factory()\n        return list((str(self.get_pk(obj)), obj) for obj in query)\n\n    def _pre_process_object_list(self, object_list):\n        return sorted(\n            object_list, key=lambda x: (x[1] or \"\", self.get_label(x[2]) or \"\")\n        )\n\n    @property\n    def choices(self):\n        if not self._choices:\n            object_list = map(\n                lambda x: (x[0], self.get_group(x[1]), x[1]), self._get_object_list()\n            )\n            # object_list is (key, group, value) tuple\n            choices = [(self.blank_value, self.blank_text)] if self.allow_blank else []\n            object_list = self._pre_process_object_list(object_list)\n            for group, data in groupby(object_list, key=lambda x: x[1]):\n                if group is not None:\n                    group_items = []\n                    for key, _, value in data:\n                        group_items.append((key, self.get_label(value)))\n                    choices.append((group, group_items))\n                else:\n                    for key, group, value in data:\n                        choices.append((key, self.get_label(value)))\n            self._choices = choices\n        return self._choices\n\n    @choices.setter\n    def choices(self, value):\n        pass\n\n    @property\n    def data(self):\n        if self._formdata is not None:\n            for pk, obj in self._get_object_list():\n                if pk == self._formdata:\n                    self.data = obj\n                    break\n        return self._data\n\n    @data.setter\n    def data(self, data):\n        self._data = data\n        self._formdata = None\n\n    def iter_choices(self):\n        \"\"\"\n        We should update how choices are iter to make sure that value from\n        internal list or tuple should be selected.\n        \"\"\"\n        for value, label in self.concrete_choices:\n            yield (\n                value,\n                label,\n                (\n                    self.coerce,\n                    self.get_pk(self.data) if self.data else self.blank_value,\n                ),\n                {},\n            )\n\n    def process_formdata(self, valuelist):\n        if valuelist:\n            if self.allow_blank and valuelist[0] == self.blank_value:\n                self.data = None\n            else:\n                self._data = None\n                self._formdata = valuelist[0]\n\n    def pre_validate(self, form):\n        data = self.data\n        if data is not None:\n            for pk, obj in self._get_object_list():\n                if data == obj:\n                    break\n            else:\n                raise ValidationError(\"Not a valid choice\")\n        elif self._formdata or not self.allow_blank:\n            raise ValidationError(\"Not a valid choice\")\n\n\nclass GroupedQuerySelectMultipleField(SelectField):\n    widget = SelectWidget(multiple=True)\n\n    def __init__(\n        self,\n        label=None,\n        validators=None,\n        query_factory=None,\n        get_pk=None,\n        get_label=None,\n        get_group=None,\n        blank_text=\"\",\n        default=None,\n        **kwargs,\n    ):\n        if default is None:\n            default = []\n        super().__init__(\n            label, validators, default=default, coerce=lambda x: x, **kwargs\n        )\n        if kwargs.get(\"allow_blank\", False):\n            import warnings\n\n            warnings.warn(\n                \"allow_blank=True does not do anything for \"\n                \"GroupedQuerySelectMultipleField.\"\n            )\n\n        self.query = None\n        self.query_factory = query_factory\n\n        if get_pk is None:\n            self.get_pk = get_pk_from_identity\n        else:\n            self.get_pk = get_pk\n\n        self.get_label = get_label\n        self.get_group = get_group\n\n        self.blank_text = blank_text\n\n        self._choices = None\n        self._invalid_formdata = False\n\n    def _get_object_list(self):\n        query = self.query if self.query is not None else self.query_factory()\n        return list((str(self.get_pk(obj)), obj) for obj in query)\n\n    def _pre_process_object_list(self, object_list):\n        return sorted(\n            object_list, key=lambda x: (x[1] or \"\", self.get_label(x[2]) or \"\")\n        )\n\n    @property\n    def choices(self):\n        if not self._choices:\n            object_list = map(\n                lambda x: (x[0], self.get_group(x[1]), x[1]), self._get_object_list()\n            )\n            # object_list is (key, group, value) tuple\n            choices = []\n            object_list = self._pre_process_object_list(object_list)\n            for group, data in groupby(object_list, key=lambda x: x[1]):\n                if group is not None:\n                    group_items = []\n                    for key, _, value in data:\n                        group_items.append((key, self.get_label(value)))\n                    choices.append((group, group_items))\n                else:\n                    for key, group, value in data:\n                        choices.append((key, self.get_label(value)))\n            self._choices = choices\n        return self._choices\n\n    @choices.setter\n    def choices(self, value):\n        pass\n\n    @property\n    def data(self):\n        formdata = self._formdata\n        if formdata is not None:\n            data = []\n            for pk, obj in self._get_object_list():\n                if not formdata:\n                    break\n                elif self.coerce(pk) in formdata:\n                    formdata.remove(self.coerce(pk))\n                    data.append(obj)\n            if formdata:\n                self._invalid_formdata = True\n            self.data = data\n        return self._data\n\n    @data.setter\n    def data(self, valuelist):\n        self._data = valuelist\n        self._formdata = None\n\n    def iter_choices(self):\n        \"\"\"\n        We should update how choices are iter to make sure that value from\n        internal list or tuple should be selected.\n        \"\"\"\n        for value, label in self.concrete_choices:\n            yield (\n                value,\n                label,\n                (self.coerce, [self.get_pk(obj) for obj in self.data or []]),\n                {},\n            )\n\n    def process_formdata(self, valuelist):\n        self._formdata = set(valuelist)\n\n    def pre_validate(self, form):\n        self.data  # This sets self._invalid_formdata\n        if self._invalid_formdata:\n            raise ValidationError(self.gettext(\"Not a valid choice\"))\n        elif self.data:\n            obj_list = list(x[1] for x in self._get_object_list())\n            for v in self.data:\n                if v not in obj_list:\n                    raise ValidationError(self.gettext(\"Not a valid choice\"))\n\n\nclass WeekDaysField(SelectMultipleField):\n    widget = ListWidget(prefix_label=False)\n    option_widget = CheckboxInput()\n\n    def __init__(self, *args, **kwargs):\n        kwargs[\"coerce\"] = lambda x: WeekDay(int(x))\n        super().__init__(*args, **kwargs)\n        self.choices = self._get_choices\n\n    def _get_choices(self):\n        days = WeekDays(\"1111111\")\n        for day in days:\n            yield day.index, day.get_name(context=\"stand-alone\")\n\n    def process_data(self, value):\n        self.data = WeekDays(value) if value else None\n\n    def process_formdata(self, valuelist):\n        self.data = WeekDays(self.coerce(x) for x in valuelist)\n\n    def pre_validate(self, form):\n        pass\n\n\nclass PhoneNumberField(StringField):\n    \"\"\"\n    A string field representing a PhoneNumber object from\n    `SQLAlchemy-Utils`_.\n\n    .. _SQLAlchemy-Utils:\n       https://github.com/kvesteri/sqlalchemy-utils\n\n    :param region:\n        Country code of the phone number.\n    :param display_format:\n        The format in which the phone number is displayed.\n    \"\"\"\n\n    widget = TelInput()\n    error_msg = \"Not a valid phone number value\"\n\n    def __init__(\n        self,\n        label=None,\n        validators=None,\n        region=\"US\",\n        display_format=\"national\",\n        **kwargs,\n    ):\n        super().__init__(label, validators, **kwargs)\n        self.region = region\n        self.display_format = display_format\n\n    def _value(self):\n        # self.data holds a PhoneNumber object if the form is valid,\n        # otherwise it will contain a string.\n        if self.data:\n            try:\n                return getattr(self.data, self.display_format)\n            except AttributeError:\n                return self.data\n        else:\n            return \"\"\n\n    def process_formdata(self, valuelist):\n        import phonenumbers\n\n        if valuelist:\n            if valuelist[0] == \"\":\n                self.data = None\n            else:\n                self.data = valuelist[0]\n                try:\n                    self.data = PhoneNumber(valuelist[0], self.region)\n                    if not self.data.is_valid_number():\n                        raise ValueError(self.gettext(self.error_msg))\n                except phonenumbers.phonenumberutil.NumberParseException:\n                    raise ValueError(self.gettext(self.error_msg))\n"
  },
  {
    "path": "wtforms_alchemy/generator.py",
    "content": "import inspect\nfrom collections import OrderedDict\nfrom decimal import Decimal\nfrom enum import Enum\n\nimport sqlalchemy as sa\nfrom sqlalchemy.orm.properties import ColumnProperty\nfrom sqlalchemy_utils import types\nfrom wtforms import (\n    BooleanField,\n    Field,\n    FloatField,\n    PasswordField,\n    TextAreaField,\n)\nfrom wtforms.widgets import CheckboxInput, TextArea\nfrom wtforms_components import (\n    ColorField,\n    DateField,\n    DateIntervalField,\n    DateTimeField,\n    DateTimeIntervalField,\n    DateTimeLocalField,\n    DecimalField,\n    DecimalIntervalField,\n    EmailField,\n    IntegerField,\n    IntIntervalField,\n    SelectField,\n    StringField,\n    TimeField,\n)\nfrom wtforms_components.widgets import (\n    ColorInput,\n    DateInput,\n    DateTimeInput,\n    DateTimeLocalInput,\n    EmailInput,\n    NumberInput,\n    TextInput,\n    TimeInput,\n)\n\nfrom .exc import (\n    AttributeTypeException,\n    InvalidAttributeException,\n    UnknownTypeException,\n)\nfrom .fields import CountryField, PhoneNumberField, WeekDaysField\nfrom .utils import (\n    choice_type_coerce_factory,\n    ClassMap,\n    flatten,\n    is_date_column,\n    is_number,\n    is_number_range,\n    is_scalar,\n    null_or_unicode,\n    strip_string,\n    translated_attributes,\n)\n\n\nclass FormGenerator:\n    \"\"\"\n    Base form generator, you can make your own form generators by inheriting\n    this class.\n    \"\"\"\n\n    # When converting SQLAlchemy types to fields this ordered dict is iterated\n    # in given order. This allows smart type conversion of different inherited\n    # type objects.\n    TYPE_MAP = ClassMap(\n        (\n            (sa.types.UnicodeText, TextAreaField),\n            (sa.types.BigInteger, IntegerField),\n            (sa.types.SmallInteger, IntegerField),\n            (sa.types.Text, TextAreaField),\n            (sa.types.Boolean, BooleanField),\n            (sa.types.Date, DateField),\n            (sa.types.DateTime, DateTimeField),\n            (sa.types.Enum, SelectField),\n            (sa.types.Float, FloatField),\n            (sa.types.Integer, IntegerField),\n            (sa.types.Numeric, DecimalField),\n            (sa.types.Unicode, StringField),\n            (sa.types.String, StringField),\n            (sa.types.Time, TimeField),\n            (sa.types.JSON, TextAreaField),\n            (types.ArrowType, DateTimeField),\n            (types.ChoiceType, SelectField),\n            (types.ColorType, ColorField),\n            (types.CountryType, CountryField),\n            (types.DateRangeType, DateIntervalField),\n            (types.DateTimeRangeType, DateTimeIntervalField),\n            (types.EmailType, EmailField),\n            (types.IntRangeType, IntIntervalField),\n            (types.NumericRangeType, DecimalIntervalField),\n            (types.PasswordType, PasswordField),\n            (types.PhoneNumberType, PhoneNumberField),\n            (types.ScalarListType, StringField),\n            (types.URLType, StringField),\n            (types.UUIDType, StringField),\n            (types.WeekDaysType, WeekDaysField),\n        )\n    )\n\n    WIDGET_MAP = OrderedDict(\n        (\n            (BooleanField, CheckboxInput),\n            (ColorField, ColorInput),\n            (DateField, DateInput),\n            (DateTimeField, DateTimeInput),\n            (DateTimeLocalField, DateTimeLocalInput),\n            (DecimalField, NumberInput),\n            (EmailField, EmailInput),\n            (FloatField, NumberInput),\n            (IntegerField, NumberInput),\n            (TextAreaField, TextArea),\n            (TimeField, TimeInput),\n            (StringField, TextInput),\n        )\n    )\n\n    def __init__(self, form_class):\n        \"\"\"\n        Initializes the form generator\n\n        :param form_class: ModelForm class to be used as the base of generation\n                           process\n        \"\"\"\n        self.form_class = form_class\n        self.model_class = self.form_class.Meta.model\n        self.meta = self.form_class.Meta\n        self.TYPE_MAP.update(self.form_class.Meta.type_map)\n\n    def create_form(self, form):\n        \"\"\"\n        Creates the form.\n\n        :param form: ModelForm instance\n        \"\"\"\n        attrs = OrderedDict()\n        for key, property_ in sa.inspect(self.model_class).attrs.items():\n            if not isinstance(property_, ColumnProperty):\n                continue\n            if self.skip_column_property(property_):\n                continue\n            attrs[key] = property_\n\n        for attr in translated_attributes(self.model_class):\n            attrs[attr.key] = attr.property\n\n        return self.create_fields(form, self.filter_attributes(attrs))\n\n    def filter_attributes(self, attrs):\n        \"\"\"\n        Filter set of model attributes based on only, exclude and include\n        meta parameters.\n\n        :param attrs: Set of attributes\n        \"\"\"\n        if self.meta.only is not None:\n            attrs = OrderedDict(\n                [\n                    (key, prop)\n                    for key, prop in map(self.validate_attribute, self.meta.only)\n                    if key\n                ]\n            )\n        else:\n            if self.meta.include:\n                attrs.update(\n                    [\n                        (key, prop)\n                        for key, prop in map(self.validate_attribute, self.meta.include)\n                        if key\n                    ]\n                )\n\n            if self.meta.exclude:\n                for key in self.meta.exclude:\n                    try:\n                        del attrs[key]\n                    except KeyError:\n                        if self.meta.attr_errors:\n                            raise InvalidAttributeException(key)\n        return attrs\n\n    def validate_attribute(self, attr_name):\n        \"\"\"\n        Finds out whether or not given sqlalchemy model attribute name is\n        valid. Returns attribute property if valid.\n\n        :param attr_name: Attribute name\n        \"\"\"\n        try:\n            attr = getattr(self.model_class, attr_name)\n        except AttributeError:\n            try:\n                translation_class = self.model_class.__translatable__[\"class\"]\n                attr = getattr(translation_class, attr_name)\n            except AttributeError:\n                if self.meta.attr_errors:\n                    raise InvalidAttributeException(attr_name)\n                else:\n                    return None, None\n        try:\n            if not isinstance(attr.property, ColumnProperty):\n                if self.meta.attr_errors:\n                    raise InvalidAttributeException(attr_name)\n                else:\n                    return None, None\n        except AttributeError:\n            raise AttributeTypeException(attr_name)\n        return attr_name, attr.property\n\n    def create_fields(self, form, properties):\n        \"\"\"\n        Creates fields for given form based on given model attributes.\n\n        :param form: form to attach the generated fields into\n        :param attributes: model attributes to generate the form fields from\n        \"\"\"\n        for key, prop in properties.items():\n            column = prop.columns[0]\n            try:\n                field = self.create_field(prop, column)\n            except UnknownTypeException:\n                if not self.meta.skip_unknown_types:\n                    raise\n                else:\n                    continue\n\n            if not hasattr(form, key):\n                setattr(form, key, field)\n\n    def skip_column_property(self, column_property):\n        \"\"\"\n        Whether or not to skip column property in the generation process.\n\n        :param column_property: SQLAlchemy ColumnProperty object\n        \"\"\"\n        if column_property._is_polymorphic_discriminator:\n            return True\n\n        return self.skip_column(column_property.columns[0])\n\n    def skip_column(self, column):\n        \"\"\"\n        Whether or not to skip column in the generation process.\n\n        :param column_property: SQLAlchemy Column object\n        \"\"\"\n        if not self.meta.include_foreign_keys and column.foreign_keys:\n            return True\n\n        if not self.meta.include_primary_keys and column.primary_key:\n            return True\n\n        if (\n            not self.meta.include_datetimes_with_default\n            and isinstance(column.type, sa.types.DateTime)\n            and column.default\n        ):\n            return True\n\n        if isinstance(column.type, types.TSVectorType):\n            return True\n\n        if self.meta.only_indexed_fields and not self.has_index(column):\n            return True\n\n        # Skip all non columns (this is the case when using column_property\n        # methods).\n        if not isinstance(column, sa.Column):\n            return True\n\n        return False\n\n    def has_index(self, column):\n        \"\"\"\n        Whether or not given column has an index.\n\n        :param column: Column object to inspect the indexes from\n        \"\"\"\n        if column.primary_key or column.foreign_keys:\n            return True\n        table = column.table\n        for index in table.indexes:\n            if len(index.columns) == 1 and column.name in index.columns:\n                return True\n        return False\n\n    def create_field(self, prop, column):\n        \"\"\"\n        Create form field for given column.\n\n        :param prop: SQLAlchemy ColumnProperty object.\n        :param column: SQLAlchemy Column object.\n        \"\"\"\n        kwargs = {}\n        field_class = self.get_field_class(column)\n        kwargs[\"default\"] = self.default(column)\n        kwargs[\"validators\"] = self.create_validators(prop, column)\n        kwargs[\"filters\"] = self.filters(column)\n        kwargs.update(self.type_agnostic_parameters(prop.key, column))\n        kwargs.update(self.type_specific_parameters(column))\n        if prop.key in self.meta.field_args:\n            kwargs.update(self.meta.field_args[prop.key])\n\n        if issubclass(field_class, DecimalField):\n            if hasattr(column.type, \"scale\"):\n                kwargs[\"places\"] = column.type.scale\n        field = field_class(**kwargs)\n        return field\n\n    def default(self, column):\n        \"\"\"\n        Return field default for given column.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if column.default and is_scalar(column.default.arg):\n            return column.default.arg\n        else:\n            if not column.nullable:\n                return self.meta.default\n\n    def filters(self, column):\n        \"\"\"\n        Return filters for given column.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        should_trim = column.info.get(\"trim\", None)\n        filters = column.info.get(\"filters\", [])\n        if (\n            isinstance(column.type, sa.types.String)\n            and self.meta.strip_string_fields\n            and should_trim is None\n        ) or should_trim is True:\n            filters.append(strip_string)\n        return filters\n\n    def date_format(self, column):\n        \"\"\"\n        Returns date format for given column.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if isinstance(column.type, sa.types.DateTime) or isinstance(\n            column.type, types.ArrowType\n        ):\n            return self.meta.datetime_format\n\n        if isinstance(column.type, sa.types.Date):\n            return self.meta.date_format\n\n    def type_specific_parameters(self, column):\n        \"\"\"\n        Returns type specific parameters for given column.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        kwargs = {}\n        if (\n            hasattr(column.type, \"enums\")\n            or column.info.get(\"choices\")\n            or isinstance(column.type, types.ChoiceType)\n        ):\n            kwargs.update(self.select_field_kwargs(column))\n\n        date_format = self.date_format(column)\n        if date_format:\n            kwargs[\"format\"] = date_format\n\n        if hasattr(column.type, \"region\"):\n            kwargs[\"region\"] = column.type.region\n\n        kwargs[\"widget\"] = self.widget(column)\n        return kwargs\n\n    def widget(self, column):\n        \"\"\"\n        Returns WTForms widget for given column.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        widget = column.info.get(\"widget\", None)\n        if widget is not None:\n            return widget\n\n        kwargs = {}\n\n        step = column.info.get(\"step\", None)\n        if step is not None:\n            kwargs[\"step\"] = step\n        else:\n            if isinstance(column.type, sa.types.Numeric):\n                if column.type.scale is not None and not column.info.get(\"choices\"):\n                    kwargs[\"step\"] = self.scale_to_step(column.type.scale)\n\n        if kwargs:\n            widget_class = self.WIDGET_MAP[self.get_field_class(column)]\n            return widget_class(**kwargs)\n\n    def scale_to_step(self, scale):\n        \"\"\"\n        Returns HTML5 compatible step attribute for given decimal scale.\n\n        :param scale: an integer that defines a Numeric column's scale\n        \"\"\"\n        return str(pow(Decimal(\"0.1\"), scale))\n\n    def type_agnostic_parameters(self, key, column):\n        \"\"\"\n        Returns all type agnostic form field parameters for given column.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        kwargs = {}\n        kwargs[\"description\"] = column.info.get(\"description\", \"\")\n        kwargs[\"label\"] = column.info.get(\"label\", key)\n        return kwargs\n\n    def select_field_kwargs(self, column):\n        \"\"\"\n        Returns key value args for SelectField based on SQLAlchemy column\n        definitions.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        kwargs = {}\n        kwargs[\"coerce\"] = self.coerce(column)\n        if isinstance(column.type, types.ChoiceType):\n            choices = column.type.choices\n            if (\n                Enum is not None\n                and isinstance(choices, type)\n                and issubclass(choices, Enum)\n            ):\n                kwargs[\"choices\"] = [(choice.value, str(choice)) for choice in choices]\n            else:\n                kwargs[\"choices\"] = choices\n        elif \"choices\" in column.info and column.info[\"choices\"]:\n            kwargs[\"choices\"] = column.info[\"choices\"]\n        else:\n            kwargs[\"choices\"] = [(enum, enum) for enum in column.type.enums]\n        return kwargs\n\n    def coerce(self, column):\n        \"\"\"\n        Returns coerce callable for given column\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if \"coerce\" in column.info:\n            return column.info[\"coerce\"]\n        if isinstance(column.type, types.ChoiceType):\n            return choice_type_coerce_factory(column.type)\n        try:\n            python_type = column.type.python_type\n        except NotImplementedError:\n            return null_or_unicode\n\n        if column.nullable and issubclass(python_type, str):\n            return null_or_unicode\n        return python_type\n\n    def create_validators(self, prop, column):\n        \"\"\"\n        Returns validators for given column\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        validators = [\n            self.required_validator(column),\n            self.length_validator(column),\n            self.unique_validator(prop.key, column),\n            self.range_validator(column),\n        ]\n        if isinstance(column.type, types.EmailType):\n            validators.append(self.get_validator(\"email\"))\n        if isinstance(column.type, types.URLType):\n            validators.append(self.get_validator(\"url\"))\n        validators = flatten([v for v in validators if v is not None])\n\n        validators.extend(self.additional_validators(prop.key, column))\n        return validators\n\n    def required_validator(self, column):\n        \"\"\"\n        Returns required / optional validator for given column based on column\n        nullability and form configuration.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if (\n            not self.meta.all_fields_optional\n            and not column.default\n            and not column.nullable\n        ):\n            type_map = self.meta.not_null_validator_type_map\n            try:\n                return type_map[column.type]\n            except KeyError:\n                if isinstance(column.type, sa.types.TypeDecorator):\n                    type_ = column.type.impl\n\n                    try:\n                        return type_map[type_]\n                    except KeyError:\n                        pass\n                if self.meta.not_null_validator is not None:\n                    return self.meta.not_null_validator\n        return self.get_validator(\"optional\")\n\n    def get_validator(self, name, **kwargs):\n        attr_name = f\"{name}_validator\"\n        attr = getattr(self.meta, attr_name)\n        if attr is None:\n            return attr\n\n        return attr(**kwargs)\n\n    def additional_validators(self, key, column):\n        \"\"\"\n        Returns additional validators for given column\n\n        :param key: String key of the column property\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        validators = []\n        if key in self.meta.validators:\n            try:\n                validators.extend(self.meta.validators[key])\n            except TypeError:\n                validators.append(self.meta.validators[key])\n\n        if \"validators\" in column.info and column.info[\"validators\"]:\n            try:\n                validators.extend(column.info[\"validators\"])\n            except TypeError:\n                validators.append(column.info[\"validators\"])\n        return validators\n\n    def unique_validator(self, key, column):\n        \"\"\"\n        Returns unique validator for given column if column has a unique index\n\n        :param key: String key of the column property\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if column.unique:\n            return self.get_validator(\n                \"unique\",\n                column=getattr(self.model_class, key),\n                get_session=self.form_class.get_session,\n            )\n\n    def range_validator(self, column):\n        \"\"\"\n        Returns range validator based on column type and column info min and\n        max arguments\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        min_ = column.info.get(\"min\")\n        max_ = column.info.get(\"max\")\n\n        if min_ is not None or max_ is not None:\n            if is_number(column.type) or is_number_range(column.type):\n                return self.get_validator(\"number_range\", min=min_, max=max_)\n            elif is_date_column(column):\n                return self.get_validator(\"date_range\", min=min_, max=max_)\n            elif isinstance(column.type, sa.types.Time):\n                return self.get_validator(\"time_range\", min=min_, max=max_)\n\n    def length_validator(self, column):\n        \"\"\"\n        Returns length validator for given column\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if (\n            isinstance(column.type, sa.types.String)\n            and hasattr(column.type, \"length\")\n            and column.type.length\n        ):\n            return self.get_validator(\"length\", max=column.type.length)\n\n    def get_field_class(self, column):\n        \"\"\"\n        Returns WTForms field class. Class is based on a custom field class\n        attribute or SQLAlchemy column type.\n\n        :param column: SQLAlchemy Column object\n        \"\"\"\n        if \"form_field_class\" in column.info and column.info[\"form_field_class\"]:\n            return column.info[\"form_field_class\"]\n        if \"choices\" in column.info and column.info[\"choices\"]:\n            return SelectField\n        if column.type not in self.TYPE_MAP and isinstance(\n            column.type, sa.types.TypeDecorator\n        ):\n            check_type = column.type.impl\n        else:\n            check_type = column.type\n\n        try:\n            column_type = self.TYPE_MAP[check_type]\n\n            if inspect.isclass(column_type) and issubclass(column_type, Field):\n                return column_type\n            else:\n                return column_type(column)\n        except KeyError:\n            raise UnknownTypeException(column)\n"
  },
  {
    "path": "wtforms_alchemy/utils.py",
    "content": "from collections import OrderedDict\nfrom enum import Enum\nfrom inspect import isclass\n\nimport sqlalchemy as sa\nfrom sqlalchemy import types\nfrom sqlalchemy_utils import IntRangeType, NumericRangeType\nfrom sqlalchemy_utils.types.choice import Choice\n\n\ndef choice_type_coerce_factory(type_):\n    \"\"\"\n    Return a function needed to coerce a ChoiceTyped column. This function is\n    then passed to generated SelectField as the default coerce function.\n\n    :param type_: ChoiceType object\n    \"\"\"\n    choices = type_.choices\n    if Enum is not None and isinstance(choices, type) and issubclass(choices, Enum):\n        key, choice_cls = \"value\", choices\n    else:\n        key, choice_cls = \"code\", Choice\n\n    def choice_coerce(value):\n        if value is None:\n            return None\n        if isinstance(value, choice_cls):\n            return getattr(value, key)\n        return type_.python_type(value)\n\n    return choice_coerce\n\n\ndef strip_string(value):\n    if isinstance(value, str):\n        return value.strip()\n    return value\n\n\ndef is_scalar(value):\n    return isinstance(value, (type(None), str, int, float, bool))\n\n\ndef null_or_unicode(value):\n    return str(value) or None\n\n\ndef null_or_int(value):\n    try:\n        return int(value)\n    except TypeError:\n        return None\n\n\ndef flatten(list_):\n    result = []\n    if isinstance(list_, list):\n        for value in list_:\n            result.extend(flatten(value))\n    else:\n        result.append(list_)\n    return result\n\n\ndef is_number(type):\n    return isinstance(type, types.Integer) or isinstance(type, types.Numeric)\n\n\ndef is_number_range(type):\n    return isinstance(type, IntRangeType) or isinstance(type, NumericRangeType)\n\n\ndef is_date_column(column):\n    return isinstance(column.type, types.Date) or isinstance(\n        column.type, types.DateTime\n    )\n\n\ndef table(model):\n    if isinstance(model, sa.schema.Table):\n        return model\n    else:\n        return model.__table__\n\n\ndef find_entity(coll, model, data):\n    \"\"\"\n    Find object in `coll` that matches `data`\n    \"\"\"\n    mapper = sa.inspect(model)\n\n    def match_pk(obj, col):\n        data_val = data.get(col.name)\n        if not data_val:\n            # name not in data, or null value\n            return False\n        value = getattr(obj, col.name)\n        try:\n            return value == col.type.python_type(data_val)\n        except ValueError:\n            # coerce failed\n            return False\n\n    for obj in coll:\n        if all(match_pk(obj, col) for col in mapper.primary_key):\n            return obj\n\n    return None\n\n\ndef translated_attributes(model):\n    \"\"\"\n    Return translated attributes for current model class. See\n    `SQLAlchemy-i18n package`_ for more information about translatable\n    attributes.\n\n    .. _`SQLAlchemy-i18n package`:\n        https://github.com/kvesteri/sqlalchemy-i18n\n\n    :param model: SQLAlchemy declarative model class\n    \"\"\"\n    try:\n        translation_class = model.__translatable__[\"class\"]\n    except AttributeError:\n        return []\n    return [\n        getattr(translation_class, column.key)\n        for column in sa.inspect(translation_class).columns\n        if not column.primary_key\n    ]\n\n\nclass ClassMap(OrderedDict):\n    \"\"\"\n    An ordered dictionary with keys as classes. ClassMap has the following\n    charasteristics:\n\n        1. Checking if a key exists not only matches exact classes but also\n        subclasses and objects which are instances of a ClassMap key.\n\n        2. Getting an item of ClassMap with a key matches subclasses and\n        instances also.\n    \"\"\"\n\n    def __init__(self, items=None):\n        if items is None:\n            items = {}\n        OrderedDict.__init__(self, items)\n\n    def __contains__(self, key):\n        \"\"\"\n        Checks if given key exists in by first trying to find an exact match.\n        If no exact match is found then this method iterates trhough keys\n        and tries to check if given key is either:\n\n            1. A subclass of one of the keys\n            2. An instance of one of the keys\n\n        The first check has the time complexity of O(1) whereas the second\n        check has O(n).\n\n        Example::\n\n\n\n            class A(object):\n                pass\n\n\n            class B(object):\n                pass\n\n\n            class A2(A):\n                pass\n\n\n            class_map = ClassMap({A: 1, B: 2})\n            assert B in class_map\n            assert A in class_map\n            assert A2 in class_map\n            assert B() in class_map\n            assert A() in class_map\n            assert A2() in class_map\n        \"\"\"\n        if OrderedDict.__contains__(self, key):\n            return True\n        test_func = issubclass if isclass(key) else isinstance\n        return any(test_func(key, class_) for class_ in self)\n\n    def __getitem__(self, key):\n        \"\"\"\n        Returns the item matching a key. The key matching has the same\n        charasteristics as __contains__ method.\n\n        Example::\n\n            class A(object):\n                pass\n\n\n            class B(object):\n                pass\n\n\n            class A2(A):\n                pass\n\n\n            class_map = ClassMap({A: 1, B: 2})\n            assert class_map[B] == 2\n            assert class_map[A] == 1\n            assert class_map[A2] == 1\n            assert class_map[B()] == 2\n            assert class_map[A()] == 1\n            assert class_map[A2()] == 1\n        \"\"\"\n        try:\n            return OrderedDict.__getitem__(self, key)\n        except KeyError:\n            if not isclass(key):\n                key = type(key)\n            for class_ in self:\n                if issubclass(key, class_):\n                    return OrderedDict.__getitem__(self, class_)\n            raise\n"
  },
  {
    "path": "wtforms_alchemy/validators.py",
    "content": "from collections.abc import Iterable, Mapping\n\nfrom sqlalchemy import Column\nfrom sqlalchemy.orm.attributes import InstrumentedAttribute\nfrom wtforms import ValidationError\n\n\nclass Unique:\n    \"\"\"Checks field values unicity against specified table fields.\n\n    :param column:\n        InstrumentedAttribute object, eg. User.name, or\n        Column object, eg. user.c.name, or\n        a field name, eg. 'name' or\n        a tuple of InstrumentedAttributes, eg. (User.name, User.email) or\n        a dictionary mapping field names to InstrumentedAttributes, eg.\n        {\n            'name': User.name,\n            'email': User.email\n        }\n    :param get_session:\n        A function that returns a SQAlchemy Session. This parameter is not\n        needed if the given model supports Flask-SQLAlchemy styled query\n        parameter.\n    :param message:\n        The error message.\n    \"\"\"\n\n    field_flags = {\"unique\": True}\n\n    def __init__(self, column, get_session=None, message=None):\n        self.column = column\n        self.message = message\n        self.get_session = get_session\n\n    @property\n    def query(self):\n        self._check_for_session(self.model)\n        if self.get_session:\n            return self.get_session().query(self.model)\n        elif hasattr(self.model, \"query\"):\n            return getattr(self.model, \"query\")\n        else:\n            raise Exception(\n                \"Validator requires either get_session or Flask-SQLAlchemy\"\n                \" styled query parameter\"\n            )\n\n    def _check_for_session(self, model):\n        if not hasattr(model, \"query\") and not self.get_session:\n            raise Exception(\"Could not obtain SQLAlchemy session.\")\n\n    def _syntaxes_as_tuples(self, form, field, column):\n        \"\"\"Converts a set of different syntaxes into a tuple of tuples\"\"\"\n        if isinstance(column, str):\n            return ((column, getattr(form.Meta.model, column)),)\n        elif isinstance(column, Mapping):\n            return tuple(\n                (x[0], self._syntaxes_as_tuples(form, field, x[1])[0][1])\n                for x in column.items()\n            )\n        elif isinstance(column, Iterable):\n            return tuple(self._syntaxes_as_tuples(form, field, x)[0] for x in column)\n        elif isinstance(column, (Column, InstrumentedAttribute)):\n            return ((column.key, column),)\n        else:\n            raise TypeError(\"Invalid syntax for column\")\n\n    def __call__(self, form, field):\n        columns = self._syntaxes_as_tuples(form, field, self.column)\n        self.model = columns[0][1].class_\n        query = self.query\n        for field_name, column in columns:\n            query = query.filter(column == form[field_name].data)\n\n        obj = query.first()\n\n        if not hasattr(form, \"_obj\"):\n            raise Exception(\n                \"Couldn't access Form._obj attribute. Either make your form \"\n                \"inherit WTForms-Alchemy ModelForm or WTForms-Components \"\n                \"ModelForm or make this attribute available in your form.\"\n            )\n\n        if obj and not form._obj == obj:\n            if self.message is None:\n                self.message = field.gettext(\"Already exists.\")\n            raise ValidationError(self.message)\n"
  }
]