mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
newforms-admin: Got StackedInline and TabularInline working. The templates still need work, and tests for FormSet-Model integration are needed.
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5473 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
fe5194ecfd
commit
ec622fc2a9
@ -1,2 +1,3 @@
|
|||||||
from django.contrib.admin.options import ModelAdmin
|
from django.contrib.admin.options import ModelAdmin
|
||||||
|
from django.contrib.admin.options import StackedInline, TabularInline
|
||||||
from django.contrib.admin.sites import AdminSite, site
|
from django.contrib.admin.sites import AdminSite, site
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django import oldforms, template
|
from django import oldforms, template
|
||||||
from django import newforms as forms
|
from django import newforms as forms
|
||||||
|
from django.newforms.formsets import all_valid
|
||||||
|
from django.newforms.models import inline_formset
|
||||||
from django.contrib.admin import widgets
|
from django.contrib.admin import widgets
|
||||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -94,6 +96,77 @@ class BoundField(object):
|
|||||||
attrs = classes and {'class': ' '.join(classes)} or {}
|
attrs = classes and {'class': ' '.join(classes)} or {}
|
||||||
return self.field.label_tag(contents=contents, attrs=attrs)
|
return self.field.label_tag(contents=contents, attrs=attrs)
|
||||||
|
|
||||||
|
class InlineOptions(object):
|
||||||
|
"""
|
||||||
|
Options for inline editing of ``model`` instances.
|
||||||
|
|
||||||
|
Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
|
||||||
|
``model`` to its parent. This is required if ``model`` has more than one
|
||||||
|
``ForeignKey`` to its parent.
|
||||||
|
"""
|
||||||
|
def __init__(self, model, name=None, extra=3, fields=None, template=None, formfield_callback=lambda f: f.formfield()):
|
||||||
|
self.model = model
|
||||||
|
self.name = name
|
||||||
|
self.extra = extra
|
||||||
|
self.fields = fields
|
||||||
|
self.template = template or self.default_template
|
||||||
|
self.verbose_name = model._meta.verbose_name
|
||||||
|
self.verbose_name_plural = model._meta.verbose_name_plural
|
||||||
|
self.prepopulated_fields = {}
|
||||||
|
self.formfield_callback = formfield_callback
|
||||||
|
|
||||||
|
class StackedInline(InlineOptions):
|
||||||
|
default_template = 'admin/edit_inline_stacked.html'
|
||||||
|
|
||||||
|
class TabularInline(InlineOptions):
|
||||||
|
default_template = 'admin/edit_inline_tabular.html'
|
||||||
|
|
||||||
|
class BoundInline(object):
|
||||||
|
def __init__(self, opts, formset):
|
||||||
|
self.opts = opts
|
||||||
|
self.formset = formset
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for form, original in zip(self.formset.change_forms, self.formset.get_inline_objects()):
|
||||||
|
yield BoundInlineObject(form, original, self.opts)
|
||||||
|
for form in self.formset.add_forms:
|
||||||
|
yield BoundInlineObject(form, None, self.opts)
|
||||||
|
|
||||||
|
def fields(self):
|
||||||
|
# HACK: each form instance has some extra fields. Getting those fields
|
||||||
|
# from the form class will take some rearranging. Get them from the
|
||||||
|
# first form instance for now.
|
||||||
|
return list(self.formset.forms[0])
|
||||||
|
|
||||||
|
def verbose_name(self):
|
||||||
|
return self.opts.verbose_name
|
||||||
|
|
||||||
|
def verbose_name_plural(self):
|
||||||
|
return self.opts.verbose_name_plural
|
||||||
|
|
||||||
|
class BoundInlineObject(object):
|
||||||
|
def __init__(self, form, original, opts):
|
||||||
|
self.opts = opts
|
||||||
|
self.base_form = form
|
||||||
|
self.form = AdminForm(form, self.fieldsets(), opts.prepopulated_fields)
|
||||||
|
self.original = original
|
||||||
|
|
||||||
|
def fieldsets(self):
|
||||||
|
"""
|
||||||
|
Generator that yields Fieldset objects for use on add and change admin
|
||||||
|
form pages.
|
||||||
|
|
||||||
|
This default implementation looks at self.fields, but subclasses can
|
||||||
|
override this implementation and do something special based on the
|
||||||
|
given HttpRequest object.
|
||||||
|
"""
|
||||||
|
if self.opts.fields is None:
|
||||||
|
default_fields = [f for f in self.base_form.fields]
|
||||||
|
yield Fieldset(fields=default_fields)
|
||||||
|
else:
|
||||||
|
for name, options in self.opts.fields:
|
||||||
|
yield Fieldset(name, options['fields'], classes=options.get('classes', '').split(' '), description=options.get('description'))
|
||||||
|
|
||||||
class ModelAdmin(object):
|
class ModelAdmin(object):
|
||||||
"Encapsulates all admin options and functionality for a given model."
|
"Encapsulates all admin options and functionality for a given model."
|
||||||
|
|
||||||
@ -292,7 +365,7 @@ class ModelAdmin(object):
|
|||||||
"""
|
"""
|
||||||
return self.queryset(request)
|
return self.queryset(request)
|
||||||
|
|
||||||
def save_add(self, request, model, form, post_url_continue):
|
def save_add(self, request, model, form, formsets, post_url_continue):
|
||||||
"""
|
"""
|
||||||
Saves the object in the "add" stage and returns an HttpResponseRedirect.
|
Saves the object in the "add" stage and returns an HttpResponseRedirect.
|
||||||
|
|
||||||
@ -302,6 +375,14 @@ class ModelAdmin(object):
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
new_object = form.save(commit=True)
|
new_object = form.save(commit=True)
|
||||||
|
|
||||||
|
if formsets:
|
||||||
|
for formset in formsets:
|
||||||
|
# HACK: it seems like the parent obejct should be passed into
|
||||||
|
# a method of something, not just set as an attribute
|
||||||
|
formset.instance = new_object
|
||||||
|
formset.save()
|
||||||
|
|
||||||
pk_value = new_object._get_pk_val()
|
pk_value = new_object._get_pk_val()
|
||||||
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
|
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
|
||||||
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
|
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
|
||||||
@ -331,11 +412,13 @@ class ModelAdmin(object):
|
|||||||
post_url = '../../../'
|
post_url = '../../../'
|
||||||
return HttpResponseRedirect(post_url)
|
return HttpResponseRedirect(post_url)
|
||||||
|
|
||||||
def save_change(self, request, model, form):
|
def save_change(self, request, model, form, formsets=None):
|
||||||
"""
|
"""
|
||||||
Saves the object in the "change" stage and returns an HttpResponseRedirect.
|
Saves the object in the "change" stage and returns an HttpResponseRedirect.
|
||||||
|
|
||||||
`form` is a bound Form instance that's verified to be valid.
|
`form` is a bound Form instance that's verified to be valid.
|
||||||
|
|
||||||
|
`formsets` is a sequence of InlineFormSet instances that are verified to be valid.
|
||||||
"""
|
"""
|
||||||
from django.contrib.admin.models import LogEntry, CHANGE
|
from django.contrib.admin.models import LogEntry, CHANGE
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -343,6 +426,10 @@ class ModelAdmin(object):
|
|||||||
new_object = form.save(commit=True)
|
new_object = form.save(commit=True)
|
||||||
pk_value = new_object._get_pk_val()
|
pk_value = new_object._get_pk_val()
|
||||||
|
|
||||||
|
if formsets:
|
||||||
|
for formset in formsets:
|
||||||
|
formset.save()
|
||||||
|
|
||||||
# Construct the change message. TODO: Temporarily commented-out,
|
# Construct the change message. TODO: Temporarily commented-out,
|
||||||
# as manipulator object doesn't exist anymore, and we don't yet
|
# as manipulator object doesn't exist anymore, and we don't yet
|
||||||
# have a way to get fields_added, fields_changed, fields_deleted.
|
# have a way to get fields_added, fields_changed, fields_deleted.
|
||||||
@ -394,15 +481,22 @@ class ModelAdmin(object):
|
|||||||
|
|
||||||
ModelForm = forms.form_for_model(model, formfield_callback=self.formfield_for_dbfield)
|
ModelForm = forms.form_for_model(model, formfield_callback=self.formfield_for_dbfield)
|
||||||
|
|
||||||
|
inline_formsets = []
|
||||||
if request.POST:
|
if request.POST:
|
||||||
new_data = request.POST.copy()
|
new_data = request.POST.copy()
|
||||||
if opts.has_field_type(models.FileField):
|
if opts.has_field_type(models.FileField):
|
||||||
new_data.update(request.FILES)
|
new_data.update(request.FILES)
|
||||||
form = ModelForm(new_data)
|
form = ModelForm(new_data)
|
||||||
if form.is_valid():
|
for FormSet in self.get_inline_formsets():
|
||||||
return self.save_add(request, model, form, '../%s/')
|
inline_formset = FormSet(data=new_data)
|
||||||
|
inline_formsets.append(inline_formset)
|
||||||
|
if form.is_valid() and all_valid(inline_formsets):
|
||||||
|
return self.save_add(request, model, form, inline_formsets, '../%s/')
|
||||||
else:
|
else:
|
||||||
form = ModelForm(initial=request.GET)
|
form = ModelForm(initial=request.GET)
|
||||||
|
for FormSet in self.get_inline_formsets():
|
||||||
|
inline_formset = FormSet()
|
||||||
|
inline_formsets.append(inline_formset)
|
||||||
|
|
||||||
c = template.RequestContext(request, {
|
c = template.RequestContext(request, {
|
||||||
'title': _('Add %s') % opts.verbose_name,
|
'title': _('Add %s') % opts.verbose_name,
|
||||||
@ -410,6 +504,7 @@ class ModelAdmin(object):
|
|||||||
'is_popup': request.REQUEST.has_key('_popup'),
|
'is_popup': request.REQUEST.has_key('_popup'),
|
||||||
'show_delete': False,
|
'show_delete': False,
|
||||||
'javascript_imports': self.javascript_add(request),
|
'javascript_imports': self.javascript_add(request),
|
||||||
|
'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
|
||||||
})
|
})
|
||||||
return render_change_form(self, model, model.AddManipulator(), c, add=True)
|
return render_change_form(self, model, model.AddManipulator(), c, add=True)
|
||||||
|
|
||||||
@ -439,16 +534,23 @@ class ModelAdmin(object):
|
|||||||
|
|
||||||
ModelForm = forms.form_for_instance(obj, formfield_callback=self.formfield_for_dbfield)
|
ModelForm = forms.form_for_instance(obj, formfield_callback=self.formfield_for_dbfield)
|
||||||
|
|
||||||
|
inline_formsets = []
|
||||||
if request.POST:
|
if request.POST:
|
||||||
new_data = request.POST.copy()
|
new_data = request.POST.copy()
|
||||||
if opts.has_field_type(models.FileField):
|
if opts.has_field_type(models.FileField):
|
||||||
new_data.update(request.FILES)
|
new_data.update(request.FILES)
|
||||||
form = ModelForm(new_data)
|
form = ModelForm(new_data)
|
||||||
|
for FormSet in self.get_inline_formsets():
|
||||||
|
inline_formset = FormSet(obj, new_data)
|
||||||
|
inline_formsets.append(inline_formset)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid() and all_valid(inline_formsets):
|
||||||
return self.save_change(request, model, form)
|
return self.save_change(request, model, form, inline_formsets)
|
||||||
else:
|
else:
|
||||||
form = ModelForm()
|
form = ModelForm()
|
||||||
|
for FormSet in self.get_inline_formsets():
|
||||||
|
inline_formset = FormSet(obj)
|
||||||
|
inline_formsets.append(inline_formset)
|
||||||
|
|
||||||
## Populate the FormWrapper.
|
## Populate the FormWrapper.
|
||||||
#oldform = oldforms.FormWrapper(manipulator, new_data, errors)
|
#oldform = oldforms.FormWrapper(manipulator, new_data, errors)
|
||||||
@ -463,7 +565,6 @@ class ModelAdmin(object):
|
|||||||
#related.get_accessor_name())
|
#related.get_accessor_name())
|
||||||
#orig_list = func()
|
#orig_list = func()
|
||||||
#oldform.order_objects.extend(orig_list)
|
#oldform.order_objects.extend(orig_list)
|
||||||
|
|
||||||
c = template.RequestContext(request, {
|
c = template.RequestContext(request, {
|
||||||
'title': _('Change %s') % opts.verbose_name,
|
'title': _('Change %s') % opts.verbose_name,
|
||||||
'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields),
|
'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields),
|
||||||
@ -471,6 +572,7 @@ class ModelAdmin(object):
|
|||||||
'original': obj,
|
'original': obj,
|
||||||
'is_popup': request.REQUEST.has_key('_popup'),
|
'is_popup': request.REQUEST.has_key('_popup'),
|
||||||
'javascript_imports': self.javascript_change(request, obj),
|
'javascript_imports': self.javascript_change(request, obj),
|
||||||
|
'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
|
||||||
})
|
})
|
||||||
return render_change_form(self, model, model.ChangeManipulator(object_id), c, change=True)
|
return render_change_form(self, model, model.ChangeManipulator(object_id), c, change=True)
|
||||||
|
|
||||||
@ -572,3 +674,10 @@ class ModelAdmin(object):
|
|||||||
"admin/object_history.html"
|
"admin/object_history.html"
|
||||||
]
|
]
|
||||||
return render_to_response(template_list, extra_context, context_instance=template.RequestContext(request))
|
return render_to_response(template_list, extra_context, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
|
def get_inline_formsets(self):
|
||||||
|
inline_formset_classes = []
|
||||||
|
for opts in self.inlines:
|
||||||
|
inline = inline_formset(self.model, opts.model, formfield_callback=self.formfield_for_dbfield, fields=opts.fields, extra=opts.extra)
|
||||||
|
inline_formset_classes.append(inline)
|
||||||
|
return inline_formset_classes
|
||||||
|
@ -63,6 +63,10 @@
|
|||||||
|
|
||||||
{% block after_field_sets %}{% endblock %}
|
{% block after_field_sets %}{% endblock %}
|
||||||
|
|
||||||
|
{% for bound_inline in bound_inlines %}
|
||||||
|
{% render_inline bound_inline %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{% block after_related_objects %}{% endblock %}
|
{% block after_related_objects %}{% endblock %}
|
||||||
|
|
||||||
{% submit_row %}
|
{% submit_row %}
|
||||||
|
@ -1,3 +1,32 @@
|
|||||||
|
{{ bound_inline.formset.management_form }}
|
||||||
|
{% for bound_inline_object in bound_inline %}
|
||||||
|
<fieldset class="module aligned {{ bfset.fieldset.classes }}">
|
||||||
|
<h2>{{ bound_inline.verbose_name|title }} #{{ forloop.counter }}</h2>
|
||||||
|
{% for bfset in bound_inline_object.form %}
|
||||||
|
<fieldset class="module aligned {{ bfset.fieldset.classes }}">
|
||||||
|
{% if bfset.fieldset.name %}<h2>{{ bfset.fieldset.name }}</h2>{% endif %}
|
||||||
|
{% if bfset.fieldset.description %}<div class="description">{{ bfset.fieldset.description }}</div>{% endif %}
|
||||||
|
{% for line in bfset %}
|
||||||
|
<div class="form-row{% if line.errors %} errors{% endif %}">
|
||||||
|
{{ line.errors }}
|
||||||
|
{% for field in line %}
|
||||||
|
{% if field.is_checkbox %}
|
||||||
|
{{ field.field }}{{ field.label_tag }}
|
||||||
|
{% else %}
|
||||||
|
{{ field.label_tag }}{{ field.field }}
|
||||||
|
{% endif %}
|
||||||
|
{% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text }}</p>{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
<!-- Old forms. Here for reference until new forms match features -->
|
||||||
|
|
||||||
{% load admin_modify %}
|
{% load admin_modify %}
|
||||||
<fieldset class="module aligned">
|
<fieldset class="module aligned">
|
||||||
{% for fcw in bound_related_object.form_field_collection_wrappers %}
|
{% for fcw in bound_related_object.form_field_collection_wrappers %}
|
||||||
@ -14,3 +43,4 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{% endcomment %}
|
@ -1,3 +1,53 @@
|
|||||||
|
{{ bound_inline.formset.management_form }}
|
||||||
|
<fieldset class="module">
|
||||||
|
<h2>{{ bound_inline.verbose_name_plural|capfirst|escape }}</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
{% for field in bound_inline.fields %}
|
||||||
|
{% if not field.is_hidden %}
|
||||||
|
<th>{{ field.label|capfirst|escape }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr></thead>
|
||||||
|
|
||||||
|
{% for bound_inline_object in bound_inline %}
|
||||||
|
|
||||||
|
<!-- still need optional original object -->
|
||||||
|
|
||||||
|
{% if bound_inline_object.form.form.errors %}
|
||||||
|
<tr class="errorlist"><td colspan="{{ bound_inline.fields|length }}">
|
||||||
|
{{ bound_inline_object.form.form.errors }}
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<tr class="{% cycle row1,row2 %}">
|
||||||
|
{% for bfset in bound_inline_object.form %}
|
||||||
|
{% for line in bfset %}
|
||||||
|
{% for field in line %}
|
||||||
|
|
||||||
|
{% if not field.field.is_hidden %}
|
||||||
|
<td>{{ field.field }}</td>
|
||||||
|
{% else %}
|
||||||
|
{{ field.field }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- still need optional view on site link -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- still need for fcw in bound_related_object.form_field_collection_wrappers -->
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
<!-- Old forms. Here for reference until new forms match features -->
|
||||||
|
|
||||||
{% load admin_modify %}
|
{% load admin_modify %}
|
||||||
<fieldset class="module">
|
<fieldset class="module">
|
||||||
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table>
|
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table>
|
||||||
@ -42,3 +92,4 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{% endcomment %}
|
@ -104,3 +104,20 @@ def field_widget(parser, token):
|
|||||||
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
|
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
|
||||||
return FieldWidgetNode(bits[1])
|
return FieldWidgetNode(bits[1])
|
||||||
field_widget = register.tag(field_widget)
|
field_widget = register.tag(field_widget)
|
||||||
|
|
||||||
|
class InlineNode(template.Node):
|
||||||
|
def __init__(self, inline_var):
|
||||||
|
self.inline_var = inline_var
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
inline = context[self.inline_var]
|
||||||
|
t = loader.get_template(inline.opts.template)
|
||||||
|
output = t.render(context)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def render_inline(parser, token):
|
||||||
|
bits = token.contents.split()
|
||||||
|
if len(bits) != 2:
|
||||||
|
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
|
||||||
|
return InlineNode(bits[1])
|
||||||
|
render_inline = register.tag(render_inline)
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
from django import newforms as forms
|
from forms import Form, ValidationError
|
||||||
|
from fields import IntegerField, BooleanField
|
||||||
|
from widgets import HiddenInput
|
||||||
|
|
||||||
# special field names
|
# special field names
|
||||||
FORM_COUNT_FIELD_NAME = 'COUNT'
|
FORM_COUNT_FIELD_NAME = 'COUNT'
|
||||||
ORDERING_FIELD_NAME = 'ORDER'
|
ORDERING_FIELD_NAME = 'ORDER'
|
||||||
DELETION_FIELD_NAME = 'DELETE'
|
DELETION_FIELD_NAME = 'DELETE'
|
||||||
|
|
||||||
class ManagementForm(forms.Form):
|
class ManagementForm(Form):
|
||||||
"""
|
"""
|
||||||
``ManagementForm`` is used to keep track of how many form instances
|
``ManagementForm`` is used to keep track of how many form instances
|
||||||
are displayed on the page. If adding new forms via javascript, you should
|
are displayed on the page. If adding new forms via javascript, you should
|
||||||
increment the count field of this form as well.
|
increment the count field of this form as well.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.base_fields[FORM_COUNT_FIELD_NAME] = forms.IntegerField(widget=forms.HiddenInput)
|
self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput)
|
||||||
super(ManagementForm, self).__init__(*args, **kwargs)
|
super(ManagementForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
class BaseFormSet(object):
|
class BaseFormSet(object):
|
||||||
@ -33,7 +35,7 @@ class BaseFormSet(object):
|
|||||||
self.change_form_count = self.total_forms - self.num_extra
|
self.change_form_count = self.total_forms - self.num_extra
|
||||||
else:
|
else:
|
||||||
# not sure that ValidationError is the best thing to raise here
|
# not sure that ValidationError is the best thing to raise here
|
||||||
raise forms.ValidationError('ManagementForm data is missing or has been tampered with')
|
raise ValidationError('ManagementForm data is missing or has been tampered with')
|
||||||
elif initial:
|
elif initial:
|
||||||
self.change_form_count = len(initial)
|
self.change_form_count = len(initial)
|
||||||
self.required_forms = len(initial)
|
self.required_forms = len(initial)
|
||||||
@ -136,9 +138,9 @@ class BaseFormSet(object):
|
|||||||
def add_fields(self, form, index):
|
def add_fields(self, form, index):
|
||||||
"""A hook for adding extra fields on to each form instance."""
|
"""A hook for adding extra fields on to each form instance."""
|
||||||
if self.orderable:
|
if self.orderable:
|
||||||
form.fields[ORDERING_FIELD_NAME] = forms.IntegerField(label='Order', initial=index+1)
|
form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1)
|
||||||
if self.deletable:
|
if self.deletable:
|
||||||
form.fields[DELETION_FIELD_NAME] = forms.BooleanField(label='Delete', required=False)
|
form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False)
|
||||||
|
|
||||||
def add_prefix(self, index):
|
def add_prefix(self, index):
|
||||||
return '%s-%s' % (self.prefix, index)
|
return '%s-%s' % (self.prefix, index)
|
||||||
@ -151,3 +153,10 @@ def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, de
|
|||||||
"""Return a FormSet for the given form class."""
|
"""Return a FormSet for the given form class."""
|
||||||
attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
|
attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
|
||||||
return type(form.__name__ + 'FormSet', (formset,), attrs)
|
return type(form.__name__ + 'FormSet', (formset,), attrs)
|
||||||
|
|
||||||
|
def all_valid(formsets):
|
||||||
|
"""Returns true if every formset in formsets is valid."""
|
||||||
|
for formset in formsets:
|
||||||
|
if not formset.is_valid():
|
||||||
|
return False
|
||||||
|
return True
|
@ -7,8 +7,9 @@ from django.utils.translation import gettext
|
|||||||
|
|
||||||
from util import ValidationError
|
from util import ValidationError
|
||||||
from forms import BaseForm, SortedDictFromList
|
from forms import BaseForm, SortedDictFromList
|
||||||
from fields import Field, ChoiceField
|
from fields import Field, ChoiceField, IntegerField
|
||||||
from widgets import Select, SelectMultiple, MultipleHiddenInput
|
from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME
|
||||||
|
from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
||||||
@ -197,3 +198,132 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|||||||
else:
|
else:
|
||||||
final_values.append(obj)
|
final_values.append(obj)
|
||||||
return final_values
|
return final_values
|
||||||
|
|
||||||
|
# Model-FormSet integration ###################################################
|
||||||
|
|
||||||
|
def initial_data(instance, fields=None):
|
||||||
|
"""
|
||||||
|
Return a dictionary from data in ``instance`` that is suitable for
|
||||||
|
use as a ``Form`` constructor's ``initial`` argument.
|
||||||
|
|
||||||
|
Provide ``fields`` to specify the names of specific fields to return.
|
||||||
|
All field values in the instance will be returned if ``fields`` is not
|
||||||
|
provided.
|
||||||
|
"""
|
||||||
|
model = instance.__class__
|
||||||
|
opts = model._meta
|
||||||
|
initial = {}
|
||||||
|
for f in opts.fields + opts.many_to_many:
|
||||||
|
if not f.editable:
|
||||||
|
continue
|
||||||
|
if fields and not f.name in fields:
|
||||||
|
continue
|
||||||
|
initial[f.name] = f.value_from_object(instance)
|
||||||
|
return initial
|
||||||
|
|
||||||
|
class BaseModelFormSet(BaseFormSet):
|
||||||
|
"""
|
||||||
|
A ``FormSet`` attatched to a particular model or sequence of model instances.
|
||||||
|
"""
|
||||||
|
model = None
|
||||||
|
|
||||||
|
def __init__(self, data=None, auto_id='id_%s', prefix=None, instances=None):
|
||||||
|
self.instances = instances
|
||||||
|
kwargs = {'data': data, 'auto_id': auto_id, 'prefix': prefix}
|
||||||
|
if instances:
|
||||||
|
kwargs['initial'] = [initial_data(instance) for instance in instances]
|
||||||
|
super(BaseModelFormSet, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def save_new(self, form, commit=True):
|
||||||
|
"""Saves and retrutns a new model instance for the given form."""
|
||||||
|
return save_instance(form, self.model(), commit=commit)
|
||||||
|
|
||||||
|
def save_instance(self, form, instance, commit=True):
|
||||||
|
"""Saves and retrutns an existing model instance for the given form."""
|
||||||
|
return save_instance(form, instance, commit=commit)
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
"""Saves model instances for every form, adding and changing instances
|
||||||
|
as necessary, and returns the list of instances.
|
||||||
|
"""
|
||||||
|
saved_instances = []
|
||||||
|
# put self.instances into a dict so they are easy to lookup by pk
|
||||||
|
instances = {}
|
||||||
|
for instance in self.instances:
|
||||||
|
instances[instance._get_pk_val()] = instance
|
||||||
|
if self.instances:
|
||||||
|
# update/save existing instances
|
||||||
|
for form in self.change_forms:
|
||||||
|
instance = instances[form.cleaned_data[self.model._meta.pk.attname]]
|
||||||
|
if form.cleaned_data[DELETION_FIELD_NAME]:
|
||||||
|
instance.delete()
|
||||||
|
else:
|
||||||
|
saved_instances.append(self.save_instance(form, instance, commit=commit))
|
||||||
|
# create/save new instances
|
||||||
|
for form in self.add_forms:
|
||||||
|
if form.is_empty():
|
||||||
|
continue
|
||||||
|
saved_instances.append(self.save_new(form, commit=commit))
|
||||||
|
return saved_instances
|
||||||
|
|
||||||
|
def add_fields(self, form, index):
|
||||||
|
"""Add a hidden field for the object's primary key."""
|
||||||
|
form.fields[self.model._meta.pk.attname] = IntegerField(required=False, widget=HiddenInput)
|
||||||
|
super(BaseModelFormSet, self).add_fields(form, index)
|
||||||
|
|
||||||
|
def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None):
|
||||||
|
form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback)
|
||||||
|
FormSet = formset_for_form(form, formset, extra, orderable, deletable)
|
||||||
|
FormSet.model = model
|
||||||
|
return FormSet
|
||||||
|
|
||||||
|
class InlineFormset(BaseModelFormSet):
|
||||||
|
"""A formset for child objects related to a parent."""
|
||||||
|
def __init__(self, instance=None, data=None):
|
||||||
|
self.instance = instance
|
||||||
|
super(InlineFormset, self).__init__(data, instances=self.get_inline_objects())
|
||||||
|
|
||||||
|
def get_inline_objects(self):
|
||||||
|
if self.instance is None:
|
||||||
|
return []
|
||||||
|
from django.db.models.fields.related import RelatedObject
|
||||||
|
# is there a better way to get the object descriptor?
|
||||||
|
rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
|
||||||
|
return getattr(self.instance, rel_name).all()
|
||||||
|
|
||||||
|
def save_new(self, form, commit=True):
|
||||||
|
kwargs = {self.fk.get_attname(): self.instance._get_pk_val()}
|
||||||
|
new_obj = self.model(**kwargs)
|
||||||
|
return save_instance(form, new_obj, commit=commit)
|
||||||
|
|
||||||
|
def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, formfield_callback=lambda f: f.formfield()):
|
||||||
|
"""
|
||||||
|
Returns an ``InlineFormset`` for the given kwargs.
|
||||||
|
|
||||||
|
You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
|
||||||
|
to ``parent_model``.
|
||||||
|
"""
|
||||||
|
from django.db.models import ForeignKey
|
||||||
|
opts = model._meta
|
||||||
|
# figure out what the ForeignKey from model to parent_model is
|
||||||
|
if fk_name is None:
|
||||||
|
fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model]
|
||||||
|
if len(fks_to_parent) == 1:
|
||||||
|
fk = fks_to_parent[0]
|
||||||
|
elif len(fks_to_parent) == 0:
|
||||||
|
raise Exception("%s has no ForeignKey to %s" % (model, parent_model))
|
||||||
|
else:
|
||||||
|
raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
|
||||||
|
# let the formset handle object deletion by default
|
||||||
|
FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, formfield_callback=formfield_callback, extra=extra, deletable=True)
|
||||||
|
# HACK: remove the ForeignKey to the parent from every form
|
||||||
|
# This should be done a line above before we pass 'fields' to formset_for_model
|
||||||
|
# an 'omit' argument would be very handy here
|
||||||
|
try:
|
||||||
|
del FormSet.form_class.base_fields[fk.name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
FormSet.parent_model = parent_model
|
||||||
|
FormSet.fk_name = fk.name
|
||||||
|
FormSet.fk = fk
|
||||||
|
return FormSet
|
||||||
|
Loading…
x
Reference in New Issue
Block a user