The difference between the returned modified class and the use of type ()

I assume this is more a python question than django, but I could not replicate this behavior anywhere else, so I will use the exact code that does not work properly.

I was working on some dynamic forms in django when I found this fragment of the factory function:

def get_employee_form(employee): """Return the form for a specific Board.""" employee_fields = EmployeeFieldModel.objects.filter(employee = employee).order_by ('order') class EmployeeForm(forms.Form): def __init__(self, *args, **kwargs): forms.Form.__init__(self, *args, **kwargs) self.employee = employee def save(self): "Do the save" for field in employee_fields: setattr(EmployeeForm, field.name, copy(type_mapping[field.type])) return type('EmployeeForm', (forms.Form, ), dict(EmployeeForm.__dict__)) 

[from: http://uswaretech.com/blog/2008/10/dynamic-forms-with-django/ ]

And one thing that I donโ€™t understand is why returning a modified EmployeeForm doesn't do the trick? I mean something like this:

 def get_employee_form(employee): #[...]same function body as before for field in employee_fields: setattr(EmployeeForm, field.name, copy(type_mapping[field.type])) return EmployeeForm 

When I tried to return the changed class, django ignored my extra fields, but the type () returning result works fine.

+3
source share
3 answers

The Lennart hypothesis is true: the metaclass is indeed the culprit. No need to guess, just look at the sources : the metaclass DeclarativeFieldsMetaclass is currently located on line 53 of this file and adds the base_fields and possibly media attributes based on what attributes the class has at creation time. On line 329 ff you will see:

 class Form(BaseForm): "A collection of Fields, plus their associated data." # This is a separate class from BaseForm in order to abstract the way # self.fields is specified. This class (Form) is the one that does the # fancy metaclass stuff purely for the semantic sugar -- it allows one # to define a form using declarative syntax. # BaseForm itself has no way of designating self.fields. __metaclass__ = DeclarativeFieldsMetaclass 

This implies some fragility when creating a new class with a type base - the supplied black magic may or may not pass! A firmer approach is to use the EmployeeForm type, which will pick up any metaclass that may be involved, i.e.:

 return type(EmployeeForm)('EmployeeForm', (forms.Form, ), EmployeeForm.__dict__) 

(no need to copy this __dict__ , by the way). The difference is subtle, but important: instead of using the direct form type 3-args, we use the 1-arg form to get the type (that is, the Metaclass) of the form class, and then call this metaclass in 3-args.

Black magic is valid, but then, the lack of frameworks that use such a use of the โ€œfantasy metaclassโ€ exclusively for semantic sugar "& c: you are in clover if you want to do exactly what the framework supports, but leaving this support may even require a little compensate for the magic (which to some extent explains why often I prefer to use a light transparent setting such as werkzeug rather than a framework that imposes magic on me like Rails or Django do: my skill about deep black magic does NOT mean that I am glad that I should use it in simple production code ... but, this is another discussion ;-).

+5
source

I just tried this with direct classes other than django and it worked. So this is not a Python problem, but a Django problem.

And in this case (although I'm not 100% sure), it is a question of what the class of the class does during class creation. I think he has a meta class, and this meta class will complete the form initialization during class creation. This means that any fields added after the class is created will be ignored.

Therefore, you need to create a new class, as is done with the type () operator, so that the code for creating the metaclass class will be involved, now with new fields.

+3
source

It is worth noting that this piece of code is a very bad tool for the desired end and includes a widespread misunderstanding about Django Form objects - that a Form object must map one-to-one with an HTML form. The right way to do something like this (which doesn't require messing with any metaclass magic) is to use several Form objects and an inline set of forms .

Or, if for some odd reason you really want to store things in one Form object, just manipulate self.fields in the Form __init__ method.

+1
source

All Articles