├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── materializecssform ├── __init__.py ├── config.py ├── meta.py ├── templates │ └── materializecssform │ │ ├── attrs.html │ │ ├── field.html │ │ ├── field_icon.html │ │ ├── form.html │ │ └── formset.html ├── templatetags │ ├── __init__.py │ └── materializecss.py └── tests.py ├── pyproject.toml ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | README.rst 56 | venv 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Florent CLAPIÉ 4 | Copyright (c) 2020 Kal Walkden, Samuel von Stachelski 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | recursive-include materializecssform *.html 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://pypi.org/project/django-materializecss-form/) 2 | 3 | # materialize-css-form 4 | Materializecss for Django Form 5 | 6 | A simple Django template tag to work with [Materializecss](http://materializecss.com/) 7 | 8 | ## Install 9 | 10 | From [PyPi](https://pypi.org/project/django-materializecss-form/): 11 | 12 | ``` 13 | pip install django-materializecss-form 14 | ``` 15 | 16 | From [GitHub](https://github.com/kalwalkden/django-materializecss-form) 17 | 18 | Add module to INSTALLED_APPS: 19 | 20 | ``` 21 | INSTALLED_APPS = ( 22 | 'materializecssform', 23 | ... 24 | ) 25 | ``` 26 | 27 | Add Materialize CSS to your project ([Official Documentation](https://materializecss.com/getting-started.html)): 28 | 29 | In your base.html: 30 | 31 | ``` 32 |
33 | 34 | {% block css %} 35 | 36 | 37 | {% endblock css %} 38 | 39 | 40 | ``` 41 | 42 | ``` 43 | 44 | 45 | 46 | {% block javascript %} 47 | 51 | 52 | 53 | 54 | 63 | {% endblock javascript %} 64 | 65 | ... 66 | 67 | 68 | ``` 69 | 70 | ## Usage 71 | 72 | Import the module simply like this: 73 | 74 | ```html 75 | {% load materializecss %} 76 | ``` 77 | 78 | ### Full form 79 | 80 | Format a whole form: 81 | 82 | ```html 83 | {{ form|materializecss }} 84 | ``` 85 | 86 | ### Individual field 87 | 88 | Format only a specific field: 89 | 90 | ```html 91 | {{ form.24 | {{ field.help_text|safe }} 25 |
26 | {% endif %} 27 |46 | {{ field.help_text|safe }} 47 |
48 | {% endif %} 49 |66 | {{ field.help_text|safe }} 67 |
68 | {% endif %} 69 | 70 |86 | {{ field.help_text|safe }} 87 |
88 | {% endif %} 89 | 90 |112 | {{ field.help_text|safe }} 113 |
114 | {% endif %} 115 | 116 | {% else %} 117 | {% if classes.icon %} 118 | {% include 'materializecssform/field_icon.html' %} 119 | {% endif %} 120 | 125 | {% if field.auto_id %} 126 | 127 | {% endif %} 128 | 129 | {% for error in field.errors %} 130 | {{ error }} 131 | {% endfor %} 132 | 133 | {% if field.help_text %} 134 |135 | {{ field.help_text|safe }} 136 |
137 | {% endif %} 138 | 139 | {% endif %} 140 | 141 |169 | {{ field.help_text|safe }} 170 |
171 | {% endif %} 172 |{{ error }}
188 | {% endfor %} 189 | 190 | {% if field.help_text %} 191 |192 | {{ field.help_text|safe }} 193 |
194 | {% endif %} 195 |{{ error }}
202 | {% endfor %} 203 | 204 |{{ error }}
231 | {% endfor %} 232 | 233 | {% if field.help_text %} 234 |235 | {{ field.help_text|safe }} 236 |
237 | {% endif %} 238 |3 | {% for non_field_error in form.non_field_errors %} 4 | {{ non_field_error }} 5 | {% endfor %} 6 |7 | {% endif %} 8 | 9 | {% for field in form.hidden_fields %} 10 | {{ field }} 11 | {% endfor %} 12 | 13 | {% for field in form.visible_fields %} 14 | {% include 'materializecssform/field.html' %} 15 | {% endfor %} 16 | 17 | -------------------------------------------------------------------------------- /materializecssform/templates/materializecssform/formset.html: -------------------------------------------------------------------------------- 1 | {{ formset.management_form }} 2 | 3 | {% for form in formset %} 4 | 5 | {% include "materializecssform/form.html" with form=form %} 6 | 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /materializecssform/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kalwalkden/django-materializecss-form/2244174c5d56377704cab96ef4f7108484f86c3d/materializecssform/templatetags/__init__.py -------------------------------------------------------------------------------- /materializecssform/templatetags/materializecss.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.template.loader import get_template 3 | from django import template 4 | from django.forms.fields import DateTimeField, DateField 5 | from django.http import QueryDict 6 | 7 | from materializecssform import config 8 | 9 | register = template.Library() 10 | 11 | 12 | @register.filter 13 | def materializecss(element, options={}): 14 | # Set default values if none of them are set 15 | label_cols = 's12' 16 | icon = '' 17 | 18 | if options: 19 | # Split options string into a list of arguments 20 | arguments = [arg.strip() for arg in options.split(',')] 21 | 22 | # Check the first argument to see if it's of form argument=value 23 | if '=' not in arguments[0]: 24 | # If not, it's a custom size, so use that 25 | label_cols = arguments[0] 26 | # Remove this from the arguments list 27 | arguments.pop(0) 28 | 29 | # Join the remaining arguments into a querystring for easy parsing 30 | options = '&'.join(arguments) 31 | qs = QueryDict(options) 32 | # Check to see if a custom size was passed in this fashion and if so prefer it 33 | if qs.get('custom_size'): 34 | label_cols = qs.get('custom_size') 35 | # Get icon if it's been set 36 | icon = qs.get('icon', default='') 37 | 38 | markup_classes = {'label': label_cols, 'value': '', 'single_value': '', 'icon': icon} 39 | return render(element, markup_classes) 40 | 41 | 42 | def _add_input_classes_widget(widget, field_errors): 43 | if _is_multi_widget(widget): 44 | for subwidget in widget.widgets: 45 | _add_input_classes_widget(subwidget, field_errors) 46 | elif not _is_checkbox_widget(widget) and not _is_multiple_checkbox_widget(widget) \ 47 | and not _is_radio_widget(widget) and not _is_file_widget(widget): 48 | classes = widget.attrs.get('class', '') 49 | if config.MATERIALIZECSS_VALIDATION: 50 | classes += ' validate' 51 | if field_errors: 52 | classes += ' invalid' 53 | widget.attrs['class'] = classes 54 | 55 | 56 | def add_input_classes(field): 57 | _add_input_classes_widget(field.field.widget, field.errors) 58 | 59 | 60 | def render(element, markup_classes): 61 | element_type = element.__class__.__name__.lower() 62 | 63 | # Get the icon set setting 64 | icon_set = config.MATERIALIZECSS_ICON_SET 65 | 66 | if element_type == 'boundfield': 67 | add_input_classes(element) 68 | template = get_template("materializecssform/field.html") 69 | context = {'field': element, 'classes': markup_classes, 'icon_set': icon_set} 70 | else: 71 | has_management = getattr(element, 'management_form', None) 72 | if has_management: 73 | for form in element.forms: 74 | for field in form.visible_fields(): 75 | add_input_classes(field) 76 | 77 | template = get_template("materializecssform/formset.html") 78 | context = {'formset': element, 'classes': markup_classes, 'icon_set': icon_set} 79 | else: 80 | for field in element.visible_fields(): 81 | add_input_classes(field) 82 | 83 | template = get_template("materializecssform/form.html") 84 | context = {'form': element, 'classes': markup_classes, 'icon_set': icon_set} 85 | 86 | return template.render(context) 87 | 88 | 89 | def _is_checkbox_widget(widget): 90 | return isinstance(widget, forms.CheckboxInput) 91 | 92 | 93 | def _is_multiple_checkbox_widget(widget): 94 | return isinstance(widget, forms.CheckboxSelectMultiple) 95 | 96 | 97 | def _is_radio_widget(widget): 98 | return isinstance(widget, forms.RadioSelect) 99 | 100 | 101 | def _is_file_widget(widget): 102 | return isinstance(widget, forms.FileInput) 103 | 104 | 105 | def _is_multi_widget(widget): 106 | return isinstance(widget, forms.MultiWidget) 107 | 108 | 109 | @register.filter 110 | def is_checkbox(field): 111 | return isinstance(field.field.widget, forms.CheckboxInput) 112 | 113 | 114 | @register.filter 115 | def is_textarea(field): 116 | return isinstance(field.field.widget, forms.Textarea) 117 | 118 | 119 | @register.filter 120 | def is_multiple_checkbox(field): 121 | return isinstance(field.field.widget, forms.CheckboxSelectMultiple) 122 | 123 | 124 | @register.filter 125 | def is_radio(field): 126 | return isinstance(field.field.widget, forms.RadioSelect) 127 | 128 | 129 | @register.filter 130 | def is_date_input(field): 131 | return isinstance(field.field, DateField) 132 | 133 | 134 | @register.filter 135 | def is_datetime_input(field): 136 | return isinstance(field.field, DateTimeField) 137 | 138 | 139 | @register.filter 140 | def is_file(field): 141 | return isinstance(field.field.widget, forms.FileInput) 142 | 143 | 144 | @register.filter 145 | def is_select(field): 146 | return isinstance(field.field.widget, forms.Select) 147 | 148 | 149 | @register.filter 150 | def is_select_multiple(field): 151 | return isinstance(field.field.widget, forms.SelectMultiple) 152 | -------------------------------------------------------------------------------- /materializecssform/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import django 4 | from django.conf import settings 5 | 6 | settings.configure( 7 | DEBUG=True, 8 | DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "name": ":memory:"}}, 9 | MIDDLEWARE_CLASSES=[], 10 | INSTALLED_APPS=[], 11 | ) 12 | django.setup() 13 | 14 | 15 | class TestTags(unittest.TestCase): 16 | def test_materializecss_tag(self): 17 | from materializecssform.templatetags import materializecss # noqa 18 | 19 | 20 | if __name__ == "__main__": 21 | unittest.main() 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=40.6.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | # Not necessary for packaging but every self-respecting Python 6 | # package should a) use black and b) fix the WRONG default. 7 | [tool.black] 8 | line-length = 79 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | import re 4 | 5 | from setuptools import find_packages, setup 6 | 7 | ################################################################### 8 | 9 | NAME = "django-materializecss-form" 10 | PACKAGES = find_packages() 11 | META_PATH = os.path.join("materializecssform", "meta.py") 12 | KEYWORDS = ["materialize", "django", "css", "materializecss", "django forms"] 13 | CLASSIFIERS = [ 14 | "Development Status :: 5 - Production/Stable", 15 | "Framework :: Django", 16 | "Intended Audience :: Developers", 17 | "Natural Language :: English", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.4", 23 | "Programming Language :: Python :: 3.5", 24 | "Programming Language :: Python :: 3.6", 25 | "Programming Language :: Python :: 3.7", 26 | "Programming Language :: Python :: 3.8", 27 | "Programming Language :: Python :: Implementation :: CPython", 28 | "Programming Language :: Python :: Implementation :: PyPy", 29 | "Topic :: Software Development :: Libraries :: Python Modules", 30 | ] 31 | INSTALL_REQUIRES = [] 32 | 33 | ################################################################### 34 | 35 | 36 | HERE = os.path.abspath(os.path.dirname(__file__)) 37 | 38 | 39 | def read(*parts): 40 | """ 41 | Build an absolute path from *parts* and and return the contents of the 42 | resulting file. Assume UTF-8 encoding. 43 | """ 44 | with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f: 45 | return f.read() 46 | 47 | 48 | META_FILE = read(META_PATH) 49 | 50 | 51 | def find_meta(meta): 52 | """ 53 | Extract __*meta*__ from META_FILE. 54 | """ 55 | meta_match = re.search( 56 | r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), META_FILE, re.M 57 | ) 58 | if meta_match: 59 | return meta_match.group(1) 60 | raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta)) 61 | 62 | 63 | if __name__ == "__main__": 64 | setup( 65 | name=NAME, 66 | description=find_meta("description"), 67 | license=find_meta("license"), 68 | url=find_meta("uri"), 69 | version=find_meta("version"), 70 | author=find_meta("author"), 71 | author_email=find_meta("email"), 72 | maintainer=find_meta("author"), 73 | maintainer_email=find_meta("email"), 74 | keywords=KEYWORDS, 75 | long_description=read("README.md"), 76 | long_description_content_type="text/markdown", 77 | packages=PACKAGES, 78 | zip_safe=False, 79 | classifiers=CLASSIFIERS, 80 | install_requires=INSTALL_REQUIRES, 81 | options={"bdist_wheel": {"universal": "1"}}, 82 | include_package_data=True, 83 | ) 84 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py37 3 | 4 | [testenv] 5 | deps = 6 | django 7 | commands = 8 | python materializecssform/tests.py 9 | --------------------------------------------------------------------------------