Django

Code

Ticket #6241: formset_refactor_7.diff

File formset_refactor_7.diff, 49.6 kB (added by brosner, 1 year ago)

updated to r6955 (this does not include any new changes since my last patch)

  • a/django/contrib/admin/options.py

    old new  
    342342            fields = flatten_fieldsets(self.declared_fieldsets) 
    343343        else: 
    344344            fields = None 
    345         return forms.form_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 
     345        return forms.modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 
    346346 
    347347    def form_change(self, request, obj): 
    348348        """ 
     
    352352            fields = flatten_fieldsets(self.declared_fieldsets) 
    353353        else: 
    354354            fields = None 
    355         return forms.form_for_instance(obj, fields=fields, formfield_callback=self.formfield_for_dbfield) 
     355        return forms.modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 
    356356 
    357357    def save_add(self, request, model, form, formsets, post_url_continue): 
    358358        """ 
     
    492492            # Object list will give 'Permission Denied', so go back to admin home 
    493493            post_url = '../../../' 
    494494 
    495         ModelForm = self.form_add(request) 
     495        Form = self.form_add(request) 
    496496        inline_formsets = [] 
    497497        obj = self.model() 
    498498        if request.method == 'POST': 
    499             form = ModelForm(request.POST, request.FILES) 
     499            form = Form(request.POST, request.FILES) 
    500500            for FormSet in self.formsets_add(request): 
    501                 inline_formset = FormSet(obj, data=request.POST, files=request.FILES) 
     501                inline_formset = FormSet(request.POST, request.FILES) 
    502502                inline_formsets.append(inline_formset) 
    503503            if all_valid(inline_formsets) and form.is_valid(): 
    504504                return self.save_add(request, model, form, inline_formsets, '../%s/') 
    505505        else: 
    506             form = ModelForm(initial=request.GET) 
     506            form = Form(initial=request.GET) 
    507507            for FormSet in self.formsets_add(request): 
    508                 inline_formset = FormSet(obj
     508                inline_formset = FormSet(
    509509                inline_formsets.append(inline_formset) 
    510510 
    511511        adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields) 
     
    552552        if request.POST and request.POST.has_key("_saveasnew"): 
    553553            return self.add_view(request, form_url='../../add/') 
    554554 
    555         ModelForm = self.form_change(request, obj) 
     555        Form = self.form_change(request, obj) 
    556556        inline_formsets = [] 
    557557        if request.method == 'POST': 
    558             form = ModelForm(request.POST, request.FILES
     558            form = Form(request.POST, request.FILES, instance=obj
    559559            for FormSet in self.formsets_change(request, obj): 
    560                 inline_formset = FormSet(obj, request.POST, request.FILES
     560                inline_formset = FormSet(request.POST, request.FILES, instance=obj
    561561                inline_formsets.append(inline_formset) 
    562562 
    563563            if all_valid(inline_formsets) and form.is_valid(): 
    564564                return self.save_change(request, model, form, inline_formsets) 
    565565        else: 
    566             form = ModelForm(
     566            form = Form(instance=obj, initial=request.GET
    567567            for FormSet in self.formsets_change(request, obj): 
    568                 inline_formset = FormSet(obj) 
     568                inline_formset = FormSet(instance=obj) 
    569569                inline_formsets.append(inline_formset) 
    570570 
    571571        ## Populate the FormWrapper. 
     
    755755    def fieldsets_add(self, request): 
    756756        if self.declared_fieldsets: 
    757757            return self.declared_fieldsets 
    758         form = self.formset_add(request).form_class 
    759         return [(None, {'fields': form.base_fields.keys()})] 
     758        formset = self.formset_add(request) 
     759        return [(None, {'fields': formset.base_fields.keys()})] 
    760760 
    761761    def fieldsets_change(self, request, obj): 
    762762        if self.declared_fieldsets: 
    763763            return self.declared_fieldsets 
    764         form = self.formset_change(request, obj).form_class 
    765         return [(None, {'fields': form.base_fields.keys()})] 
     764        formset = self.formset_change(request, obj) 
     765        return [(None, {'fields': formset.base_fields.keys()})] 
    766766 
    767767class StackedInline(InlineModelAdmin): 
    768768    template = 'admin/edit_inline/stacked.html' 
     
    778778        self.opts = inline 
    779779        self.formset = formset 
    780780        self.fieldsets = fieldsets 
     781        # place orderable and deletable here since _meta is inaccesible in the 
     782        # templates. 
     783        self.orderable = formset._meta.orderable 
     784        self.deletable = formset._meta.deletable 
    781785 
    782786    def __iter__(self): 
    783787        for form, original in zip(self.formset.change_forms, self.formset.get_queryset()): 
     
    787791 
    788792    def fields(self): 
    789793        for field_name in flatten_fieldsets(self.fieldsets): 
    790             yield self.formset.form_class.base_fields[field_name] 
     794            yield self.formset.base_fields[field_name] 
    791795 
    792796class InlineAdminForm(AdminForm): 
    793797    """ 
  • a/django/contrib/admin/templates/admin/edit_inline/tabular.html

    old new  
    1111         <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th> 
    1212        {% endif %} 
    1313     {% endfor %} 
    14      {% if inline_admin_formset.formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %} 
     14     {% if inline_admin_formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %} 
    1515     </tr></thead> 
    1616    
    1717     {% for inline_admin_form in inline_admin_formset %} 
     
    4545          {% endfor %} 
    4646        {% endfor %} 
    4747                 
    48         {% if inline_admin_formset.formset.deletable %}<td class="delete">{{ inline_admin_form.deletion_field.field }}</td>{% endif %} 
     48        {% if inline_admin_formset.deletable %}<td class="delete">{{ inline_admin_form.deletion_field.field }}</td>{% endif %} 
    4949         
    5050        </tr> 
    5151 
  • a/django/newforms/formsets.py

    old new  
    1 from forms import Form 
    2 from fields import IntegerField, BooleanField 
     1 
     2from warnings import warn 
     3 
     4from django.utils.datastructures import SortedDict 
     5from django.utils.translation import ugettext_lazy as _ 
     6 
     7from forms import BaseForm, Form 
     8from fields import Field, IntegerField, BooleanField 
     9from options import FormSetOptions 
    310from widgets import HiddenInput, Media 
    411from util import ErrorList, ValidationError 
    512 
    6 __all__ = ('BaseFormSet', 'formset_for_form', 'all_valid') 
     13__all__ = ('BaseFormSet', 'FormSet', 'formset_for_form', 'all_valid') 
    714 
    815# special field names 
    916FORM_COUNT_FIELD_NAME = 'COUNT' 
     
    2027        self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput) 
    2128        super(ManagementForm, self).__init__(*args, **kwargs) 
    2229 
     30class BaseFormSetMetaclass(type): 
     31    def __new__(cls, name, bases, attrs, **options): 
     32        fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 
     33        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 
     34         
     35        # If this class is subclassing another FormSet, ad that FormSet's fields. 
     36        # Note that we loop over the bases in *reverse*. This is necessary in 
     37        # order to preserve the correct order of fields. 
     38        for base in bases[::-1]: 
     39            if hasattr(base, "base_fields"): 
     40                fields = base.base_fields.items() + fields 
     41        attrs["base_fields"] = SortedDict(fields) 
     42         
     43        opts = FormSetOptions(options and options or attrs.get("Meta", None)) 
     44        attrs["_meta"] = opts 
     45         
     46        return type.__new__(cls, name, bases, attrs) 
     47 
    2348class BaseFormSet(object): 
    2449    """A collection of instances of the same Form class.""" 
    2550 
     
    3762            self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix) 
    3863            if self.management_form.is_valid(): 
    3964                self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME] 
    40                 self.required_forms = self.total_forms - self.num_extra 
    41                 self.change_form_count = self.total_forms - self.num_extra 
     65                self.required_forms = self.total_forms - self._meta.num_extra 
     66                self.change_form_count = self.total_forms - self._meta.num_extra 
    4267            else: 
    4368                # not sure that ValidationError is the best thing to raise here 
    4469                raise ValidationError('ManagementForm data is missing or has been tampered with') 
    4570        elif initial: 
    4671            self.change_form_count = len(initial) 
    4772            self.required_forms = len(initial) 
    48             self.total_forms = self.required_forms + self.num_extra 
     73            self.total_forms = self.required_forms + self._meta.num_extra 
    4974            self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 
    5075        else: 
    5176            self.change_form_count = 0 
    5277            self.required_forms = 0 
    53             self.total_forms = self.num_extra 
     78            self.total_forms = self._meta.num_extra 
    5479            self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 
    5580 
    5681    def _get_add_forms(self): 
    5782        """Return a list of all the add forms in this ``FormSet``.""" 
    58         FormClass = self.form_class 
    5983        if not hasattr(self, '_add_forms'): 
    6084            add_forms = [] 
    6185            for i in range(self.change_form_count, self.total_forms): 
     
    6488                    kwargs['data'] = self.data 
    6589                if self.files: 
    6690                    kwargs['files'] = self.files 
    67                 add_form = FormClass(**kwargs) 
     91                add_form = self.get_form_class(i)(**kwargs) 
    6892                self.add_fields(add_form, i) 
    6993                add_forms.append(add_form) 
    7094            self._add_forms = add_forms 
     
    7397 
    7498    def _get_change_forms(self): 
    7599        """Return a list of all the change forms in this ``FormSet``.""" 
    76         FormClass = self.form_class 
    77100        if not hasattr(self, '_change_forms'): 
    78101            change_forms = [] 
    79102            for i in range(0, self.change_form_count): 
     
    84107                    kwargs['files'] = self.files 
    85108                if self.initial: 
    86109                    kwargs['initial'] = self.initial[i] 
    87                 change_form = FormClass(**kwargs) 
     110                change_form = self.get_form_class(i)(**kwargs) 
    88111                self.add_fields(change_form, i) 
    89112                change_forms.append(change_form) 
    90             self._change_forms= change_forms 
     113            self._change_forms = change_forms 
    91114        return self._change_forms 
    92115    change_forms = property(_get_change_forms) 
    93116 
     
    117140        # Process change forms 
    118141        for form in self.change_forms: 
    119142            if form.is_valid(): 
    120                 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     143                if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
    121144                    self.deleted_data.append(form.cleaned_data) 
    122145                else: 
    123146                    self.cleaned_data.append(form.cleaned_data) 
     
    144167        add_errors.reverse() 
    145168        errors.extend(add_errors) 
    146169        # Sort cleaned_data if the formset is orderable. 
    147         if self.orderable: 
     170        if self._meta.orderable: 
    148171            self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME]) 
    149172        # Give self.clean() a chance to do validation 
    150173        try: 
     
    167190        via formset.non_form_errors() 
    168191        """ 
    169192        return self.cleaned_data 
     193     
     194    def get_form_class(self, index): 
     195        """ 
     196        A hook to change a form class object. 
     197        """ 
     198        FormClass = self._meta.form 
     199        FormClass.base_fields = self.base_fields 
     200        return FormClass 
    170201 
    171202    def add_fields(self, form, index): 
    172203        """A hook for adding extra fields on to each form instance.""" 
    173         if self.orderable: 
     204        if self._meta.orderable: 
    174205            form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1) 
    175         if self.deletable: 
     206        if self._meta.deletable: 
    176207            form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False) 
    177208 
    178209    def add_prefix(self, index): 
     
    192223        else: 
    193224            return Media() 
    194225    media = property(_get_media) 
     226 
     227class FormSet(BaseFormSet): 
     228    __metaclass__ = BaseFormSetMetaclass 
    195229     
    196 def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False): 
     230def formset_for_form(form, formset=FormSet, num_extra=1, orderable=False, 
     231                     deletable=False): 
    197232    """Return a FormSet for the given form class.""" 
    198     attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable} 
    199     return type(form.__name__ + 'FormSet', (formset,), attrs) 
    200  
     233    warn("formset_for_form is deprecated, use FormSet instead.", 
     234         PendingDeprecationWarning, 
     235         stacklevel=3) 
     236    return BaseFormSetMetaclass( 
     237        form.__name__ + "FormSet", (formset,), form.base_fields, 
     238        form=form, num_extra=num_extra, orderable=orderable, 
     239        deletable=deletable) 
     240         
    201241def all_valid(formsets): 
    202242    """Returns true if every formset in formsets is valid.""" 
    203243    valid = True 
  • a/django/newforms/models.py

    old new  
    1313from util import ValidationError, ErrorList 
    1414from forms import BaseForm 
    1515from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES 
    16 from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME 
     16from formsets import FormSetOptions, BaseFormSet, formset_for_form, DELETION_FIELD_NAME 
     17from options import ModelFormOptions, ModelFormSetOptions, InlineFormSetOptions 
    1718from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput 
    1819 
    1920__all__ = ( 
    2021    'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 
    2122    'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 
     23    'ModelFormSet', 'InlineFormset', 'modelform_for_model', 
    2224    'formset_for_model', 'inline_formset', 
    2325    'ModelChoiceField', 'ModelMultipleChoiceField', 
    2426) 
     
    207209            field_list.append((f.name, formfield)) 
    208210    return SortedDict(field_list) 
    209211 
    210 class ModelFormOptions(object): 
    211     def __init__(self, options=None): 
    212         self.model = getattr(options, 'model', None) 
    213         self.fields = getattr(options, 'fields', None) 
    214         self.exclude = getattr(options, 'exclude', None) 
    215  
    216212class ModelFormMetaclass(type): 
     213    opts_class = ModelFormOptions 
     214     
    217215    def __new__(cls, name, bases, attrs, 
    218                 formfield_callback=lambda f: f.formfield()): 
     216                formfield_callback=lambda f: f.formfield(), **options): 
    219217        fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 
    220218        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 
    221219 
     
    227225                fields = base.base_fields.items() + fields 
    228226        declared_fields = SortedDict(fields) 
    229227 
    230         opts = ModelFormOptions(attrs.get('Meta', None)) 
     228        opts = cls.opts_class(options and options or attrs.get('Meta', None)) 
    231229        attrs['_meta'] = opts 
    232230 
    233231        # Don't allow more than one Meta model defenition in bases. The fields 
     
    260258        else: 
    261259            attrs['base_fields'] = declared_fields 
    262260        return type.__new__(cls, name, bases, attrs) 
    263  
     261         
    264262class BaseModelForm(BaseForm): 
    265263    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
    266264                 initial=None, error_class=ErrorList, label_suffix=':', instance=None): 
     
    293291class ModelForm(BaseModelForm): 
    294292    __metaclass__ = ModelFormMetaclass 
    295293 
     294# this should really be named form_for_model. 
     295def modelform_for_model(model, form=ModelForm, 
     296                        formfield_callback=lambda f: f.formfield(), **options): 
     297    opts = model._meta 
     298    options.update({"model": model}) 
     299    return ModelFormMetaclass(opts.object_name + "ModelForm", (form,), 
     300                              {}, formfield_callback, **options) 
     301 
    296302 
    297303# Fields ##################################################################### 
    298304 
     
    407413 
    408414# Model-FormSet integration ################################################### 
    409415 
    410 def initial_data(instance, fields=None): 
    411     """ 
    412     Return a dictionary from data in ``instance`` that is suitable for 
    413     use as a ``Form`` constructor's ``initial`` argument. 
    414  
    415     Provide ``fields`` to specify the names of specific fields to return. 
    416     All field values in the instance will be returned if ``fields`` is not 
    417     provided. 
    418     """ 
    419     # avoid a circular import 
    420     from django.db.models.fields.related import ManyToManyField 
    421     opts = instance._meta 
    422     initial = {} 
    423     for f in opts.fields + opts.many_to_many: 
    424         if not f.editable: 
    425             continue 
    426         if fields and not f.name in fields: 
    427             continue 
    428         if isinstance(f, ManyToManyField): 
    429             # MultipleChoiceWidget needs a list of ints, not object instances. 
    430             initial[f.name] = [obj.pk for obj in f.value_from_object(instance)] 
    431         else: 
    432             initial[f.name] = f.value_from_object(instance) 
    433     return initial 
     416class ModelFormSetMetaclass(ModelFormMetaclass): 
     417    opts_class = ModelFormSetOptions 
    434418 
    435419class BaseModelFormSet(BaseFormSet): 
    436420    """ 
    437421    A ``FormSet`` for editing a queryset and/or adding new objects to it. 
    438422    """ 
    439     model = None 
    440     queryset = None 
    441423 
    442     def __init__(self, qs, data=None, files=None, auto_id='id_%s', prefix=None): 
     424    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None): 
    443425        kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 
    444         self.queryset = qs 
    445         kwargs['initial'] = [initial_data(obj) for obj in qs] 
     426        opts = self._meta 
     427        self.queryset = self.get_queryset(**kwargs) 
     428        initial_data = [] 
     429        for obj in self.queryset: 
     430            initial_data.append(model_to_dict(obj, opts.fields, opts.exclude)) 
     431        kwargs['initial'] = initial_data 
    446432        super(BaseModelFormSet, self).__init__(**kwargs) 
     433     
     434    def get_queryset(self, **kwargs): 
     435        """ 
     436        Hook to returning a queryset for this model. 
     437        """ 
     438        return self._meta.model._default_manager.all() 
    447439 
    448440    def save_new(self, form, commit=True): 
    449441        """Saves and returns a new model instance for the given form.""" 
    450         return save_instance(form, self.model(), commit=commit) 
     442        return save_instance(form, self._meta.model(), commit=commit) 
    451443 
    452444    def save_instance(self, form, instance, commit=True): 
    453445        """Saves and returns an existing model instance for the given form.""" 
     
    464456            return [] 
    465457        # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk 
    466458        existing_objects = {} 
     459        opts = self._meta 
    467460        for obj in self.queryset: 
    468461            existing_objects[obj.pk] = obj 
    469462        saved_instances = [] 
    470463        for form in self.change_forms: 
    471             obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]] 
    472             if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     464            obj = existing_objects[form.cleaned_data[opts.model._meta.pk.attname]] 
     465            if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
    473466                obj.delete() 
    474467            else: 
    475468                saved_instances.append(self.save_instance(form, obj, commit=commit)) 
     
    477470 
    478471    def save_new_objects(self, commit=True): 
    479472        new_objects = [] 
     473        opts = self._meta 
    480474        for form in self.add_forms: 
    481475            if form.is_empty(): 
    482476                continue 
    483477            # If someone has marked an add form for deletion, don't save the 
    484478            # object. At some point it would be nice if we didn't display 
    485479            # the deletion widget for add forms. 
    486             if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     480            if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
    487481                continue 
    488482            new_objects.append(self.save_new(form, commit=commit)) 
    489483        return new_objects 
    490484 
    491485    def add_fields(self, form, index): 
    492486        """Add a hidden field for the object's primary key.""" 
    493         self._pk_field_name = self.model._meta.pk.attname 
     487        self._pk_field_name = self._meta.model._meta.pk.attname 
    494488        form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) 
    495489        super(BaseModelFormSet, self).add_fields(form, index) 
    496490 
    497 def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), 
    498                       formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None): 
     491class ModelFormSet(BaseModelFormSet): 
     492    __metaclass__ = ModelFormSetMetaclass 
     493 
     494def formset_for_model(model, formset=BaseModelFormSet, 
     495                      formfield_callback=lambda f: f.formfield(), **options): 
    499496    """ 
    500497    Returns a FormSet class for the given Django model class. This FormSet 
    501498    will contain change forms for every instance of the given model as well 
     
    504501    This is essentially the same as ``formset_for_queryset``, but automatically 
    505502    uses the model's default manager to determine the queryset. 
    506503    """ 
    507     form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback) 
    508     FormSet = formset_for_form(form, formset, extra, orderable, deletable) 
    509     FormSet.model = model 
    510     return FormSet 
     504    opts = model._meta 
     505    options.update({"model": model}) 
     506    return ModelFormSetMetaclass(opts.object_name + "ModelFormSet", (formset,), 
     507                                 {}, **options) 
     508 
     509class InlineFormSetMetaclass(ModelFormSetMetaclass): 
     510    opts_class = InlineFormSetOptions 
     511     
     512    def __new__(cls, name, bases, attrs, 
     513                formfield_callback=lambda f: f.formfield(), **options): 
     514        formset = super(InlineFormSetMetaclass, cls).__new__(cls, name, bases, attrs, 
     515            formfield_callback, **options) 
     516        # If this isn't a subclass of InlineFormset, don't do anything special. 
     517        try: 
     518            if not filter(lambda b: issubclass(b, InlineFormset), bases): 
     519                return formset 
     520        except NameError: 
     521            # 'InlineFormset' isn't defined yet, meaning we're looking at 
     522            # Django's own InlineFormset class, defined below. 
     523            return formset 
     524        opts = formset._meta 
     525        # resolve the foreign key 
     526        fk = cls.resolve_foreign_key(opts.parent_model, opts.model, opts.fk_name) 
     527        # remove the fk from base_fields to keep it transparent to the form. 
     528        try: 
     529            del formset.base_fields[fk.name] 
     530        except KeyError: 
     531            pass 
     532        formset.fk = fk 
     533        return formset 
     534     
     535    def _resolve_foreign_key(cls, parent_model, model, fk_name=None): 
     536        """ 
     537        Finds and returns the ForeignKey from model to parent if there is one. 
     538        If fk_name is provided, assume it is the name of the ForeignKey field. 
     539        """ 
     540        # avoid a circular import 
     541        from django.db.models import ForeignKey 
     542        opts = model._meta 
     543        if fk_name: 
     544            fks_to_parent = [f for f in opts.fields if f.name == fk_name] 
     545            if len(fks_to_parent) == 1: 
     546                fk = fks_to_parent[0] 
     547                if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 
     548                    raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 
     549            elif len(fks_to_parent) == 0: 
     550                raise Exception("%s has no field named '%s'" % (model, fk_name)) 
     551        else: 
     552            # Try to discover what the ForeignKey from model to parent_model is 
     553            fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 
     554            if len(fks_to_parent) == 1: 
     555                fk = fks_to_parent[0] 
     556            elif len(fks_to_parent) == 0: 
     557                raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 
     558            else: 
     559                raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 
     560        return fk 
     561    resolve_foreign_key = classmethod(_resolve_foreign_key) 
    511562 
    512 class InlineFormset(BaseModelFormSet): 
     563class BaseInlineFormSet(BaseModelFormSet): 
    513564    """A formset for child objects related to a parent.""" 
    514     def __init__(self, instance, data=None, files=None): 
     565    def __init__(self, *args, **kwargs): 
    515566        from django.db.models.fields.related import RelatedObject 
    516         self.instance = instance 
     567        opts = self._meta 
     568        self.instance = kwargs.pop("instance", None) 
    517569        # is there a better way to get the object descriptor? 
    518         self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() 
    519         qs = self.get_queryset() 
    520         super(InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name
     570        rel_name = RelatedObject(self.fk.rel.to, opts.model, self.fk).get_accessor_name() 
     571        kwargs["prefix"] = rel_name 
     572        super(BaseInlineFormSet, self).__init__(*args, **kwargs
    521573 
    522     def get_queryset(self): 
     574    def get_queryset(self, **kwargs): 
    523575        """ 
    524576        Returns this FormSet's queryset, but restricted to children of  
    525577        self.instance 
    526578        """ 
    527         kwargs = {self.fk.name: self.instance} 
    528         return self.model._default_manager.filter(**kwargs
     579        queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs) 
     580        return queryset.filter(**{self.fk.name: self.instance}
    529581 
    530582    def save_new(self, form, commit=True): 
    531583        kwargs = {self.fk.get_attname(): self.instance.pk} 
    532         new_obj = self.model(**kwargs) 
     584        new_obj = self._meta.model(**kwargs) 
    533585        return save_instance(form, new_obj, commit=commit) 
    534586 
    535 def get_foreign_key(parent_model, model, fk_name=None): 
    536     """ 
    537     Finds and returns the ForeignKey from model to parent if there is one. 
    538     If fk_name is provided, assume it is the name of the ForeignKey field. 
    539     """ 
    540     # avoid circular import 
    541     from django.db.models import ForeignKey 
    542     opts = model._meta 
    543     if fk_name: 
    544         fks_to_parent = [f for f in opts.fields if f.name == fk_name] 
    545         if len(fks_to_parent) == 1: 
    546             fk = fks_to_parent[0] 
    547             if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 
    548                 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 
    549         elif len(fks_to_parent) == 0: 
    550             raise Exception("%s has no field named '%s'" % (model, fk_name)) 
    551     else: 
    552         # Try to discover what the ForeignKey from model to parent_model is 
    553         fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 
    554         if len(fks_to_parent) == 1: 
    555             fk = fks_to_parent[0] 
    556         elif len(fks_to_parent) == 0: 
    557             raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 
    558         else: 
    559             raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 
    560     return fk 
     587class InlineFormset(BaseInlineFormSet): 
     588    __metaclass__ = InlineFormSetMetaclass 
    561589 
    562 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()): 
     590def inline_formset(parent_model, model, formset=InlineFormset, 
     591                   formfield_callback=lambda f: f.formfield(), **options): 
    563592    """ 
    564593    Returns an ``InlineFormset`` for the given kwargs. 
    565594 
    566595    You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` 
    567596    to ``parent_model``. 
    568597    """ 
    569     fk = get_foreign_key(parent_model, model, fk_name=fk_name) 
    570     # let the formset handle object deletion by default 
    571     FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, 
    572                                 formfield_callback=formfield_callback, 
    573                                 extra=extra, orderable=orderable, 
    574                                 deletable=deletable) 
    575     # HACK: remove the ForeignKey to the parent from every form 
    576     # This should be done a line above before we pass 'fields' to formset_for_model 
    577     # an 'omit' argument would be very handy here 
    578     try: 
    579         del FormSet.form_class.base_fields[fk.name] 
    580     except KeyError: 
    581         pass 
    582     FormSet.fk = fk 
    583     return FormSet 
     598    opts = model._meta 
     599    options.update({"parent_model": parent_model, "model": model}) 
     600    return InlineFormSetMetaclass(opts.object_name + "InlineFormset", (formset,), 
     601                                  {}, formfield_callback, **options) 
  • /dev/null

    old new  
     1 
     2from forms import BaseForm 
     3 
     4class BaseFormOptions(object): 
     5    """ 
     6    The base class for all options that are associated to a form object. 
     7    """ 
     8    def __init__(self, options=None): 
     9        self.fields = self._dynamic_attribute(options, "fields") 
     10        self.exclude = self._dynamic_attribute(options, "exclude") 
     11     
     12    def _dynamic_attribute(self, obj, key, default=None): 
     13        try: 
     14            return getattr(obj, key) 
     15        except AttributeError: 
     16            try: 
     17                return obj[key] 
     18            except (TypeError, KeyError): 
     19                # key doesnt exist in obj or obj is None 
     20                return default 
     21 
     22class ModelFormOptions(BaseFormOptions): 
     23    """ 
     24    Encapsulates the options on a ModelForm class. 
     25    """ 
     26    def __init__(self, options=None): 
     27        self.model = self._dynamic_attribute(options, "model") 
     28        super(ModelFormOptions, self).__init__(options) 
     29 
     30class FormSetOptions(BaseFormOptions): 
     31    """ 
     32    Encapsulates the options on a FormSet class. 
     33    """ 
     34    def __init__(self, options=None): 
     35        self.form = self._dynamic_attribute(options, "form", BaseForm) 
     36        self.num_extra = self._dynamic_attribute(options, "num_extra", 1) 
     37        self.orderable = self._dynamic_attribute(options, "orderable", False) 
     38        self.deletable = self._dynamic_attribute(options, "deletable", False) 
     39        super(FormSetOptions, self).__init__(options) 
     40 
     41class ModelFormSetOptions(FormSetOptions, ModelFormOptions): 
     42    pass 
     43 
     44class InlineFormSetOptions(ModelFormSetOptions): 
     45    def __init__(self, options=None): 
     46        super(InlineFormSetOptions, self).__init__(options) 
     47        self.parent_model = self._dynamic_attribute(options, "parent_model") 
     48        self.fk_name = self._dynamic_attribute(options, "fk_name") 
     49        self.deletable = self._dynamic_attribute(options, "deletable", True) 
     50     
  • a/tests/modeltests/model_formsets/models.py

    old new  
    1616 
    1717__test__ = {'API_TESTS': """ 
    1818 
    19 >>> from django.newforms.models import formset_for_model 
     19>>> from django import newforms as forms 
     20>>> from django.newforms.models import formset_for_model, ModelFormSet 
    2021 
    21 >>> qs = Author.objects.all() 
    22 >>> AuthorFormSet = formset_for_model(Author, extra=3) 
     22A bare bones verion. 
    2323 
    24 >>> formset = AuthorFormSet(qs) 
     24>>> class AuthorFormSet(ModelFormSet): 
     25...     class Meta: 
     26...         model = Author 
     27>>> AuthorFormSet.base_fields.keys() 
     28['name'] 
     29 
     30Extra fields. 
     31 
     32>>> class AuthorFormSet(ModelFormSet): 
     33...     published = forms.BooleanField() 
     34... 
     35...     class Meta: 
     36...         model = Author 
     37>>> AuthorFormSet.base_fields.keys() 
     38['name', 'published'] 
     39 
     40Lets create a formset that is bound to a model. 
     41 
     42>>> class AuthorFormSet(ModelFormSet): 
     43...     class Meta: 
     44...         model = Author 
     45...         num_extra = 3 
     46 
     47>>> formset = AuthorFormSet() 
    2548>>> for form in formset.forms: 
    2649...     print form.as_p() 
    2750<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p> 
     
    3558...     'form-2-name': '', 
    3659... } 
    3760 
    38 >>> formset = AuthorFormSet(qs, data=data) 
     61>>> formset = AuthorFormSet(data) 
    3962>>> formset.is_valid() 
    4063True 
    4164 
     
    4972 
    5073 
    5174Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing 
    52 authors with an extra form to add him. This time we'll use formset_for_queryset. 
    53 We *could* use formset_for_queryset to restrict the Author objects we edit, 
    54 but in that case we'll use it to display them in alphabetical order by name. 
    55  
    56 >>> qs = Author.objects.order_by('name') 
    57 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=False) 
    58  
    59 >>> formset = AuthorFormSet(qs) 
     75authors with an extra form to add him. When subclassing ModelFormSet you can 
     76override the get_queryset method to return any queryset we like, but in this 
     77case we'll use it to display it in alphabetical order by name. 
     78 
     79>>> class AuthorFormSet(ModelFormSet): 
     80...     class Meta: 
     81...         model = Author 
     82...         num_extra = 1 
     83...         deletable = False 
     84... 
     85...     def get_queryset(self, **kwargs): 
     86...         qs = super(AuthorFormSet, self).get_queryset(**kwargs) 
     87...         return qs.order_by('name') 
     88 
     89>>> formset = AuthorFormSet() 
    6090>>> for form in formset.forms: 
    6191...     print form.as_p() 
    6292<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p> 
     
    73103...     'form-2-name': 'Paul Verlaine', 
    74104... } 
    75105 
    76 >>> formset = AuthorFormSet(qs, data=data) 
     106>>> formset = AuthorFormSet(data) 
    77107>>> formset.is_valid() 
    78108True 
    79109 
     
    90120This probably shouldn't happen, but it will. If an add form was marked for 
    91121deltetion, make sure we don't save that form. 
    92122 
    93 >>> qs = Author.objects.order_by('name') 
    94 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=True) 
    95  
    96 >>> formset = AuthorFormSet(qs) 
     123>>> class AuthorFormSet(ModelFormSet): 
     124...     class Meta: 
     125...         model = Author 
     126...         num_extra = 1 
     127...         deletable = True 
     128... 
     129...     def get_queryset(self, **kwargs): 
     130...         qs = super(AuthorFormSet, self).get_queryset(**kwargs) 
     131...         return qs.order_by('name') 
     132 
     133>>> formset = AuthorFormSet() 
    97134>>> for form in formset.forms: 
    98135...     print form.as_p() 
    99136<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p> 
     
    117154...     'form-3-DELETE': 'on', 
    118155... } 
    119156 
    120 >>> formset = AuthorFormSet(qs, data=data) 
     157>>> formset = AuthorFormSet(data) 
    121158>>> formset.is_valid() 
    122159True 
    123160 
     
    134171We can also create a formset that is tied to a parent model. This is how the 
    135172admin system's edit inline functionality works. 
    136173 
    137 >>> from django.newforms.models import inline_formset 
     174>>> from django.newforms.models import inline_formset, InlineFormset 
     175 
     176>>> class AuthorBooksFormSet(InlineFormset): 
     177...     class Meta: 
     178...         parent_model = Author 
     179...         model = Book 
     180...         num_extra = 3 
     181...         deletable = False 
    138182 
    139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3) 
    140183>>> author = Author.objects.get(name='Charles Baudelaire') 
    141184 
    142 >>> formset = AuthorBooksFormSet(author) 
     185>>> formset = AuthorBooksFormSet(instance=author) 
    143186>>> for form in formset.forms: 
    144187...     print form.as_p() 
    145188<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p> 
     
    153196...     'book_set-2-title': '', 
    154197... } 
    155198 
    156 >>> formset = AuthorBooksFormSet(author, data=data
     199>>> formset = AuthorBooksFormSet(data, instance=author
    157200>>> formset.is_valid() 
    158201True 
    159202 
     
    169212one. This time though, an edit form will be available for every existing 
    170213book. 
    171214 
    172 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2) 
     215>>> class AuthorBooksFormSet(InlineFormset): 
     216...     class Meta: 
     217...         parent_model = Author 
     218...         model = Book 
     219...         num_extra = 2 
     220...         deletable = False 
     221 
    173222>>> author = Author.objects.get(name='Charles Baudelaire') 
    174223 
    175 >>> formset = AuthorBooksFormSet(author) 
     224>>> formset = AuthorBooksFormSet(instance=author) 
    176225>>> for form in formset.forms: 
    177226...     print form.as_p() 
    178227<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p> 
     
    187236...     'book_set-2-title': '', 
    188237... } 
    189238 
    190 >>> formset = AuthorBooksFormSet(author, data=data
     239>>> formset = AuthorBooksFormSet(data, instance=author
    191240>>> formset.is_valid() 
    192241True 
    193242 
  • a/tests/regressiontests/forms/formsets.py

    old new  
    22formset_tests = """ 
    33# Basic FormSet creation and usage ############################################ 
    44 
    5 FormSet allows us to use multiple instance of the same form on 1 page. For now, 
    6 the best way to create a FormSet is by using the formset_for_form function
     5FormSet allows us to use multiple instance of the same form on 1 page. Create 
     6the formset as you would a regular form by defining the fields declaratively
    77 
    88>>> from django.newforms import Form, CharField, IntegerField, ValidationError 
    9 >>> from django.newforms.formsets import formset_for_form, BaseFormSet 
     9>>> from django.newforms import BooleanField 
     10>>> from django.newforms.formsets import formset_for_form, BaseFormSet, FormSet 
    1011 
    11 >>> class Choice(Form): 
     12>>> class ChoiceFormSet(FormSet): 
    1213...     choice = CharField() 
    1314...     votes = IntegerField() 
    1415 
    15 >>> ChoiceFormSet = formset_for_form(Choice) 
    16  
    1716 
    1817A FormSet constructor takes the same arguments as Form. Let's create a FormSet 
    1918for adding data. By default, it displays 1 blank form. It can display more, 
     
    145144>>> formset.errors 
    146145[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}] 
    147146 
     147# Subclassing a FormSet class ################################################# 
     148 
     149We can subclass a FormSet to add addition fields to an already exisiting 
     150FormSet. 
     151 
     152>>> class SecondChoiceFormSet(ChoiceFormSet): 
     153...     is_public = BooleanField() 
     154 
     155>>> formset = SecondChoiceFormSet(auto_id=False, prefix="choices") 
     156>>> for form in formset.forms: 
     157...     print form.as_ul() 
     158<li>Choice: <input type="text" name="choices-0-choice" /></li> 
     15