Multiple inheritance of newforms and modelforms

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from django import newforms as forms
from somewhere import classmaker

class FormToMixin(forms.Form):
    question = forms.CharField(max_length=100, required=True)
    answer = forms.CharField(max_length = 20, required=True)

class FormFromModel(forms.ModelForm):
    class Meta:
        model = SomeModel

class CombinedForm(FormToMixin, FormFromModel):
    # Magically resolves the metaclass conflict for you
    __metaclass__ = classmaker()

# The below code defines classmaker() - you should put this in a separate
# module and import it above your form definitions.
# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197

import inspect, types, __builtin__

############## preliminary: two utility functions #####################

def skip_redundant(iterable, skipset=None):
    "Redundant items are repeated items or items in the original skipset."
    if skipset is None: skipset = set()
    for item in iterable:
        if item not in skipset:
            skipset.add(item)
            yield item


def remove_redundant(metaclasses):
    skipset = set([types.ClassType])
    for meta in metaclasses: # determines the metaclasses to be skipped
        skipset.update(inspect.getmro(meta)[1:])
    return tuple(skip_redundant(metaclasses, skipset))

##################################################################
## now the core of the module: two mutually recursive functions ##
##################################################################

memoized_metaclasses_map = {}

def get_noconflict_metaclass(bases, left_metas, right_metas):
    """Not intended to be used outside of this module, unless you know
    what you are doing."""
    # make tuple of needed metaclasses in specified priority order
    metas = left_metas + tuple(map(type, bases)) + right_metas
    needed_metas = remove_redundant(metas)

    # return existing confict-solving meta, if any
    if needed_metas in memoized_metaclasses_map:
      return memoized_metaclasses_map[needed_metas]
    # nope: compute, memoize and return needed conflict-solving meta
    elif not needed_metas:         # wee, a trivial case, happy us
        meta = type
    elif len(needed_metas) == 1: # another trivial case
       meta = needed_metas[0]
    # check for recursion, can happen i.e. for Zope ExtensionClasses
    elif needed_metas == bases: 
        raise TypeError("Incompatible root metatypes", needed_metas)
    else: # gotta work ...
        metaname = '_' + ''.join([m.__name__ for m in needed_metas])
        meta = classmaker()(metaname, needed_metas, {})
    memoized_metaclasses_map[needed_metas] = meta
    return meta

def classmaker(left_metas=(), right_metas=()):
    def make_class(name, bases, adict):
        metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
        return metaclass(name, bases, adict)
    return make_class

More like this

  1. newforms and ModelForm by danfairs 4 years, 4 months ago
  2. unique validation for ModelForm by whiskybar 4 years, 2 months ago
  3. Automatically slugify slug fields in your models by Aliquip 5 years, 2 months ago
  4. ModelForm-based create_update generic views by carljm 3 years, 10 months ago
  5. FieldsetForm by Ciantic 5 years, 1 month ago

Comments

blpiko (on April 14, 2008):

Great! But this doesn't not work if you want to multiple inheritance two ModelForm.

You must do this first:

# Because ModelFormMetaclass will call get_declared_fields method with
# with_base_fields=False, we modify it with True.
from django.newforms import models as nmodels
gdf = nmodels.get_declared_fields
nmodels.get_declared_fields = \
    lambda bases, attrs, with_base_fields: gdf(bases, attrs, True)

Please see http://code.djangoproject.com/ticket/7018 for more details.

:)

#

visik7 (on June 1, 2009):

I got only the first form to be displayed

#

chachra (on April 3, 2011):

same here. only 1 form displays?

#

simonkagwe (on September 28, 2011):

Didn't work for me also. Only fields from one form were being displayed.

If all you need to do is add fields to an existing form, my snippet may be of help: http://djangosnippets.org/snippets/2556/

#

(Forgotten your password?)