├── 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 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/mysite/templates/form_2.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% load crispy_forms_tags %}
4 |
5 | {% block content %}
6 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/mysite/templates/custom_checkbox.html:
--------------------------------------------------------------------------------
1 | {% load crispy_forms_field %}
2 |
3 |
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 | [](https://python.org)
4 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------