filter students
This commit is contained in:
@ -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
|
|
||||||
|
52
app/admin.py
52
app/admin.py
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
19
app/migrations/0003_alter_announcement_content.py
Normal file
19
app/migrations/0003_alter_announcement_content.py
Normal 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ść'),
|
||||||
|
),
|
||||||
|
]
|
33
app/migrations/0004_auto_20221205_0348.py
Normal file
33
app/migrations/0004_auto_20221205_0348.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
22
crispy_forms/LICENSE
Normal 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
22
crispy_forms/LICENSE.txt
Normal 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
1
crispy_forms/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = "1.14.0"
|
22
crispy_forms/base.py
Normal file
22
crispy_forms/base.py
Normal 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
1132
crispy_forms/bootstrap.py
Normal file
File diff suppressed because it is too large
Load Diff
16
crispy_forms/exceptions.py
Normal file
16
crispy_forms/exceptions.py
Normal 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
350
crispy_forms/helper.py
Normal 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
1002
crispy_forms/layout.py
Normal file
File diff suppressed because it is too large
Load Diff
156
crispy_forms/layout_slice.py
Normal file
156
crispy_forms/layout_slice.py
Normal 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)
|
12
crispy_forms/templates/bootstrap3/accordion-group.html
Normal file
12
crispy_forms/templates/bootstrap3/accordion-group.html
Normal 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>
|
3
crispy_forms/templates/bootstrap3/accordion.html
Normal file
3
crispy_forms/templates/bootstrap3/accordion.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="panel-group" id="{{ accordion.css_id }}">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
22
crispy_forms/templates/bootstrap3/betterform.html
Normal file
22
crispy_forms/templates/bootstrap3/betterform.html
Normal 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 %}
|
||||||
|
|
9
crispy_forms/templates/bootstrap3/display_form.html
Normal file
9
crispy_forms/templates/bootstrap3/display_form.html
Normal 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 %}
|
8
crispy_forms/templates/bootstrap3/errors.html
Normal file
8
crispy_forms/templates/bootstrap3/errors.html
Normal 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 %}
|
9
crispy_forms/templates/bootstrap3/errors_formset.html
Normal file
9
crispy_forms/templates/bootstrap3/errors_formset.html
Normal 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 %}
|
||||||
|
|
52
crispy_forms/templates/bootstrap3/field.html
Normal file
52
crispy_forms/templates/bootstrap3/field.html
Normal 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 %}
|
13
crispy_forms/templates/bootstrap3/inputs.html
Normal file
13
crispy_forms/templates/bootstrap3/inputs.html
Normal 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 %}
|
4
crispy_forms/templates/bootstrap3/layout/alert.html
Normal file
4
crispy_forms/templates/bootstrap3/layout/alert.html
Normal 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">×</button>{% endif %}
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
1
crispy_forms/templates/bootstrap3/layout/attrs.html
Normal file
1
crispy_forms/templates/bootstrap3/layout/attrs.html
Normal 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 %}
|
9
crispy_forms/templates/bootstrap3/layout/baseinput.html
Normal file
9
crispy_forms/templates/bootstrap3/layout/baseinput.html
Normal 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 }}
|
||||||
|
/>
|
1
crispy_forms/templates/bootstrap3/layout/button.html
Normal file
1
crispy_forms/templates/bootstrap3/layout/button.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<button {{ button.flat_attrs }}>{{ button.content }}</button>
|
@ -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>
|
@ -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>
|
@ -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 %}
|
3
crispy_forms/templates/bootstrap3/layout/column.html
Normal file
3
crispy_forms/templates/bootstrap3/layout/column.html
Normal 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>
|
4
crispy_forms/templates/bootstrap3/layout/div.html
Normal file
4
crispy_forms/templates/bootstrap3/layout/div.html
Normal 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>
|
@ -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 %}
|
@ -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 %}
|
@ -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>
|
6
crispy_forms/templates/bootstrap3/layout/fieldset.html
Normal file
6
crispy_forms/templates/bootstrap3/layout/fieldset.html
Normal 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>
|
@ -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>
|
7
crispy_forms/templates/bootstrap3/layout/help_text.html
Normal file
7
crispy_forms/templates/bootstrap3/layout/help_text.html
Normal 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 %}
|
@ -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 %}
|
21
crispy_forms/templates/bootstrap3/layout/inline_field.html
Normal file
21
crispy_forms/templates/bootstrap3/layout/inline_field.html
Normal 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 %}
|
15
crispy_forms/templates/bootstrap3/layout/modal.html
Normal file
15
crispy_forms/templates/bootstrap3/layout/modal.html
Normal 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">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ fields }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
25
crispy_forms/templates/bootstrap3/layout/multifield.html
Normal file
25
crispy_forms/templates/bootstrap3/layout/multifield.html
Normal 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>
|
@ -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 %}
|
21
crispy_forms/templates/bootstrap3/layout/radioselect.html
Normal file
21
crispy_forms/templates/bootstrap3/layout/radioselect.html
Normal 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>
|
@ -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 %}
|
3
crispy_forms/templates/bootstrap3/layout/row.html
Normal file
3
crispy_forms/templates/bootstrap3/layout/row.html
Normal 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>
|
1
crispy_forms/templates/bootstrap3/layout/tab-link.html
Normal file
1
crispy_forms/templates/bootstrap3/layout/tab-link.html
Normal 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>
|
6
crispy_forms/templates/bootstrap3/layout/tab.html
Normal file
6
crispy_forms/templates/bootstrap3/layout/tab.html
Normal 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>
|
@ -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>
|
39
crispy_forms/templates/bootstrap3/multifield.html
Normal file
39
crispy_forms/templates/bootstrap3/multifield.html
Normal 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 %}
|
57
crispy_forms/templates/bootstrap3/table_inline_formset.html
Normal file
57
crispy_forms/templates/bootstrap3/table_inline_formset.html
Normal 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 %}
|
11
crispy_forms/templates/bootstrap3/uni_form.html
Normal file
11
crispy_forms/templates/bootstrap3/uni_form.html
Normal 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 %}
|
8
crispy_forms/templates/bootstrap3/uni_formset.html
Normal file
8
crispy_forms/templates/bootstrap3/uni_formset.html
Normal 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 %}
|
14
crispy_forms/templates/bootstrap3/whole_uni_form.html
Normal file
14
crispy_forms/templates/bootstrap3/whole_uni_form.html
Normal 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 %}
|
30
crispy_forms/templates/bootstrap3/whole_uni_formset.html
Normal file
30
crispy_forms/templates/bootstrap3/whole_uni_formset.html
Normal 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 %}
|
17
crispy_forms/templates/bootstrap4/accordion-group.html
Normal file
17
crispy_forms/templates/bootstrap4/accordion-group.html
Normal 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>
|
3
crispy_forms/templates/bootstrap4/accordion.html
Normal file
3
crispy_forms/templates/bootstrap4/accordion.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div id="{{ accordion.css_id }}" role="tablist">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
22
crispy_forms/templates/bootstrap4/betterform.html
Normal file
22
crispy_forms/templates/bootstrap4/betterform.html
Normal 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 %}
|
||||||
|
|
9
crispy_forms/templates/bootstrap4/display_form.html
Normal file
9
crispy_forms/templates/bootstrap4/display_form.html
Normal 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 %}
|
8
crispy_forms/templates/bootstrap4/errors.html
Normal file
8
crispy_forms/templates/bootstrap4/errors.html
Normal 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 %}
|
9
crispy_forms/templates/bootstrap4/errors_formset.html
Normal file
9
crispy_forms/templates/bootstrap4/errors_formset.html
Normal 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 %}
|
||||||
|
|
81
crispy_forms/templates/bootstrap4/field.html
Normal file
81
crispy_forms/templates/bootstrap4/field.html
Normal 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 %}
|
13
crispy_forms/templates/bootstrap4/inputs.html
Normal file
13
crispy_forms/templates/bootstrap4/inputs.html
Normal 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 %}
|
4
crispy_forms/templates/bootstrap4/layout/alert.html
Normal file
4
crispy_forms/templates/bootstrap4/layout/alert.html
Normal 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">×</button>{% endif %}
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
1
crispy_forms/templates/bootstrap4/layout/attrs.html
Normal file
1
crispy_forms/templates/bootstrap4/layout/attrs.html
Normal 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 %}
|
9
crispy_forms/templates/bootstrap4/layout/baseinput.html
Normal file
9
crispy_forms/templates/bootstrap4/layout/baseinput.html
Normal 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 }}
|
||||||
|
/>
|
1
crispy_forms/templates/bootstrap4/layout/button.html
Normal file
1
crispy_forms/templates/bootstrap4/layout/button.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<button {{ button.flat_attrs }}>{{ button.content }}</button>
|
@ -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>
|
@ -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>
|
@ -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 %}
|
6
crispy_forms/templates/bootstrap4/layout/column.html
Normal file
6
crispy_forms/templates/bootstrap4/layout/column.html
Normal 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>
|
||||||
|
|
||||||
|
|
4
crispy_forms/templates/bootstrap4/layout/div.html
Normal file
4
crispy_forms/templates/bootstrap4/layout/div.html
Normal 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>
|
@ -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 %}
|
@ -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 %}
|
52
crispy_forms/templates/bootstrap4/layout/field_file.html
Normal file
52
crispy_forms/templates/bootstrap4/layout/field_file.html
Normal 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>
|
@ -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>
|
6
crispy_forms/templates/bootstrap4/layout/fieldset.html
Normal file
6
crispy_forms/templates/bootstrap4/layout/fieldset.html
Normal 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>
|
@ -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>
|
7
crispy_forms/templates/bootstrap4/layout/help_text.html
Normal file
7
crispy_forms/templates/bootstrap4/layout/help_text.html
Normal 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 %}
|
@ -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 %}
|
29
crispy_forms/templates/bootstrap4/layout/inline_field.html
Normal file
29
crispy_forms/templates/bootstrap4/layout/inline_field.html
Normal 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 %}
|
15
crispy_forms/templates/bootstrap4/layout/modal.html
Normal file
15
crispy_forms/templates/bootstrap4/layout/modal.html
Normal 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">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ fields }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
27
crispy_forms/templates/bootstrap4/layout/multifield.html
Normal file
27
crispy_forms/templates/bootstrap4/layout/multifield.html
Normal 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 %}
|
@ -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 %}
|
29
crispy_forms/templates/bootstrap4/layout/radioselect.html
Normal file
29
crispy_forms/templates/bootstrap4/layout/radioselect.html
Normal 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>
|
@ -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 %}
|
3
crispy_forms/templates/bootstrap4/layout/row.html
Normal file
3
crispy_forms/templates/bootstrap4/layout/row.html
Normal 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>
|
1
crispy_forms/templates/bootstrap4/layout/tab-link.html
Normal file
1
crispy_forms/templates/bootstrap4/layout/tab-link.html
Normal 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>
|
6
crispy_forms/templates/bootstrap4/layout/tab.html
Normal file
6
crispy_forms/templates/bootstrap4/layout/tab.html
Normal 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>
|
@ -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>
|
57
crispy_forms/templates/bootstrap4/table_inline_formset.html
Normal file
57
crispy_forms/templates/bootstrap4/table_inline_formset.html
Normal 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 %}
|
11
crispy_forms/templates/bootstrap4/uni_form.html
Normal file
11
crispy_forms/templates/bootstrap4/uni_form.html
Normal 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 %}
|
8
crispy_forms/templates/bootstrap4/uni_formset.html
Normal file
8
crispy_forms/templates/bootstrap4/uni_formset.html
Normal 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 %}
|
14
crispy_forms/templates/bootstrap4/whole_uni_form.html
Normal file
14
crispy_forms/templates/bootstrap4/whole_uni_form.html
Normal 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 %}
|
30
crispy_forms/templates/bootstrap4/whole_uni_formset.html
Normal file
30
crispy_forms/templates/bootstrap4/whole_uni_formset.html
Normal 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 %}
|
0
crispy_forms/templatetags/__init__.py
Normal file
0
crispy_forms/templatetags/__init__.py
Normal file
168
crispy_forms/templatetags/crispy_forms_field.py
Normal file
168
crispy_forms/templatetags/crispy_forms_field.py
Normal 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)
|
162
crispy_forms/templatetags/crispy_forms_filters.py
Normal file
162
crispy_forms/templatetags/crispy_forms_filters.py
Normal 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)
|
267
crispy_forms/templatetags/crispy_forms_tags.py
Normal file
267
crispy_forms/templatetags/crispy_forms_tags.py
Normal 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
Reference in New Issue
Block a user