adm-ntuh-net/ntuh/dojango/forms/models.py
2024-12-12 10:19:16 +08:00

212 lines
9 KiB
Python
Executable file

from django.forms import *
from django.forms.models import BaseModelFormSet
from django.forms.models import BaseInlineFormSet
from django.forms.models import ModelChoiceIterator
from django.forms.models import InlineForeignKeyHiddenInput, InlineForeignKeyField
from django.utils.text import capfirst
from formsets import BaseFormSet
from django.db.models import fields
from dojango.forms.fields import *
from dojango.forms.widgets import DojoWidgetMixin, Textarea, Select, SelectMultiple, HiddenInput
__all__ = (
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
)
class ModelChoiceField(DojoFieldMixin, models.ModelChoiceField):
"""
Overwritten 'ModelChoiceField' using the 'DojoFieldMixin' functionality.
"""
widget = Select
class ModelMultipleChoiceField(DojoFieldMixin, models.ModelMultipleChoiceField):
"""
Overwritten 'ModelMultipleChoiceField' using the 'DojoFieldMixin' functonality.
"""
widget = SelectMultiple
# Fields #####################################################################
class InlineForeignKeyHiddenInput(DojoWidgetMixin, InlineForeignKeyHiddenInput):
"""
Overwritten InlineForeignKeyHiddenInput to use the dojango widget mixin
"""
dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
class InlineForeignKeyField(DojoFieldMixin, InlineForeignKeyField, Field):
"""
Overwritten InlineForeignKeyField to use the dojango field mixin and passing
the dojango InlineForeignKeyHiddenInput as widget.
"""
def __init__(self, parent_instance, *args, **kwargs):
self.parent_instance = parent_instance
self.pk_field = kwargs.pop("pk_field", False)
self.to_field = kwargs.pop("to_field", None)
if self.parent_instance is not None:
if self.to_field:
kwargs["initial"] = getattr(self.parent_instance, self.to_field)
else:
kwargs["initial"] = self.parent_instance.pk
kwargs["required"] = False
kwargs["widget"] = InlineForeignKeyHiddenInput
# don't call the the superclass of this one. Use the superclass of the
# normal django InlineForeignKeyField
Field.__init__(self, *args, **kwargs)
# our customized model field => form field map
# here it is defined which form field is used by which model field, when creating a ModelForm
MODEL_TO_FORM_FIELD_MAP = (
# (model_field, form_field, [optional widget])
# the order of these fields is very important for inherited model fields
# e.g. the CharField must be checked at last, because several other
# fields are a subclass of it.
(fields.CommaSeparatedIntegerField, CharField),
(fields.DateTimeField, DateTimeField), # must be in front of the DateField
(fields.DateField, DateField),
(fields.DecimalField, DecimalField),
(fields.EmailField, EmailField),
(fields.FilePathField, FilePathField),
(fields.FloatField, FloatField),
(fields.related.ForeignKey, ModelChoiceField),
(fields.files.ImageField, ImageField),
(fields.files.FileField, FileField),
(fields.IPAddressField, IPAddressField),
(fields.related.ManyToManyField, ModelMultipleChoiceField),
(fields.NullBooleanField, CharField),
(fields.BooleanField, BooleanField),
(fields.PositiveSmallIntegerField, IntegerField),
(fields.PositiveIntegerField, IntegerField),
(fields.SlugField, SlugField),
(fields.SmallIntegerField, IntegerField),
(fields.IntegerField, IntegerField),
(fields.TimeField, TimeField),
(fields.URLField, URLField),
(fields.TextField, CharField, Textarea),
(fields.CharField, CharField),
)
def formfield_function(field, **kwargs):
"""
Custom formfield function, so we can inject our own form fields. The
mapping of model fields to form fields is defined in 'MODEL_TO_FORM_FIELD_MAP'.
It uses the default django mapping as fallback, if there is no match in our
custom map.
field -- a model field
"""
for field_map in MODEL_TO_FORM_FIELD_MAP:
if isinstance(field, field_map[0]):
defaults = {}
if field.choices:
# the normal django field forms.TypedChoiceField is wired hard
# within the original db/models/fields.py.
# If we use our custom Select widget, we also have to pass in
# some additional validation field attributes.
defaults['widget'] = Select(attrs={
'extra_field_attrs':{
'required':not field.blank,
'help_text':field.help_text,
}
})
elif len(field_map) == 3:
defaults['widget']=field_map[2]
defaults.update(kwargs)
return field.formfield(form_class=field_map[1], **defaults)
# return the default formfield, if there is no equivalent
return field.formfield(**kwargs)
# ModelForms #################################################################
def fields_for_model(*args, **kwargs):
"""Changed fields_for_model function, where we use our own formfield_callback"""
kwargs["formfield_callback"] = formfield_function
return models.fields_for_model(*args, **kwargs)
class ModelFormMetaclass(models.ModelFormMetaclass):
"""
Overwritten 'ModelFormMetaClass'. We attach our own formfield generation
function.
"""
def __new__(cls, name, bases, attrs):
# this is how we can replace standard django form fields with dojo ones
attrs["formfield_callback"] = formfield_function
return super(ModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
class ModelForm(models.ModelForm):
"""
Overwritten 'ModelForm' using the metaclass defined above.
"""
__metaclass__ = ModelFormMetaclass
def modelform_factory(*args, **kwargs):
"""Changed modelform_factory function, where we use our own formfield_callback"""
kwargs["formfield_callback"] = formfield_function
kwargs["form"] = ModelForm
return models.modelform_factory(*args, **kwargs)
# ModelFormSets ##############################################################
class BaseModelFormSet(BaseModelFormSet, BaseFormSet):
def add_fields(self, form, index):
"""Overwritten BaseModelFormSet using the dojango BaseFormSet and
the ModelChoiceField.
NOTE: This method was copied from django 1.3 beta 1"""
from django.db.models import AutoField, OneToOneField, ForeignKey
self._pk_field = pk = self.model._meta.pk
def pk_is_not_editable(pk):
return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField))
or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
if pk_is_not_editable(pk) or pk.name not in form.fields:
if form.is_bound:
pk_value = form.instance.pk
else:
try:
if index is not None:
pk_value = self.get_queryset()[index].pk
else:
pk_value = None
except IndexError:
pk_value = None
if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey):
qs = pk.rel.to._default_manager.get_query_set()
else:
qs = self.model._default_manager.get_query_set()
qs = qs.using(form.instance._state.db)
form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput)
BaseFormSet.add_fields(self, form, index)
def modelformset_factory(*args, **kwargs):
"""Changed modelformset_factory function, where we use our own formfield_callback"""
kwargs["formfield_callback"] = formfield_function
kwargs["formset"] = BaseModelFormSet
return models.modelformset_factory(*args, **kwargs)
# InlineFormSets #############################################################
class BaseInlineFormSet(BaseInlineFormSet, BaseModelFormSet):
"""Overwritten BaseInlineFormSet using the dojango InlineForeignKeyFields.
NOTE: This method was copied from django 1.1"""
def add_fields(self, form, index):
super(BaseInlineFormSet, self).add_fields(form, index)
if self._pk_field == self.fk:
form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True)
else:
kwargs = {
'label': getattr(form.fields.get(self.fk.name), 'label', capfirst(self.fk.verbose_name))
}
if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name:
kwargs['to_field'] = self.fk.rel.field_name
form.fields[self.fk.name] = InlineForeignKeyField(self.instance, **kwargs)
def inlineformset_factory(*args, **kwargs):
"""Changed inlineformset_factory function, where we use our own formfield_callback"""
kwargs["formfield_callback"] = formfield_function
kwargs["formset"] = BaseInlineFormSet
return models.inlineformset_factory(*args, **kwargs)