├── mysite ├── __init__.py ├── core │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── views.py │ └── forms.py ├── templates │ ├── crispy_form.html │ ├── success.html │ ├── form_1.html │ ├── form_2.html │ ├── custom_checkbox.html │ ├── form_3.html │ └── base.html ├── wsgi.py ├── urls.py └── settings.py ├── requirements.txt ├── manage.py ├── README.md ├── LICENSE └── .gitignore /mysite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mysite/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mysite/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.1.3 2 | django-crispy-forms==1.7.2 3 | -------------------------------------------------------------------------------- /mysite/core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /mysite/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mysite/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /mysite/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /mysite/templates/crispy_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load crispy_forms_tags %} 4 | 5 | {% block content %} 6 | {% crispy form %} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /mysite/templates/success.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Form processed with success!

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /mysite/templates/form_1.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 | {% csrf_token %} 6 | {{ form.as_table }}
7 | 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /mysite/templates/form_2.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load crispy_forms_tags %} 4 | 5 | {% block content %} 6 |
7 | {% csrf_token %} 8 | {{ form|crispy }} 9 | 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /mysite/templates/custom_checkbox.html: -------------------------------------------------------------------------------- 1 | {% load crispy_forms_field %} 2 | 3 |
4 |
5 | {% crispy_field field 'class' 'custom-control-input' %} 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite 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', 'mysite.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /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', 'mysite.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 | -------------------------------------------------------------------------------- /mysite/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import RedirectView 3 | 4 | from mysite.core import views 5 | 6 | 7 | urlpatterns = [ 8 | path('', RedirectView.as_view(url='/form/1/'), name='index'), 9 | path('form/1/', views.AddressFormView.as_view(template_name='form_1.html'), name='form_1'), 10 | path('form/2/', views.AddressFormView.as_view(template_name='form_2.html'), name='form_2'), 11 | path('form/3/', views.AddressFormView.as_view(template_name='form_3.html'), name='form_3'), 12 | path('form/4/', views.CrispyAddressFormView.as_view(), name='form_4'), 13 | path('form/5/', views.CustomFieldFormView.as_view(), name='form_5'), 14 | path('success/', views.SuccessView.as_view(), name='success'), 15 | ] 16 | -------------------------------------------------------------------------------- /mysite/core/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import FormView, TemplateView 2 | from django.urls import reverse_lazy 3 | 4 | from .forms import AddressForm, CrispyAddressForm, CustomFieldForm 5 | 6 | 7 | class AddressFormView(FormView): 8 | form_class = AddressForm 9 | success_url = reverse_lazy('success') 10 | 11 | 12 | class CrispyAddressFormView(FormView): 13 | form_class = CrispyAddressForm 14 | success_url = reverse_lazy('success') 15 | template_name = 'crispy_form.html' 16 | 17 | 18 | class CustomFieldFormView(FormView): 19 | form_class = CustomFieldForm 20 | success_url = reverse_lazy('success') 21 | template_name = 'crispy_form.html' 22 | 23 | 24 | class SuccessView(TemplateView): 25 | template_name = 'success.html' 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Crispy Forms Examples 2 | 3 | [![Python Version](https://img.shields.io/badge/python-3.6-brightgreen.svg)](https://python.org) 4 | [![Django Version](https://img.shields.io/badge/django-2.1-brightgreen.svg)](https://djangoproject.com) 5 | 6 | Code example used in the tutorial "Advanced Form Rendering with Django Crispy Forms". 7 | 8 | ## Running the Project Locally 9 | 10 | First, clone the repository to your local machine: 11 | 12 | ```bash 13 | git clone https://github.com/sibtc/advanced-crispy-forms-examples.git 14 | ``` 15 | 16 | Install the requirements: 17 | 18 | ```bash 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | Apply the migrations: 23 | 24 | ```bash 25 | python manage.py migrate 26 | ``` 27 | 28 | Finally, run the development server: 29 | 30 | ```bash 31 | python manage.py runserver 32 | ``` 33 | 34 | The project will be available at **127.0.0.1:8000**. 35 | 36 | 37 | ## License 38 | 39 | The source code is released under the [MIT License](https://github.com/sibtc/advanced-crispy-forms-examples/blob/master/LICENSE). 40 | -------------------------------------------------------------------------------- /mysite/templates/form_3.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load crispy_forms_tags %} 4 | 5 | {% block content %} 6 |
7 | {% csrf_token %} 8 |
9 |
10 | {{ form.email|as_crispy_field }} 11 |
12 |
13 | {{ form.password|as_crispy_field }} 14 |
15 |
16 | {{ form.address_1|as_crispy_field }} 17 | {{ form.address_2|as_crispy_field }} 18 |
19 |
20 | {{ form.city|as_crispy_field }} 21 |
22 |
23 | {{ form.state|as_crispy_field }} 24 |
25 |
26 | {{ form.zip_code|as_crispy_field }} 27 |
28 |
29 | {{ form.check_me_out|as_crispy_field }} 30 | 31 |
32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simple is Better Than Complex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .DS_Store 104 | *.sqlite3 105 | media/ 106 | -------------------------------------------------------------------------------- /mysite/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}My Site{% endblock %} 8 | 13 | 14 | 15 |
16 | 33 |
34 |
35 | {% block content %} 36 | {% endblock %} 37 |
38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /mysite/core/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from crispy_forms.helper import FormHelper 4 | from crispy_forms.layout import Layout, Div, Submit, Row, Column, Field 5 | 6 | 7 | STATES = ( 8 | ('', 'Choose...'), 9 | ('MG', 'Minas Gerais'), 10 | ('SP', 'Sao Paulo'), 11 | ('RJ', 'Rio de Janeiro') 12 | ) 13 | 14 | class AddressForm(forms.Form): 15 | email = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Email'})) 16 | password = forms.CharField(widget=forms.PasswordInput()) 17 | address_1 = forms.CharField(label='Address', widget=forms.TextInput(attrs={'placeholder': '1234 Main St'})) 18 | address_2 = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Apartment, studio, or floor'})) 19 | city = forms.CharField() 20 | state = forms.ChoiceField(choices=STATES) 21 | zip_code = forms.CharField(label='Zip') 22 | check_me_out = forms.BooleanField(required=False) 23 | 24 | 25 | class CrispyAddressForm(AddressForm): 26 | def __init__(self, *args, **kwargs): 27 | super().__init__(*args, **kwargs) 28 | self.helper = FormHelper() 29 | self.helper.layout = Layout( 30 | Row( 31 | Column('email', css_class='form-group col-md-6 mb-0'), 32 | Column('password', css_class='form-group col-md-6 mb-0'), 33 | css_class='form-row' 34 | ), 35 | 'address_1', 36 | 'address_2', 37 | Row( 38 | Column('city', css_class='form-group col-md-6 mb-0'), 39 | Column('state', css_class='form-group col-md-4 mb-0'), 40 | Column('zip_code', css_class='form-group col-md-2 mb-0'), 41 | css_class='form-row' 42 | ), 43 | 'check_me_out', 44 | Submit('submit', 'Sign in') 45 | ) 46 | 47 | 48 | class CustomCheckbox(Field): 49 | template = 'custom_checkbox.html' 50 | 51 | 52 | class CustomFieldForm(AddressForm): 53 | def __init__(self, *args, **kwargs): 54 | super().__init__(*args, **kwargs) 55 | self.helper = FormHelper() 56 | self.helper.layout = Layout( 57 | Row( 58 | Column('email', css_class='form-group col-md-6 mb-0'), 59 | Column('password', css_class='form-group col-md-6 mb-0'), 60 | css_class='form-row' 61 | ), 62 | 'address_1', 63 | 'address_2', 64 | Row( 65 | Column('city', css_class='form-group col-md-6 mb-0'), 66 | Column('state', css_class='form-group col-md-4 mb-0'), 67 | Column('zip_code', css_class='form-group col-md-2 mb-0'), 68 | css_class='form-row' 69 | ), 70 | CustomCheckbox('check_me_out'), 71 | Submit('submit', 'Sign in') 72 | ) 73 | -------------------------------------------------------------------------------- /mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.3. 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 = 'w(gn#4op(yq4-_@@z0zsw2-!c-ai4#wb48a1y^(ke)y)c1q(&y' 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 | 'crispy_forms', 42 | 43 | 'mysite.core', 44 | ] 45 | 46 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 47 | 48 | MIDDLEWARE = [ 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | 58 | ROOT_URLCONF = 'mysite.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [ 64 | os.path.join(BASE_DIR, 'mysite/templates') 65 | ], 66 | 'APP_DIRS': True, 67 | 'OPTIONS': { 68 | 'context_processors': [ 69 | 'django.template.context_processors.debug', 70 | 'django.template.context_processors.request', 71 | 'django.contrib.auth.context_processors.auth', 72 | 'django.contrib.messages.context_processors.messages', 73 | ], 74 | }, 75 | }, 76 | ] 77 | 78 | WSGI_APPLICATION = 'mysite.wsgi.application' 79 | 80 | 81 | # Database 82 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 83 | 84 | DATABASES = { 85 | 'default': { 86 | 'ENGINE': 'django.db.backends.sqlite3', 87 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 88 | } 89 | } 90 | 91 | 92 | # Password validation 93 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 94 | 95 | AUTH_PASSWORD_VALIDATORS = [ 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 107 | }, 108 | ] 109 | 110 | 111 | # Internationalization 112 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 113 | 114 | LANGUAGE_CODE = 'en-us' 115 | 116 | TIME_ZONE = 'UTC' 117 | 118 | USE_I18N = True 119 | 120 | USE_L10N = True 121 | 122 | USE_TZ = True 123 | 124 | 125 | # Static files (CSS, JavaScript, Images) 126 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 127 | 128 | STATIC_URL = '/static/' 129 | --------------------------------------------------------------------------------