├── .github └── workflows │ ├── CI.yml │ ├── build_docs.yml │ ├── docs.yml │ └── publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── crispy_tailwind ├── __init__.py ├── layout.py ├── tailwind.py ├── templates │ └── tailwind │ │ ├── display_form.html │ │ ├── errors.html │ │ ├── errors_formset.html │ │ ├── field.html │ │ ├── inputs.html │ │ ├── layout │ │ ├── alert.html │ │ ├── attrs.html │ │ ├── baseinput.html │ │ ├── button.html │ │ ├── buttonholder.html │ │ ├── checkboxselectmultiple.html │ │ ├── checkboxselectmultiple_inline.html │ │ ├── column.html │ │ ├── div.html │ │ ├── field_errors.html │ │ ├── field_errors_block.html │ │ ├── field_with_buttons.html │ │ ├── fieldset.html │ │ ├── formactions.html │ │ ├── help_text.html │ │ ├── help_text_and_errors.html │ │ ├── inline_field.html │ │ ├── prepended_appended_text.html │ │ ├── radioselect.html │ │ ├── radioselect_inline.html │ │ ├── row.html │ │ ├── select.html │ │ └── select_option.html │ │ ├── table_inline_formset.html │ │ ├── uni_form.html │ │ ├── uni_formset.html │ │ ├── whole_uni_form.html │ │ └── whole_uni_formset.html └── templatetags │ ├── __init__.py │ ├── tailwind_field.py │ └── tailwind_filters.py ├── docs ├── Makefile ├── conf.py ├── contributing.txt ├── custom.txt ├── examples.txt ├── getting_started.txt ├── img │ ├── Buttons.png │ ├── alert.png │ ├── crispy_failing.png │ ├── crispy_form.png │ ├── custom_button.png │ ├── field_with_buttons.png │ ├── fieldset.png │ ├── formset_failing.png │ ├── inline_field.png │ ├── prepended.png │ └── row_col.png ├── index.txt ├── layout_objects.txt ├── make.bat └── requirements.txt ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── tests ├── __init__.py ├── conftest.py ├── forms.py ├── results │ ├── alert │ │ ├── alert.html │ │ ├── alert_custom.html │ │ └── alert_dismiss_false.html │ ├── crispy_addon │ │ └── crispy_addon.html │ ├── filter │ │ ├── crispy_filter.html │ │ ├── crispy_filter_lt50.html │ │ └── formset.html │ ├── helper │ │ ├── buttons.html │ │ ├── buttons_with_css.html │ │ ├── charfield.html │ │ ├── charfield_failing.html │ │ ├── charfield_failing_lt50.html │ │ ├── col_row.html │ │ ├── col_row_lt50.html │ │ ├── crispy_layout.html │ │ ├── div.html │ │ ├── div_lt50.html │ │ ├── field_with_buttons.html │ │ ├── fieldset.html │ │ ├── fieldset_lt50.html │ │ ├── formset.html │ │ ├── formset_errors.html │ │ ├── formset_errors_lt50.html │ │ ├── formset_form_tag.html │ │ ├── formset_form_tag_lt50.html │ │ ├── formset_lt50.html │ │ ├── inline_checkbox.html │ │ ├── inline_field.html │ │ ├── inline_radio.html │ │ ├── multiple_checkbox.html │ │ ├── non_form_errors.html │ │ ├── non_form_errors_lt50.html │ │ ├── password.html │ │ ├── radio.html │ │ └── select.html │ ├── prepended │ │ ├── appended_errors.html │ │ ├── appended_errors_lt50.html │ │ ├── appended_text.html │ │ ├── prepended_appended_errors.html │ │ ├── prepended_appended_errors_lt50.html │ │ ├── prepended_appended_text.html │ │ ├── prepended_errors.html │ │ ├── prepended_errors_lt50.html │ │ ├── prepended_help.html │ │ ├── prepended_help_lt50.html │ │ ├── prepended_long_text.html │ │ ├── prepended_no_label.html │ │ └── prepended_text.html │ └── table_inline_formset │ │ ├── table_inline_formset.html │ │ ├── table_inline_formset_failing.html │ │ ├── table_inline_formset_failing_lt50.html │ │ └── table_inline_formset_lt50.html ├── test_alert.py ├── test_crispy_addon.py ├── test_filter.py ├── test_helper.py ├── test_prepended_appended.py ├── test_settings.py ├── test_table_inline_formset.py ├── test_tailwind.py ├── urls.py └── utils.py └── tox.ini /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | linting: 14 | name: Linting 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Python 3.12 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.12" 23 | 24 | - name: Install required packages 25 | run: python -m pip install --upgrade tox 26 | 27 | - name: Run linters 28 | run: tox -e lint 29 | 30 | 31 | tests: 32 | name: Python ${{ matrix.python-version }} 33 | runs-on: ubuntu-latest 34 | 35 | strategy: 36 | matrix: 37 | python-version: 38 | - '3.8' 39 | - '3.9' 40 | - '3.10' 41 | - '3.11' 42 | - '3.12' 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | 47 | - uses: actions/setup-python@v5 48 | with: 49 | python-version: ${{ matrix.python-version }} 50 | cache: pip 51 | cache-dependency-path: requirements/*.txt 52 | 53 | - name: Upgrade packaging tools 54 | run: python -m pip install --upgrade pip setuptools virtualenv wheel 55 | 56 | - name: Install dependencies 57 | run: python -m pip install --upgrade codecov tox 58 | 59 | - name: Run tox targets for Python ${{ matrix.python-version }} 60 | run: | 61 | ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") 62 | TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') tox 63 | 64 | - name: Upload coverage to Codecov 65 | uses: codecov/codecov-action@v3 66 | with: 67 | flags: unittests 68 | name: codecov-umbrella 69 | fail_ci_if_error: false 70 | 71 | 72 | deploy: 73 | name: Deploy 74 | needs: 75 | - linting 76 | - tests 77 | runs-on: ubuntu-latest 78 | if: github.ref=='refs/heads/main' && github.event_name!='pull_request' 79 | 80 | permissions: 81 | contents: write 82 | id-token: write 83 | 84 | steps: 85 | - uses: actions/checkout@v4 86 | 87 | - name: Set up Python 88 | uses: actions/setup-python@v5 89 | with: 90 | python-version: "3.10" 91 | 92 | - name: Check release 93 | id: check_release 94 | run: | 95 | python -m pip install autopub httpx==0.18.2 96 | python -m pip install https://github.com/scikit-build/github-release/archive/master.zip 97 | autopub check 98 | 99 | - name: Publish 100 | if: ${{ steps.check_release.outputs.autopub_release=='true' }} 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 103 | run: | 104 | autopub prepare 105 | autopub commit 106 | autopub build 107 | autopub githubrelease 108 | 109 | - name: Upload package to PyPI 110 | if: ${{ steps.check_release.outputs.autopub_release=='true' }} 111 | uses: pypa/gh-action-pypi-publish@release/v1 112 | -------------------------------------------------------------------------------- /.github/workflows/build_docs.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - test-docs 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: '3.12' 19 | cache: pip 20 | cache-dependency-path: requirements.txt 21 | 22 | - name: Upgrade pip 23 | run: | 24 | # install pip=>20.1 to use "pip cache dir" 25 | python -m pip install --upgrade pip 26 | 27 | - name: Install dependencies 28 | run: python -m pip install -r ./requirements.txt 29 | 30 | - run: | 31 | cd docs 32 | make html 33 | 34 | - name: Deploy 35 | uses: peaceiris/actions-gh-pages@v3 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | publish_dir: docs/_build/html 39 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs Check 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: ammaraskar/sphinx-action@master 14 | with: 15 | docs-folder: "docs/" 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | test: 10 | name: Python ${{ matrix.python-version }} 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | python-version: 16 | - '3.8' 17 | - '3.9' 18 | - '3.10' 19 | - '3.11' 20 | - '3.12' 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: actions/setup-python@v4 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | cache: pip 29 | cache-dependency-path: requirements/*.txt 30 | 31 | - name: Upgrade packaging tools 32 | run: python -m pip install --upgrade pip setuptools virtualenv wheel 33 | 34 | - name: Install dependencies 35 | run: python -m pip install --upgrade codecov tox 36 | 37 | - name: Run tox targets for ${{ matrix.python-version }} 38 | run: | 39 | ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") 40 | TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') tox 41 | - name: Run lint 42 | if: ${{ matrix.python-version == '3.12' }} 43 | run: | 44 | tox -e lint 45 | 46 | deploy: 47 | runs-on: ubuntu-latest 48 | needs: [test] 49 | steps: 50 | - uses: actions/checkout@v3 51 | - name: Set up Python 52 | uses: actions/setup-python@v3 53 | with: 54 | python-version: '3.12' 55 | - name: Install dependencies 56 | run: | 57 | pip install build twine 58 | - name: Publish 59 | env: 60 | TWINE_USERNAME: __token__ 61 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 62 | run: | 63 | python -m build 64 | twine upload dist/* 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | 143 | .idea/ 144 | venv/ 145 | 146 | # Visual studio code 147 | .vscode 148 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # See https://pre-commit.com/hooks.html for info on hooks 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-ast 8 | - id: check-case-conflict 9 | - id: check-toml 10 | - id: check-yaml 11 | - id: debug-statements 12 | - id: detect-private-key 13 | - id: end-of-file-fixer 14 | - id: trailing-whitespace 15 | 16 | - repo: https://github.com/psf/black 17 | rev: 24.10.0 18 | hooks: 19 | - id: black 20 | - repo: https://github.com/PyCQA/isort 21 | rev: 5.13.2 22 | hooks: 23 | - id: isort 24 | - repo: https://github.com/PyCQA/flake8 25 | rev: 7.1.1 26 | hooks: 27 | - id: flake8 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | Next Release - TBC 5 | ------------------ 6 | 7 | * Added support for Django 5.1. 8 | 9 | Contributed by [David S.](https://github.com/smithdc1) via [PR #167](https://github.com/django-crispy-forms/crispy-tailwind/pull/167/) 10 | 11 | 1.0.3 - 2024-02-13 12 | ------------------ 13 | 14 | * Fixed stray closing divs in template files (second try 😅) 15 | 16 | Contributed by [Ronny V.](https://github.com/GitRon) via [PR #162](https://github.com/django-crispy-forms/crispy-tailwind/pull/162/) 17 | 18 | 19 | 1.0.2 - 2024-02-13 20 | ------------------ 21 | 22 | * Fixed stray closing divs in template files 23 | 24 | Contributed by [Ronny V.](https://github.com/GitRon) via [PR #160](https://github.com/django-crispy-forms/crispy-tailwind/pull/160/) 25 | 26 | 27 | 1.0.1 - 2024-02-06 28 | ------------------ 29 | 30 | * Add `build_attrs` filter to `tailwind_filters.py` 31 | * Add `attrs.html` template for Tailwind layout 32 | * Refactor `select.html` and `select_option.html` templates 33 | * Add ID attribute to `select` elements 34 | 35 | 1.0.0 - 2024-01-09 36 | ------------------ 37 | 38 | * Added support for Django 5.0 (#142) 39 | * Added support for Python 3.11 and 3.12 (#142) 40 | * Added support for Python 3.10 (#116) 41 | * Added support for Django 4.2 (#135) 42 | * Dropped support for Django 2.2 (#116) 43 | * Dropped support for Django 3.2, 4.0 and 4.1 (#138) 44 | * Dropped support for Python 3.6 (#116) 45 | * Dropped support for Python 3.7 (#135) 46 | * Increased minimum supported django-crispy-forms version to 2.0 (#135) 47 | * Added docs about Tailwind CLI template discovery management command (#144) 48 | * Fixed bug with select template and disabled property (#118) 49 | 50 | 0.5.0 - 2021-04-21 51 | ------------------ 52 | 53 | * Added support for custom widgets (#92) 54 | * Confirmed support for Django 3.2 (#91) 55 | * Dropped support for Django 3.1 (#91) 56 | 57 | See [Release Notes](https://github.com/django-crispy-forms/crispy-tailwind/milestone/5?closed=1) 58 | for full change log. 59 | 60 | 0.4.0 - 2021-03-22 61 | ------------------ 62 | 63 | * Fixed compatibility with django-crispy-forms 1.11.2 (#86) 64 | * Fixed field names when using formsets (#84) 65 | 66 | See [Release Notes](https://github.com/django-crispy-forms/crispy-tailwind/milestone/4?closed=1) 67 | for full change log. 68 | 69 | 0.3.0 - 2021-02-14 70 | ------------------ 71 | 72 | * Fixed non form errors (#77) 73 | * Various documentation improvements 74 | * Python 3.9 (#60) and Django 3.1 (#56) support 75 | 76 | See [Release Notes](https://github.com/django-crispy-forms/crispy-tailwind/milestone/3?closed=1) 77 | for full change log. 78 | 79 | 0.2.0 - 2020-07-11 80 | ------------------ 81 | 82 | * Support for Formsets 83 | * Prepended and Appended inputs 84 | * Customisable buttons 85 | * Much improved test coverage 86 | * Improved documentation. Docs now include details on how to use the majority 87 | of the core layout objects, crispy filter and add-on 88 | 89 | See [Release Notes](https://github.com/django-crispy-forms/crispy-tailwind/milestone/2?closed=1) 90 | for full change log. 91 | 92 | 0.1.0 - 2020-06-09 93 | ------------------ 94 | 95 | * First Release, please do come and test! 96 | * Opinionated forms can be rendered with crispy filter 97 | * Limited set of layout objects are also available for crispy tags, also with 98 | opinionated rendering. 99 | 100 | See [Release Notes](https://github.com/django-crispy-forms/crispy-tailwind/milestone/1) 101 | for full change log 102 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 David Smith and contributors. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include MANIFEST.in 3 | include README.rst 4 | recursive-include crispy_tailwind/templates * 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Crispy-Tailwind 3 | =============== 4 | 5 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 6 | :target: https://github.com/psf/black 7 | 8 | A `Tailwind CSS`_ template pack for the wonderful django-crispy-forms_. 9 | 10 | **WARNING** 11 | 12 | This project is still in its early stages of development. Any contributions to 13 | the package would be very welcomed. 14 | 15 | Currently the template pack allows the use of the ``|crispy`` filter to style 16 | your form. Here is an example image. 17 | 18 | .. image:: https://django-crispy-forms.github.io/crispy-tailwind/_images/crispy_form.png 19 | 20 | How to install 21 | -------------- 22 | 23 | Install via pip. :: 24 | 25 | pip install crispy-tailwind 26 | 27 | You will need to update your project's settings file to add ``crispy_forms`` 28 | and ``crispy_tailwind`` to your project's ``INSTALLED_APPS`` setting. Also set 29 | ``tailwind`` as an allowed template pack and as the default template pack 30 | for your project:: 31 | 32 | INSTALLED_APPS = ( 33 | ... 34 | "crispy_forms", 35 | "crispy_tailwind", 36 | ... 37 | ) 38 | 39 | CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind" 40 | 41 | CRISPY_TEMPLATE_PACK = "tailwind" 42 | 43 | How to use 44 | ---------- 45 | 46 | This project is still in its early stages. 47 | 48 | Current functionality allows the ``|crispy`` filter to be used to style your 49 | form. In your template: 50 | 51 | 1. Load the filter: ``{% load tailwind_filters %}`` 52 | 2. Apply the crispy filter: ``{{ form|crispy }}`` 53 | 54 | We can also use the ``{% crispy %}`` tag to allow usage of crispy-forms' 55 | ``FormHelper`` and ``Layout``. In your template: 56 | 57 | 1. Load the crispy tag: ``{% load crispy_forms_tags %}`` 58 | 2. Add ``FormHelper`` to your form and use crispy-forms to set-up your form 59 | 3. Use the crispy tag ``{% crispy form %}`` in your template 60 | 61 | Documentation 62 | ------------- 63 | 64 | The documentation for this project is available here: 65 | https://django-crispy-forms.github.io/crispy-tailwind/index.html 66 | 67 | FAQs 68 | ---- 69 | 70 | What about custom widgets? 71 | ========================== 72 | 73 | The template pack includes default styles for widgets included in Django 74 | itself. `Styling of widget instances`_ can be done by using the ``widget.attrs`` 75 | argument when creating the widget. 76 | 77 | For example the following form will render 78 | ````:: 79 | 80 | class CustomTextWidget(forms.TextInput): 81 | pass 82 | 83 | class CustomTextWidgetForm(forms.Form): 84 | name = forms.CharField( 85 | widget=CustomTextWidget(attrs={"class": "custom-css"}) 86 | ) 87 | 88 | .. _`Styling of widget instances` : https://docs.djangoproject.com/en/dev/ref/forms/widgets/#styling-widget-instances 89 | .. _Tailwind CSS: https://tailwindcss.com/ 90 | .. _django-crispy-forms: https://github.com/django-crispy-forms/django-crispy-forms 91 | -------------------------------------------------------------------------------- /crispy_tailwind/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.3" 2 | -------------------------------------------------------------------------------- /crispy_tailwind/layout.py: -------------------------------------------------------------------------------- 1 | from crispy_forms.bootstrap import Alert 2 | from crispy_forms.layout import BaseInput 3 | 4 | 5 | class Submit(BaseInput): 6 | """ 7 | Used to create a Submit button descriptor for the {% crispy %} template tag:: 8 | submit = Submit('Search the Site', 'search this site') 9 | .. note:: The first argument is also slugified and turned into the id for the submit button. 10 | 11 | This is a customised version for Tailwind to add Tailwind CSS style by default 12 | """ 13 | 14 | input_type = "submit" 15 | 16 | def __init__(self, *args, css_class=None, **kwargs): 17 | if css_class is None: 18 | self.field_classes = "bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" 19 | else: 20 | self.field_classes = css_class 21 | super().__init__(*args, **kwargs) 22 | 23 | 24 | class Reset(BaseInput): 25 | """ 26 | Used to create a Reset button input descriptor for the {% crispy %} template tag:: 27 | reset = Reset('Reset This Form', 'Revert Me!') 28 | .. note:: The first argument is also slugified and turned into the id for the reset. 29 | 30 | This is a customised version for Tailwind to add Tailwind CSS style by default 31 | """ 32 | 33 | input_type = "reset" 34 | 35 | def __init__(self, *args, css_class=None, **kwargs): 36 | if css_class is None: 37 | self.field_classes = "bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" 38 | else: 39 | self.field_classes = css_class 40 | super().__init__(*args, **kwargs) 41 | 42 | 43 | class Button(BaseInput): 44 | """ 45 | Used to create a button descriptor for the {% crispy %} template tag:: 46 | submit = Button('Search the Site', 'search this site') 47 | .. note:: The first argument is also slugified and turned into the id for the submit button. 48 | 49 | This is a customised version for Tailwind to add Tailwind CSS style by default 50 | """ 51 | 52 | input_type = "button" 53 | 54 | def __init__(self, *args, css_class=None, **kwargs): 55 | if css_class is None: 56 | self.field_classes = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" 57 | else: 58 | self.field_classes = css_class 59 | super().__init__(*args, **kwargs) 60 | 61 | 62 | class Alert(Alert): 63 | css_class = "" 64 | -------------------------------------------------------------------------------- /crispy_tailwind/tailwind.py: -------------------------------------------------------------------------------- 1 | # todo 2 | # 1: this file needs a tidy up 3 | # 2: is this the right implementation. Maybe we can use class converters? 4 | 5 | import re 6 | 7 | 8 | class CSSContainer: 9 | def __init__(self, css_styles): 10 | default_items = [ 11 | # widgets 12 | "text", 13 | "number", 14 | "email", 15 | "url", 16 | "password", 17 | "hidden", 18 | "multiplehidden", 19 | "file", 20 | "clearablefile", 21 | "textarea", 22 | "date", 23 | "datetime", 24 | "time", 25 | "checkbox", 26 | "select", 27 | "nullbooleanselect", 28 | "selectmultiple", 29 | "radioselect", 30 | "checkboxselectmultiple", 31 | "multi", 32 | "splitdatetime", 33 | "splithiddendatetime", 34 | "selectdate", 35 | # other items 36 | "error_border", 37 | ] 38 | 39 | base = css_styles.get("base", "") 40 | for item in default_items: 41 | setattr(self, item, base) 42 | 43 | for key, value in css_styles.items(): 44 | if key != "base": 45 | # get current attribute and rejoin with a set, also to ensure a space between each attribute 46 | current_class = set(getattr(self, key).split()) 47 | current_class.update(set(value.split())) 48 | new_classes = " ".join(current_class) 49 | setattr(self, key, new_classes) 50 | 51 | def __repr__(self): 52 | return str(self.__dict__) 53 | 54 | def __add__(self, other): 55 | for field, css_class in other.items(): 56 | current_class = set(getattr(self, field).split()) 57 | current_class.update(set(css_class.split())) 58 | new_classes = " ".join(current_class) 59 | setattr(self, field, new_classes) 60 | return self 61 | 62 | def __sub__(self, other): 63 | for field, css_class in other.items(): 64 | current_class = set(getattr(self, field).split()) 65 | removed_classes = set(css_class.split()) 66 | new_classes = " ".join(current_class - removed_classes) 67 | setattr(self, field, new_classes) 68 | return self 69 | 70 | def get_input_class(self, field): 71 | widget_name = re.sub(r"widget$|input$", "", field.field.widget.__class__.__name__.lower()) 72 | return getattr(self, widget_name, "") 73 | -------------------------------------------------------------------------------- /crispy_tailwind/templates/tailwind/display_form.html: -------------------------------------------------------------------------------- 1 | {% if form.form_html %} 2 | {% if include_media %}{{ form.media }}{% endif %} 3 | {% if form_show_errors %} 4 | {% include "tailwind/errors.html" %} 5 | {% endif %} 6 | {{ form.form_html }} 7 | {% else %} 8 | {% include "tailwind/uni_form.html" %} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /crispy_tailwind/templates/tailwind/errors.html: -------------------------------------------------------------------------------- 1 | {% if form.non_field_errors %} 2 |
{{ error }}
4 | {% endfor %} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /crispy_tailwind/templates/tailwind/layout/field_errors_block.html: -------------------------------------------------------------------------------- 1 | {% if form_show_errors and field.errors %} 2 | {% for error in field.errors %} 3 |{{ error }}
4 | {% endfor %} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /crispy_tailwind/templates/tailwind/layout/field_with_buttons.html: -------------------------------------------------------------------------------- 1 | {% load tailwind_field %} 2 | 3 |{{ field.help_text|safe }}
4 | {% else %} 5 | {{ field.help_text|safe }} 6 | {% endif %} 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /crispy_tailwind/templates/tailwind/layout/help_text_and_errors.html: -------------------------------------------------------------------------------- 1 | {% if help_text_inline and not error_text_inline %} 2 | {% include 'tailwind/layout/help_text.html' %} 3 | {% endif %} 4 | 5 | {% if error_text_inline %} 6 | {% include 'tailwind/layout/field_errors.html' %} 7 | {% else %} 8 | {% include 'tailwind/layout/field_errors_block.html' %} 9 | {% endif %} 10 | 11 | {% if not help_text_inline %} 12 | {% include 'tailwind/layout/help_text.html' %} 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /crispy_tailwind/templates/tailwind/layout/inline_field.html: -------------------------------------------------------------------------------- 1 | {% load tailwind_field %} 2 | 3 | {% if field.is_hidden %} 4 | {{ field }} 5 | {% else %} 6 | {% if field|is_checkbox %} 7 |