Initial commit. Basic models mostly done.
This commit is contained in:
11
venv/lib/python3.8/site-packages/django/forms/__init__.py
Normal file
11
venv/lib/python3.8/site-packages/django/forms/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Django validation and HTML form handling.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ValidationError # NOQA
|
||||
from django.forms.boundfield import * # NOQA
|
||||
from django.forms.fields import * # NOQA
|
||||
from django.forms.forms import * # NOQA
|
||||
from django.forms.formsets import * # NOQA
|
||||
from django.forms.models import * # NOQA
|
||||
from django.forms.widgets import * # NOQA
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
269
venv/lib/python3.8/site-packages/django/forms/boundfield.py
Normal file
269
venv/lib/python3.8/site-packages/django/forms/boundfield.py
Normal file
@@ -0,0 +1,269 @@
|
||||
import datetime
|
||||
|
||||
from django.forms.utils import flatatt, pretty_name
|
||||
from django.forms.widgets import Textarea, TextInput
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import conditional_escape, format_html, html_safe
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
__all__ = ('BoundField',)
|
||||
|
||||
|
||||
@html_safe
|
||||
class BoundField:
|
||||
"A Field plus data"
|
||||
def __init__(self, form, field, name):
|
||||
self.form = form
|
||||
self.field = field
|
||||
self.name = name
|
||||
self.html_name = form.add_prefix(name)
|
||||
self.html_initial_name = form.add_initial_prefix(name)
|
||||
self.html_initial_id = form.add_initial_prefix(self.auto_id)
|
||||
if self.field.label is None:
|
||||
self.label = pretty_name(name)
|
||||
else:
|
||||
self.label = self.field.label
|
||||
self.help_text = field.help_text or ''
|
||||
|
||||
def __str__(self):
|
||||
"""Render this field as an HTML widget."""
|
||||
if self.field.show_hidden_initial:
|
||||
return self.as_widget() + self.as_hidden(only_initial=True)
|
||||
return self.as_widget()
|
||||
|
||||
@cached_property
|
||||
def subwidgets(self):
|
||||
"""
|
||||
Most widgets yield a single subwidget, but others like RadioSelect and
|
||||
CheckboxSelectMultiple produce one subwidget for each choice.
|
||||
|
||||
This property is cached so that only one database query occurs when
|
||||
rendering ModelChoiceFields.
|
||||
"""
|
||||
id_ = self.field.widget.attrs.get('id') or self.auto_id
|
||||
attrs = {'id': id_} if id_ else {}
|
||||
attrs = self.build_widget_attrs(attrs)
|
||||
return [
|
||||
BoundWidget(self.field.widget, widget, self.form.renderer)
|
||||
for widget in self.field.widget.subwidgets(self.html_name, self.value(), attrs=attrs)
|
||||
]
|
||||
|
||||
def __bool__(self):
|
||||
# BoundField evaluates to True even if it doesn't have subwidgets.
|
||||
return True
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.subwidgets)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.subwidgets)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
# Prevent unnecessary reevaluation when accessing BoundField's attrs
|
||||
# from templates.
|
||||
if not isinstance(idx, (int, slice)):
|
||||
raise TypeError(
|
||||
'BoundField indices must be integers or slices, not %s.'
|
||||
% type(idx).__name__
|
||||
)
|
||||
return self.subwidgets[idx]
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
"""
|
||||
Return an ErrorList (empty if there are no errors) for this field.
|
||||
"""
|
||||
return self.form.errors.get(self.name, self.form.error_class())
|
||||
|
||||
def as_widget(self, widget=None, attrs=None, only_initial=False):
|
||||
"""
|
||||
Render the field by rendering the passed widget, adding any HTML
|
||||
attributes passed as attrs. If a widget isn't specified, use the
|
||||
field's default widget.
|
||||
"""
|
||||
widget = widget or self.field.widget
|
||||
if self.field.localize:
|
||||
widget.is_localized = True
|
||||
attrs = attrs or {}
|
||||
attrs = self.build_widget_attrs(attrs, widget)
|
||||
if self.auto_id and 'id' not in widget.attrs:
|
||||
attrs.setdefault('id', self.html_initial_id if only_initial else self.auto_id)
|
||||
return widget.render(
|
||||
name=self.html_initial_name if only_initial else self.html_name,
|
||||
value=self.value(),
|
||||
attrs=attrs,
|
||||
renderer=self.form.renderer,
|
||||
)
|
||||
|
||||
def as_text(self, attrs=None, **kwargs):
|
||||
"""
|
||||
Return a string of HTML for representing this as an <input type="text">.
|
||||
"""
|
||||
return self.as_widget(TextInput(), attrs, **kwargs)
|
||||
|
||||
def as_textarea(self, attrs=None, **kwargs):
|
||||
"""Return a string of HTML for representing this as a <textarea>."""
|
||||
return self.as_widget(Textarea(), attrs, **kwargs)
|
||||
|
||||
def as_hidden(self, attrs=None, **kwargs):
|
||||
"""
|
||||
Return a string of HTML for representing this as an <input type="hidden">.
|
||||
"""
|
||||
return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""
|
||||
Return the data for this BoundField, or None if it wasn't given.
|
||||
"""
|
||||
return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
|
||||
|
||||
def value(self):
|
||||
"""
|
||||
Return the value for this BoundField, using the initial value if
|
||||
the form is not bound or the data otherwise.
|
||||
"""
|
||||
data = self.initial
|
||||
if self.form.is_bound:
|
||||
data = self.field.bound_data(self.data, data)
|
||||
return self.field.prepare_value(data)
|
||||
|
||||
def label_tag(self, contents=None, attrs=None, label_suffix=None):
|
||||
"""
|
||||
Wrap the given contents in a <label>, if the field has an ID attribute.
|
||||
contents should be mark_safe'd to avoid HTML escaping. If contents
|
||||
aren't given, use the field's HTML-escaped label.
|
||||
|
||||
If attrs are given, use them as HTML attributes on the <label> tag.
|
||||
|
||||
label_suffix overrides the form's label_suffix.
|
||||
"""
|
||||
contents = contents or self.label
|
||||
if label_suffix is None:
|
||||
label_suffix = (self.field.label_suffix if self.field.label_suffix is not None
|
||||
else self.form.label_suffix)
|
||||
# Only add the suffix if the label does not end in punctuation.
|
||||
# Translators: If found as last label character, these punctuation
|
||||
# characters will prevent the default label_suffix to be appended to the label
|
||||
if label_suffix and contents and contents[-1] not in _(':?.!'):
|
||||
contents = format_html('{}{}', contents, label_suffix)
|
||||
widget = self.field.widget
|
||||
id_ = widget.attrs.get('id') or self.auto_id
|
||||
if id_:
|
||||
id_for_label = widget.id_for_label(id_)
|
||||
if id_for_label:
|
||||
attrs = {**(attrs or {}), 'for': id_for_label}
|
||||
if self.field.required and hasattr(self.form, 'required_css_class'):
|
||||
attrs = attrs or {}
|
||||
if 'class' in attrs:
|
||||
attrs['class'] += ' ' + self.form.required_css_class
|
||||
else:
|
||||
attrs['class'] = self.form.required_css_class
|
||||
attrs = flatatt(attrs) if attrs else ''
|
||||
contents = format_html('<label{}>{}</label>', attrs, contents)
|
||||
else:
|
||||
contents = conditional_escape(contents)
|
||||
return mark_safe(contents)
|
||||
|
||||
def css_classes(self, extra_classes=None):
|
||||
"""
|
||||
Return a string of space-separated CSS classes for this field.
|
||||
"""
|
||||
if hasattr(extra_classes, 'split'):
|
||||
extra_classes = extra_classes.split()
|
||||
extra_classes = set(extra_classes or [])
|
||||
if self.errors and hasattr(self.form, 'error_css_class'):
|
||||
extra_classes.add(self.form.error_css_class)
|
||||
if self.field.required and hasattr(self.form, 'required_css_class'):
|
||||
extra_classes.add(self.form.required_css_class)
|
||||
return ' '.join(extra_classes)
|
||||
|
||||
@property
|
||||
def is_hidden(self):
|
||||
"""Return True if this BoundField's widget is hidden."""
|
||||
return self.field.widget.is_hidden
|
||||
|
||||
@property
|
||||
def auto_id(self):
|
||||
"""
|
||||
Calculate and return the ID attribute for this BoundField, if the
|
||||
associated Form has specified auto_id. Return an empty string otherwise.
|
||||
"""
|
||||
auto_id = self.form.auto_id # Boolean or string
|
||||
if auto_id and '%s' in str(auto_id):
|
||||
return auto_id % self.html_name
|
||||
elif auto_id:
|
||||
return self.html_name
|
||||
return ''
|
||||
|
||||
@property
|
||||
def id_for_label(self):
|
||||
"""
|
||||
Wrapper around the field widget's `id_for_label` method.
|
||||
Useful, for example, for focusing on this field regardless of whether
|
||||
it has a single widget or a MultiWidget.
|
||||
"""
|
||||
widget = self.field.widget
|
||||
id_ = widget.attrs.get('id') or self.auto_id
|
||||
return widget.id_for_label(id_)
|
||||
|
||||
@cached_property
|
||||
def initial(self):
|
||||
data = self.form.get_initial_for_field(self.field, self.name)
|
||||
# If this is an auto-generated default date, nix the microseconds for
|
||||
# standardized handling. See #22502.
|
||||
if (isinstance(data, (datetime.datetime, datetime.time)) and
|
||||
not self.field.widget.supports_microseconds):
|
||||
data = data.replace(microsecond=0)
|
||||
return data
|
||||
|
||||
def build_widget_attrs(self, attrs, widget=None):
|
||||
widget = widget or self.field.widget
|
||||
attrs = dict(attrs) # Copy attrs to avoid modifying the argument.
|
||||
if widget.use_required_attribute(self.initial) and self.field.required and self.form.use_required_attribute:
|
||||
attrs['required'] = True
|
||||
if self.field.disabled:
|
||||
attrs['disabled'] = True
|
||||
return attrs
|
||||
|
||||
|
||||
@html_safe
|
||||
class BoundWidget:
|
||||
"""
|
||||
A container class used for iterating over widgets. This is useful for
|
||||
widgets that have choices. For example, the following can be used in a
|
||||
template:
|
||||
|
||||
{% for radio in myform.beatles %}
|
||||
<label for="{{ radio.id_for_label }}">
|
||||
{{ radio.choice_label }}
|
||||
<span class="radio">{{ radio.tag }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
"""
|
||||
def __init__(self, parent_widget, data, renderer):
|
||||
self.parent_widget = parent_widget
|
||||
self.data = data
|
||||
self.renderer = renderer
|
||||
|
||||
def __str__(self):
|
||||
return self.tag(wrap_label=True)
|
||||
|
||||
def tag(self, wrap_label=False):
|
||||
context = {'widget': {**self.data, 'wrap_label': wrap_label}}
|
||||
return self.parent_widget._render(self.template_name, context, self.renderer)
|
||||
|
||||
@property
|
||||
def template_name(self):
|
||||
if 'template_name' in self.data:
|
||||
return self.data['template_name']
|
||||
return self.parent_widget.template_name
|
||||
|
||||
@property
|
||||
def id_for_label(self):
|
||||
return 'id_%s_%s' % (self.data['name'], self.data['index'])
|
||||
|
||||
@property
|
||||
def choice_label(self):
|
||||
return self.data['label']
|
||||
1203
venv/lib/python3.8/site-packages/django/forms/fields.py
Normal file
1203
venv/lib/python3.8/site-packages/django/forms/fields.py
Normal file
File diff suppressed because it is too large
Load Diff
501
venv/lib/python3.8/site-packages/django/forms/forms.py
Normal file
501
venv/lib/python3.8/site-packages/django/forms/forms.py
Normal file
@@ -0,0 +1,501 @@
|
||||
"""
|
||||
Form classes
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
||||
# BoundField is imported for backwards compatibility in Django 1.9
|
||||
from django.forms.boundfield import BoundField # NOQA
|
||||
from django.forms.fields import Field, FileField
|
||||
# pretty_name is imported for backwards compatibility in Django 1.9
|
||||
from django.forms.utils import ErrorDict, ErrorList, pretty_name # NOQA
|
||||
from django.forms.widgets import Media, MediaDefiningClass
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import conditional_escape, html_safe
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from .renderers import get_default_renderer
|
||||
|
||||
__all__ = ('BaseForm', 'Form')
|
||||
|
||||
|
||||
class DeclarativeFieldsMetaclass(MediaDefiningClass):
|
||||
"""Collect Fields declared on the base classes."""
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
# Collect fields from current class.
|
||||
current_fields = []
|
||||
for key, value in list(attrs.items()):
|
||||
if isinstance(value, Field):
|
||||
current_fields.append((key, value))
|
||||
attrs.pop(key)
|
||||
attrs['declared_fields'] = dict(current_fields)
|
||||
|
||||
new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs)
|
||||
|
||||
# Walk through the MRO.
|
||||
declared_fields = {}
|
||||
for base in reversed(new_class.__mro__):
|
||||
# Collect fields from base class.
|
||||
if hasattr(base, 'declared_fields'):
|
||||
declared_fields.update(base.declared_fields)
|
||||
|
||||
# Field shadowing.
|
||||
for attr, value in base.__dict__.items():
|
||||
if value is None and attr in declared_fields:
|
||||
declared_fields.pop(attr)
|
||||
|
||||
new_class.base_fields = declared_fields
|
||||
new_class.declared_fields = declared_fields
|
||||
|
||||
return new_class
|
||||
|
||||
|
||||
@html_safe
|
||||
class BaseForm:
|
||||
"""
|
||||
The main implementation of all the Form logic. Note that this class is
|
||||
different than Form. See the comments by the Form class for more info. Any
|
||||
improvements to the form API should be made to this class, not to the Form
|
||||
class.
|
||||
"""
|
||||
default_renderer = None
|
||||
field_order = None
|
||||
prefix = None
|
||||
use_required_attribute = True
|
||||
|
||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
||||
initial=None, error_class=ErrorList, label_suffix=None,
|
||||
empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
|
||||
self.is_bound = data is not None or files is not None
|
||||
self.data = MultiValueDict() if data is None else data
|
||||
self.files = MultiValueDict() if files is None else files
|
||||
self.auto_id = auto_id
|
||||
if prefix is not None:
|
||||
self.prefix = prefix
|
||||
self.initial = initial or {}
|
||||
self.error_class = error_class
|
||||
# Translators: This is the default suffix added to form field labels
|
||||
self.label_suffix = label_suffix if label_suffix is not None else _(':')
|
||||
self.empty_permitted = empty_permitted
|
||||
self._errors = None # Stores the errors after clean() has been called.
|
||||
|
||||
# The base_fields class attribute is the *class-wide* definition of
|
||||
# fields. Because a particular *instance* of the class might want to
|
||||
# alter self.fields, we create self.fields here by copying base_fields.
|
||||
# Instances should always modify self.fields; they should not modify
|
||||
# self.base_fields.
|
||||
self.fields = copy.deepcopy(self.base_fields)
|
||||
self._bound_fields_cache = {}
|
||||
self.order_fields(self.field_order if field_order is None else field_order)
|
||||
|
||||
if use_required_attribute is not None:
|
||||
self.use_required_attribute = use_required_attribute
|
||||
|
||||
if self.empty_permitted and self.use_required_attribute:
|
||||
raise ValueError(
|
||||
'The empty_permitted and use_required_attribute arguments may '
|
||||
'not both be True.'
|
||||
)
|
||||
|
||||
# Initialize form renderer. Use a global default if not specified
|
||||
# either as an argument or as self.default_renderer.
|
||||
if renderer is None:
|
||||
if self.default_renderer is None:
|
||||
renderer = get_default_renderer()
|
||||
else:
|
||||
renderer = self.default_renderer
|
||||
if isinstance(self.default_renderer, type):
|
||||
renderer = renderer()
|
||||
self.renderer = renderer
|
||||
|
||||
def order_fields(self, field_order):
|
||||
"""
|
||||
Rearrange the fields according to field_order.
|
||||
|
||||
field_order is a list of field names specifying the order. Append fields
|
||||
not included in the list in the default order for backward compatibility
|
||||
with subclasses not overriding field_order. If field_order is None,
|
||||
keep all fields in the order defined in the class. Ignore unknown
|
||||
fields in field_order to allow disabling fields in form subclasses
|
||||
without redefining ordering.
|
||||
"""
|
||||
if field_order is None:
|
||||
return
|
||||
fields = {}
|
||||
for key in field_order:
|
||||
try:
|
||||
fields[key] = self.fields.pop(key)
|
||||
except KeyError: # ignore unknown fields
|
||||
pass
|
||||
fields.update(self.fields) # add remaining fields in original order
|
||||
self.fields = fields
|
||||
|
||||
def __str__(self):
|
||||
return self.as_table()
|
||||
|
||||
def __repr__(self):
|
||||
if self._errors is None:
|
||||
is_valid = "Unknown"
|
||||
else:
|
||||
is_valid = self.is_bound and not self._errors
|
||||
return '<%(cls)s bound=%(bound)s, valid=%(valid)s, fields=(%(fields)s)>' % {
|
||||
'cls': self.__class__.__name__,
|
||||
'bound': self.is_bound,
|
||||
'valid': is_valid,
|
||||
'fields': ';'.join(self.fields),
|
||||
}
|
||||
|
||||
def __iter__(self):
|
||||
for name in self.fields:
|
||||
yield self[name]
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""Return a BoundField with the given name."""
|
||||
try:
|
||||
field = self.fields[name]
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
"Key '%s' not found in '%s'. Choices are: %s." % (
|
||||
name,
|
||||
self.__class__.__name__,
|
||||
', '.join(sorted(self.fields)),
|
||||
)
|
||||
)
|
||||
if name not in self._bound_fields_cache:
|
||||
self._bound_fields_cache[name] = field.get_bound_field(self, name)
|
||||
return self._bound_fields_cache[name]
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
"""Return an ErrorDict for the data provided for the form."""
|
||||
if self._errors is None:
|
||||
self.full_clean()
|
||||
return self._errors
|
||||
|
||||
def is_valid(self):
|
||||
"""Return True if the form has no errors, or False otherwise."""
|
||||
return self.is_bound and not self.errors
|
||||
|
||||
def add_prefix(self, field_name):
|
||||
"""
|
||||
Return the field name with a prefix appended, if this Form has a
|
||||
prefix set.
|
||||
|
||||
Subclasses may wish to override.
|
||||
"""
|
||||
return '%s-%s' % (self.prefix, field_name) if self.prefix else field_name
|
||||
|
||||
def add_initial_prefix(self, field_name):
|
||||
"""Add an 'initial' prefix for checking dynamic initial values."""
|
||||
return 'initial-%s' % self.add_prefix(field_name)
|
||||
|
||||
def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
|
||||
"Output HTML. Used by as_table(), as_ul(), as_p()."
|
||||
top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
|
||||
output, hidden_fields = [], []
|
||||
|
||||
for name, field in self.fields.items():
|
||||
html_class_attr = ''
|
||||
bf = self[name]
|
||||
bf_errors = self.error_class(bf.errors)
|
||||
if bf.is_hidden:
|
||||
if bf_errors:
|
||||
top_errors.extend(
|
||||
[_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)}
|
||||
for e in bf_errors])
|
||||
hidden_fields.append(str(bf))
|
||||
else:
|
||||
# Create a 'class="..."' attribute if the row should have any
|
||||
# CSS classes applied.
|
||||
css_classes = bf.css_classes()
|
||||
if css_classes:
|
||||
html_class_attr = ' class="%s"' % css_classes
|
||||
|
||||
if errors_on_separate_row and bf_errors:
|
||||
output.append(error_row % str(bf_errors))
|
||||
|
||||
if bf.label:
|
||||
label = conditional_escape(bf.label)
|
||||
label = bf.label_tag(label) or ''
|
||||
else:
|
||||
label = ''
|
||||
|
||||
if field.help_text:
|
||||
help_text = help_text_html % field.help_text
|
||||
else:
|
||||
help_text = ''
|
||||
|
||||
output.append(normal_row % {
|
||||
'errors': bf_errors,
|
||||
'label': label,
|
||||
'field': bf,
|
||||
'help_text': help_text,
|
||||
'html_class_attr': html_class_attr,
|
||||
'css_classes': css_classes,
|
||||
'field_name': bf.html_name,
|
||||
})
|
||||
|
||||
if top_errors:
|
||||
output.insert(0, error_row % top_errors)
|
||||
|
||||
if hidden_fields: # Insert any hidden fields in the last row.
|
||||
str_hidden = ''.join(hidden_fields)
|
||||
if output:
|
||||
last_row = output[-1]
|
||||
# Chop off the trailing row_ender (e.g. '</td></tr>') and
|
||||
# insert the hidden fields.
|
||||
if not last_row.endswith(row_ender):
|
||||
# This can happen in the as_p() case (and possibly others
|
||||
# that users write): if there are only top errors, we may
|
||||
# not be able to conscript the last row for our purposes,
|
||||
# so insert a new, empty row.
|
||||
last_row = (normal_row % {
|
||||
'errors': '',
|
||||
'label': '',
|
||||
'field': '',
|
||||
'help_text': '',
|
||||
'html_class_attr': html_class_attr,
|
||||
'css_classes': '',
|
||||
'field_name': '',
|
||||
})
|
||||
output.append(last_row)
|
||||
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
|
||||
else:
|
||||
# If there aren't any rows in the output, just append the
|
||||
# hidden fields.
|
||||
output.append(str_hidden)
|
||||
return mark_safe('\n'.join(output))
|
||||
|
||||
def as_table(self):
|
||||
"Return this form rendered as HTML <tr>s -- excluding the <table></table>."
|
||||
return self._html_output(
|
||||
normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
|
||||
error_row='<tr><td colspan="2">%s</td></tr>',
|
||||
row_ender='</td></tr>',
|
||||
help_text_html='<br><span class="helptext">%s</span>',
|
||||
errors_on_separate_row=False,
|
||||
)
|
||||
|
||||
def as_ul(self):
|
||||
"Return this form rendered as HTML <li>s -- excluding the <ul></ul>."
|
||||
return self._html_output(
|
||||
normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
|
||||
error_row='<li>%s</li>',
|
||||
row_ender='</li>',
|
||||
help_text_html=' <span class="helptext">%s</span>',
|
||||
errors_on_separate_row=False,
|
||||
)
|
||||
|
||||
def as_p(self):
|
||||
"Return this form rendered as HTML <p>s."
|
||||
return self._html_output(
|
||||
normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
|
||||
error_row='%s',
|
||||
row_ender='</p>',
|
||||
help_text_html=' <span class="helptext">%s</span>',
|
||||
errors_on_separate_row=True,
|
||||
)
|
||||
|
||||
def non_field_errors(self):
|
||||
"""
|
||||
Return an ErrorList of errors that aren't associated with a particular
|
||||
field -- i.e., from Form.clean(). Return an empty ErrorList if there
|
||||
are none.
|
||||
"""
|
||||
return self.errors.get(NON_FIELD_ERRORS, self.error_class(error_class='nonfield'))
|
||||
|
||||
def add_error(self, field, error):
|
||||
"""
|
||||
Update the content of `self._errors`.
|
||||
|
||||
The `field` argument is the name of the field to which the errors
|
||||
should be added. If it's None, treat the errors as NON_FIELD_ERRORS.
|
||||
|
||||
The `error` argument can be a single error, a list of errors, or a
|
||||
dictionary that maps field names to lists of errors. An "error" can be
|
||||
either a simple string or an instance of ValidationError with its
|
||||
message attribute set and a "list or dictionary" can be an actual
|
||||
`list` or `dict` or an instance of ValidationError with its
|
||||
`error_list` or `error_dict` attribute set.
|
||||
|
||||
If `error` is a dictionary, the `field` argument *must* be None and
|
||||
errors will be added to the fields that correspond to the keys of the
|
||||
dictionary.
|
||||
"""
|
||||
if not isinstance(error, ValidationError):
|
||||
# Normalize to ValidationError and let its constructor
|
||||
# do the hard work of making sense of the input.
|
||||
error = ValidationError(error)
|
||||
|
||||
if hasattr(error, 'error_dict'):
|
||||
if field is not None:
|
||||
raise TypeError(
|
||||
"The argument `field` must be `None` when the `error` "
|
||||
"argument contains errors for multiple fields."
|
||||
)
|
||||
else:
|
||||
error = error.error_dict
|
||||
else:
|
||||
error = {field or NON_FIELD_ERRORS: error.error_list}
|
||||
|
||||
for field, error_list in error.items():
|
||||
if field not in self.errors:
|
||||
if field != NON_FIELD_ERRORS and field not in self.fields:
|
||||
raise ValueError(
|
||||
"'%s' has no field named '%s'." % (self.__class__.__name__, field))
|
||||
if field == NON_FIELD_ERRORS:
|
||||
self._errors[field] = self.error_class(error_class='nonfield')
|
||||
else:
|
||||
self._errors[field] = self.error_class()
|
||||
self._errors[field].extend(error_list)
|
||||
if field in self.cleaned_data:
|
||||
del self.cleaned_data[field]
|
||||
|
||||
def has_error(self, field, code=None):
|
||||
return field in self.errors and (
|
||||
code is None or
|
||||
any(error.code == code for error in self.errors.as_data()[field])
|
||||
)
|
||||
|
||||
def full_clean(self):
|
||||
"""
|
||||
Clean all of self.data and populate self._errors and self.cleaned_data.
|
||||
"""
|
||||
self._errors = ErrorDict()
|
||||
if not self.is_bound: # Stop further processing.
|
||||
return
|
||||
self.cleaned_data = {}
|
||||
# If the form is permitted to be empty, and none of the form data has
|
||||
# changed from the initial data, short circuit any validation.
|
||||
if self.empty_permitted and not self.has_changed():
|
||||
return
|
||||
|
||||
self._clean_fields()
|
||||
self._clean_form()
|
||||
self._post_clean()
|
||||
|
||||
def _clean_fields(self):
|
||||
for name, field in self.fields.items():
|
||||
# value_from_datadict() gets the data from the data dictionaries.
|
||||
# Each widget type knows how to retrieve its own data, because some
|
||||
# widgets split data over several HTML fields.
|
||||
if field.disabled:
|
||||
value = self.get_initial_for_field(field, name)
|
||||
else:
|
||||
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
|
||||
try:
|
||||
if isinstance(field, FileField):
|
||||
initial = self.get_initial_for_field(field, name)
|
||||
value = field.clean(value, initial)
|
||||
else:
|
||||
value = field.clean(value)
|
||||
self.cleaned_data[name] = value
|
||||
if hasattr(self, 'clean_%s' % name):
|
||||
value = getattr(self, 'clean_%s' % name)()
|
||||
self.cleaned_data[name] = value
|
||||
except ValidationError as e:
|
||||
self.add_error(name, e)
|
||||
|
||||
def _clean_form(self):
|
||||
try:
|
||||
cleaned_data = self.clean()
|
||||
except ValidationError as e:
|
||||
self.add_error(None, e)
|
||||
else:
|
||||
if cleaned_data is not None:
|
||||
self.cleaned_data = cleaned_data
|
||||
|
||||
def _post_clean(self):
|
||||
"""
|
||||
An internal hook for performing additional cleaning after form cleaning
|
||||
is complete. Used for model validation in model forms.
|
||||
"""
|
||||
pass
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Hook for doing any extra form-wide cleaning after Field.clean() has been
|
||||
called on every field. Any ValidationError raised by this method will
|
||||
not be associated with a particular field; it will have a special-case
|
||||
association with the field named '__all__'.
|
||||
"""
|
||||
return self.cleaned_data
|
||||
|
||||
def has_changed(self):
|
||||
"""Return True if data differs from initial."""
|
||||
return bool(self.changed_data)
|
||||
|
||||
@cached_property
|
||||
def changed_data(self):
|
||||
data = []
|
||||
for name, field in self.fields.items():
|
||||
prefixed_name = self.add_prefix(name)
|
||||
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
|
||||
if not field.show_hidden_initial:
|
||||
# Use the BoundField's initial as this is the value passed to
|
||||
# the widget.
|
||||
initial_value = self[name].initial
|
||||
else:
|
||||
initial_prefixed_name = self.add_initial_prefix(name)
|
||||
hidden_widget = field.hidden_widget()
|
||||
try:
|
||||
initial_value = field.to_python(hidden_widget.value_from_datadict(
|
||||
self.data, self.files, initial_prefixed_name))
|
||||
except ValidationError:
|
||||
# Always assume data has changed if validation fails.
|
||||
data.append(name)
|
||||
continue
|
||||
if field.has_changed(initial_value, data_value):
|
||||
data.append(name)
|
||||
return data
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
"""Return all media required to render the widgets on this form."""
|
||||
media = Media()
|
||||
for field in self.fields.values():
|
||||
media = media + field.widget.media
|
||||
return media
|
||||
|
||||
def is_multipart(self):
|
||||
"""
|
||||
Return True if the form needs to be multipart-encoded, i.e. it has
|
||||
FileInput, or False otherwise.
|
||||
"""
|
||||
return any(field.widget.needs_multipart_form for field in self.fields.values())
|
||||
|
||||
def hidden_fields(self):
|
||||
"""
|
||||
Return a list of all the BoundField objects that are hidden fields.
|
||||
Useful for manual form layout in templates.
|
||||
"""
|
||||
return [field for field in self if field.is_hidden]
|
||||
|
||||
def visible_fields(self):
|
||||
"""
|
||||
Return a list of BoundField objects that aren't hidden fields.
|
||||
The opposite of the hidden_fields() method.
|
||||
"""
|
||||
return [field for field in self if not field.is_hidden]
|
||||
|
||||
def get_initial_for_field(self, field, field_name):
|
||||
"""
|
||||
Return initial data for field on form. Use initial data from the form
|
||||
or the field, in that order. Evaluate callable values.
|
||||
"""
|
||||
value = self.initial.get(field_name, field.initial)
|
||||
if callable(value):
|
||||
value = value()
|
||||
return value
|
||||
|
||||
|
||||
class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
|
||||
"A collection of Fields, plus their associated data."
|
||||
# This is a separate class from BaseForm in order to abstract the way
|
||||
# self.fields is specified. This class (Form) is the one that does the
|
||||
# fancy metaclass stuff purely for the semantic sugar -- it allows one
|
||||
# to define a form using declarative syntax.
|
||||
# BaseForm itself has no way of designating self.fields.
|
||||
465
venv/lib/python3.8/site-packages/django/forms/formsets.py
Normal file
465
venv/lib/python3.8/site-packages/django/forms/formsets.py
Normal file
@@ -0,0 +1,465 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import Form
|
||||
from django.forms.fields import BooleanField, IntegerField
|
||||
from django.forms.utils import ErrorList
|
||||
from django.forms.widgets import HiddenInput, NumberInput
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import html_safe
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _, ngettext
|
||||
|
||||
__all__ = ('BaseFormSet', 'formset_factory', 'all_valid')
|
||||
|
||||
# special field names
|
||||
TOTAL_FORM_COUNT = 'TOTAL_FORMS'
|
||||
INITIAL_FORM_COUNT = 'INITIAL_FORMS'
|
||||
MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS'
|
||||
MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
|
||||
ORDERING_FIELD_NAME = 'ORDER'
|
||||
DELETION_FIELD_NAME = 'DELETE'
|
||||
|
||||
# default minimum number of forms in a formset
|
||||
DEFAULT_MIN_NUM = 0
|
||||
|
||||
# default maximum number of forms in a formset, to prevent memory exhaustion
|
||||
DEFAULT_MAX_NUM = 1000
|
||||
|
||||
|
||||
class ManagementForm(Form):
|
||||
"""
|
||||
Keep track of how many form instances are displayed on the page. If adding
|
||||
new forms via JavaScript, you should increment the count field of this form
|
||||
as well.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
|
||||
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
|
||||
# MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT are output with the rest of
|
||||
# the management form, but only for the convenience of client-side
|
||||
# code. The POST value of them returned from the client is not checked.
|
||||
self.base_fields[MIN_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
|
||||
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
@html_safe
|
||||
class BaseFormSet:
|
||||
"""
|
||||
A collection of instances of the same Form class.
|
||||
"""
|
||||
ordering_widget = NumberInput
|
||||
|
||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
||||
initial=None, error_class=ErrorList, form_kwargs=None):
|
||||
self.is_bound = data is not None or files is not None
|
||||
self.prefix = prefix or self.get_default_prefix()
|
||||
self.auto_id = auto_id
|
||||
self.data = data or {}
|
||||
self.files = files or {}
|
||||
self.initial = initial
|
||||
self.form_kwargs = form_kwargs or {}
|
||||
self.error_class = error_class
|
||||
self._errors = None
|
||||
self._non_form_errors = None
|
||||
|
||||
def __str__(self):
|
||||
return self.as_table()
|
||||
|
||||
def __iter__(self):
|
||||
"""Yield the forms in the order they should be rendered."""
|
||||
return iter(self.forms)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Return the form at the given index, based on the rendering order."""
|
||||
return self.forms[index]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.forms)
|
||||
|
||||
def __bool__(self):
|
||||
"""
|
||||
Return True since all formsets have a management form which is not
|
||||
included in the length.
|
||||
"""
|
||||
return True
|
||||
|
||||
@cached_property
|
||||
def management_form(self):
|
||||
"""Return the ManagementForm instance for this FormSet."""
|
||||
if self.is_bound:
|
||||
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
|
||||
if not form.is_valid():
|
||||
raise ValidationError(
|
||||
_('ManagementForm data is missing or has been tampered with'),
|
||||
code='missing_management_form',
|
||||
)
|
||||
else:
|
||||
form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
|
||||
TOTAL_FORM_COUNT: self.total_form_count(),
|
||||
INITIAL_FORM_COUNT: self.initial_form_count(),
|
||||
MIN_NUM_FORM_COUNT: self.min_num,
|
||||
MAX_NUM_FORM_COUNT: self.max_num
|
||||
})
|
||||
return form
|
||||
|
||||
def total_form_count(self):
|
||||
"""Return the total number of forms in this FormSet."""
|
||||
if self.is_bound:
|
||||
# return absolute_max if it is lower than the actual total form
|
||||
# count in the data; this is DoS protection to prevent clients
|
||||
# from forcing the server to instantiate arbitrary numbers of
|
||||
# forms
|
||||
return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
|
||||
else:
|
||||
initial_forms = self.initial_form_count()
|
||||
total_forms = max(initial_forms, self.min_num) + self.extra
|
||||
# Allow all existing related objects/inlines to be displayed,
|
||||
# but don't allow extra beyond max_num.
|
||||
if initial_forms > self.max_num >= 0:
|
||||
total_forms = initial_forms
|
||||
elif total_forms > self.max_num >= 0:
|
||||
total_forms = self.max_num
|
||||
return total_forms
|
||||
|
||||
def initial_form_count(self):
|
||||
"""Return the number of forms that are required in this FormSet."""
|
||||
if self.is_bound:
|
||||
return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
|
||||
else:
|
||||
# Use the length of the initial data if it's there, 0 otherwise.
|
||||
initial_forms = len(self.initial) if self.initial else 0
|
||||
return initial_forms
|
||||
|
||||
@cached_property
|
||||
def forms(self):
|
||||
"""Instantiate forms at first property access."""
|
||||
# DoS protection is included in total_form_count()
|
||||
return [
|
||||
self._construct_form(i, **self.get_form_kwargs(i))
|
||||
for i in range(self.total_form_count())
|
||||
]
|
||||
|
||||
def get_form_kwargs(self, index):
|
||||
"""
|
||||
Return additional keyword arguments for each individual formset form.
|
||||
|
||||
index will be None if the form being constructed is a new empty
|
||||
form.
|
||||
"""
|
||||
return self.form_kwargs.copy()
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
"""Instantiate and return the i-th form instance in a formset."""
|
||||
defaults = {
|
||||
'auto_id': self.auto_id,
|
||||
'prefix': self.add_prefix(i),
|
||||
'error_class': self.error_class,
|
||||
# Don't render the HTML 'required' attribute as it may cause
|
||||
# incorrect validation for extra, optional, and deleted
|
||||
# forms in the formset.
|
||||
'use_required_attribute': False,
|
||||
}
|
||||
if self.is_bound:
|
||||
defaults['data'] = self.data
|
||||
defaults['files'] = self.files
|
||||
if self.initial and 'initial' not in kwargs:
|
||||
try:
|
||||
defaults['initial'] = self.initial[i]
|
||||
except IndexError:
|
||||
pass
|
||||
# Allow extra forms to be empty, unless they're part of
|
||||
# the minimum forms.
|
||||
if i >= self.initial_form_count() and i >= self.min_num:
|
||||
defaults['empty_permitted'] = True
|
||||
defaults.update(kwargs)
|
||||
form = self.form(**defaults)
|
||||
self.add_fields(form, i)
|
||||
return form
|
||||
|
||||
@property
|
||||
def initial_forms(self):
|
||||
"""Return a list of all the initial forms in this formset."""
|
||||
return self.forms[:self.initial_form_count()]
|
||||
|
||||
@property
|
||||
def extra_forms(self):
|
||||
"""Return a list of all the extra forms in this formset."""
|
||||
return self.forms[self.initial_form_count():]
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = self.form(
|
||||
auto_id=self.auto_id,
|
||||
prefix=self.add_prefix('__prefix__'),
|
||||
empty_permitted=True,
|
||||
use_required_attribute=False,
|
||||
**self.get_form_kwargs(None)
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
@property
|
||||
def cleaned_data(self):
|
||||
"""
|
||||
Return a list of form.cleaned_data dicts for every form in self.forms.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__)
|
||||
return [form.cleaned_data for form in self.forms]
|
||||
|
||||
@property
|
||||
def deleted_forms(self):
|
||||
"""Return a list of forms that have been marked for deletion."""
|
||||
if not self.is_valid() or not self.can_delete:
|
||||
return []
|
||||
# construct _deleted_form_indexes which is just a list of form indexes
|
||||
# that have had their deletion widget set to True
|
||||
if not hasattr(self, '_deleted_form_indexes'):
|
||||
self._deleted_form_indexes = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
# if this is an extra form and hasn't changed, don't consider it
|
||||
if i >= self.initial_form_count() and not form.has_changed():
|
||||
continue
|
||||
if self._should_delete_form(form):
|
||||
self._deleted_form_indexes.append(i)
|
||||
return [self.forms[i] for i in self._deleted_form_indexes]
|
||||
|
||||
@property
|
||||
def ordered_forms(self):
|
||||
"""
|
||||
Return a list of form in the order specified by the incoming data.
|
||||
Raise an AttributeError if ordering is not allowed.
|
||||
"""
|
||||
if not self.is_valid() or not self.can_order:
|
||||
raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
|
||||
# Construct _ordering, which is a list of (form_index, order_field_value)
|
||||
# tuples. After constructing this list, we'll sort it by order_field_value
|
||||
# so we have a way to get to the form indexes in the order specified
|
||||
# by the form data.
|
||||
if not hasattr(self, '_ordering'):
|
||||
self._ordering = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
# if this is an extra form and hasn't changed, don't consider it
|
||||
if i >= self.initial_form_count() and not form.has_changed():
|
||||
continue
|
||||
# don't add data marked for deletion to self.ordered_data
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
continue
|
||||
self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
|
||||
# After we're done populating self._ordering, sort it.
|
||||
# A sort function to order things numerically ascending, but
|
||||
# None should be sorted below anything else. Allowing None as
|
||||
# a comparison value makes it so we can leave ordering fields
|
||||
# blank.
|
||||
|
||||
def compare_ordering_key(k):
|
||||
if k[1] is None:
|
||||
return (1, 0) # +infinity, larger than any number
|
||||
return (0, k[1])
|
||||
self._ordering.sort(key=compare_ordering_key)
|
||||
# Return a list of form.cleaned_data dicts in the order specified by
|
||||
# the form data.
|
||||
return [self.forms[i[0]] for i in self._ordering]
|
||||
|
||||
@classmethod
|
||||
def get_default_prefix(cls):
|
||||
return 'form'
|
||||
|
||||
@classmethod
|
||||
def get_ordering_widget(cls):
|
||||
return cls.ordering_widget
|
||||
|
||||
def non_form_errors(self):
|
||||
"""
|
||||
Return an ErrorList of errors that aren't associated with a particular
|
||||
form -- i.e., from formset.clean(). Return an empty ErrorList if there
|
||||
are none.
|
||||
"""
|
||||
if self._non_form_errors is None:
|
||||
self.full_clean()
|
||||
return self._non_form_errors
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
"""Return a list of form.errors for every form in self.forms."""
|
||||
if self._errors is None:
|
||||
self.full_clean()
|
||||
return self._errors
|
||||
|
||||
def total_error_count(self):
|
||||
"""Return the number of errors across all forms in the formset."""
|
||||
return len(self.non_form_errors()) +\
|
||||
sum(len(form_errors) for form_errors in self.errors)
|
||||
|
||||
def _should_delete_form(self, form):
|
||||
"""Return whether or not the form was marked for deletion."""
|
||||
return form.cleaned_data.get(DELETION_FIELD_NAME, False)
|
||||
|
||||
def is_valid(self):
|
||||
"""Return True if every form in self.forms is valid."""
|
||||
if not self.is_bound:
|
||||
return False
|
||||
# We loop over every form.errors here rather than short circuiting on the
|
||||
# first failure to make sure validation gets triggered for every form.
|
||||
forms_valid = True
|
||||
# This triggers a full clean.
|
||||
self.errors
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
# This form is going to be deleted so any of its errors
|
||||
# shouldn't cause the entire formset to be invalid.
|
||||
continue
|
||||
forms_valid &= form.is_valid()
|
||||
return forms_valid and not self.non_form_errors()
|
||||
|
||||
def full_clean(self):
|
||||
"""
|
||||
Clean all of self.data and populate self._errors and
|
||||
self._non_form_errors.
|
||||
"""
|
||||
self._errors = []
|
||||
self._non_form_errors = self.error_class()
|
||||
empty_forms_count = 0
|
||||
|
||||
if not self.is_bound: # Stop further processing.
|
||||
return
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
# Empty forms are unchanged forms beyond those with initial data.
|
||||
if not form.has_changed() and i >= self.initial_form_count():
|
||||
empty_forms_count += 1
|
||||
# Accessing errors calls full_clean() if necessary.
|
||||
# _should_delete_form() requires cleaned_data.
|
||||
form_errors = form.errors
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
continue
|
||||
self._errors.append(form_errors)
|
||||
try:
|
||||
if (self.validate_max and
|
||||
self.total_form_count() - len(self.deleted_forms) > self.max_num) or \
|
||||
self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
|
||||
raise ValidationError(ngettext(
|
||||
"Please submit %d or fewer forms.",
|
||||
"Please submit %d or fewer forms.", self.max_num) % self.max_num,
|
||||
code='too_many_forms',
|
||||
)
|
||||
if (self.validate_min and
|
||||
self.total_form_count() - len(self.deleted_forms) - empty_forms_count < self.min_num):
|
||||
raise ValidationError(ngettext(
|
||||
"Please submit %d or more forms.",
|
||||
"Please submit %d or more forms.", self.min_num) % self.min_num,
|
||||
code='too_few_forms')
|
||||
# Give self.clean() a chance to do cross-form validation.
|
||||
self.clean()
|
||||
except ValidationError as e:
|
||||
self._non_form_errors = self.error_class(e.error_list)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Hook for doing any extra formset-wide cleaning after Form.clean() has
|
||||
been called on every form. Any ValidationError raised by this method
|
||||
will not be associated with a particular form; it will be accessible
|
||||
via formset.non_form_errors()
|
||||
"""
|
||||
pass
|
||||
|
||||
def has_changed(self):
|
||||
"""Return True if data in any form differs from initial."""
|
||||
return any(form.has_changed() for form in self)
|
||||
|
||||
def add_fields(self, form, index):
|
||||
"""A hook for adding extra fields on to each form instance."""
|
||||
if self.can_order:
|
||||
# Only pre-fill the ordering field for initial forms.
|
||||
if index is not None and index < self.initial_form_count():
|
||||
form.fields[ORDERING_FIELD_NAME] = IntegerField(
|
||||
label=_('Order'),
|
||||
initial=index + 1,
|
||||
required=False,
|
||||
widget=self.get_ordering_widget(),
|
||||
)
|
||||
else:
|
||||
form.fields[ORDERING_FIELD_NAME] = IntegerField(
|
||||
label=_('Order'),
|
||||
required=False,
|
||||
widget=self.get_ordering_widget(),
|
||||
)
|
||||
if self.can_delete:
|
||||
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)
|
||||
|
||||
def add_prefix(self, index):
|
||||
return '%s-%s' % (self.prefix, index)
|
||||
|
||||
def is_multipart(self):
|
||||
"""
|
||||
Return True if the formset needs to be multipart, i.e. it
|
||||
has FileInput, or False otherwise.
|
||||
"""
|
||||
if self.forms:
|
||||
return self.forms[0].is_multipart()
|
||||
else:
|
||||
return self.empty_form.is_multipart()
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
# All the forms on a FormSet are the same, so you only need to
|
||||
# interrogate the first form for media.
|
||||
if self.forms:
|
||||
return self.forms[0].media
|
||||
else:
|
||||
return self.empty_form.media
|
||||
|
||||
def as_table(self):
|
||||
"Return this formset rendered as HTML <tr>s -- excluding the <table></table>."
|
||||
# XXX: there is no semantic division between forms here, there
|
||||
# probably should be. It might make sense to render each form as a
|
||||
# table row with each field as a td.
|
||||
forms = ' '.join(form.as_table() for form in self)
|
||||
return mark_safe(str(self.management_form) + '\n' + forms)
|
||||
|
||||
def as_p(self):
|
||||
"Return this formset rendered as HTML <p>s."
|
||||
forms = ' '.join(form.as_p() for form in self)
|
||||
return mark_safe(str(self.management_form) + '\n' + forms)
|
||||
|
||||
def as_ul(self):
|
||||
"Return this formset rendered as HTML <li>s."
|
||||
forms = ' '.join(form.as_ul() for form in self)
|
||||
return mark_safe(str(self.management_form) + '\n' + forms)
|
||||
|
||||
|
||||
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
|
||||
can_delete=False, max_num=None, validate_max=False,
|
||||
min_num=None, validate_min=False):
|
||||
"""Return a FormSet for the given form class."""
|
||||
if min_num is None:
|
||||
min_num = DEFAULT_MIN_NUM
|
||||
if max_num is None:
|
||||
max_num = DEFAULT_MAX_NUM
|
||||
# hard limit on forms instantiated, to prevent memory-exhaustion attacks
|
||||
# limit is simply max_num + DEFAULT_MAX_NUM (which is 2*DEFAULT_MAX_NUM
|
||||
# if max_num is None in the first place)
|
||||
absolute_max = max_num + DEFAULT_MAX_NUM
|
||||
attrs = {
|
||||
'form': form,
|
||||
'extra': extra,
|
||||
'can_order': can_order,
|
||||
'can_delete': can_delete,
|
||||
'min_num': min_num,
|
||||
'max_num': max_num,
|
||||
'absolute_max': absolute_max,
|
||||
'validate_min': validate_min,
|
||||
'validate_max': validate_max,
|
||||
}
|
||||
return type(form.__name__ + 'FormSet', (formset,), attrs)
|
||||
|
||||
|
||||
def all_valid(formsets):
|
||||
"""Validate every formset and return True if all are valid."""
|
||||
valid = True
|
||||
for formset in formsets:
|
||||
valid &= formset.is_valid()
|
||||
return valid
|
||||
@@ -0,0 +1 @@
|
||||
{% for name, value in widget.attrs.items() %}{% if value is not sameas False %} {{ name }}{% if value is not sameas True %}="{{ value }}"{% endif %}{% endif %}{% endfor %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input_option.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/multiple_input.html" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
|
||||
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}">
|
||||
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
|
||||
{{ widget.input_text }}:{% endif %}
|
||||
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
|
||||
@@ -0,0 +1 @@
|
||||
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/multiwidget.html" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% set id = widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
|
||||
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for widget in options %}
|
||||
<li>{% include widget.template_name %}</li>{% endfor %}{% if group %}
|
||||
</ul></li>{% endif %}{% endfor %}
|
||||
</ul>
|
||||
@@ -0,0 +1 @@
|
||||
{% for widget in widget.subwidgets -%}{% include widget.template_name %}{%- endfor %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/multiple_input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input_option.html" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
|
||||
<optgroup label="{{ group_name }}">{% endif %}{% for widget in group_choices %}
|
||||
{% include widget.template_name %}{% endfor %}{% if group_name %}
|
||||
</optgroup>{% endif %}{% endfor %}
|
||||
</select>
|
||||
@@ -0,0 +1 @@
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
@@ -0,0 +1 @@
|
||||
<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %}>{{ widget.label }}</option>
|
||||
@@ -0,0 +1 @@
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1,2 @@
|
||||
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
||||
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
1365
venv/lib/python3.8/site-packages/django/forms/models.py
Normal file
1365
venv/lib/python3.8/site-packages/django/forms/models.py
Normal file
File diff suppressed because it is too large
Load Diff
70
venv/lib/python3.8/site-packages/django/forms/renderers.py
Normal file
70
venv/lib/python3.8/site-packages/django/forms/renderers.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import functools
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.template.backends.django import DjangoTemplates
|
||||
from django.template.loader import get_template
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
try:
|
||||
from django.template.backends.jinja2 import Jinja2
|
||||
except ImportError:
|
||||
def Jinja2(params):
|
||||
raise ImportError("jinja2 isn't installed")
|
||||
|
||||
ROOT = Path(__file__).parent
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def get_default_renderer():
|
||||
renderer_class = import_string(settings.FORM_RENDERER)
|
||||
return renderer_class()
|
||||
|
||||
|
||||
class BaseRenderer:
|
||||
def get_template(self, template_name):
|
||||
raise NotImplementedError('subclasses must implement get_template()')
|
||||
|
||||
def render(self, template_name, context, request=None):
|
||||
template = self.get_template(template_name)
|
||||
return template.render(context, request=request).strip()
|
||||
|
||||
|
||||
class EngineMixin:
|
||||
def get_template(self, template_name):
|
||||
return self.engine.get_template(template_name)
|
||||
|
||||
@cached_property
|
||||
def engine(self):
|
||||
return self.backend({
|
||||
'APP_DIRS': True,
|
||||
'DIRS': [str(ROOT / self.backend.app_dirname)],
|
||||
'NAME': 'djangoforms',
|
||||
'OPTIONS': {},
|
||||
})
|
||||
|
||||
|
||||
class DjangoTemplates(EngineMixin, BaseRenderer):
|
||||
"""
|
||||
Load Django templates from the built-in widget templates in
|
||||
django/forms/templates and from apps' 'templates' directory.
|
||||
"""
|
||||
backend = DjangoTemplates
|
||||
|
||||
|
||||
class Jinja2(EngineMixin, BaseRenderer):
|
||||
"""
|
||||
Load Jinja2 templates from the built-in widget templates in
|
||||
django/forms/jinja2 and from apps' 'jinja2' directory.
|
||||
"""
|
||||
backend = Jinja2
|
||||
|
||||
|
||||
class TemplatesSetting(BaseRenderer):
|
||||
"""
|
||||
Load templates using template.loader.get_template() which is configured
|
||||
based on settings.TEMPLATES.
|
||||
"""
|
||||
def get_template(self, template_name):
|
||||
return get_template(template_name)
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input_option.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/multiple_input.html" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
|
||||
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}">
|
||||
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
|
||||
{{ widget.input_text }}:{% endif %}
|
||||
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
|
||||
@@ -0,0 +1 @@
|
||||
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/multiwidget.html" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
|
||||
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
|
||||
<li>{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
|
||||
</ul></li>{% endif %}{% endfor %}
|
||||
</ul>{% endwith %}
|
||||
@@ -0,0 +1 @@
|
||||
{% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/multiple_input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input_option.html" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
|
||||
<optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
|
||||
{% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
|
||||
</optgroup>{% endif %}{% endfor %}
|
||||
</select>
|
||||
@@ -0,0 +1 @@
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
@@ -0,0 +1 @@
|
||||
<option value="{{ widget.value|stringformat:'s' }}"{% include "django/forms/widgets/attrs.html" %}>{{ widget.label }}</option>
|
||||
@@ -0,0 +1 @@
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1,2 @@
|
||||
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
||||
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
178
venv/lib/python3.8/site-packages/django/forms/utils.py
Normal file
178
venv/lib/python3.8/site-packages/django/forms/utils.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import json
|
||||
from collections import UserList
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError # backwards compatibility
|
||||
from django.utils import timezone
|
||||
from django.utils.html import escape, format_html, format_html_join, html_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def pretty_name(name):
|
||||
"""Convert 'first_name' to 'First name'."""
|
||||
if not name:
|
||||
return ''
|
||||
return name.replace('_', ' ').capitalize()
|
||||
|
||||
|
||||
def flatatt(attrs):
|
||||
"""
|
||||
Convert a dictionary of attributes to a single string.
|
||||
The returned string will contain a leading space followed by key="value",
|
||||
XML-style pairs. In the case of a boolean value, the key will appear
|
||||
without a value. It is assumed that the keys do not need to be
|
||||
XML-escaped. If the passed dictionary is empty, then return an empty
|
||||
string.
|
||||
|
||||
The result is passed through 'mark_safe' (by way of 'format_html_join').
|
||||
"""
|
||||
key_value_attrs = []
|
||||
boolean_attrs = []
|
||||
for attr, value in attrs.items():
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
boolean_attrs.append((attr,))
|
||||
elif value is not None:
|
||||
key_value_attrs.append((attr, value))
|
||||
|
||||
return (
|
||||
format_html_join('', ' {}="{}"', sorted(key_value_attrs)) +
|
||||
format_html_join('', ' {}', sorted(boolean_attrs))
|
||||
)
|
||||
|
||||
|
||||
@html_safe
|
||||
class ErrorDict(dict):
|
||||
"""
|
||||
A collection of errors that knows how to display itself in various formats.
|
||||
|
||||
The dictionary keys are the field names, and the values are the errors.
|
||||
"""
|
||||
def as_data(self):
|
||||
return {f: e.as_data() for f, e in self.items()}
|
||||
|
||||
def get_json_data(self, escape_html=False):
|
||||
return {f: e.get_json_data(escape_html) for f, e in self.items()}
|
||||
|
||||
def as_json(self, escape_html=False):
|
||||
return json.dumps(self.get_json_data(escape_html))
|
||||
|
||||
def as_ul(self):
|
||||
if not self:
|
||||
return ''
|
||||
return format_html(
|
||||
'<ul class="errorlist">{}</ul>',
|
||||
format_html_join('', '<li>{}{}</li>', self.items())
|
||||
)
|
||||
|
||||
def as_text(self):
|
||||
output = []
|
||||
for field, errors in self.items():
|
||||
output.append('* %s' % field)
|
||||
output.append('\n'.join(' * %s' % e for e in errors))
|
||||
return '\n'.join(output)
|
||||
|
||||
def __str__(self):
|
||||
return self.as_ul()
|
||||
|
||||
|
||||
@html_safe
|
||||
class ErrorList(UserList, list):
|
||||
"""
|
||||
A collection of errors that knows how to display itself in various formats.
|
||||
"""
|
||||
def __init__(self, initlist=None, error_class=None):
|
||||
super().__init__(initlist)
|
||||
|
||||
if error_class is None:
|
||||
self.error_class = 'errorlist'
|
||||
else:
|
||||
self.error_class = 'errorlist {}'.format(error_class)
|
||||
|
||||
def as_data(self):
|
||||
return ValidationError(self.data).error_list
|
||||
|
||||
def get_json_data(self, escape_html=False):
|
||||
errors = []
|
||||
for error in self.as_data():
|
||||
message = next(iter(error))
|
||||
errors.append({
|
||||
'message': escape(message) if escape_html else message,
|
||||
'code': error.code or '',
|
||||
})
|
||||
return errors
|
||||
|
||||
def as_json(self, escape_html=False):
|
||||
return json.dumps(self.get_json_data(escape_html))
|
||||
|
||||
def as_ul(self):
|
||||
if not self.data:
|
||||
return ''
|
||||
|
||||
return format_html(
|
||||
'<ul class="{}">{}</ul>',
|
||||
self.error_class,
|
||||
format_html_join('', '<li>{}</li>', ((e,) for e in self))
|
||||
)
|
||||
|
||||
def as_text(self):
|
||||
return '\n'.join('* %s' % e for e in self)
|
||||
|
||||
def __str__(self):
|
||||
return self.as_ul()
|
||||
|
||||
def __repr__(self):
|
||||
return repr(list(self))
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in list(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return list(self) == other
|
||||
|
||||
def __getitem__(self, i):
|
||||
error = self.data[i]
|
||||
if isinstance(error, ValidationError):
|
||||
return next(iter(error))
|
||||
return error
|
||||
|
||||
def __reduce_ex__(self, *args, **kwargs):
|
||||
# The `list` reduce function returns an iterator as the fourth element
|
||||
# that is normally used for repopulating. Since we only inherit from
|
||||
# `list` for `isinstance` backward compatibility (Refs #17413) we
|
||||
# nullify this iterator as it would otherwise result in duplicate
|
||||
# entries. (Refs #23594)
|
||||
info = super(UserList, self).__reduce_ex__(*args, **kwargs)
|
||||
return info[:3] + (None, None)
|
||||
|
||||
|
||||
# Utilities for time zone support in DateTimeField et al.
|
||||
|
||||
def from_current_timezone(value):
|
||||
"""
|
||||
When time zone support is enabled, convert naive datetimes
|
||||
entered in the current time zone to aware datetimes.
|
||||
"""
|
||||
if settings.USE_TZ and value is not None and timezone.is_naive(value):
|
||||
current_timezone = timezone.get_current_timezone()
|
||||
try:
|
||||
return timezone.make_aware(value, current_timezone)
|
||||
except Exception as exc:
|
||||
raise ValidationError(
|
||||
_('%(datetime)s couldn’t be interpreted '
|
||||
'in time zone %(current_timezone)s; it '
|
||||
'may be ambiguous or it may not exist.'),
|
||||
code='ambiguous_timezone',
|
||||
params={'datetime': value, 'current_timezone': current_timezone}
|
||||
) from exc
|
||||
return value
|
||||
|
||||
|
||||
def to_current_timezone(value):
|
||||
"""
|
||||
When time zone support is enabled, convert aware datetimes
|
||||
to naive datetimes in the current time zone for display.
|
||||
"""
|
||||
if settings.USE_TZ and value is not None and timezone.is_aware(value):
|
||||
return timezone.make_naive(value)
|
||||
return value
|
||||
1074
venv/lib/python3.8/site-packages/django/forms/widgets.py
Normal file
1074
venv/lib/python3.8/site-packages/django/forms/widgets.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user