├── .editorconfig ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.md ├── CONTRIBUTING.md ├── HISTORY.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── browser_support.md ├── example_project.md ├── forms.md ├── how_it_works.md ├── index.md ├── limitations.md ├── management_commands.md ├── media │ ├── django-front-end-validators.gif │ └── validate-no-gmail-siblings.png └── template_tags.md ├── example ├── db.sqlite3 ├── front_end_validator_example │ ├── __init__.py │ ├── forms.py │ ├── models.py │ ├── settings.py │ ├── templates │ │ ├── base.html │ │ └── front_end_validator_example │ │ │ └── samplemodel_form.html │ ├── urls.py │ ├── validators.py │ ├── views.py │ └── wsgi.py ├── manage.py └── requirements.txt ├── front_end_validators ├── __init__.py ├── admin.py ├── forms.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── transpile_validators.py ├── models.py ├── static │ ├── css │ │ └── front_end_validators.css │ ├── img │ │ └── .gitignore │ └── js │ │ └── front_end_validators.js ├── templates │ └── front_end_validators │ │ ├── base.html │ │ ├── script_tag_transcrypt_36.html │ │ └── script_tag_transcrypt_37.html ├── templatetags │ └── front_end_validators.py ├── urls.py └── utils.py ├── manage.py ├── mkdocs.yml ├── requirements.txt ├── requirements_dev.txt ├── requirements_test.txt ├── runtests.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── settings.py └── urls.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | indent_size = 2 22 | 23 | [Makefile] 24 | indent_style = tab 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [johnfraney] 4 | ko_fi: johnfraney 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Package version: 2 | * Django version: 3 | * Python version: 4 | * Operating System: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. 9 | Tell us what happened, what went wrong, and what you expected to happen. 10 | 11 | ### What I Did 12 | 13 | ``` 14 | Paste the command(s) you ran and the output. 15 | If there was a crash, please include the traceback here. 16 | ``` 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Environments 23 | .env 24 | .venv 25 | env/ 26 | venv/ 27 | ENV/ 28 | env.bak/ 29 | venv.bak/ 30 | 31 | # Node 32 | node_modules/ 33 | 34 | # Installer logs 35 | pip-log.txt 36 | 37 | # Unit test / coverage reports 38 | .coverage 39 | .tox 40 | nosetests.xml 41 | htmlcov 42 | 43 | # Translations 44 | *.mo 45 | 46 | # Mr Developer 47 | .mr.developer.cfg 48 | .project 49 | .pydevproject 50 | 51 | # IDEs 52 | .idea 53 | .vscode 54 | 55 | # Complexity 56 | output/*.html 57 | output/*/index.html 58 | 59 | # Docs 60 | docs/_build 61 | site/ 62 | 63 | # Example site 64 | /example/front_end_validator_example/__javascript__ 65 | /example/front_end_validator_example/__target__ 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | 5 | matrix: 6 | fast_finish: true 7 | allow_failures: 8 | - env: TOX_ENV=py37-django-21 9 | - env: TOX_ENV=py37-django-20 10 | include: 11 | - name: "3.6 Unit Test - Django 2.1" 12 | python: "3.6" 13 | env: TOX_ENV=py36-django-21 14 | - name: "3.7 Unit Test - Django 2.1" 15 | python: "3.7" 16 | env: TOX_ENV=py37-django-21 17 | - name: "3.6 Unit Test - Django 2.0" 18 | python: "3.6" 19 | env: TOX_ENV=py36-django-20 20 | - name: "3.7 Unit Test - Django 2.0" 21 | python: "3.7" 22 | env: TOX_ENV=py37-django-20 23 | - name: "3.6 Unit Test - Django 1.11" 24 | python: "3.6" 25 | env: TOX_ENV=py36-django-111 26 | 27 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 28 | install: pip install -r requirements_test.txt 29 | 30 | # command to run tests using coverage, e.g. python setup.py test 31 | script: tox -e $TOX_ENV 32 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ## Development Lead 4 | 5 | * John Franey (johnfraney) 6 | 7 | ## Contributors 8 | 9 | None yet. Why not be the first? 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every 4 | little bit helps, and credit will always be given. 5 | 6 | You can contribute in many ways: 7 | 8 | ## Types of Contributions 9 | 10 | ### Report Bugs 11 | 12 | Report bugs at https://github.com/johnfraney/django-front-end-validators/issues. 13 | 14 | If you are reporting a bug, please include: 15 | 16 | * Your operating system name and version. 17 | * Any details about your local setup that might be helpful in troubleshooting. 18 | * Detailed steps to reproduce the bug. 19 | 20 | ### Fix Bugs 21 | 22 | Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. 23 | 24 | ### Implement Features 25 | 26 | Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. 27 | 28 | ### Write Documentation 29 | 30 | Django NER Trainer could always use more documentation, whether as part of the official Django NER Trainer docs, in docstrings, or even on the web in blog posts, articles, and such. 31 | 32 | ### Submit Feedback 33 | 34 | The best way to send feedback is to file an issue at https://github.com/johnfraney/django-front-end-validators/issues. 35 | 36 | If you are proposing a feature: 37 | 38 | * Explain in detail how it would work. 39 | * Keep the scope as narrow as possible, to make it easier to implement. 40 | * Remember that this is a volunteer-driven project, and that contributions are welcome :) 41 | 42 | ## Get Started! 43 | 44 | Ready to contribute? Here's how to set up `django-front-end-validators` for local development. 45 | 46 | 1. Fork the `django-front-end-validators` repo on GitHub. 47 | 2. Clone your fork locally 48 | 49 | ```bash 50 | $ git clone git@github.com:your_name_here/django-front-end-validators.git 51 | ``` 52 | 53 | 3. Install your local copy into a virtualenv. Assuming you have `virtualenvwrapper` installed, this is how you set up your fork for local development 54 | 55 | ```bash 56 | $ mkvirtualenv django-front-end-validators 57 | $ cd django-front-end-validators/ 58 | $ python setup.py develop 59 | ``` 60 | 61 | 4. Create a branch for local development 62 | ```bash 63 | $ git checkout -b name-of-your-bugfix-or-feature 64 | ``` 65 | 66 | 5. When you're done making changes, check that your changes pass `flake8` and the tests, including testing other Python versions with `tox` 67 | 68 | ```bash 69 | $ pip install flake8 tox 70 | $ flake8 front_end_validators tests 71 | $ python setup.py test 72 | $ tox 73 | ``` 74 | 75 | 6. Commit your changes and push your branch to GitHub 76 | 77 | ```bash 78 | $ git add . 79 | $ git commit -m "Your detailed description of your changes." 80 | $ git push origin name-of-your-bugfix-or-feature 81 | ``` 82 | 83 | 7. Submit a pull request through the GitHub website. 84 | 85 | ## Pull Request Guidelines 86 | 87 | Before you submit a pull request, check that it meets these guidelines: 88 | 89 | 1. The pull request should include tests. 90 | 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.md. 91 | 3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check https://travis-ci.org/johnfraney/django-front-end-validators/pull_requests and make sure that the tests pass for all supported Python versions. 92 | 93 | ## Tips 94 | 95 | To run a subset of tests 96 | 97 | ```bash 98 | $ python -m unittest tests.test_front_end_validators 99 | ``` 100 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, John Franey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.md 2 | include CONTRIBUTING.md 3 | include HISTORY.md 4 | include LICENSE 5 | include README.md 6 | recursive-include front_end_validators *.html *.png *.gif *js *.css *jpg *jpeg *svg *py 7 | recursive-include front_end_validators/static * 8 | recursive-include front_end_validators/templates * 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | define BROWSER_PYSCRIPT 4 | import os, webbrowser, sys 5 | try: 6 | from urllib import pathname2url 7 | except: 8 | from urllib.request import pathname2url 9 | 10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 11 | endef 12 | export BROWSER_PYSCRIPT 13 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 14 | 15 | help: 16 | @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' 17 | 18 | clean: clean-build clean-pyc 19 | 20 | clean-build: ## remove build artifacts 21 | rm -fr build/ 22 | rm -fr dist/ 23 | rm -fr *.egg-info 24 | 25 | clean-pyc: ## remove Python file artifacts 26 | find . -name '*.pyc' -exec rm -f {} + 27 | find . -name '*.pyo' -exec rm -f {} + 28 | find . -name '*~' -exec rm -f {} + 29 | 30 | lint: ## check style with flake8 31 | flake8 front_end_validators tests 32 | 33 | test: ## run tests quickly with the default Python 34 | python runtests.py tests 35 | 36 | test-all: ## run tests on every Python version with tox 37 | tox 38 | 39 | coverage: ## check code coverage quickly with the default Python 40 | coverage run runtests.py 41 | coverage report -m 42 | coverage html 43 | $(BROWSER) htmlcov/index.html 44 | 45 | docs: ## generate documentation 46 | mkdocs build --clean 47 | $(BROWSER) site/index.html 48 | 49 | docs-deploy: ## deploy documentation to GitHub pages 50 | mkdocs gh-deploy 51 | 52 | release: clean ## package and upload a release 53 | python setup.py sdist 54 | python setup.py bdist_wheel 55 | twine upload dist/* 56 | 57 | release-test: clean ## package and upload a release 58 | python setup.py sdist 59 | python setup.py bdist_wheel 60 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 61 | 62 | sdist: clean ## package 63 | python setup.py sdist 64 | ls -l dist 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Front End Validators 2 | 3 | [ 4 | ![PyPI](https://img.shields.io/pypi/v/django-front-end-validators.svg) 5 | ![PyPI](https://img.shields.io/pypi/pyversions/django-front-end-validators.svg) 6 | ![PyPI](https://img.shields.io/pypi/djversions/django-front-end-validators.svg) 7 | ![PyPI](https://img.shields.io/pypi/l/django-front-end-validators.svg) 8 | ](https://pypi.org/project/django-front-end-validators/) 9 | [![TravisCI](https://travis-ci.org/johnfraney/django-front-end-validators.svg?branch=master)](https://travis-ci.org/johnfraney/django-front-end-validators) 10 | 11 | Django Front End Validators allows you to reuse server-side [model field validators](https://docs.djangoproject.com/en/dev/ref/validators/) to perform front end form validation in JavaScript. 12 | 13 | 14 | ## Documentation 15 | 16 | Documentation is available in the [docs directory](./docs/index.md) and at https://johnfraney.github.io/django-front-end-validators. 17 | 18 | 19 | ## Supported Python versions 20 | 21 | Currently support is planned for only Python 3.6 and 3.7, on which Transcrypt 3.6 and 3.7 depend. 22 | 23 | 24 | ## Credits 25 | 26 | Tools used in rendering this package: 27 | 28 | [Transcrypt](http://www.transcrypt.org/) 29 | 30 | [Cookiecutter](https://github.com/audreyr/cookiecutter) 31 | 32 | [`cookiecutter-djangopackage`](https://github.com/pydanny/cookiecutter-djangopackage) 33 | 34 | 35 | 36 | ## Code of Conduct 37 | 38 | Everyone interacting in the project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [PyPA Code of Conduct](https://www.pypa.io/en/latest/code-of-conduct/). 39 | -------------------------------------------------------------------------------- /docs/browser_support.md: -------------------------------------------------------------------------------- 1 | Because this package uses [Transcrypt](http://www.transcrypt.org/) to transpile Python to JavaScript, browser support depends on the version of Transcrypt installed in your project. 2 | 3 | Currently this package supports Transcrypt versions 3.6 and 3.7, which transpile Python code into different versions of JavaScript: 4 | 5 | | Transcrypt Version | JavaScript Version | Browser Support | 6 | | ------------------ | ------------------ | --------------- | 7 | | 3.6 | ES5 | 100% | 8 | | 3.7 | ES6 | [>75%](https://caniuse.com/#feat=es6-module) | 9 | 10 | 11 | !!! note 12 | This plugin will respect whether you have Transcrypt 3.6 or 3.7 installed, so you can choose whether to generate newer or better-supported JavaScript by specifying the Transcrypt version in your project's requirements. 13 | -------------------------------------------------------------------------------- /docs/example_project.md: -------------------------------------------------------------------------------- 1 | An example project is included in the repo's `example` directory. 2 | 3 | 4 | ## Set-up 5 | 6 | To run the example project: 7 | 8 | ```bash 9 | # Enter the example directory 10 | cd example 11 | 12 | # Install the dependencies (in a virtual environment, ideally) 13 | pip install -r requirements.txt 14 | 15 | # Transpile the validators from Python to JavaScript 16 | 17 | ./manage.py transpile_validators 18 | 19 | # Run the server 20 | ./manage.py runserver 21 | ``` 22 | 23 | Visit `http://127.0.0.1:8000` in your browser to view a form with a number of validator functions. 24 | 25 | ## Demo 26 | 27 | ![Example project form with JS validators gif](./media/django-front-end-validators.gif) 28 | -------------------------------------------------------------------------------- /docs/forms.md: -------------------------------------------------------------------------------- 1 | ## `FrontEndValidatorsModelForm` 2 | 3 | This class extends Django's `ModelForm` to add a `data-validators` attribute to any field that contains validators. `data-validators` lists references to JavaScript validator functions. 4 | -------------------------------------------------------------------------------- /docs/how_it_works.md: -------------------------------------------------------------------------------- 1 | Django Front End Validators validates your form inputs as-you-type using the same validation logic specified in your model field's `validators`. It plays nicely with native HTML5 input validation, too. 2 | 3 | The heavy lifting of converting model field validators from Python to JavaScript is performed by [Transcrypt](http://www.transcrypt.org/) using the [`transpile_validators` managment command](management_commands.md). 4 | 5 | ## Example 6 | 7 | Let's say you've got this validator on a field called `email`: 8 | 9 | ```python 10 | from django.core.exceptions import ValidationError 11 | 12 | 13 | def validate_no_gmail_siblings(value): 14 | if '+' in value and value.endswith('gmail.com'): 15 | raise ValidationError( 16 | "Please use your plain Gmail address" 17 | ) 18 | ``` 19 | 20 | When the field is rendered in a [`FrontEndValidatorsModelForm`](forms.md), it will look like this: 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | A JavaScript plugin included with the [`{% front_end_validators_js %}` template tag](template_tags.md) checks the input value against the validators listed in `data-validators` in an `oninput` event listener, providing real-time feedback. 27 | 28 | If the value fails the `validators.validate_no_gmail_siblings` check, a custom validation error message is added to the field using the [HTML5 Constraint API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation). This prevents the form from being submitted until the validation error is fixed: 29 | 30 | ![Email field with failing no Gmail sibling validator](media/validate-no-gmail-siblings.png) 31 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | Django Front End Validators allows you to reuse server-side [model field validators](https://docs.djangoproject.com/en/dev/ref/validators/) to perform front end form validation in JavaScript. 4 | 5 | For more information, see [How It Works](how_it_works.md). 6 | 7 | ## Quickstart 8 | 9 | #### Install Django Front End Validators 10 | 11 | ```bash 12 | pip install django-front-end-validators 13 | ``` 14 | 15 | #### Update `INSTALLED_APPS` 16 | 17 | ```python 18 | INSTALLED_APPS = ( 19 | ... 20 | 'front_end_validators', 21 | ... 22 | ) 23 | ``` 24 | 25 | #### Configure settings 26 | 27 | ```python 28 | # The directory into which Transcrypt transpiles JS 29 | STATICFILES_DIRS = [ 30 | # Transcrypt 3.7: 31 | 'your_project/__target__' 32 | # Transcrypt 3.6: 33 | 'your_project/__javascript__', 34 | ] 35 | 36 | # The path to your validators.py file 37 | VALIDATORS_FILE = os.path.join(BASE_DIR, 'your_project/validators.py') 38 | ``` 39 | 40 | #### Add template tag 41 | 42 | ```html+django 43 | {% load front_end_validators %} 44 | 45 | 46 | {{ form.media.css }} 47 | 48 | 49 | {% block content %}{% endblock %} 50 | {% front_end_validators_js %} 51 | {{ form.media.js }} 52 | 53 | 54 | ``` 55 | 56 | #### Transpile validators 57 | 58 | ```bash 59 | ./manage.py transpile_validators 60 | ``` 61 | 62 | #### Write forms 63 | 64 | ```python 65 | from front_end_validators.forms import FrontEndValidatorsModelForm 66 | from .models import YourModel 67 | 68 | 69 | class YourModelForm(FrontEndValidatorsModelForm): 70 | class Meta: 71 | model = YourModel 72 | fields = '__all__' 73 | ``` 74 | 75 | ## Running Tests 76 | 77 | Does the code actually work? 78 | 79 | ```bash 80 | source /bin/activate 81 | (myenv) $ pip install tox 82 | (myenv) $ tox 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/limitations.md: -------------------------------------------------------------------------------- 1 | ## Only function-style validators 2 | 3 | Although Django supports both [class-based and function validators](https://docs.djangoproject.com/en/dev/ref/validators/), this package supports only validators written as functions. If supporting class-based validators is important to you, please open an [issue](https://github.com/johnfraney/django-front-end-validators/issues). 4 | 5 | 6 | ## No i18n 7 | 8 | Because the transpiled JavaScript runs in the browser, it doesn't have direct access to the database or filesystem. If you know of a way to get this to work, please open a [pull request](https://github.com/johnfraney/django-front-end-validators/pulls)! 9 | 10 | 11 | ## Example 12 | 13 | The [Django validators documentation](https://docs.djangoproject.com/en/dev/ref/validators/#writing-validators) gives this as en example `ValidationError`: 14 | 15 | ```python 16 | from django.core.exceptions import ValidationError 17 | from django.utils.translation import gettext_lazy as _ 18 | 19 | def validate_even(value): 20 | if value % 2 != 0: 21 | raise ValidationError( 22 | _('%(value)s is not an even number'), 23 | params={'value': value}, 24 | ) 25 | ``` 26 | 27 | After removing `ugettext_lazy`, we get a validator that will work on the front end: 28 | 29 | ```python 30 | from django.core.exceptions import ValidationError 31 | 32 | def validate_even(value): 33 | if value % 2 != 0: 34 | raise ValidationError( 35 | '%(value)s is not an even number', 36 | params={'value': value}, 37 | ) 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/management_commands.md: -------------------------------------------------------------------------------- 1 | ## `transpile_validators` 2 | 3 | This management command uses [Transcrypt](http://www.transcrypt.org/) to convert your project's `validators.py` file from Python to JavaScript. 4 | 5 | The transpiled JavaScript is placed in alongside your `validators.py` file in the following directory: 6 | 7 | | Transcrypt Version | Output Directory | 8 | | ------------------ | ---------------- | 9 | | 3.6 | `__javascript__` | 10 | | 3.7 | `__target__` | 11 | 12 | !!! info 13 | The relevant directory should be added to your project's `STATICFILES_DIRS` settings. 14 | -------------------------------------------------------------------------------- /docs/media/django-front-end-validators.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/docs/media/django-front-end-validators.gif -------------------------------------------------------------------------------- /docs/media/validate-no-gmail-siblings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/docs/media/validate-no-gmail-siblings.png -------------------------------------------------------------------------------- /docs/template_tags.md: -------------------------------------------------------------------------------- 1 | ## `{% front_end_validators_js %}` 2 | 3 | This template tag includes two pieces of JavaScript: 4 | 5 | | JavaScript file | Description | 6 | | --------------- | ----------- | 7 | |`front_end_validators.js` | Form logic tying validation functions to input fields using the [HTML5 Constraint API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) | 8 | | `validators.[min.]js` | Transpiled validator functions | 9 | -------------------------------------------------------------------------------- /example/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/example/db.sqlite3 -------------------------------------------------------------------------------- /example/front_end_validator_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/example/front_end_validator_example/__init__.py -------------------------------------------------------------------------------- /example/front_end_validator_example/forms.py: -------------------------------------------------------------------------------- 1 | from front_end_validators.forms import FrontEndValidatorsModelForm 2 | from .models import SampleModel 3 | 4 | 5 | class SampleModelForm(FrontEndValidatorsModelForm): 6 | class Meta: 7 | model = SampleModel 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /example/front_end_validator_example/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from . import validators 3 | 4 | 5 | class SampleModel(models.Model): 6 | future_year = models.PositiveIntegerField( 7 | validators=[ 8 | validators.validate_four_digits, 9 | validators.validate_future_year, 10 | ], 11 | help_text="A four-digit year in the future" 12 | ) 13 | all_caps = models.CharField( 14 | max_length=100, 15 | validators=[ 16 | validators.validate_all_caps, 17 | ], 18 | help_text="A value in ALL CAPS" 19 | ) 20 | https_url = models.URLField( 21 | 'HTTPS URL', 22 | max_length=200, 23 | validators=[ 24 | validators.validate_https, 25 | ], 26 | help_text="A URL starting with 'https'. Works on top of native HTML5 URL validation." 27 | ) 28 | png_image = models.ImageField( 29 | 'PNG image', 30 | validators=[ 31 | validators.validate_png, 32 | ], 33 | help_text="An image file with a .png extension" 34 | ) 35 | -------------------------------------------------------------------------------- /example/front_end_validator_example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for front_end_validator_example project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '0q$1w*p6dn=6isix7##fmffd$e3153#wt^me_uy+cfro3t^o1r' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'front_end_validators', 42 | 'front_end_validator_example', 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'front_end_validator_example.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'front_end_validator_example.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.sqlite3', 82 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 108 | 109 | LANGUAGE_CODE = 'en-us' 110 | 111 | TIME_ZONE = 'UTC' 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 122 | 123 | STATIC_URL = '/static/' 124 | 125 | STATICFILES_DIRS = [ 126 | # 'front_end_validator_example/__target__', 127 | 'front_end_validator_example/__javascript__', 128 | ] 129 | 130 | # Front End Validators settings 131 | VALIDATORS_FILE = os.path.join(BASE_DIR, 'front_end_validator_example/validators.py') 132 | -------------------------------------------------------------------------------- /example/front_end_validator_example/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load front_end_validators staticfiles %} 2 | 3 | 4 | 5 | Front End Validator Example 6 | 12 | 13 | 14 | {% block content %}{% endblock %} 15 | {% front_end_validators_js %} 16 | {{ form.media.js }} 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/front_end_validator_example/templates/front_end_validator_example/samplemodel_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 | {% csrf_token %} 6 | 7 | {{ form.as_table }} 8 |
9 | 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /example/front_end_validator_example/urls.py: -------------------------------------------------------------------------------- 1 | """front_end_validator_example URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | from .views import SampleModelCreate 19 | 20 | urlpatterns = [ 21 | path('admin/', admin.site.urls), 22 | path('', SampleModelCreate.as_view()), 23 | ] 24 | -------------------------------------------------------------------------------- /example/front_end_validator_example/validators.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | from datetime import datetime 3 | 4 | 5 | def validate_all_caps(value): 6 | if value != value.upper(): 7 | raise ValidationError( 8 | '%(value)s must be all caps', 9 | params={'value': value}, 10 | ) 11 | 12 | 13 | def validate_four_digits(value): 14 | if len(str(value)) is not 4: 15 | raise ValidationError( 16 | "Value must be four digits" 17 | ) 18 | 19 | 20 | def validate_future_year(value): 21 | current_year = datetime.now().year 22 | if value <= current_year: 23 | raise ValidationError( 24 | '%(value)s is not a future year', 25 | params={'value': value}, 26 | ) 27 | 28 | 29 | def validate_https(value): 30 | if not value.startswith('https'): 31 | raise ValidationError( 32 | "Value must start with 'https'" 33 | ) 34 | 35 | 36 | def validate_png(value): 37 | if not str(value).endswith('.png'): 38 | raise ValidationError( 39 | "Image must be a PNG" 40 | ) 41 | -------------------------------------------------------------------------------- /example/front_end_validator_example/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import CreateView 2 | from .forms import SampleModelForm 3 | from .models import SampleModel 4 | 5 | 6 | class SampleModelCreate(CreateView): 7 | model = SampleModel 8 | form_class = SampleModelForm 9 | -------------------------------------------------------------------------------- /example/front_end_validator_example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for front_end_validator_example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'front_end_validator_example.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'front_end_validator_example.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | Pillow 3 | # Your app requirements. 4 | -r ../requirements.txt 5 | -r ../requirements_test.txt 6 | 7 | # Your app in editable mode. 8 | -e ../ 9 | -------------------------------------------------------------------------------- /front_end_validators/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.1' 2 | -------------------------------------------------------------------------------- /front_end_validators/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /front_end_validators/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | class FrontEndValidatorsModelForm(forms.ModelForm): 5 | def __init__(self, *args, **kwargs): 6 | super().__init__(*args, **kwargs) 7 | model = self._meta.model 8 | model_field_map = {} 9 | for model_field in model._meta.fields: 10 | model_field_map[model_field.name] = model_field 11 | for field_name, field in self.fields.items(): 12 | if not hasattr(model, field_name): 13 | continue 14 | validators = model_field_map[field_name].validators 15 | valid_validators = [] 16 | for validator in validators: 17 | if hasattr(validator, '__name__'): 18 | valid_validators.append(f'validators.{validator.__name__}') 19 | 20 | valid_validators_string = str(valid_validators).replace("'", "") 21 | 22 | self.fields[field_name].widget.attrs.update({ 23 | 'data-validators': valid_validators_string 24 | }) 25 | -------------------------------------------------------------------------------- /front_end_validators/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/front_end_validators/management/__init__.py -------------------------------------------------------------------------------- /front_end_validators/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/front_end_validators/management/commands/__init__.py -------------------------------------------------------------------------------- /front_end_validators/management/commands/transpile_validators.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import os 3 | import subprocess 4 | from django.core.exceptions import ImproperlyConfigured 5 | from django.core.management.base import BaseCommand, CommandError 6 | from django.conf import settings 7 | 8 | 9 | class Command(BaseCommand): 10 | help = 'Transpiles Python model field validators to JS using Transcrypt' 11 | 12 | def handle(self, *args, **options): 13 | try: 14 | validators_file = settings.VALIDATORS_FILE 15 | except AttributeError as err: 16 | raise CommandError('VALIDATORS_FILE setting needs to be defined') 17 | 18 | validators_file_basename = os.path.basename(validators_file) 19 | 20 | if not validators_file_basename == 'validators.py': 21 | error_text = ( 22 | 'VALIDATORS_FILE setting must point to a file named validators.py. ' 23 | 'Your file:\n{}'.format(validators_file_basename) 24 | ) 25 | raise CommandError(error_text) 26 | 27 | if not os.path.exists(validators_file): 28 | raise CommandError('validators.py file could not be found: {}'.format( 29 | validators_file 30 | )) 31 | 32 | try: 33 | subprocess.run(['transcrypt', '-b', '-m', validators_file], check=True) 34 | except subprocess.CalledProcessError: 35 | raise CommandError( 36 | 'Transcrypt could not transpile your validators. See above for details.' 37 | ) 38 | self.stdout.write(self.style.SUCCESS('Successfully transpiled validators')) 39 | -------------------------------------------------------------------------------- /front_end_validators/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /front_end_validators/static/css/front_end_validators.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/front_end_validators/static/css/front_end_validators.css -------------------------------------------------------------------------------- /front_end_validators/static/img/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/front_end_validators/static/img/.gitignore -------------------------------------------------------------------------------- /front_end_validators/static/js/front_end_validators.js: -------------------------------------------------------------------------------- 1 | var fieldsToValidate = document.querySelectorAll('[data-validators]') 2 | for (fieldToValidate of fieldsToValidate) { 3 | fieldToValidate.oninput = function() { 4 | var fieldValidators = eval(this.attributes['data-validators'].value) 5 | var failingValidators = [] 6 | for (validatorFunction of fieldValidators) { 7 | try { 8 | validatorFunction(this.value) 9 | } 10 | catch(e) { 11 | this.setCustomValidity(e.message) 12 | failingValidators.push(validatorFunction) 13 | } 14 | } 15 | if (failingValidators.length === 0) { 16 | this.setCustomValidity('') 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /front_end_validators/templates/front_end_validators/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% comment %} 3 | As the developer of this package, don't place anything here if you can help it 4 | since this allows developers to have interoperability between your template 5 | structure and their own. 6 | 7 | Example: Developer melding the 2SoD pattern to fit inside with another pattern:: 8 | 9 | {% extends "base.html" %} 10 | {% load static %} 11 | 12 | 13 | {% block extra_js %} 14 | 15 | 16 | {% block javascript %} 17 | 18 | {% endblock javascript %} 19 | 20 | {% endblock extra_js %} 21 | {% endcomment %} 22 | -------------------------------------------------------------------------------- /front_end_validators/templates/front_end_validators/script_tag_transcrypt_36.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /front_end_validators/templates/front_end_validators/script_tag_transcrypt_37.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | -------------------------------------------------------------------------------- /front_end_validators/templatetags/front_end_validators.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.template.loader import get_template 3 | from front_end_validators.utils import get_transcrypt_version 4 | 5 | 6 | register = template.Library() 7 | 8 | transcrypt_version = get_transcrypt_version() 9 | if transcrypt_version.startswith('3.6'): 10 | script_tag_template_path = 'front_end_validators/script_tag_transcrypt_36.html' 11 | elif transcrypt_version.startswith('3.7'): 12 | script_tag_template_path = 'front_end_validators/script_tag_transcrypt_37.html' 13 | script_tag_template = get_template(script_tag_template_path) 14 | 15 | 16 | @register.inclusion_tag(script_tag_template) 17 | def front_end_validators_js(): 18 | return 19 | -------------------------------------------------------------------------------- /front_end_validators/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import include, url 3 | 4 | 5 | app_name = 'front_end_validators' 6 | urlpatterns = [ 7 | ] 8 | -------------------------------------------------------------------------------- /front_end_validators/utils.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | 3 | 4 | def get_transcrypt_version(): 5 | """Returns installed Transcrypt version as a string""" 6 | transcrypt_version = pkg_resources.get_distribution('transcrypt').version 7 | return transcrypt_version 8 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import unicode_literals, absolute_import 4 | 5 | import os 6 | import sys 7 | 8 | if __name__ == "__main__": 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") 10 | from django.core.management import execute_from_command_line 11 | 12 | execute_from_command_line(sys.argv) 13 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 'Django Front End Validators' 2 | repo_name: 'johnfraney/django-front-end-validators' 3 | repo_url: 'https://github.com/johnfraney/django-front-end-validators' 4 | 5 | nav: 6 | - Home: index.md 7 | - How It Works: how_it_works.md 8 | - Limitations: limitations.md 9 | - Browser Support: browser_support.md 10 | - Forms: forms.md 11 | - Template Tags: template_tags.md 12 | - Management Commands: management_commands.md 13 | - Example Project: example_project.md 14 | 15 | dev_addr: '127.0.0.1:8008' 16 | 17 | # Copyright 18 | copyright: 'Copyright © 2018 John Franey' 19 | 20 | extra: 21 | social: 22 | - type: 'github' 23 | link: 'https://github.com/johnfraney' 24 | 25 | # Theme configuration 26 | theme: 27 | name: 'material' 28 | logo: 29 | icon: 'check' 30 | palette: 31 | primary: 'green' 32 | accent: 'light green' 33 | 34 | # Extensions 35 | markdown_extensions: 36 | - admonition 37 | - codehilite 38 | 39 | # Google Analytics 40 | google_analytics: 41 | - 'UA-125394319-3' 42 | - 'auto' 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Additional requirements go here 2 | transcrypt<3.8 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion==0.5.3 2 | twine==1.11.0 3 | wheel==0.31.1 4 | 5 | # Docs 6 | mkdocs==1.0.3 7 | mkdocs-material==3.0.4 8 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | coverage==4.5.1 2 | mock>=2.0.0 3 | flake8>=3.5.0 4 | tox>=3.2.1 5 | codecov>=2.0.15 6 | 7 | # Additional test requirements go here 8 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 3 | from __future__ import unicode_literals, absolute_import 4 | 5 | import os 6 | import sys 7 | 8 | import django 9 | from django.conf import settings 10 | from django.test.utils import get_runner 11 | 12 | 13 | def run_tests(*test_args): 14 | if not test_args: 15 | test_args = ['tests'] 16 | 17 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' 18 | django.setup() 19 | TestRunner = get_runner(settings) 20 | test_runner = TestRunner() 21 | failures = test_runner.run_tests(test_args) 22 | sys.exit(bool(failures)) 23 | 24 | 25 | if __name__ == '__main__': 26 | run_tests(*sys.argv[1:]) 27 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:front_end_validators/__init__.py] 9 | 10 | [wheel] 11 | universal = 1 12 | 13 | [flake8] 14 | ignore = D203 15 | exclude = 16 | front_end_validators/migrations, 17 | .git, 18 | .tox, 19 | docs/conf.py, 20 | env, 21 | build, 22 | dist 23 | max-line-length = 119 24 | 25 | [coverage:run] 26 | branch = True 27 | source = front_end_validators 28 | omit = 29 | *migrations* 30 | *tests* 31 | *env* 32 | *venv* 33 | 34 | [coverage:report] 35 | omit = 36 | *site-packages* 37 | *tests* 38 | *.tox* 39 | show_missing = True 40 | exclude_lines = 41 | raise NotImplementedError 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | import sys 6 | 7 | try: 8 | from setuptools import setup 9 | except ImportError: 10 | from distutils.core import setup 11 | 12 | 13 | def get_version(*file_paths): 14 | """Retrieves the version from front_end_validators/__init__.py""" 15 | filename = os.path.join(os.path.dirname(__file__), *file_paths) 16 | version_file = open(filename).read() 17 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 18 | version_file, re.M) 19 | if version_match: 20 | return version_match.group(1) 21 | raise RuntimeError('Unable to find version string.') 22 | 23 | 24 | version = get_version("front_end_validators", "__init__.py") 25 | 26 | 27 | if sys.argv[-1] == 'publish': 28 | try: 29 | import wheel 30 | print("Wheel version: ", wheel.__version__) 31 | except ImportError: 32 | print('Wheel library missing. Please run "pip install wheel"') 33 | sys.exit() 34 | os.system('python setup.py sdist upload') 35 | os.system('python setup.py bdist_wheel upload') 36 | sys.exit() 37 | 38 | if sys.argv[-1] == 'tag': 39 | print("Tagging the version on git:") 40 | os.system("git tag -a %s -m 'version %s'" % (version, version)) 41 | os.system("git push --tags") 42 | sys.exit() 43 | 44 | readme = open('README.md').read() 45 | history = open('HISTORY.md').read() 46 | 47 | setup( 48 | name='django-front-end-validators', 49 | version=version, 50 | description="""Use model field validator functions for front end JS validation""", 51 | long_description=readme + '\n\n' + history, 52 | long_description_content_type="text/markdown", 53 | author='John Franey', 54 | author_email='johnfraney@gmail.com', 55 | url='https://github.com/johnfraney/django-front-end-validators', 56 | packages=[ 57 | 'front_end_validators', 58 | ], 59 | include_package_data=True, 60 | install_requires=[ 61 | 'transcrypt>=3.6' 62 | ], 63 | license="MIT", 64 | zip_safe=False, 65 | keywords='django-front-end-validators', 66 | classifiers=[ 67 | 'Development Status :: 3 - Alpha', 68 | 'Framework :: Django :: 1.11', 69 | 'Framework :: Django :: 2.0', 70 | 'Framework :: Django :: 2.1', 71 | 'Intended Audience :: Developers', 72 | 'License :: OSI Approved :: BSD License', 73 | 'Natural Language :: English', 74 | 'Programming Language :: Python :: 3', 75 | 'Programming Language :: Python :: 3.6', 76 | 'Programming Language :: Python :: 3.7', 77 | ], 78 | ) 79 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfraney/django-front-end-validators/a3c8805d9c84b2ac4c6e6a08839a192acf6453c7/tests/__init__.py -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | import django 5 | 6 | DEBUG = True 7 | USE_TZ = True 8 | 9 | # SECURITY WARNING: keep the secret key used in production secret! 10 | SECRET_KEY = "11111111111111111111111111111111111111111111111111" 11 | 12 | DATABASES = { 13 | "default": { 14 | "ENGINE": "django.db.backends.sqlite3", 15 | "NAME": ":memory:", 16 | } 17 | } 18 | 19 | ROOT_URLCONF = "tests.urls" 20 | 21 | INSTALLED_APPS = [ 22 | "django.contrib.auth", 23 | "django.contrib.contenttypes", 24 | "django.contrib.sites", 25 | "front_end_validators", 26 | ] 27 | 28 | SITE_ID = 1 29 | 30 | if django.VERSION >= (1, 10): 31 | MIDDLEWARE = () 32 | else: 33 | MIDDLEWARE_CLASSES = () 34 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from django.conf.urls import url, include 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^', include('front_end_validators.urls', namespace='front_end_validators')), 9 | ] 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | {py36,py37}-django-21 4 | {py36,py37}-django-20 5 | {py36}-django-111 6 | 7 | 8 | [testenv] 9 | setenv = 10 | PYTHONPATH = {toxinidir}:{toxinidir}/front_end_validators 11 | commands = coverage run --source front_end_validators runtests.py 12 | deps = 13 | django-111: Django>=1.11,<1.12 14 | django-20: Django>=2.0,<2.1 15 | django-21: Django>=2.1,<2.2 16 | -r {toxinidir}/requirements.txt 17 | -r {toxinidir}/requirements_test.txt 18 | basepython = 19 | py37: python3.7 20 | py36: python3.6 21 | --------------------------------------------------------------------------------