Django

Code

Ticket #6241: formset_refactor_5.diff

File formset_refactor_5.diff, 61.2 kB (added by brosner, 1 year ago)

use ModelForm? in ModelAdmin?. refactors formset code.

  • 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) 
     
    209211            field_list.append((f.name, formfield)) 
    210212    return SortedDict(field_list) 
    211213 
    212 class ModelFormOptions(object): 
    213     def __init__(self, options=None): 
    214         self.model = getattr(options, 'model', None) 
    215         self.fields = getattr(options, 'fields', None) 
    216         self.exclude = getattr(options, 'exclude', None) 
    217  
    218214class ModelFormMetaclass(type): 
    219     def __new__(cls, name, bases, attrs): 
    220         # TODO: no way to specify formfield_callback yet, do we need one, or 
    221         # should it be a special case for the admin? 
     215    opts_class = ModelFormOptions 
     216     
     217    def __new__(cls, name, bases, attrs, 
     218                formfield_callback=lambda f: f.formfield(), **options): 
    222219        fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 
    223220        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 
    224221 
     
    230227                fields = base.base_fields.items() + fields 
    231228        declared_fields = SortedDict(fields) 
    232229 
    233         opts = ModelFormOptions(attrs.get('Meta', None)) 
     230        opts = cls.opts_class(options and options or attrs.get('Meta', None)) 
    234231        attrs['_meta'] = opts 
    235232 
    236233        # Don't allow more than one Meta model defenition in bases. The fields 
     
    255252                base_model = getattr(base_opts, 'model', None) 
    256253                if base_model is not None: 
    257254                    raise ImproperlyConfigured('%s defines more than one model.' % name) 
    258             model_fields = fields_for_model(opts.model, opts.fields, opts.exclude
     255            model_fields = fields_for_model(opts.model, opts.fields, opts.exclude, formfield_callback
    259256            # fields declared in base classes override fields from the model 
    260257            model_fields.update(declared_fields) 
    261258            attrs['base_fields'] = model_fields 
    262259        else: 
    263260            attrs['base_fields'] = declared_fields 
    264261        return type.__new__(cls, name, bases, attrs) 
    265  
     262         
    266263class BaseModelForm(BaseForm): 
    267     def __init__(self, instance, data=None, files=None, auto_id='id_%s', prefix=None, 
    268                  initial=None, error_class=ErrorList, label_suffix=':'): 
     264    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
     265                 initial=None, error_class=ErrorList, label_suffix=':', instance=None): 
    269266        self.instance = instance 
    270267        opts = self._meta 
    271         object_data = model_to_dict(instance, opts.fields, opts.exclude) 
     268        if instance is None: 
     269            # if we didn't get an instance, instantiate a new one 
     270            self.instance = opts.model() 
     271            object_data = {} 
     272        else: 
     273            self.instance = instance 
     274            object_data = model_to_dict(instance, opts.fields, opts.exclude) 
    272275        # if initial was provided, it should override the values from instance 
    273276        if initial is not None: 
    274277            object_data.update(initial) 
     
    290293class ModelForm(BaseModelForm): 
    291294    __metaclass__ = ModelFormMetaclass 
    292295 
     296# this should really be named form_for_model. 
     297def modelform_for_model(model, form=ModelForm, 
     298                        formfield_callback=lambda f: f.formfield(), **options): 
     299    opts = model._meta 
     300    options.update({"model": model}) 
     301    return ModelFormMetaclass(opts.object_name + "ModelForm", (form,), 
     302                              {}, formfield_callback, **options) 
     303 
    293304 
    294305# Fields ##################################################################### 
    295306 
     
    404415 
    405416# Model-FormSet integration ################################################### 
    406417 
    407 def initial_data(instance, fields=None): 
    408     """ 
    409     Return a dictionary from data in ``instance`` that is suitable for 
    410     use as a ``Form`` constructor's ``initial`` argument. 
    411  
    412     Provide ``fields`` to specify the names of specific fields to return. 
    413     All field values in the instance will be returned if ``fields`` is not 
    414     provided. 
    415     """ 
    416     # avoid a circular import 
    417     from django.db.models.fields.related import ManyToManyField 
    418     opts = instance._meta 
    419     initial = {} 
    420     for f in opts.fields + opts.many_to_many: 
    421         if not f.editable: 
    422             continue 
    423         if fields and not f.name in fields: 
    424             continue 
    425         if isinstance(f, ManyToManyField): 
    426             # MultipleChoiceWidget needs a list of ints, not object instances. 
    427             initial[f.name] = [obj.pk for obj in f.value_from_object(instance)] 
    428         else: 
    429             initial[f.name] = f.value_from_object(instance) 
    430     return initial 
     418class ModelFormSetMetaclass(ModelFormMetaclass): 
     419    opts_class = ModelFormSetOptions 
    431420 
    432421class BaseModelFormSet(BaseFormSet): 
    433422    """ 
    434423    A ``FormSet`` for editing a queryset and/or adding new objects to it. 
    435424    """ 
    436     model = None 
    437     queryset = None 
    438425 
    439     def __init__(self, qs, data=None, files=None, auto_id='id_%s', prefix=None): 
     426    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None): 
    440427        kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 
    441         self.queryset = qs 
    442         kwargs['initial'] = [initial_data(obj) for obj in qs] 
     428        opts = self._meta 
     429        self.queryset = self.get_queryset(**kwargs) 
     430        initial_data = [] 
     431        for obj in self.queryset: 
     432            initial_data.append(model_to_dict(obj, opts.fields, opts.exclude)) 
     433        kwargs['initial'] = initial_data 
    443434        super(BaseModelFormSet, self).__init__(**kwargs) 
     435     
     436    def get_queryset(self, **kwargs): 
     437        """ 
     438        Hook to returning a queryset for this model. 
     439        """ 
     440        return self._meta.model._default_manager.all() 
    444441 
    445442    def save_new(self, form, commit=True): 
    446443        """Saves and returns a new model instance for the given form.""" 
    447         return save_instance(form, self.model(), commit=commit) 
     444        return save_instance(form, self._meta.model(), commit=commit) 
    448445 
    449446    def save_instance(self, form, instance, commit=True): 
    450447        """Saves and returns an existing model instance for the given form.""" 
     
    461458            return [] 
    462459        # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk 
    463460        existing_objects = {} 
     461        opts = self._meta 
    464462        for obj in self.queryset: 
    465463            existing_objects[obj.pk] = obj 
    466464        saved_instances = [] 
    467465        for form in self.change_forms: 
    468             obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]] 
    469             if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     466            obj = existing_objects[form.cleaned_data[opts.model._meta.pk.attname]] 
     467            if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
    470468                obj.delete() 
    471469            else: 
    472470                saved_instances.append(self.save_instance(form, obj, commit=commit)) 
     
    474472 
    475473    def save_new_objects(self, commit=True): 
    476474        new_objects = [] 
     475        opts = self._meta 
    477476        for form in self.add_forms: 
    478477            if form.is_empty(): 
    479478                continue 
    480479            # If someone has marked an add form for deletion, don't save the 
    481480            # object. At some point it would be nice if we didn't display 
    482481            # the deletion widget for add forms. 
    483             if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     482            if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
    484483                continue 
    485484            new_objects.append(self.save_new(form, commit=commit)) 
    486485        return new_objects 
    487486 
    488487    def add_fields(self, form, index): 
    489488        """Add a hidden field for the object's primary key.""" 
    490         self._pk_field_name = self.model._meta.pk.attname 
     489        self._pk_field_name = self._meta.model._meta.pk.attname 
    491490        form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) 
    492491        super(BaseModelFormSet, self).add_fields(form, index) 
    493492 
    494 def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), 
    495                       formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None): 
     493class ModelFormSet(BaseModelFormSet): 
     494    __metaclass__ = ModelFormSetMetaclass 
     495 
     496def formset_for_model(model, formset=BaseModelFormSet, 
     497                      formfield_callback=lambda f: f.formfield(), **options): 
    496498    """ 
    497499    Returns a FormSet class for the given Django model class. This FormSet 
    498500    will contain change forms for every instance of the given model as well 
     
    501503    This is essentially the same as ``formset_for_queryset``, but automatically 
    502504    uses the model's default manager to determine the queryset. 
    503505    """ 
    504     form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback) 
    505     FormSet = formset_for_form(form, formset, extra, orderable, deletable) 
    506     FormSet.model = model 
    507     return FormSet 
     506    opts = model._meta 
     507    options.update({"model": model}) 
     508    return ModelFormSetMetaclass(opts.object_name + "ModelFormSet", (formset,), 
     509                                 {}, **options) 
     510 
     511class InlineFormSetMetaclass(ModelFormSetMetaclass): 
     512    opts_class = InlineFormSetOptions 
     513     
     514    def __new__(cls, name, bases, attrs, 
     515                formfield_callback=lambda f: f.formfield(), **options): 
     516        formset = super(InlineFormSetMetaclass, cls).__new__(cls, name, bases, attrs, 
     517            formfield_callback, **options) 
     518        # If this isn't a subclass of InlineFormset, don't do anything special. 
     519        try: 
     520            if not filter(lambda b: issubclass(b, InlineFormset), bases): 
     521                return formset 
     522        except NameError: 
     523            # 'InlineFormset' isn't defined yet, meaning we're looking at 
     524            # Django's own InlineFormset class, defined below. 
     525            return formset 
     526        opts = formset._meta 
     527        # resolve the foreign key 
     528        fk = cls.resolve_foreign_key(opts.parent_model, opts.model, opts.fk_name) 
     529        # remove the fk from base_fields to keep it transparent to the form. 
     530        try: 
     531            del formset.base_fields[fk.name] 
     532        except KeyError: 
     533            pass 
     534        formset.fk = fk 
     535        return formset 
     536     
     537    def _resolve_foreign_key(cls, parent_model, model, fk_name=None): 
     538        """ 
     539        Finds and returns the ForeignKey from model to parent if there is one. 
     540        If fk_name is provided, assume it is the name of the ForeignKey field. 
     541        """ 
     542        # avoid a circular import 
     543        from django.db.models import ForeignKey 
     544        opts = model._meta 
     545        if fk_name: 
     546            fks_to_parent = [f for f in opts.fields if f.name == fk_name] 
     547            if len(fks_to_parent) == 1: 
     548                fk = fks_to_parent[0] 
     549                if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 
     550                    raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 
     551            elif len(fks_to_parent) == 0: 
     552                raise Exception("%s has no field named '%s'" % (model, fk_name)) 
     553        else: 
     554            # Try to discover what the ForeignKey from model to parent_model is 
     555            fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 
     556            if len(fks_to_parent) == 1: 
     557                fk = fks_to_parent[0] 
     558            elif len(fks_to_parent) == 0: 
     559                raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 
     560            else: 
     561                raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 
     562        return fk 
     563    resolve_foreign_key = classmethod(_resolve_foreign_key) 
    508564 
    509 class InlineFormset(BaseModelFormSet): 
     565class BaseInlineFormSet(BaseModelFormSet): 
    510566    """A formset for child objects related to a parent.""" 
    511     def __init__(self, instance, data=None, files=None): 
     567    def __init__(self, *args, **kwargs): 
    512568        from django.db.models.fields.related import RelatedObject 
    513         self.instance = instance 
     569        opts = self._meta 
     570        self.instance = kwargs.pop("instance", None) 
    514571        # is there a better way to get the object descriptor? 
    515         self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() 
    516         qs = self.get_queryset() 
    517         super(InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name
     572        rel_name = RelatedObject(self.fk.rel.to, opts.model, self.fk).get_accessor_name() 
     573        kwargs["prefix"] = rel_name 
     574        super(BaseInlineFormSet, self).__init__(*args, **kwargs
    518575 
    519     def get_queryset(self): 
     576    def get_queryset(self, **kwargs): 
    520577        """ 
    521578        Returns this FormSet's queryset, but restricted to children of  
    522579        self.instance 
    523580        """ 
    524         kwargs = {self.fk.name: self.instance} 
    525         return self.model._default_manager.filter(**kwargs
     581        queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs) 
     582        return queryset.filter(**{self.fk.name: self.instance}
    526583 
    527584    def save_new(self, form, commit=True): 
    528585        kwargs = {self.fk.get_attname(): self.instance.pk} 
    529         new_obj = self.model(**kwargs) 
     586        new_obj = self._meta.model(**kwargs) 
    530587        return save_instance(form, new_obj, commit=commit) 
    531588 
    532 def get_foreign_key(parent_model, model, fk_name=None): 
    533     """ 
    534     Finds and returns the ForeignKey from model to parent if there is one. 
    535     If fk_name is provided, assume it is the name of the ForeignKey field. 
    536     """ 
    537     # avoid circular import 
    538     from django.db.models import ForeignKey 
    539     opts = model._meta 
    540     if fk_name: 
    541         fks_to_parent = [f for f in opts.fields if f.name == fk_name] 
    542         if len(fks_to_parent) == 1: 
    543             fk = fks_to_parent[0] 
    544             if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 
    545                 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 
    546         elif len(fks_to_parent) == 0: 
    547             raise Exception("%s has no field named '%s'" % (model, fk_name)) 
    548     else: 
    549         # Try to discover what the ForeignKey from model to parent_model is 
    550         fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 
    551         if len(fks_to_parent) == 1: 
    552             fk = fks_to_parent[0] 
    553         elif len(fks_to_parent) == 0: 
    554             raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 
    555         else: 
    556             raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 
    557     return fk 
     589class InlineFormset(BaseInlineFormSet): 
     590    __metaclass__ = InlineFormSetMetaclass 
    558591 
    559 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()): 
     592def inline_formset(parent_model, model, formset=InlineFormset, 
     593                   formfield_callback=lambda f: f.formfield(), **options): 
    560594    """ 
    561595    Returns an ``InlineFormset`` for the given kwargs. 
    562596 
    563597    You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` 
    564598    to ``parent_model``. 
    565599    """ 
    566     fk = get_foreign_key(parent_model, model, fk_name=fk_name) 
    567     # let the formset handle object deletion by default 
    568     FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, 
    569                                 formfield_callback=formfield_callback, 
    570                                 extra=extra, orderable=orderable, 
    571                                 deletable=deletable) 
    572     # HACK: remove the ForeignKey to the parent from every form 
    573     # This should be done a line above before we pass 'fields' to formset_for_model 
    574     # an 'omit' argument would be very handy here 
    575     try: 
    576         del FormSet.form_class.base_fields[fk.name] 
    577     except KeyError: 
    578         pass 
    579     FormSet.fk = fk 
    580     return FormSet 
     600    opts = model._meta 
     601    options.update({"parent_model": parent_model, "model": model}) 
     602    return InlineFormSetMetaclass(opts.object_name + "InlineFormset", (formset,), 
     603                                  {}, 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    def __init__(self, options=None): 
     43        super(ModelFormSetOptions, self).__init__(options) 
     44        self.deletable = True 
     45 
     46class InlineFormSetOptions(ModelFormSetOptions): 
     47    def __init__(self, options=None): 
     48        super(InlineFormSetOptions, self).__init__(options) 
     49        self.parent_model = self._dynamic_attribute(options, "parent_model") 
     50        self.fk_name = self._dynamic_attribute(options, "fk_name") 
     51     
  • a/tests/modeltests/model_forms/models.py

    old new  
    167167>>> class CategoryForm(ModelForm): 
    168168...     class Meta: 
    169169...         model = Category 
    170 >>> f = CategoryForm(Category()
     170>>> f = CategoryForm(
    171171>>> print f 
    172172<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> 
    173173<tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr> 
     
    179179>>> print f['name'] 
    180180<input id="id_name" type="text" name="name" maxlength="20" /> 
    181181 
    182 >>> f = CategoryForm(Category(), auto_id=False) 
     182>>> f = CategoryForm(auto_id=False) 
    183183>>> print f.as_ul() 
    184184<li>Name: <input type="text" name="name" maxlength="20" /></li> 
    185185<li>Slug: <input type="text" name="slug" maxlength="20" /></li> 
    186186<li>The URL: <input type="text" name="url" maxlength="40" /></li> 
    187187 
    188 >>> f = CategoryForm(Category(), {'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) 
     188>>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) 
    189189>>> f.is_valid() 
    190190True 
    191191>>> f.cleaned_data 
     
    196196>>> Category.objects.all() 
    197197[<Category: Entertainment>] 
    198198 
    199 >>> f = CategoryForm(Category(), {'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) 
     199>>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) 
    200200>>> f.is_valid() 
    201201True 
    202202>>> f.cleaned_data 
     
    210210If you call save() with commit=False, then it will return an object that 
    211211hasn't yet been saved to the database. In this case, it's up to you to call 
    212212save() on the resulting model instance. 
    213 >>> f = CategoryForm(Category(), {'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) 
     213>>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) 
    214214>>> f.is_valid() 
    215215True 
    216216>>> f.cleaned_data 
     
    225225[<Category: Entertainment>, <Category: It's a test>, <Category: Third test>] 
    226226 
    227227If you call save() with invalid data, you'll get a ValueError. 
    228 >>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'}) 
     228>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) 
    229229>>> f.errors 
    230230{'name': [u'This field is required.'], 'slug': [u'This field is required.']} 
    231231>>> f.cleaned_data 
     
    236236Traceback (most recent call last): 
    237237... 
    238238ValueError: The Category could not be created because the data didn't validate. 
    239 >>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'}) 
     239>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) 
    240240>>> f.save() 
    241241Traceback (most recent call last): 
    242242... 
     
    253253>>> class ArticleForm(ModelForm): 
    254254...     class Meta: 
    255255...         model = Article 
    256 >>> f = ArticleForm(Article(), auto_id=False) 
     256>>> f = ArticleForm(auto_id=False) 
    257257>>> print f 
    258258<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> 
    259259<tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr> 
     
    286286...     class Meta: 
    287287...         model = Article 
    288288...         fields = ('headline','pub_date') 
    289 >>> f = PartialArticleForm(Article(), auto_id=False) 
     289>>> f = PartialArticleForm(auto_id=False) 
    290290>>> print f 
    291291<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> 
    292292<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr> 
     
    298298>>> class RoykoForm(ModelForm): 
    299299...     class Meta: 
    300300...         model = Writer 
    301 >>> f = RoykoForm(w, auto_id=False
     301>>> f = RoykoForm(auto_id=False, instance=w
    302302>>> print f 
    303303<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr> 
    304304 
     
    309309>>> class TestArticleForm(ModelForm): 
    310310...     class Meta: 
    311311...         model = Article 
    312 >>> f = TestArticleForm(art, auto_id=False
     312>>> f = TestArticleForm(auto_id=False, instance=art
    313313>>> print f.as_ul() 
    314314<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li> 
    315315<li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li> 
     
    331331<option value="2">It&#39;s a test</option> 
    332332<option value="3">Third test</option> 
    333333</select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li> 
    334 >>> f = TestArticleForm(art, {'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}
     334>>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}, instance=art
    335335>>> f.is_valid() 
    336336True 
    337337>>> test_art = f.save() 
     
    347347...     class Meta: 
    348348...         model = Article 
    349349...         fields=('headline', 'slug', 'pub_date') 
    350 >>> f = PartialArticleForm(art, {'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False
     350>>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False, instance=art
    351351>>> print f.as_ul() 
    352352<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> 
    353353<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> 
     
    370370>>> class TestArticleForm(ModelForm): 
    371371...     class Meta: 
    372372...         model = Article 
    373 >>> f = TestArticleForm(new_art, auto_id=False
     373>>> f = TestArticleForm(auto_id=False, instance=new_art
    374374>>> print f.as_ul() 
    375375<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> 
    376376<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> 
     
    393393<option value="3">Third test</option> 
    394394</select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li> 
    395395 
    396 >>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', 
    397 ...     'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}
     396>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', 
     397...     'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art
    398398>>> new_art = f.save() 
    399399>>> new_art.id 
    4004001 
     
    403403[<Category: Entertainment>, <Category: It's a test>] 
    404404 
    405405Now, submit form data with no categories. This deletes the existing categories. 
    406 >>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', 
    407 ...     'writer': u'1', 'article': u'Hello.'}
     406>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', 
     407...     'writer': u'1', 'article': u'Hello.'}, instance=new_art
    408408>>> new_art = f.save() 
    409409>>> new_art.id 
    4104101 
     
    416416>>> class ArticleForm(ModelForm): 
    417417...     class Meta: 
    418418...         model = Article 
    419 >>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', 
     419>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', 
    420420...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) 
    421421>>> new_art = f.save() 
    422422>>