filter students

This commit is contained in:
Nikola Kubiczek 2022-12-05 04:12:09 +01:00
parent 54014321db
commit 563e8c4c62
242 changed files with 51221 additions and 41 deletions

View File

@ -36,9 +36,3 @@ DB_PASSWORD=...
$ python manage.py migrate $ python manage.py migrate
$ python manage.py runserver $ python manage.py runserver
``` ```
### Problemy z instalacją
Mogą pojawić się problemy związane z instalacją `django-crispy-forms==1.12.0`.
Najpierw trzeba zainstalować wszystkie pakiety, potem dokładnie wersję `1.12.0`.
Pomimo błędów ta wersja jest potrzebna do działania

View File

@ -2,7 +2,6 @@ from django.apps import apps
from django.contrib import admin from django.contrib import admin
from django.shortcuts import HttpResponse from django.shortcuts import HttpResponse
from django.contrib.admin.sites import AlreadyRegistered from django.contrib.admin.sites import AlreadyRegistered
from django_summernote.admin import SummernoteModelAdmin
from .models import Announcement, Edition, School, Student from .models import Announcement, Edition, School, Student
from django.db import transaction from django.db import transaction
@ -10,10 +9,6 @@ from io import BytesIO
import xlsxwriter import xlsxwriter
class AnnouncementModelAdmin(SummernoteModelAdmin):
summernote_fields = ['content']
class EditionModelAdmin(admin.ModelAdmin): class EditionModelAdmin(admin.ModelAdmin):
list_display = ('__str__', 'active') list_display = ('__str__', 'active')
ordering = ('-active', '-year') ordering = ('-active', '-year')
@ -33,7 +28,8 @@ class EditionModelAdmin(admin.ModelAdmin):
if index == 0: if index == 0:
continue continue
Student.objects.filter(identifier=row[0]).update(score_first=row[1], score_second=row[2]) Student.objects.filter(identifier=row[0]).update(
score_first=row[1], score_second=row[2])
if '_update-disable-submissions' in request.POST and obj.active and obj.submissions: if '_update-disable-submissions' in request.POST and obj.active and obj.submissions:
if obj.active is True: if obj.active is True:
@ -49,7 +45,8 @@ class EditionModelAdmin(admin.ModelAdmin):
if '_generate-student-list' in request.POST: if '_generate-student-list' in request.POST:
year = obj.year year = obj.year
students = Student.objects.filter(identifier__startswith=year) students = Student.objects.filter(identifier__startswith=year)
schools_set = {int(student.identifier.split('-')[1]) for student in students} schools_set = {int(student.identifier.split('-')
[1]) for student in students}
output = BytesIO() output = BytesIO()
workbook = xlsxwriter.Workbook(output) workbook = xlsxwriter.Workbook(output)
@ -69,15 +66,21 @@ class EditionModelAdmin(admin.ModelAdmin):
row = 1 row = 1
for school_id in schools_set: for school_id in schools_set:
for student in sorted(Student.objects.filter(identifier__startswith=f'{year}-{school_id}-'), key=lambda x: x.identifier.split('-')[2]): for student in sorted(Student.objects.filter(identifier__startswith=f'{year}-{school_id}-'), key=lambda x: x.identifier.split('-')[2]):
worksheet.write(row, 0, student.identifier) # Identyfikator # Identyfikator
worksheet.write(row, 1, student.score_first) # Wynik - eliminacje worksheet.write(row, 0, student.identifier)
worksheet.write(row, 2, student.score_second) # Wynik - finał # Wynik - eliminacje
worksheet.write(row, 1, student.score_first)
# Wynik - finał
worksheet.write(row, 2, student.score_second)
worksheet.write(row, 3, student.name) # Imię worksheet.write(row, 3, student.name) # Imię
worksheet.write(row, 4, student.surname) # Nazwisko worksheet.write(row, 4, student.surname) # Nazwisko
worksheet.write(row, 5, student.grade) # Klasa worksheet.write(row, 5, student.grade) # Klasa
worksheet.write(row, 6, student.school_name) # Szkoła - nazwa # Szkoła - nazwa
worksheet.write(row, 7, student.school_town) # Szkoła - miejscowość worksheet.write(row, 6, student.school_name)
worksheet.write(row, 8, student.school_address) # Szkoła - adres # Szkoła - miejscowość
worksheet.write(row, 7, student.school_town)
# Szkoła - adres
worksheet.write(row, 8, student.school_address)
row += 1 row += 1
@ -93,14 +96,33 @@ class EditionModelAdmin(admin.ModelAdmin):
return super().response_change(request, obj) return super().response_change(request, obj)
class StudentYearFilter(admin.SimpleListFilter):
title = 'aktualna edycja'
parameter_name = 'aktualna edycja'
def lookups(self, request, model_admin):
return (
('Yes', 'Jedynie aktualna edycja'),
)
def queryset(self, request, queryset):
value = self.value()
year = Edition.current().year
if value == 'Yes':
return queryset.filter(identifier__startswith=str(year))
return queryset
class StudentModelAdmin(admin.ModelAdmin): class StudentModelAdmin(admin.ModelAdmin):
list_display = ('identifier', '__str__', 'grade') list_display = ('identifier', '__str__', 'grade')
search_fields = ('identifier', 'name', 'surname', 'grade') search_fields = ('identifier', 'name', 'surname', 'grade')
ordering = ('identifier',) ordering = ('-identifier',)
list_filter = (StudentYearFilter,)
admin.site.register(Student, StudentModelAdmin) admin.site.register(Student, StudentModelAdmin)
admin.site.register(Announcement, AnnouncementModelAdmin)
admin.site.register(Edition, EditionModelAdmin) admin.site.register(Edition, EditionModelAdmin)

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.5 on 2022-12-05 02:41
from django.db import migrations
import tinymce.models
class Migration(migrations.Migration):
dependencies = [
('app', '0002_auto_20220420_1823'),
]
operations = [
migrations.AlterField(
model_name='announcement',
name='content',
field=tinymce.models.HTMLField(verbose_name='Treść'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.5 on 2022-12-05 02:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0003_alter_announcement_content'),
]
operations = [
migrations.AlterField(
model_name='edition',
name='active',
field=models.BooleanField(default=False, verbose_name='Aktualna edycja'),
),
migrations.AlterField(
model_name='edition',
name='scores_available',
field=models.BooleanField(default=False, verbose_name='Wyniki finałowe opublikowane'),
),
migrations.AlterField(
model_name='edition',
name='scores_eliminations',
field=models.BooleanField(default=False, verbose_name='Wyniki z eliminacji opublikowane dla nauczycieli'),
),
migrations.AlterField(
model_name='edition',
name='submissions',
field=models.BooleanField(default=False, verbose_name='Rejestracja drużyn'),
),
]

View File

@ -1,26 +1,35 @@
from django.contrib.auth.models import PermissionsMixin from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.db import models, transaction from django.db import models, transaction
from tinymce.models import HTMLField
class Edition(models.Model): class Edition(models.Model):
year = models.PositiveIntegerField('Rok', unique=True) year = models.PositiveIntegerField('Rok', unique=True)
roman = models.CharField('Nr. edycji', max_length=25) roman = models.CharField('Nr. edycji', max_length=25)
active = models.BooleanField('Aktualna', default=False) active = models.BooleanField('Aktualna edycja', default=False)
submissions = models.BooleanField('Możliwość zgłoszenia drużyny', default=False) submissions = models.BooleanField(
'Rejestracja drużyn', default=False)
scores_available = models.BooleanField('Wyniki dostępne dla wszystkich', default=False) scores_eliminations = models.BooleanField(
scores_eliminations = models.BooleanField('Wyniki z eliminacji dostępne dla nauczycieli', default=False) 'Wyniki z eliminacji opublikowane dla nauczycieli', default=False)
scores_available = models.BooleanField(
'Wyniki finałowe opublikowane', default=False)
entry_threshold = models.IntegerField('Próg punkowy - wejście do finału', default=0) entry_threshold = models.IntegerField(
award_threshold = models.IntegerField('Próg punktowy - wyróżnienie', default=0) 'Próg punkowy - wejście do finału', default=0)
laureate_threshold = models.IntegerField('Próg punktowy - tytuł laureata', default=0) award_threshold = models.IntegerField(
'Próg punktowy - wyróżnienie', default=0)
laureate_threshold = models.IntegerField(
'Próg punktowy - tytuł laureata', default=0)
scores = models.FileField('Wyniki do wczytania', blank=True, upload_to='wyniki', scores = models.FileField('Wyniki do wczytania', blank=True, upload_to='wyniki',
help_text='Uwaga! Wyniki muszą być zawarte w pliku .csv. Kolumny po kolei to odpowiednio: identyfikator ucznia, wynik z eliminacji, wynik z finału.') help_text='Uwaga! Wyniki muszą być zawarte w pliku .csv. Kolumny po kolei to odpowiednio: identyfikator ucznia, wynik z eliminacji, wynik z finału.')
first_test = models.FileField('Zadania eliminacyjne', blank=True, upload_to='eliminacje') first_test = models.FileField(
second_test = models.FileField('Zadania finałowe', blank=True, upload_to='finaly') 'Zadania eliminacyjne', blank=True, upload_to='eliminacje')
second_test = models.FileField(
'Zadania finałowe', blank=True, upload_to='finaly')
def __str__(self): def __str__(self):
return f'Edycja {self.roman}' return f'Edycja {self.roman}'
@ -43,7 +52,8 @@ class Edition(models.Model):
class Student(models.Model): class Student(models.Model):
name = models.CharField('Imię', max_length=50) name = models.CharField('Imię', max_length=50)
surname = models.CharField('Nazwisko', max_length=50) surname = models.CharField('Nazwisko', max_length=50)
grade = models.PositiveIntegerField('Klasa', choices=map(lambda x: (x, x), range(1, 9))) grade = models.PositiveIntegerField(
'Klasa', choices=map(lambda x: (x, x), range(1, 9)))
score_first = models.IntegerField('Wynik z eliminacji', default=0) score_first = models.IntegerField('Wynik z eliminacji', default=0)
score_second = models.IntegerField('Wynik z finału', default=0) score_second = models.IntegerField('Wynik z finału', default=0)
@ -65,7 +75,7 @@ class Student(models.Model):
class Announcement(models.Model): class Announcement(models.Model):
title = models.CharField('Tytuł', max_length=250) title = models.CharField('Tytuł', max_length=250)
content = models.TextField('Treść') content = HTMLField('Treść')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):
@ -115,8 +125,10 @@ class School(AbstractBaseUser, PermissionsMixin):
is_staff = models.BooleanField('Staff status', default=False) is_staff = models.BooleanField('Staff status', default=False)
is_active = models.BooleanField('Active status', default=True) is_active = models.BooleanField('Active status', default=True)
request_notifications = models.BooleanField('Powiadomienia przy wysłaniu zgłoszenia', default=False) request_notifications = models.BooleanField(
email_notifications = models.BooleanField('Powiadomienia przy wysłaniu wiadomości email', default=False) 'Powiadomienia przy wysłaniu zgłoszenia', default=False)
email_notifications = models.BooleanField(
'Powiadomienia przy wysłaniu wiadomości email', default=False)
objects = UserManager() objects = UserManager()

View File

@ -3,7 +3,7 @@
<div class="submit-row"> <div class="submit-row">
<input <input
type="submit" type="submit"
value="Zaktualizuj wyniki uczniów" value="Wczytaj wyniki uczniów"
name="_update-student-scores" name="_update-student-scores"
/> />
<input <input

View File

@ -20,7 +20,7 @@
{% else %} {% else %}
{% if ongoing.scores_eliminations %} {% if ongoing.scores_eliminations %}
<th>Liczba punktów zdobytych podczas eliminacji</th> <th>Liczba punktów zdobytych podczas eliminacji</th>
{% if ongoing.entry_threshold > 0 %} {% if ongoing.entry_threshold >= 0 %}
<th>Wynik eliminacji</th> <th>Wynik eliminacji</th>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -44,7 +44,7 @@
</td> </td>
{% if ongoing.entry_threshold > 0 %} {% if ongoing.entry_threshold > 0 %}
<td> <td>
{% if student.score_first > ongoing.entry_threshold %} {% if student.score_first >= ongoing.entry_threshold %}
<b>Uczeń zakwalifikowany do finału</b> <b>Uczeń zakwalifikowany do finału</b>
{% else %} {% else %}
Uczeń niezakwalifikowany do finału Uczeń niezakwalifikowany do finału

22
crispy_forms/LICENSE Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2009 Daniel Greenfeld and contributors.
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:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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.

22
crispy_forms/LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2009-2021 Miguel Araujo, Daniel Feldroy and contributors.
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:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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.

1
crispy_forms/__init__.py Normal file
View File

@ -0,0 +1 @@
__version__ = "1.14.0"

22
crispy_forms/base.py Normal file
View File

@ -0,0 +1,22 @@
class KeepContext:
"""
Context manager that receives a `django.template.Context` instance and a list of keys
Once the context manager is exited, it removes `keys` from the context, to avoid
side effects in later layout objects that may use the same context variables.
Layout objects should use `extra_context` to introduce context variables, never
touch context object themselves, that could introduce side effects.
"""
def __init__(self, context, keys):
self.context = context
self.keys = keys
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
for key in list(self.keys):
if key in self.context:
del self.context[key]

1132
crispy_forms/bootstrap.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
class CrispyError(Exception):
pass
class FormHelpersException(CrispyError):
"""
This is raised when building a form via helpers throws an error.
We want to catch form helper errors as soon as possible because
debugging templatetags is never fun.
"""
pass
class DynamicError(CrispyError):
pass

350
crispy_forms/helper.py Normal file
View File

@ -0,0 +1,350 @@
import re
from django.urls import NoReverseMatch, reverse
from django.utils.safestring import mark_safe
from crispy_forms.exceptions import FormHelpersException
from crispy_forms.layout import Layout
from crispy_forms.layout_slice import LayoutSlice
from crispy_forms.utils import TEMPLATE_PACK, flatatt, list_difference, render_field
class DynamicLayoutHandler:
def _check_layout(self):
if self.layout is None:
raise FormHelpersException("You need to set a layout in your FormHelper")
def _check_layout_and_form(self):
self._check_layout()
if self.form is None:
raise FormHelpersException("You need to pass a form instance to your FormHelper")
def all(self):
"""
Returns all layout objects of first level of depth
"""
self._check_layout()
return LayoutSlice(self.layout, slice(0, len(self.layout.fields), 1))
def filter(self, *LayoutClasses, max_level=0, greedy=False):
"""
Returns a LayoutSlice pointing to layout objects of type `LayoutClass`
"""
self._check_layout()
filtered_layout_objects = self.layout.get_layout_objects(LayoutClasses, max_level=max_level, greedy=greedy)
return LayoutSlice(self.layout, filtered_layout_objects)
def filter_by_widget(self, widget_type):
"""
Returns a LayoutSlice pointing to fields with widgets of `widget_type`
"""
self._check_layout_and_form()
layout_field_names = self.layout.get_field_names()
# Let's filter all fields with widgets like widget_type
filtered_fields = []
for pointer in layout_field_names:
if isinstance(self.form.fields[pointer.name].widget, widget_type):
filtered_fields.append(pointer)
return LayoutSlice(self.layout, filtered_fields)
def exclude_by_widget(self, widget_type):
"""
Returns a LayoutSlice pointing to fields with widgets NOT matching `widget_type`
"""
self._check_layout_and_form()
layout_field_names = self.layout.get_field_names()
# Let's exclude all fields with widgets like widget_type
filtered_fields = []
for pointer in layout_field_names:
if not isinstance(self.form.fields[pointer.name].widget, widget_type):
filtered_fields.append(pointer)
return LayoutSlice(self.layout, filtered_fields)
def __getitem__(self, key):
"""
Return a LayoutSlice that makes changes affect the current instance of the layout
and not a copy.
"""
# when key is a string containing the field name
if isinstance(key, str):
# Django templates access FormHelper attributes using dictionary [] operator
# This could be a helper['form_id'] access, not looking for a field
if hasattr(self, key):
return getattr(self, key)
self._check_layout()
layout_field_names = self.layout.get_field_names()
filtered_field = []
for pointer in layout_field_names:
# There can be an empty pointer
if pointer.name == key:
filtered_field.append(pointer)
return LayoutSlice(self.layout, filtered_field)
return LayoutSlice(self.layout, key)
def __setitem__(self, key, value):
self.layout[key] = value
def __delitem__(self, key):
del self.layout.fields[key]
def __len__(self):
if self.layout is not None:
return len(self.layout.fields)
else:
return 0
class FormHelper(DynamicLayoutHandler):
"""
This class controls the form rendering behavior of the form passed to
the `{% crispy %}` tag. For doing so you will need to set its attributes
and pass the corresponding helper object to the tag::
{% crispy form form.helper %}
Let's see what attributes you can set and what form behaviors they apply to:
**form_method**: Specifies form method attribute.
You can set it to 'POST' or 'GET'. Defaults to 'POST'
**form_action**: Applied to the form action attribute:
- Can be a named url in your URLconf that can be executed via the `{% url %}` template tag. \
Example: 'show_my_profile'. In your URLconf you could have something like::
path('show/profile/', 'show_my_profile_view', name = 'show_my_profile')
- It can simply point to a URL '/whatever/blabla/'.
**form_id**: Generates a form id for dom identification.
If no id provided then no id attribute is created on the form.
**form_class**: String containing separated CSS classes to be applied
to form class attribute.
**form_group_wrapper_class**: String containing separated CSS classes to be applied
to each row of inputs.
**form_tag**: It specifies if <form></form> tags should be rendered when using a Layout.
If set to False it renders the form without the <form></form> tags. Defaults to True.
**form_error_title**: If a form has `non_field_errors` to display, they
are rendered in a div. You can set title's div with this attribute.
Example: "Oooops!" or "Form Errors"
**formset_error_title**: If a formset has `non_form_errors` to display, they
are rendered in a div. You can set title's div with this attribute.
**include_media**: Whether to automatically include form media. Set to False if
you want to manually include form media outside the form. Defaults to True.
Public Methods:
**add_input(input)**: You can add input buttons using this method. Inputs
added using this method will be rendered at the end of the form/formset.
**add_layout(layout)**: You can add a `Layout` object to `FormHelper`. The Layout
specifies in a simple, clean and DRY way how the form fields should be rendered.
You can wrap fields, order them, customize pretty much anything in the form.
Best way to add a helper to a form is adding a property named helper to the form
that returns customized `FormHelper` object::
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
class MyForm(forms.Form):
title = forms.CharField(_("Title"))
@property
def helper(self):
helper = FormHelper()
helper.form_id = 'this-form-rocks'
helper.form_class = 'search'
helper.add_input(Submit('save', 'save'))
[...]
return helper
You can use it in a template doing::
{% load crispy_forms_tags %}
{% crispy form %}
"""
_form_method = "post"
_form_action = ""
form = None
form_id = ""
form_class = ""
form_group_wrapper_class = ""
layout = None
form_tag = True
form_error_title = ""
formset_error_title = ""
form_show_errors = True
render_unmentioned_fields = False
render_hidden_fields = False
render_required_fields = False
_help_text_inline = False
_error_text_inline = True
form_show_labels = True
template = None
field_template = None
disable_csrf = False
use_custom_control = True
label_class = ""
field_class = ""
include_media = True
def __init__(self, form=None):
self.attrs = {}
self.inputs = []
if form is not None:
self.form = form
self.layout = self.build_default_layout(form)
def build_default_layout(self, form):
return Layout(*form.fields.keys())
@property
def form_method(self):
return self._form_method
@form_method.setter
def form_method(self, method):
if method.lower() not in ("get", "post"):
raise FormHelpersException(
"Only GET and POST are valid in the \
form_method helper attribute"
)
self._form_method = method.lower()
@property
def form_action(self):
try:
return reverse(self._form_action)
except NoReverseMatch:
return self._form_action
@form_action.setter
def form_action(self, action):
self._form_action = action
@property
def help_text_inline(self):
return self._help_text_inline
@help_text_inline.setter
def help_text_inline(self, flag):
self._help_text_inline = flag
self._error_text_inline = not flag
@property
def error_text_inline(self):
return self._error_text_inline
@error_text_inline.setter
def error_text_inline(self, flag):
self._error_text_inline = flag
self._help_text_inline = not flag
def add_input(self, input_object):
self.inputs.append(input_object)
def add_layout(self, layout):
self.layout = layout
def render_layout(self, form, context, template_pack=TEMPLATE_PACK):
"""
Returns safe html of the rendering of the layout
"""
form.rendered_fields = set()
form.crispy_field_template = self.field_template
# This renders the specified Layout strictly
html = self.layout.render(form, context, template_pack=template_pack)
# Rendering some extra fields if specified
if self.render_unmentioned_fields or self.render_hidden_fields or self.render_required_fields:
fields = tuple(form.fields.keys())
left_fields_to_render = list_difference(fields, form.rendered_fields)
for field in left_fields_to_render:
if (
self.render_unmentioned_fields
or (self.render_hidden_fields and form.fields[field].widget.is_hidden)
or (self.render_required_fields and form.fields[field].widget.is_required)
):
html += render_field(field, form, context, template_pack=template_pack)
return mark_safe(html)
def get_attributes(self, template_pack=TEMPLATE_PACK): # noqa: C901
"""
Used by crispy_forms_tags to get helper attributes
"""
attrs = self.attrs.copy() if self.attrs else {}
if self.form_action:
attrs["action"] = self.form_action.strip()
if self.form_id:
attrs["id"] = self.form_id.strip()
if self.form_class:
attrs["class"] = self.form_class.strip()
if self.form_group_wrapper_class:
attrs["form_group_wrapper_class"] = self.form_group_wrapper_class
items = {
"attrs": attrs,
"disable_csrf": self.disable_csrf,
"error_text_inline": self.error_text_inline,
"field_class": self.field_class,
"field_template": self.field_template or "%s/field.html" % template_pack,
"flat_attrs": flatatt(attrs),
"form_error_title": self.form_error_title.strip(),
"form_method": self.form_method.strip(),
"form_show_errors": self.form_show_errors,
"form_show_labels": self.form_show_labels,
"form_tag": self.form_tag,
"formset_error_title": self.formset_error_title.strip(),
"help_text_inline": self.help_text_inline,
"include_media": self.include_media,
"label_class": self.label_class,
"use_custom_control": self.use_custom_control,
}
if template_pack == "bootstrap4":
if "form-horizontal" in self.form_class.split():
bootstrap_size_match = re.findall(r"col(-(xl|lg|md|sm))?-(\d+)", self.label_class)
if bootstrap_size_match:
offset_pattern = "offset%s-%s"
items["bootstrap_checkbox_offsets"] = [
offset_pattern % (m[0], m[-1]) for m in bootstrap_size_match
]
else:
bootstrap_size_match = re.findall(r"col-(lg|md|sm|xs)-(\d+)", self.label_class)
if bootstrap_size_match:
offset_pattern = "col-%s-offset-%s"
items["bootstrap_checkbox_offsets"] = [offset_pattern % m for m in bootstrap_size_match]
if self.inputs:
items["inputs"] = self.inputs
for attribute_name, value in self.__dict__.items():
if (
attribute_name not in items
and attribute_name not in ["layout", "inputs"]
and not attribute_name.startswith("_")
):
items[attribute_name] = value
return items

1002
crispy_forms/layout.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
from crispy_forms.bootstrap import Container
from crispy_forms.exceptions import DynamicError
from crispy_forms.layout import Fieldset, MultiField
class LayoutSlice:
# List of layout objects that need args passed first before fields
args_first = (Fieldset, MultiField, Container)
def __init__(self, layout, key):
self.layout = layout
if isinstance(key, int):
self.slice = slice(key, key + 1, 1)
else:
self.slice = key
def wrapped_object(self, LayoutClass, fields, *args, **kwargs):
"""
Returns a layout object of type `LayoutClass` with `args` and `kwargs` that
wraps `fields` inside.
"""
if args:
if isinstance(fields, list):
fields = tuple(fields)
else:
fields = (fields,)
if LayoutClass in self.args_first:
arguments = args + fields
else:
arguments = fields + args
return LayoutClass(*arguments, **kwargs)
else:
if isinstance(fields, list):
return LayoutClass(*fields, **kwargs)
else:
return LayoutClass(fields, **kwargs)
def pre_map(self, function):
"""
Iterates over layout objects pointed in `self.slice` executing `function` on them.
It passes `function` penultimate layout object and the position where to find last one
"""
if isinstance(self.slice, slice):
for i in range(*self.slice.indices(len(self.layout.fields))):
function(self.layout, i)
elif isinstance(self.slice, list):
# A list of pointers Ex: [[[0, 0], 'div'], [[0, 2, 3], 'field_name']]
for pointer in self.slice:
positions = pointer.positions
# If it's pointing first level
if len(positions) == 1:
function(self.layout, positions[-1])
else:
layout_object = self.layout.fields[positions[0]]
for i in positions[1:-1]:
layout_object = layout_object.fields[i]
try:
function(layout_object, positions[-1])
except IndexError:
# We could avoid this exception, recalculating pointers.
# However this case is most of the time an undesired behavior
raise DynamicError(
"Trying to wrap a field within an already wrapped field, \
recheck your filter or layout"
)
def wrap(self, LayoutClass, *args, **kwargs):
"""
Wraps every layout object pointed in `self.slice` under a `LayoutClass` instance with
`args` and `kwargs` passed.
"""
def wrap_object(layout_object, j):
layout_object.fields[j] = self.wrapped_object(LayoutClass, layout_object.fields[j], *args, **kwargs)
self.pre_map(wrap_object)
def wrap_once(self, LayoutClass, *args, **kwargs):
"""
Wraps every layout object pointed in `self.slice` under a `LayoutClass` instance with
`args` and `kwargs` passed, unless layout object's parent is already a subclass of
`LayoutClass`.
"""
def wrap_object_once(layout_object, j):
if not isinstance(layout_object, LayoutClass):
layout_object.fields[j] = self.wrapped_object(LayoutClass, layout_object.fields[j], *args, **kwargs)
self.pre_map(wrap_object_once)
def wrap_together(self, LayoutClass, *args, **kwargs):
"""
Wraps all layout objects pointed in `self.slice` together under a `LayoutClass`
instance with `args` and `kwargs` passed.
"""
if isinstance(self.slice, slice):
# The start of the slice is replaced
start = self.slice.start if self.slice.start is not None else 0
self.layout.fields[start] = self.wrapped_object(
LayoutClass, self.layout.fields[self.slice], *args, **kwargs
)
# The rest of places of the slice are removed, as they are included in the previous
for i in reversed(range(*self.slice.indices(len(self.layout.fields)))):
if i != start:
del self.layout.fields[i]
elif isinstance(self.slice, list):
raise DynamicError("wrap_together doesn't work with filter, only with [] operator")
def map(self, function):
"""
Iterates over layout objects pointed in `self.slice` executing `function` on them
It passes `function` last layout object
"""
if isinstance(self.slice, slice):
for i in range(*self.slice.indices(len(self.layout.fields))):
function(self.layout.fields[i])
elif isinstance(self.slice, list):
# A list of pointers Ex: [[[0, 0], 'div'], [[0, 2, 3], 'field_name']]
for pointer in self.slice:
positions = pointer.positions
layout_object = self.layout.fields[positions[0]]
for i in positions[1:]:
previous_layout_object = layout_object
layout_object = layout_object.fields[i]
# If update_attrs is applied to a string, we call to its wrapping layout object
if function.__name__ == "update_attrs" and isinstance(layout_object, str):
function(previous_layout_object)
else:
function(layout_object)
def update_attributes(self, **original_kwargs):
"""
Updates attributes of every layout object pointed in `self.slice` using kwargs
"""
def update_attrs(layout_object):
kwargs = original_kwargs.copy()
if hasattr(layout_object, "attrs"):
if "css_class" in kwargs:
if "class" in layout_object.attrs:
layout_object.attrs["class"] += " %s" % kwargs.pop("css_class")
else:
layout_object.attrs["class"] = kwargs.pop("css_class")
layout_object.attrs.update(kwargs)
self.map(update_attrs)

View File

@ -0,0 +1,12 @@
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#{{ div.data_parent }}" href="#{{ div.css_id }}">{{ div.name }}</a>
</h4>
</div>
<div id="{{ div.css_id }}" class="panel-collapse collapse{% if div.active %} in{% endif %}" >
<div class="panel-body">
{{ fields }}
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
<div class="panel-group" id="{{ accordion.css_id }}">
{{ content }}
</div>

View File

@ -0,0 +1,22 @@
{% for fieldset in form.fieldsets %}
<fieldset class="fieldset-{{ forloop.counter }} {{ fieldset.classes }}">
{% if fieldset.legend %}
<legend>{{ fieldset.legend }}</legend>
{% endif %}
{% if fieldset.description %}
<p class="description">{{ fieldset.description }}</p>
{% endif %}
{% for field in fieldset %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% include "bootstrap3/field.html" %}
{% endif %}
{% endfor %}
{% if not forloop.last or not fieldset_open %}
</fieldset>
{% endif %}
{% endfor %}

View File

@ -0,0 +1,9 @@
{% if form.form_html %}
{% if include_media %}{{ form.media }}{% endif %}
{% if form_show_errors %}
{% include "bootstrap3/errors.html" %}
{% endif %}
{{ form.form_html }}
{% else %}
{% include "bootstrap3/uni_form.html" %}
{% endif %}

View File

@ -0,0 +1,8 @@
{% if form.non_field_errors %}
<div class="alert alert-block alert-danger">
{% if form_error_title %}<h4 class="alert-heading">{{ form_error_title }}</h4>{% endif %}
<ul>
{{ form.non_field_errors|unordered_list }}
</ul>
</div>
{% endif %}

View File

@ -0,0 +1,9 @@
{% if formset.non_form_errors %}
<div class="alert alert-block alert-danger">
{% if formset_error_title %}<h4 class="alert-heading">{{ formset_error_title }}</h4>{% endif %}
<ul>
{{ formset.non_form_errors|unordered_list }}
</ul>
</div>
{% endif %}

View File

@ -0,0 +1,52 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox %}
<div class="form-group">
{% if label_class %}
<div class="controls {% for offset in bootstrap_checkbox_offsets %}{{ offset }} {% endfor %}{{ field_class }}">
{% endif %}
{% endif %}
<{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" {% if not field|is_checkbox %}class="form-group{% else %}class="checkbox{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if form_show_errors%}{% if field.errors %} has-error{% endif %}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label and not field|is_checkbox and form_show_labels %}
<label {% if field.id_for_label and not field|is_radioselect %}for="{{ field.id_for_label }}" {% endif %} class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
{% if field|is_checkboxselectmultiple %}
{% include 'bootstrap3/layout/checkboxselectmultiple.html' %}
{% endif %}
{% if field|is_radioselect %}
{% include 'bootstrap3/layout/radioselect.html' %}
{% endif %}
{% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
{% if field|is_checkbox and form_show_labels %}
<label for="{{ field.id_for_label }}" class="{% if field.field.required %} requiredField{% endif %}">
{% crispy_field field %}
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
{% else %}
<div class="controls {{ field_class }}">
{% if field|is_multivalue %}
{% crispy_field field %}
{% else %}
{% crispy_field field 'class' 'form-control' %}
{% endif %}
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
</div>
{% endif %}
{% endif %}
</{% if tag %}{{ tag }}{% else %}div{% endif %}>
{% if field|is_checkbox %}
{% if label_class %}
</div>
{% endif %}
</div>
{% endif %}
{% endif %}

View File

@ -0,0 +1,13 @@
{% if inputs %}
<div class="form-group">
{% if label_class %}
<div class="aab controls {{ label_class }}"></div>
{% endif %}
<div class="controls {{ field_class }}">
{% for input in inputs %}
{% include "bootstrap3/layout/baseinput.html" %}
{% endfor %}
</div>
</div>
{% endif %}

View File

@ -0,0 +1,4 @@
<div{% if alert.css_id %} id="{{ alert.css_id }}"{% endif %}{% if alert.css_class %} class="{{ alert.css_class }}"{% endif %}>
{% if dismiss %}<button type="button" class="close" data-dismiss="alert">&times;</button>{% endif %}
{{ content }}
</div>

View File

@ -0,0 +1 @@
{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}

View File

@ -0,0 +1,9 @@
<input type="{{ input.input_type }}"
name="{% if input.name|wordcount > 1 %}{{ input.name|slugify }}{% else %}{{ input.name }}{% endif %}"
value="{{ input.value }}"
{% if input.input_type != "hidden" %}
class="{{ input.field_classes }}"
id="{{ input.id }}"
{% endif %}
{{ input.flat_attrs }}
/>

View File

@ -0,0 +1 @@
<button {{ button.flat_attrs }}>{{ button.content }}</button>

View File

@ -0,0 +1,4 @@
<div {% if buttonholder.css_id %}id="{{ buttonholder.css_id }}"{% endif %}
class="buttonHolder{% if buttonholder.css_class %} {{ buttonholder.css_class }}{% endif %}">
{{ fields_output }}
</div>

View File

@ -0,0 +1,21 @@
{% load crispy_forms_filters %}
{% load l10n %}
<div class="controls {{ field_class }}"{% if flat_attrs %} {{ flat_attrs }}{% endif %}>
{% include 'bootstrap3/layout/field_errors_block.html' %}
{% for group, options, index in field|optgroups %}
{% if group %}<strong>{{ group }}</strong>{% endif %}
{% for option in options %}
{% if not inline_class %}<div class="checkbox">{% endif %}
<label class="{% if inline_class %}checkbox-{{ inline_class }}{% endif %}" for="{{ option.attrs.id }}">
<input type="checkbox" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "bootstrap3/layout/attrs.html" with widget=option %}>
{{ option.label|unlocalize }}
</label>
{% if not inline_class %}</div>{% endif %}
{% endfor %}
{% endfor %}
{% include 'bootstrap3/layout/help_text.html' %}
</div>

View File

@ -0,0 +1,14 @@
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if form_show_errors and field.errors %} has-error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label %}
<label for="{{ field.auto_id }}" class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
{% include 'bootstrap3/layout/checkboxselectmultiple.html' %}
</div>
{% endif %}

View File

@ -0,0 +1,3 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="formColumn {{ div.css_class|default:'' }}" {{ div.flat_attrs }}>
{{ fields }}
</div>

View File

@ -0,0 +1,4 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %}
{% if div.css_class %}class="{{ div.css_class }}"{% endif %} {{ div.flat_attrs }}>
{{ fields }}
</div>

View File

@ -0,0 +1,5 @@
{% if form_show_errors and field.errors %}
{% for error in field.errors %}
<span id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="help-block"><strong>{{ error }}</strong></span>
{% endfor %}
{% endif %}

View File

@ -0,0 +1,5 @@
{% if form_show_errors and field.errors %}
{% for error in field.errors %}
<p id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="help-block"><strong>{{ error }}</strong></p>
{% endfor %}
{% endif %}

View File

@ -0,0 +1,17 @@
{% load crispy_forms_field %}
<div{% if div.css_id %} id="{{ div.css_id }}"{% endif %} class="form-group{% if form_show_errors and field.errors %} has-error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}{% if div.css_class %} {{ div.css_class }}{% endif %}" {{ div.flat_attrs }}>
{% if field.label and form_show_labels %}
<label for="{{ field.id_for_label }}" class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
<div class="controls {{ field_class }}">
<div class="input-group">
{% crispy_field field %}
<span class="input-group-btn{% if active %} active{% endif %}{% if input_size %} {{ input_size }}{% endif %}">{{ buttons }}</span>
</div>
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
</div>
</div>

View File

@ -0,0 +1,6 @@
<fieldset {% if fieldset.css_id %}id="{{ fieldset.css_id }}"{% endif %}
{% if fieldset.css_class %}class="{{ fieldset.css_class }}"{% endif %}
{{ fieldset.flat_attrs }}>
{% if legend %}<legend>{{ legend }}</legend>{% endif %}
{{ fields }}
</fieldset>

View File

@ -0,0 +1,9 @@
<div{% if formactions.flat_attrs %} {{ formactions.flat_attrs }}{% endif %} class="form-group {{ formactions.css_class }}"{% if formactions.id %} id="{{ formactions.id }}"{% endif %}>
{% if label_class %}
<div class="aab controls {{ label_class }}"></div>
{% endif %}
<div class="controls {{ field_class }}">
{{ fields_output }}
</div>
</div>

View File

@ -0,0 +1,7 @@
{% if field.help_text %}
{% if help_text_inline %}
<span id="hint_{{ field.auto_id }}" class="help-block">{{ field.help_text|safe }}</span>
{% else %}
<div id="hint_{{ field.auto_id }}" class="help-block">{{ field.help_text|safe }}</div>
{% endif %}
{% endif %}

View File

@ -0,0 +1,13 @@
{% if help_text_inline and not error_text_inline %}
{% include 'bootstrap3/layout/help_text.html' %}
{% endif %}
{% if error_text_inline %}
{% include 'bootstrap3/layout/field_errors.html' %}
{% else %}
{% include 'bootstrap3/layout/field_errors_block.html' %}
{% endif %}
{% if not help_text_inline %}
{% include 'bootstrap3/layout/help_text.html' %}
{% endif %}

View File

@ -0,0 +1,21 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox %}
<div id="div_{{ field.auto_id }}" class="checkbox{% if wrapper_class %} {{ wrapper_class }}{% endif %}">
<label for="{{ field.id_for_label }}" class="{% if field.field.required %} requiredField{% endif %}">
{% crispy_field field 'class' 'checkbox' %}
{{ field.label }}
</label>
</div>
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}">
<label for="{{ field.id_for_label }}" class="sr-only{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}
</label>
{% crispy_field field 'placeholder' field.label %}
</div>
{% endif %}
{% endif %}

View File

@ -0,0 +1,15 @@
<div id="{{ modal.css_id }}" class="modal fade {{ modal.css_class }}" {{ modal.flat_attrs }}>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title {{ modal.title_class }}" id="{{ modal.title_id }}">{{ modal.title }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" style="float: left">&times;</span>
</button>
</div>
<div class="modal-body">
{{ fields }}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
<div {% if multifield.css_id or errors %}id="{{ multifield.css_id }}"{% endif %}
{% if multifield.css_class %}class="{{ multifield.css_class }}"{% endif %}
{{ multifield.flat_attrs }}>
{% if form_show_errors %}
<div class="alert alert-danger" role="alert">
{% for field in multifield.bound_fields %}
{% if field.errors %}
{% for error in field.errors %}
<p id="error_{{ forloop.counter }}_{{ field.auto_id }}">{{ error }}</p>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if multifield.label_html %}
<p {% if multifield.label_class %}class="{{ multifield.label_class }}"{% endif %}>{{ multifield.label_html }}</p>
{% endif %}
<div class="multiField">
{{ fields_output }}
</div>
</div>

View File

@ -0,0 +1,24 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if form_show_errors and field.errors %} has-error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label and form_show_labels %}
<label for="{{ field.id_for_label }}" class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
<div class="controls {{ field_class }}">
<div class="input-group">
{% if crispy_prepended_text %}<span class="input-group-addon{% if active %} active{% endif %}{% if input_size %} {{ input_size }}{% endif %}">{{ crispy_prepended_text }}</span>{% endif %}
{% crispy_field field 'class' 'form-control' %}
{% if crispy_appended_text %}<span class="input-group-addon{% if active %} active{% endif %}{% if input_size %} {{ input_size }}{% endif %}">{{ crispy_appended_text }}</span>{% endif %}
</div>
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
</div>
</div>
{% endif %}

View File

@ -0,0 +1,21 @@
{% load crispy_forms_filters %}
{% load l10n %}
<div class="controls {{ field_class }}"{% if flat_attrs %} {{ flat_attrs }}{% endif %}>
{% include 'bootstrap3/layout/field_errors_block.html' %}
{% for group, options, index in field|optgroups %}
{% if group %}<strong>{{ group }}</strong>{% endif %}
{% for option in options %}
{% if not inline_class %}<div class="radio">{% endif %}
<label for="{{ option.attrs.id }}" class="{% if inline_class %}radio-{{ inline_class }}{% endif %}">
<input type="radio" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "bootstrap3/layout/attrs.html" with widget=option %}>
{{ option.label|unlocalize }}
</label>
{% if not inline_class %}</div>{% endif %}
{% endfor %}
{% endfor %}
{% include 'bootstrap3/layout/help_text.html' %}
</div>

View File

@ -0,0 +1,14 @@
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if form_show_errors and field.errors %} has-error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label %}
<label for="{{ field.auto_id }}" class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
{% include 'bootstrap3/layout/radioselect.html' %}
</div>
{% endif %}

View File

@ -0,0 +1,3 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="row {{ div.css_class|default:'' }}" {{ div.flat_attrs }}>
{{ fields }}
</div>

View File

@ -0,0 +1 @@
<li class="tab-pane{% if 'active' in link.css_class %} active{% endif %}"><a href="#{{ link.css_id }}" data-toggle="tab">{{ link.name|capfirst }}{% if tab.errors %}!{% endif %}</a></li>

View File

@ -0,0 +1,6 @@
<ul{% if tabs.css_id %} id="{{ tabs.css_id }}"{% endif %} class="nav nav-tabs">
{{ links }}
</ul>
<div class="tab-content panel-body">
{{ content }}
</div>

View File

@ -0,0 +1,10 @@
{% load crispy_forms_field %}
<div id="div_{{ field.auto_id }}" class="form-group{% if form_show_errors and field.errors %} error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
<label class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}</label>
<div class="controls {{ field_class }}">
{% crispy_field field 'disabled' 'disabled' %}
{% include 'bootstrap3/layout/help_text.html' %}
</div>
</div>

View File

@ -0,0 +1,39 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox %}
{% if field.errors %}<div class="has-error">{% endif %}
<div class="checkbox">
{% if field.label %}
<label for="{{ field.id_for_label }}"{% if labelclass %} class="{{ labelclass }}"{% endif %}>
{% endif %}
{% crispy_field field %}
{{ field.label }}
{% if field.label %}
</label>
{% endif %}
{% if field.help_text %}
<span id="help_{{ field.auto_id }}" class="help-block">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% if field.errors %}</div>{% endif %}
{% else %}
<div class="form-group {% if field.errors %}has-error{% endif %}">
{% if field.label %}
<label class="control-label" for="{{ field.id_for_label }}"{% if labelclass %} class="{{ labelclass }}"{% endif %}>
{{ field.label }}
</label>
{% endif %}
{% crispy_field field %}
{% if field.help_text %}
<span id="help_{{ field.auto_id }}" class="help-block">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% endif %}
{% endif %}

View File

@ -0,0 +1,57 @@
{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% load crispy_forms_field %}
{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
{% if formset_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
<div>
{{ formset.management_form|crispy }}
</div>
<table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-striped table-condensed">
<thead>
{% if formset.readonly and not formset.queryset.exists %}
{% else %}
<tr>
{% for field in formset.forms.0 %}
{% if field.label and not field.is_hidden %}
<th for="{{ field.auto_id }}" class="control-label {% if field.field.required %}requiredField{% endif %}">
{{ field.label }}{% if field.field.required and not field|is_checkbox %}<span class="asteriskField">*</span>{% endif %}
</th>
{% endif %}
{% endfor %}
</tr>
{% endif %}
</thead>
<tbody>
<tr class="hidden empty-form">
{% for field in formset.empty_form %}
{% include 'bootstrap3/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% for form in formset %}
{% if form_show_errors and not form.is_extra %}
{% include "bootstrap3/errors.html" %}
{% endif %}
<tr>
{% for field in form %}
{% include 'bootstrap3/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% include "bootstrap3/inputs.html" %}
{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}

View File

@ -0,0 +1,11 @@
{% load crispy_forms_utils %}
{% specialspaceless %}
{% if include_media %}{{ form.media }}{% endif %}
{% if form_show_errors %}
{% include "bootstrap3/errors.html" %}
{% endif %}
{% for field in form %}
{% include field_template %}
{% endfor %}
{% endspecialspaceless %}

View File

@ -0,0 +1,8 @@
{% with formset.management_form as form %}
{% include 'bootstrap3/uni_form.html' %}
{% endwith %}
{% for form in formset %}
<div class="multiField">
{% include 'bootstrap3/uni_form.html' %}
</div>
{% endfor %}

View File

@ -0,0 +1,14 @@
{% load crispy_forms_utils %}
{% specialspaceless %}
{% if form_tag %}<form {{ flat_attrs }} method="{{ form_method }}" {% if form.is_multipart %} enctype="multipart/form-data"{% endif %}>{% endif %}
{% if form_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
{% include "bootstrap3/display_form.html" %}
{% include "bootstrap3/inputs.html" %}
{% if form_tag %}</form>{% endif %}
{% endspecialspaceless %}

View File

@ -0,0 +1,30 @@
{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
{% if formset_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
<div>
{{ formset.management_form|crispy }}
</div>
{% include "bootstrap3/errors_formset.html" %}
{% for form in formset %}
{% include "bootstrap3/display_form.html" %}
{% endfor %}
{% if inputs %}
<div class="form-actions">
{% for input in inputs %}
{% include "bootstrap3/layout/baseinput.html" %}
{% endfor %}
</div>
{% endif %}
{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}

View File

@ -0,0 +1,17 @@
<div class="card mb-2">
<div class="card-header" role="tab">
<h5 class="mb-0">
<a data-toggle="collapse" href="#{{ div.css_id }}" aria-expanded="true"
aria-controls="{{ div.css_id }}">
{{ div.name }}
</a>
</h5>
</div>
<div id="{{ div.css_id }}" class="collapse{% if div.active %} show{% endif %}" role="tabpanel"
aria-labelledby="{{ div.css_id }}" data-parent="#{{ div.data_parent }}">
<div class="card-body">
{{ fields }}
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
<div id="{{ accordion.css_id }}" role="tablist">
{{ content }}
</div>

View File

@ -0,0 +1,22 @@
{% for fieldset in form.fieldsets %}
<fieldset class="fieldset-{{ forloop.counter }} {{ fieldset.classes }}">
{% if fieldset.legend %}
<legend>{{ fieldset.legend }}</legend>
{% endif %}
{% if fieldset.description %}
<p class="description">{{ fieldset.description }}</p>
{% endif %}
{% for field in fieldset %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% include "bootstrap4/field.html" %}
{% endif %}
{% endfor %}
{% if not forloop.last or not fieldset_open %}
</fieldset>
{% endif %}
{% endfor %}

View File

@ -0,0 +1,9 @@
{% if form.form_html %}
{% if include_media %}{{ form.media }}{% endif %}
{% if form_show_errors %}
{% include "bootstrap4/errors.html" %}
{% endif %}
{{ form.form_html }}
{% else %}
{% include "bootstrap4/uni_form.html" %}
{% endif %}

View File

@ -0,0 +1,8 @@
{% if form.non_field_errors %}
<div class="alert alert-block alert-danger">
{% if form_error_title %}<h4 class="alert-heading">{{ form_error_title }}</h4>{% endif %}
<ul class="m-0">
{{ form.non_field_errors|unordered_list }}
</ul>
</div>
{% endif %}

View File

@ -0,0 +1,9 @@
{% if formset.non_form_errors %}
<div class="alert alert-block alert-danger">
{% if formset_error_title %}<h4 class="alert-heading">{{ formset_error_title }}</h4>{% endif %}
<ul class="m-0">
{{ formset.non_form_errors|unordered_list }}
</ul>
</div>
{% endif %}

View File

@ -0,0 +1,81 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox %}
<div class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}">
{% if label_class %}
<div class="{% for offset in bootstrap_checkbox_offsets %}{{ offset }} {% endfor %}{{ field_class }}">
{% endif %}
{% endif %}
<{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="{% if not field|is_checkbox %}form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% else %}{%if use_custom_control%}{% if tag != 'td' %}custom-control {%endif%} custom-checkbox{% else %}form-check{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label and not field|is_checkbox and form_show_labels %}
{# not field|is_radioselect in row below can be removed once Django 3.2 is no longer supported #}
<label {% if field.id_for_label and not field|is_radioselect %}for="{{ field.id_for_label }}" {% endif %}class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
{% if field|is_checkboxselectmultiple %}
{% include 'bootstrap4/layout/checkboxselectmultiple.html' %}
{% endif %}
{% if field|is_radioselect %}
{% include 'bootstrap4/layout/radioselect.html' %}
{% endif %}
{% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
{% if field|is_checkbox and form_show_labels %}
{%if use_custom_control%}
{% if field.errors %}
{% crispy_field field 'class' 'custom-control-input is-invalid' %}
{% else %}
{% crispy_field field 'class' 'custom-control-input' %}
{% endif %}
{% else %}
{% if field.errors %}
{% crispy_field field 'class' 'form-check-input is-invalid' %}
{% else %}
{% crispy_field field 'class' 'form-check-input' %}
{% endif %}
{% endif %}
<label for="{{ field.id_for_label }}" class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% include 'bootstrap4/layout/help_text_and_errors.html' %}
{% elif field|is_file and use_custom_control %}
{% include 'bootstrap4/layout/field_file.html' %}
{% else %}
<div{% if field_class %} class="{{ field_class }}"{% endif %}>
{% if field|is_select and use_custom_control %}
{% if field.errors %}
{% crispy_field field 'class' 'custom-select is-invalid' %}
{% else %}
{% crispy_field field 'class' 'custom-select' %}
{% endif %}
{% elif field|is_file %}
{% if field.errors %}
{% crispy_field field 'class' 'form-control-file is-invalid' %}
{% else %}
{% crispy_field field 'class' 'form-control-file' %}
{% endif %}
{% else %}
{% if field.errors %}
{% crispy_field field 'class' 'form-control is-invalid' %}
{% else %}
{% crispy_field field 'class' 'form-control' %}
{% endif %}
{% endif %}
{% include 'bootstrap4/layout/help_text_and_errors.html' %}
</div>
{% endif %}
{% endif %}
</{% if tag %}{{ tag }}{% else %}div{% endif %}>
{% if field|is_checkbox %}
{% if label_class %}
</div>
{% endif %}
</div>
{% endif %}
{% endif %}

View File

@ -0,0 +1,13 @@
{% if inputs %}
<div class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}">
{% if label_class %}
<div class="aab {{ label_class }}"></div>
{% endif %}
<div class="{{ field_class }}">
{% for input in inputs %}
{% include "bootstrap4/layout/baseinput.html" %}
{% endfor %}
</div>
</div>
{% endif %}

View File

@ -0,0 +1,4 @@
<div{% if alert.css_id %} id="{{ alert.css_id }}"{% endif %}{% if alert.css_class %} class="{{ alert.css_class }}"{% endif %}>
{% if dismiss %}<button type="button" class="close" data-dismiss="alert">&times;</button>{% endif %}
{{ content }}
</div>

View File

@ -0,0 +1 @@
{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}

View File

@ -0,0 +1,9 @@
<input type="{{ input.input_type }}"
name="{% if input.name|wordcount > 1 %}{{ input.name|slugify }}{% else %}{{ input.name }}{% endif %}"
value="{{ input.value }}"
{% if input.input_type != "hidden" %}
class="{{ input.field_classes }}"
id="{{ input.id }}"
{% endif %}
{{ input.flat_attrs }}
/>

View File

@ -0,0 +1 @@
<button {{ button.flat_attrs }}>{{ button.content }}</button>

View File

@ -0,0 +1,4 @@
<div {% if buttonholder.css_id %}id="{{ buttonholder.css_id }}"{% endif %}
class="buttonHolder{% if buttonholder.css_class %} {{ buttonholder.css_class }}{% endif %}">
{{ fields_output }}
</div>

View File

@ -0,0 +1,29 @@
{% load crispy_forms_filters %}
{% load l10n %}
<div {% if field_class %}class="{{ field_class }}"{% endif %}{% if flat_attrs %} {{ flat_attrs }}{% endif %}>
{% for group, options, index in field|optgroups %}
{% if group %}<strong>{{ group }}</strong>{% endif %}
{% for option in options %}
<div class="{%if use_custom_control%}custom-control custom-checkbox{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}">
<input type="checkbox" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{% if field.errors %} is-invalid{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "bootstrap4/layout/attrs.html" with widget=option %}>
<label class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}" for="{{ option.attrs.id }}">
{{ option.label|unlocalize }}
</label>
{% if field.errors and forloop.last and not inline_class and forloop.parentloop.last %}
{% include 'bootstrap4/layout/field_errors_block.html' %}
{% endif %}
</div>
{% endfor %}
{% endfor %}
{% if field.errors and inline_class %}
<div class="w-100 {%if use_custom_control%}custom-control custom-checkbox{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}">
{# the following input is only meant to allow boostrap to render the error message as it has to be after an invalid input. As the input has no name, no data will be sent. #}
<input type="checkbox" class="custom-control-input {% if field.errors %}is-invalid{%endif%}">
{% include 'bootstrap4/layout/field_errors_block.html' %}
</div>
{% endif %}
{% include 'bootstrap4/layout/help_text.html' %}
</div>

View File

@ -0,0 +1,14 @@
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label %}
<label {% if field.id_for_label %}for="{{ field.id_for_label }}" {% endif %} class="{{ label_class }}{% if not inline_class %} col-form-label{% endif %}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
{% include 'bootstrap4/layout/checkboxselectmultiple.html' %}
</div>
{% endif %}

View File

@ -0,0 +1,6 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %}
class="{% if 'col' in div.css_class %}{{ div.css_class|default:'' }}{% else %}col-md {{ div.css_class|default:'' }}{% endif %}" {{ div.flat_attrs }}>
{{ fields }}
</div>

View File

@ -0,0 +1,4 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %}
{% if div.css_class %}class="{{ div.css_class }}"{% endif %} {{ div.flat_attrs }}>
{{ fields }}
</div>

View File

@ -0,0 +1,5 @@
{% if form_show_errors and field.errors %}
{% for error in field.errors %}
<span id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="invalid-feedback"><strong>{{ error }}</strong></span>
{% endfor %}
{% endif %}

View File

@ -0,0 +1,5 @@
{% if form_show_errors and field.errors %}
{% for error in field.errors %}
<p id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="invalid-feedback"><strong>{{ error }}</strong></p>
{% endfor %}
{% endif %}

View File

@ -0,0 +1,52 @@
{% load crispy_forms_field %}
<div class="{{ field_class }} mb-2">
{% for widget in field.subwidgets %}
{% if widget.data.is_initial %}
<div class="input-group mb-2">
<div class="input-group-prepend">
<span class="input-group-text">{{ widget.data.initial_text }}</span>
</div>
<div class="form-control d-flex h-auto">
<span class="text-break" style="flex-grow:1;min-width:0">
<a href="{{ field.value.url }}">{{ field.value }}</a>
</span>
{% if not widget.data.required %}
<span class="align-self-center ml-2">
<span class="custom-control custom-checkbox">
<input type="checkbox" name="{{ widget.data.checkbox_name }}" id="{{ widget.data.checkbox_id }}" class="custom-control-input"{% if field.field.disabled %} disabled{% endif %} >
<label class="custom-control-label mb-0" for="{{ widget.data.checkbox_id }}">{{ widget.data.clear_checkbox_label }}</label>
</span>
</span>
{% endif %}
</div>
</div>
<div class="input-group mb-0">
<div class="input-group-prepend">
<span class="input-group-text">{{ widget.data.input_text }}</span>
</div>
{% endif %}
<div class="form-control custom-file{% if field.errors %} is-invalid{%endif%}" style="border:0">
<input type="{{ widget.data.type }}" name="{{ widget.data.name }}" class="custom-file-input{% if widget.data.attrs.class %} {{ widget.data.attrs.class }}{% endif %}{% if field.errors %} is-invalid{%endif%}"{% if field.field.disabled %} disabled{% endif %}{% for name, value in widget.data.attrs.items %}{% if value is not False and name != 'class' %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}>
<label class="custom-file-label text-truncate" for="{{ field.id_for_label }}">---</label>
<script type="text/javascript" id="script-{{ field.id_for_label }}">
document.getElementById("script-{{ field.id_for_label }}").parentNode.querySelector('.custom-file-input').onchange = function (e){
var filenames = "";
for (let i=0;i<e.target.files.length;i++){
filenames+=(i>0?", ":"")+e.target.files[i].name;
}
e.target.parentNode.querySelector('.custom-file-label').textContent=filenames;
}
</script>
</div>
{% if not widget.data.is_initial %}
{% include 'bootstrap4/layout/help_text_and_errors.html' %}
{% endif %}
{% if widget.data.is_initial %}
</div>
<div class="input-group mb-0">
{% include 'bootstrap4/layout/help_text_and_errors.html' %}
</div>
{% endif %}
{% endfor %}
</div>

View File

@ -0,0 +1,17 @@
{% load crispy_forms_field %}
<div{% if div.css_id %} id="{{ div.css_id }}"{% endif %} class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}{% if div.css_class %} {{ div.css_class }}{% endif %}" {{ div.flat_attrs }}>
{% if field.label and form_show_labels %}
<label for="{{ field.id_for_label }}" class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
<div class="{{ field_class }}">
<div class="input-group {% if div.input_size %} {{ div.input_size }}{% endif %}">
{% crispy_field field 'class' 'form-control' %}
<span class="input-group-append{% if active %} active{% endif %}">{{ buttons }}</span>
</div>
{% include 'bootstrap4/layout/help_text_and_errors.html' %}
</div>
</div>

View File

@ -0,0 +1,6 @@
<fieldset {% if fieldset.css_id %}id="{{ fieldset.css_id }}"{% endif %}
{% if fieldset.css_class%}class="{{ fieldset.css_class }}"{% endif %}
{{ fieldset.flat_attrs }}>
{% if legend %}<legend>{{ legend }}</legend>{% endif %}
{{ fields }}
</fieldset>

View File

@ -0,0 +1,9 @@
<div{% if formactions.flat_attrs %} {{ formactions.flat_attrs }}{% endif %} class="form-group{% if 'form-horizontal' in form_class %} row{% endif %} {{ formactions.css_class }}" {% if formactions.id %} id="{{ formactions.id }}"{% endif %}>
{% if label_class %}
<div class="aab {{ label_class }}"></div>
{% endif %}
<div class="{{ field_class }}">
{{ fields_output }}
</div>
</div>

View File

@ -0,0 +1,7 @@
{% if field.help_text %}
{% if help_text_inline %}
<span id="hint_{{ field.auto_id }}" class="text-muted">{{ field.help_text|safe }}</span>
{% else %}
<small id="hint_{{ field.auto_id }}" class="form-text text-muted">{{ field.help_text|safe }}</small>
{% endif %}
{% endif %}

View File

@ -0,0 +1,13 @@
{% if help_text_inline and not error_text_inline %}
{% include 'bootstrap4/layout/help_text.html' %}
{% endif %}
{% if error_text_inline %}
{% include 'bootstrap4/layout/field_errors.html' %}
{% else %}
{% include 'bootstrap4/layout/field_errors_block.html' %}
{% endif %}
{% if not help_text_inline %}
{% include 'bootstrap4/layout/help_text.html' %}
{% endif %}

View File

@ -0,0 +1,29 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field|is_checkbox %}
<div id="div_{{ field.auto_id }}" class="form-check form-check-inline{% if wrapper_class %} {{ wrapper_class }}{% endif %}">
<label for="{{ field.id_for_label }}" class="form-check-label{% if field.field.required %} requiredField{% endif %}">
{% if field.errors %}
{% crispy_field field 'class' 'form-check-input is-invalid' %}
{% else %}
{% crispy_field field 'class' 'form-check-input' %}
{% endif %}
{{ field.label }}
</label>
</div>
{% else %}
<div id="div_{{ field.auto_id }}" class="input-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}">
<label for="{{ field.id_for_label }}" class="sr-only{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}
</label>
{% if field.errors %}
{% crispy_field field 'placeholder' field.label 'class' 'form-control is-invalid' %}
{% else %}
{% crispy_field field 'placeholder' field.label 'class' 'form-control' %}
{% endif %}
</div>
{% endif %}
{% endif %}

View File

@ -0,0 +1,15 @@
<div id="{{ modal.css_id }}" class="modal fade {{ modal.css_class }}" {{ modal.flat_attrs }}>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title {{ modal.title_class }}" id="{{ modal.title_id }}">{{ modal.title }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" style="float: left">&times;</span>
</button>
</div>
<div class="modal-body">
{{ fields }}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field.label %}
<label for="{{ field.id_for_label }}"{% if labelclass %} class="{{ labelclass }}"{% endif %}>
{% endif %}
{% if field|is_checkbox %}
{% crispy_field field %}
{% endif %}
{% if field.label %}
{{ field.label }}
{% endif %}
{% if not field|is_checkbox %}
{% crispy_field field %}
{% endif %}
{% if field.label %}
</label>
{% endif %}
{% endif %}

View File

@ -0,0 +1,51 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if 'form-horizontal' in form_class %} row{% endif %}{% if form_group_wrapper_class %} {{ form_group_wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label and form_show_labels %}
<label for="{{ field.id_for_label }}" class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
<div class="{{ field_class }}">
<div class="input-group{% if input_size %} {{ input_size }}{% endif %}">
{% if crispy_prepended_text %}
<div class="input-group-prepend{% if active %} active{% endif %}">
<span class="input-group-text">{{ crispy_prepended_text }}</span>
</div>
{% endif %}
{% if field|is_select and use_custom_control %}
{% if field.errors %}
{% crispy_field field 'class' 'custom-select is-invalid' %}
{% else %}
{% crispy_field field 'class' 'custom-select' %}
{% endif %}
{% else %}
{% if field.errors %}
{% crispy_field field 'class' 'form-control is-invalid' %}
{% else %}
{% crispy_field field 'class' 'form-control' %}
{% endif %}
{% endif %}
{% if crispy_appended_text %}
<div class="input-group-append{% if active %} active{% endif %}">
<span class="input-group-text">{{ crispy_appended_text }}</span>
</div>
{% endif %}
{% if error_text_inline %}
{% include 'bootstrap4/layout/field_errors.html' %}
{% else %}
{% include 'bootstrap4/layout/field_errors_block.html' %}
{% endif %}
</div>
{% if not help_text_inline %}
{% include 'bootstrap4/layout/help_text.html' %}
{% endif %}
</div>
</div>
{% endif %}

View File

@ -0,0 +1,29 @@
{% load crispy_forms_filters %}
{% load l10n %}
<div {% if field_class %}class="{{ field_class }}"{% endif %}{% if flat_attrs %} {{ flat_attrs }}{% endif %}>
{% for group, options, index in field|optgroups %}
{% if group %}<strong>{{ group }}</strong>{% endif %}
{% for option in options %}
<div class="{%if use_custom_control%}custom-control custom-radio{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}">
<input type="radio" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{% if field.errors %} is-invalid{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "bootstrap4/layout/attrs.html" with widget=option %}>
<label class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}" for="{{ option.attrs.id }}">
{{ option.label|unlocalize }}
</label>
{% if field.errors and forloop.last and not inline_class and forloop.parentloop.last %}
{% include 'bootstrap4/layout/field_errors_block.html' %}
{% endif %}
</div>
{% endfor %}
{% endfor %}
{% if field.errors and inline_class %}
<div class="w-100 {%if use_custom_control%}custom-control custom-radio{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}">
{# the following input is only meant to allow boostrap to render the error message as it has to be after an invalid input. As the input has no name, no data will be sent. #}
<input type="checkbox" class="custom-control-input {% if field.errors %}is-invalid{%endif%}">
{% include 'bootstrap4/layout/field_errors_block.html' %}
</div>
{% endif %}
{% include 'bootstrap4/layout/help_text.html' %}
</div>

View File

@ -0,0 +1,14 @@
{% if field.is_hidden %}
{{ field }}
{% else %}
<div id="div_{{ field.auto_id }}" class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label %}
<label {% if field.id_for_label %}for="{{ field.id_for_label }}" {% endif %}class="{{ label_class }}{% if not inline_class %} col-form-label{% endif %}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
{% include 'bootstrap4/layout/radioselect.html' %}
</div>
{% endif %}

View File

@ -0,0 +1,3 @@
<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="form-row {{ div.css_class|default:'' }}" {{ div.flat_attrs }}>
{{ fields }}
</div>

View File

@ -0,0 +1 @@
<li class="nav-item"><a class="nav-link{% if 'active' in link.css_class %} active{% endif %}" href="#{{ link.css_id }}" data-toggle="tab">{{ link.name|capfirst }}{% if tab.errors %}!{% endif %}</a></li>

View File

@ -0,0 +1,6 @@
<ul{% if tabs.css_id %} id="{{ tabs.css_id }}"{% endif %} class="nav nav-tabs">
{{ links }}
</ul>
<div class="tab-content card-body">
{{ content }}
</div>

View File

@ -0,0 +1,14 @@
{% load crispy_forms_field %}
<div id="div_{{ field.auto_id }}" class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if form_show_errors and field.errors %} error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
<label class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}</label>
<div class="{{ field_class }}">
{% if field|is_select and use_custom_control %}
{% crispy_field field 'class' 'custom-select' 'disabled' 'disabled' %}
{% elif field|is_file %}
{% crispy_field field 'class' 'form-control-file' 'disabled' 'disabled' %}
{% else %}
{% crispy_field field 'class' 'form-control' 'disabled' 'disabled' %}
{% endif %}
{% include 'bootstrap4/layout/help_text.html' %}
</div>
</div>

View File

@ -0,0 +1,57 @@
{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% load crispy_forms_field %}
{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
{% if formset_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
<div>
{{ formset.management_form|crispy }}
</div>
<table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-striped table-sm">
<thead>
{% if formset.readonly and not formset.queryset.exists %}
{% else %}
<tr>
{% for field in formset.forms.0 %}
{% if field.label and not field.is_hidden %}
<th for="{{ field.auto_id }}" class="col-form-label {% if field.field.required %}requiredField{% endif %}">
{{ field.label }}{% if field.field.required and not field|is_checkbox %}<span class="asteriskField">*</span>{% endif %}
</th>
{% endif %}
{% endfor %}
</tr>
{% endif %}
</thead>
<tbody>
<tr class="d-none empty-form">
{% for field in formset.empty_form %}
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% for form in formset %}
{% if form_show_errors and not form.is_extra %}
{% include "bootstrap4/errors.html" %}
{% endif %}
<tr>
{% for field in form %}
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% include "bootstrap4/inputs.html" %}
{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}

View File

@ -0,0 +1,11 @@
{% load crispy_forms_utils %}
{% specialspaceless %}
{% if include_media %}{{ form.media }}{% endif %}
{% if form_show_errors %}
{% include "bootstrap4/errors.html" %}
{% endif %}
{% for field in form %}
{% include field_template %}
{% endfor %}
{% endspecialspaceless %}

View File

@ -0,0 +1,8 @@
{% with formset.management_form as form %}
{% include 'bootstrap4/uni_form.html' %}
{% endwith %}
{% for form in formset %}
<div class="multiField">
{% include 'bootstrap4/uni_form.html' %}
</div>
{% endfor %}

View File

@ -0,0 +1,14 @@
{% load crispy_forms_utils %}
{% specialspaceless %}
{% if form_tag %}<form {{ flat_attrs }} method="{{ form_method }}" {% if form.is_multipart %} enctype="multipart/form-data"{% endif %}>{% endif %}
{% if form_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
{% include "bootstrap4/display_form.html" %}
{% include "bootstrap4/inputs.html" %}
{% if form_tag %}</form>{% endif %}
{% endspecialspaceless %}

View File

@ -0,0 +1,30 @@
{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
{% if formset_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
<div>
{{ formset.management_form|crispy }}
</div>
{% include "bootstrap4/errors_formset.html" %}
{% for form in formset %}
{% include "bootstrap4/display_form.html" %}
{% endfor %}
{% if inputs %}
<div class="form-actions">
{% for input in inputs %}
{% include "bootstrap4/layout/baseinput.html" %}
{% endfor %}
</div>
{% endif %}
{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}

View File

View File

@ -0,0 +1,168 @@
from django import forms, template
from django.conf import settings
from django.template import Context, Variable, loader
from crispy_forms.utils import get_template_pack
register = template.Library()
@register.filter
def is_checkbox(field):
return isinstance(field.field.widget, forms.CheckboxInput)
@register.filter
def is_password(field):
return isinstance(field.field.widget, forms.PasswordInput)
@register.filter
def is_radioselect(field):
return isinstance(field.field.widget, forms.RadioSelect) and not isinstance(
field.field.widget, forms.CheckboxSelectMultiple
)
@register.filter
def is_select(field):
return isinstance(field.field.widget, forms.Select)
@register.filter
def is_checkboxselectmultiple(field):
return isinstance(field.field.widget, forms.CheckboxSelectMultiple)
@register.filter
def is_file(field):
return isinstance(field.field.widget, forms.FileInput)
@register.filter
def is_clearable_file(field):
return isinstance(field.field.widget, forms.ClearableFileInput)
@register.filter
def is_multivalue(field):
return isinstance(field.field.widget, forms.MultiWidget)
@register.filter
def classes(field):
"""
Returns CSS classes of a field
"""
return field.widget.attrs.get("class", None)
@register.filter
def css_class(field):
"""
Returns widgets class name in lowercase
"""
return field.field.widget.__class__.__name__.lower()
def pairwise(iterable):
"""s -> (s0,s1), (s2,s3), (s4, s5), ..."""
a = iter(iterable)
return zip(a, a)
class CrispyFieldNode(template.Node):
def __init__(self, field, attrs):
self.field = field
self.attrs = attrs
def render(self, context):
# Nodes are not threadsafe so we must store and look up our instance
# variables in the current rendering context first
if self not in context.render_context:
context.render_context[self] = (
Variable(self.field),
self.attrs,
)
field, attrs = context.render_context[self]
field = field.resolve(context)
# There are special django widgets that wrap actual widgets,
# such as forms.widgets.MultiWidget, admin.widgets.RelatedFieldWidgetWrapper
widgets = getattr(field.field.widget, "widgets", [getattr(field.field.widget, "widget", field.field.widget)])
if isinstance(attrs, dict):
attrs = [attrs] * len(widgets)
converters = getattr(settings, "CRISPY_CLASS_CONVERTERS", {})
for widget, attr in zip(widgets, attrs):
class_name = widget.__class__.__name__.lower()
class_name = converters.get(class_name, class_name)
css_class = widget.attrs.get("class", "")
if css_class:
if css_class.find(class_name) == -1:
css_class += " %s" % class_name
else:
css_class = class_name
widget.attrs["class"] = css_class
for attribute_name, attribute in attr.items():
attribute_name = Variable(attribute_name).resolve(context)
attributes = Variable(attribute).resolve(context)
if attribute_name in widget.attrs:
# multiple attribtes are in a single string, e.g.
# "form-control is-invalid"
for attr in attributes.split():
if attr not in widget.attrs[attribute_name].split():
widget.attrs[attribute_name] += " " + attr
else:
widget.attrs[attribute_name] = attributes
return str(field)
@register.tag(name="crispy_field")
def crispy_field(parser, token):
"""
{% crispy_field field attrs %}
"""
token = token.split_contents()
field = token.pop(1)
attrs = {}
# We need to pop tag name, or pairwise would fail
token.pop(0)
for attribute_name, value in pairwise(token):
attrs[attribute_name] = value
return CrispyFieldNode(field, attrs)
@register.simple_tag()
def crispy_addon(field, append="", prepend="", form_show_labels=True):
"""
Renders a form field using bootstrap's prepended or appended text::
{% crispy_addon form.my_field prepend="$" append=".00" %}
You can also just prepend or append like so
{% crispy_addon form.my_field prepend="$" %}
{% crispy_addon form.my_field append=".00" %}
"""
if field:
context = Context({"field": field, "form_show_errors": True, "form_show_labels": form_show_labels})
template = loader.get_template("%s/layout/prepended_appended_text.html" % get_template_pack())
context["crispy_prepended_text"] = prepend
context["crispy_appended_text"] = append
if not prepend and not append:
raise TypeError("Expected a prepend and/or append argument")
context = context.flatten()
return template.render(context)

View File

@ -0,0 +1,162 @@
from functools import lru_cache
from django import template
from django.conf import settings
from django.forms import boundfield
from django.forms.formsets import BaseFormSet
from django.template import Context
from django.template.loader import get_template
from django.utils.safestring import mark_safe
from crispy_forms.exceptions import CrispyError
from crispy_forms.utils import TEMPLATE_PACK, flatatt
@lru_cache()
def uni_formset_template(template_pack=TEMPLATE_PACK):
return get_template("%s/uni_formset.html" % template_pack)
@lru_cache()
def uni_form_template(template_pack=TEMPLATE_PACK):
return get_template("%s/uni_form.html" % template_pack)
register = template.Library()
@register.filter(name="crispy")
def as_crispy_form(form, template_pack=TEMPLATE_PACK, label_class="", field_class=""):
"""
The original and still very useful way to generate a div elegant form/formset::
{% load crispy_forms_tags %}
<form class="my-class" method="post">
{% csrf_token %}
{{ myform|crispy }}
</form>
or, if you want to explicitly set the template pack::
{{ myform|crispy:"bootstrap4" }}
In ``bootstrap3`` or ``bootstrap4`` for horizontal forms you can do::
{{ myform|label_class:"col-lg-2",field_class:"col-lg-8" }}
"""
c = Context(
{
"field_class": field_class,
"field_template": "%s/field.html" % template_pack,
"form_show_errors": True,
"form_show_labels": True,
"label_class": label_class,
}
).flatten()
if isinstance(form, BaseFormSet):
template = uni_formset_template(template_pack)
c["formset"] = form
else:
template = uni_form_template(template_pack)
c["form"] = form
return template.render(c)
@register.filter(name="as_crispy_errors")
def as_crispy_errors(form, template_pack=TEMPLATE_PACK):
"""
Renders only form errors the same way as django-crispy-forms::
{% load crispy_forms_tags %}
{{ form|as_crispy_errors }}
or::
{{ form|as_crispy_errors:"bootstrap4" }}
"""
if isinstance(form, BaseFormSet):
template = get_template("%s/errors_formset.html" % template_pack)
c = Context({"formset": form}).flatten()
else:
template = get_template("%s/errors.html" % template_pack)
c = Context({"form": form}).flatten()
return template.render(c)
@register.filter(name="as_crispy_field")
def as_crispy_field(field, template_pack=TEMPLATE_PACK, label_class="", field_class=""):
"""
Renders a form field like a django-crispy-forms field::
{% load crispy_forms_tags %}
{{ form.field|as_crispy_field }}
or::
{{ form.field|as_crispy_field:"bootstrap4" }}
"""
if not isinstance(field, boundfield.BoundField) and settings.DEBUG:
raise CrispyError("|as_crispy_field got passed an invalid or inexistent field")
attributes = {
"field": field,
"form_show_errors": True,
"form_show_labels": True,
"label_class": label_class,
"field_class": field_class,
}
helper = getattr(field.form, "helper", None)
template_path = None
if helper is not None:
attributes.update(helper.get_attributes(template_pack))
template_path = helper.field_template
if not template_path:
template_path = "%s/field.html" % template_pack
template = get_template(template_path)
c = Context(attributes).flatten()
return template.render(c)
@register.filter(name="flatatt")
def flatatt_filter(attrs):
return mark_safe(flatatt(attrs))
@register.filter
def optgroups(field):
"""
A template filter to help rendering of fields with optgroups.
Returns:
A tuple of label, option, index
label: Group label for grouped optgroups (`None` if inputs are not
grouped).
option: A dict containing information to render the option::
{
"name": "checkbox_select_multiple",
"value": 1,
"label": 1,
"selected": False,
"index": "0",
"attrs": {"id": "id_checkbox_select_multiple_0"},
"type": "checkbox",
"template_name": "django/forms/widgets/checkbox_option.html",
"wrap_label": True,
}
index: Group index
"""
id_ = field.field.widget.attrs.get("id") or field.auto_id
attrs = {"id": id_} if id_ else {}
attrs = field.build_widget_attrs(attrs)
values = field.field.widget.format_value(field.value())
return field.field.widget.optgroups(field.html_name, values, attrs)

View File

@ -0,0 +1,267 @@
from functools import lru_cache
from django import template
from django.conf import settings
from django.forms.formsets import BaseFormSet
from django.template.loader import get_template
from crispy_forms.helper import FormHelper
from crispy_forms.utils import TEMPLATE_PACK, get_template_pack
register = template.Library()
# We import the filters, so they are available when doing load crispy_forms_tags
from crispy_forms.templatetags.crispy_forms_filters import * # NOQA: F403,F401, E402 isort:skip
class ForLoopSimulator:
"""
Simulates a forloop tag, precisely::
{% for form in formset.forms %}
If `{% crispy %}` is rendering a formset with a helper, We inject a `ForLoopSimulator` object
in the context as `forloop` so that formset forms can do things like::
Fieldset("Item {{ forloop.counter }}", [...])
HTML("{% if forloop.first %}First form text{% endif %}"
"""
def __init__(self, formset):
self.len_values = len(formset.forms)
# Shortcuts for current loop iteration number.
self.counter = 1
self.counter0 = 0
# Reverse counter iteration numbers.
self.revcounter = self.len_values
self.revcounter0 = self.len_values - 1
# Boolean values designating first and last times through loop.
self.first = True
self.last = 0 == self.len_values - 1
def iterate(self):
"""
Updates values as if we had iterated over the for
"""
self.counter += 1
self.counter0 += 1
self.revcounter -= 1
self.revcounter0 -= 1
self.first = False
self.last = self.revcounter0 == self.len_values - 1
class BasicNode(template.Node):
"""
Basic Node object that we can rely on for Node objects in normal
template tags. I created this because most of the tags we'll be using
will need both the form object and the helper string. This handles
both the form object and parses out the helper string into attributes
that templates can easily handle.
"""
def __init__(self, form, helper, template_pack=None):
self.form = form
self.helper = helper
self.template_pack = template_pack or get_template_pack()
def get_render(self, context):
"""
Returns a `Context` object with all the necessary stuff for rendering the form
:param context: `django.template.Context` variable holding the context for the node
`self.form` and `self.helper` are resolved into real Python objects resolving them
from the `context`. The `actual_form` can be a form or a formset. If it's a formset
`is_formset` is set to True. If the helper has a layout we use it, for rendering the
form or the formset's forms.
"""
# Nodes are not thread safe in multithreaded environments
# https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#thread-safety-considerations
if self not in context.render_context:
context.render_context[self] = (
template.Variable(self.form),
template.Variable(self.helper) if self.helper else None,
)
form, helper = context.render_context[self]
actual_form = form.resolve(context)
if self.helper is not None:
helper = helper.resolve(context)
else:
# If the user names the helper within the form `helper` (standard), we use it
# This allows us to have simplified tag syntax: {% crispy form %}
helper = FormHelper() if not hasattr(actual_form, "helper") else actual_form.helper
# use template_pack from helper, if defined
try:
if helper.template_pack:
self.template_pack = helper.template_pack
except AttributeError:
pass
self.actual_helper = helper
# We get the response dictionary
is_formset = isinstance(actual_form, BaseFormSet)
response_dict = self.get_response_dict(helper, context, is_formset)
node_context = context.__copy__()
node_context.update({"is_bound": actual_form.is_bound})
node_context.update(response_dict)
final_context = node_context.__copy__()
# If we have a helper's layout we use it, for the form or the formset's forms
if helper and helper.layout:
if not is_formset:
actual_form.form_html = helper.render_layout(
actual_form, node_context, template_pack=self.template_pack
)
else:
forloop = ForLoopSimulator(actual_form)
helper.render_hidden_fields = True
for form in actual_form:
node_context.update({"forloop": forloop})
node_context.update({"formset_form": form})
form.form_html = helper.render_layout(form, node_context, template_pack=self.template_pack)
forloop.iterate()
if is_formset:
final_context["formset"] = actual_form
else:
final_context["form"] = actual_form
return final_context
def get_response_dict(self, helper, context, is_formset):
"""
Returns a dictionary with all the parameters necessary to render the form/formset in a template.
:param context: `django.template.Context` for the node
:param is_formset: Boolean value. If set to True, indicates we are working with a formset.
"""
if not isinstance(helper, FormHelper):
raise TypeError("helper object provided to {% crispy %} tag must be a crispy.helper.FormHelper object.")
attrs = helper.get_attributes(template_pack=self.template_pack)
form_type = "form"
if is_formset:
form_type = "formset"
# We take form/formset parameters from attrs if they are set, otherwise we use defaults
response_dict = {
"%s_action" % form_type: attrs["attrs"].get("action", ""),
"%s_attrs" % form_type: attrs.get("attrs", ""),
"%s_class" % form_type: attrs["attrs"].get("class", ""),
"%s_id" % form_type: attrs["attrs"].get("id", ""),
"%s_method" % form_type: attrs.get("form_method", "post"),
"%s_tag" % form_type: attrs.get("form_tag", True),
"disable_csrf": attrs.get("disable_csrf", False),
"error_text_inline": attrs.get("error_text_inline", True),
"field_class": attrs.get("field_class", ""),
"field_template": attrs.get("field_template", ""),
"flat_attrs": attrs.get("flat_attrs", ""),
"form_error_title": attrs.get("form_error_title", None),
"form_show_errors": attrs.get("form_show_errors", True),
"form_show_labels": attrs.get("form_show_labels", True),
"formset_error_title": attrs.get("formset_error_title", None),
"help_text_inline": attrs.get("help_text_inline", False),
"include_media": attrs.get("include_media", True),
"inputs": attrs.get("inputs", []),
"is_formset": is_formset,
"label_class": attrs.get("label_class", ""),
"template_pack": self.template_pack,
}
# Handles custom attributes added to helpers
for attribute_name, value in attrs.items():
if attribute_name not in response_dict:
response_dict[attribute_name] = value
if "csrf_token" in context:
response_dict["csrf_token"] = context["csrf_token"]
return response_dict
@lru_cache()
def whole_uni_formset_template(template_pack=TEMPLATE_PACK):
return get_template("%s/whole_uni_formset.html" % template_pack)
@lru_cache()
def whole_uni_form_template(template_pack=TEMPLATE_PACK):
return get_template("%s/whole_uni_form.html" % template_pack)
class CrispyFormNode(BasicNode):
def render(self, context):
c = self.get_render(context).flatten()
if self.actual_helper is not None and getattr(self.actual_helper, "template", False):
template = get_template(self.actual_helper.template)
else:
if c["is_formset"]:
template = whole_uni_formset_template(self.template_pack)
else:
template = whole_uni_form_template(self.template_pack)
return template.render(c)
# {% crispy %} tag
@register.tag(name="crispy")
def do_uni_form(parser, token):
"""
You need to pass in at least the form/formset object, and can also pass in the
optional `crispy_forms.helpers.FormHelper` object.
helper (optional): A `crispy_forms.helper.FormHelper` object.
Usage::
{% load crispy_tags %}
{% crispy form form.helper %}
You can also provide the template pack as the third argument::
{% crispy form form.helper 'bootstrap' %}
If the `FormHelper` attribute is named `helper` you can simply do::
{% crispy form %}
{% crispy form 'bootstrap' %}
"""
tokens = token.split_contents()
form = tokens.pop(1)
helper = None
template_pack = "'%s'" % get_template_pack()
# {% crispy form helper %}
try:
helper = tokens.pop(1)
except IndexError:
pass
# {% crispy form helper 'bootstrap' %}
try:
template_pack = tokens.pop(1)
except IndexError:
pass
# {% crispy form 'bootstrap' %}
if helper is not None and isinstance(helper, str) and ("'" in helper or '"' in helper):
template_pack = helper
helper = None
if template_pack is not None:
template_pack = template_pack[1:-1]
ALLOWED_TEMPLATE_PACKS = getattr(
settings, "CRISPY_ALLOWED_TEMPLATE_PACKS", ("uni_form", "bootstrap3", "bootstrap4")
)
if template_pack not in ALLOWED_TEMPLATE_PACKS:
raise template.TemplateSyntaxError(
"crispy tag's template_pack argument should be in %s" % str(ALLOWED_TEMPLATE_PACKS)
)
return CrispyFormNode(form, helper, template_pack=template_pack)

Some files were not shown because too many files have changed in this diff Show More