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 StackedInline, TabularInline
|
||||
from django.contrib.admin.sites import AdminSite, site
|
||||
|
@ -1,5 +1,7 @@
|
||||
from django import oldforms, template
|
||||
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.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||
from django.db import models
|
||||
@ -94,6 +96,77 @@ class BoundField(object):
|
||||
attrs = classes and {'class': ' '.join(classes)} or {}
|
||||
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):
|
||||
"Encapsulates all admin options and functionality for a given model."
|
||||
|
||||
@ -292,7 +365,7 @@ class ModelAdmin(object):
|
||||
"""
|
||||
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.
|
||||
|
||||
@ -302,6 +375,14 @@ class ModelAdmin(object):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
opts = model._meta
|
||||
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()
|
||||
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}
|
||||
@ -331,11 +412,13 @@ class ModelAdmin(object):
|
||||
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.
|
||||
|
||||
`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.contenttypes.models import ContentType
|
||||
@ -343,6 +426,10 @@ class ModelAdmin(object):
|
||||
new_object = form.save(commit=True)
|
||||
pk_value = new_object._get_pk_val()
|
||||
|
||||
if formsets:
|
||||
for formset in formsets:
|
||||
formset.save()
|
||||
|
||||
# Construct the change message. TODO: Temporarily commented-out,
|
||||
# as manipulator object doesn't exist anymore, and we don't yet
|
||||
# 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)
|
||||
|
||||
inline_formsets = []
|
||||
if request.POST:
|
||||
new_data = request.POST.copy()
|
||||
if opts.has_field_type(models.FileField):
|
||||
new_data.update(request.FILES)
|
||||
form = ModelForm(new_data)
|
||||
if form.is_valid():
|
||||
return self.save_add(request, model, form, '../%s/')
|
||||
for FormSet in self.get_inline_formsets():
|
||||
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:
|
||||
form = ModelForm(initial=request.GET)
|
||||
for FormSet in self.get_inline_formsets():
|
||||
inline_formset = FormSet()
|
||||
inline_formsets.append(inline_formset)
|
||||
|
||||
c = template.RequestContext(request, {
|
||||
'title': _('Add %s') % opts.verbose_name,
|
||||
@ -410,6 +504,7 @@ class ModelAdmin(object):
|
||||
'is_popup': request.REQUEST.has_key('_popup'),
|
||||
'show_delete': False,
|
||||
'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)
|
||||
|
||||
@ -439,16 +534,23 @@ class ModelAdmin(object):
|
||||
|
||||
ModelForm = forms.form_for_instance(obj, formfield_callback=self.formfield_for_dbfield)
|
||||
|
||||
inline_formsets = []
|
||||
if request.POST:
|
||||
new_data = request.POST.copy()
|
||||
if opts.has_field_type(models.FileField):
|
||||
new_data.update(request.FILES)
|
||||
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():
|
||||
return self.save_change(request, model, form)
|
||||
if form.is_valid() and all_valid(inline_formsets):
|
||||
return self.save_change(request, model, form, inline_formsets)
|
||||
else:
|
||||
form = ModelForm()
|
||||
for FormSet in self.get_inline_formsets():
|
||||
inline_formset = FormSet(obj)
|
||||
inline_formsets.append(inline_formset)
|
||||
|
||||
## Populate the FormWrapper.
|
||||
#oldform = oldforms.FormWrapper(manipulator, new_data, errors)
|
||||
@ -463,7 +565,6 @@ class ModelAdmin(object):
|
||||
#related.get_accessor_name())
|
||||
#orig_list = func()
|
||||
#oldform.order_objects.extend(orig_list)
|
||||
|
||||
c = template.RequestContext(request, {
|
||||
'title': _('Change %s') % opts.verbose_name,
|
||||
'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields),
|
||||
@ -471,6 +572,7 @@ class ModelAdmin(object):
|
||||
'original': obj,
|
||||
'is_popup': request.REQUEST.has_key('_popup'),
|
||||
'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)
|
||||
|
||||
@ -572,3 +674,10 @@ class ModelAdmin(object):
|
||||
"admin/object_history.html"
|
||||
]
|
||||
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 %}
|
||||
|
||||
{% for bound_inline in bound_inlines %}
|
||||
{% render_inline bound_inline %}
|
||||
{% endfor %}
|
||||
|
||||
{% block after_related_objects %}{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
<fieldset class="module aligned">
|
||||
{% for fcw in bound_related_object.form_field_collection_wrappers %}
|
||||
@ -14,3 +43,4 @@
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</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 %}
|
||||
<fieldset class="module">
|
||||
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table>
|
||||
@ -42,3 +92,4 @@
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
{% endcomment %}
|
@ -104,3 +104,20 @@ def field_widget(parser, token):
|
||||
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
|
||||
return FieldWidgetNode(bits[1])
|
||||
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
|
||||
FORM_COUNT_FIELD_NAME = 'COUNT'
|
||||
ORDERING_FIELD_NAME = 'ORDER'
|
||||
DELETION_FIELD_NAME = 'DELETE'
|
||||
|
||||
class ManagementForm(forms.Form):
|
||||
class ManagementForm(Form):
|
||||
"""
|
||||
``ManagementForm`` is used to 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[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)
|
||||
|
||||
class BaseFormSet(object):
|
||||
@ -33,7 +35,7 @@ class BaseFormSet(object):
|
||||
self.change_form_count = self.total_forms - self.num_extra
|
||||
else:
|
||||
# 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:
|
||||
self.change_form_count = len(initial)
|
||||
self.required_forms = len(initial)
|
||||
@ -136,9 +138,9 @@ class BaseFormSet(object):
|
||||
def add_fields(self, form, index):
|
||||
"""A hook for adding extra fields on to each form instance."""
|
||||
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:
|
||||
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):
|
||||
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."""
|
||||
attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
|
||||
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 forms import BaseForm, SortedDictFromList
|
||||
from fields import Field, ChoiceField
|
||||
from widgets import Select, SelectMultiple, MultipleHiddenInput
|
||||
from fields import Field, ChoiceField, IntegerField
|
||||
from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME
|
||||
from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
|
||||
|
||||
__all__ = (
|
||||
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
||||
@ -197,3 +198,132 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
||||
else:
|
||||
final_values.append(obj)
|
||||
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