2009-08-09 6 views
3

Je suppose que c'est plus une question python qu'une django, mais je ne pouvais pas répliquer ce comportement ailleurs, donc je vais utiliser le code exact qui ne fonctionne pas comme attendu.Différence entre renvoyer la classe modifiée et utiliser type()

je travaillais sur certaines formes dynamiques django, quand je trouve cet extrait de fonction de l'usine:

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__)) 

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

Et il y a une chose que je ne comprends pas, pourquoi retourner EmployeeForm modifié ne fait pas l'affaire? je veux dire quelque chose comme ceci:

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 

Quand j'ai essayé de retourner django de classe modifiée ignoré mes champs supplémentaires, mais le retour résultat de le type() fonctionne parfaitement.

Répondre

5

L'hypothèse de Lennart est correcte: une métaclasse est en effet le coupable. Pas besoin de deviner, il suffit de regarder the sources: la métaclasse est DeclarativeFieldsMetaclass actuellement à la ligne 53 de ce fichier, et ajoute les attributs base_fields et éventuellement media en fonction des attributs de la classe au moment de la création. À la ligne 329 et suivants vous voyez:

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 

Cela implique qu'il ya une certaine fragilité dans la création d'une nouvelle classe avec base type - la magie noire fournie peut ou non mener à bien! Une approche plus solide est d'utiliser le type de EmployeeForm qui prendra tous les métaclasse qui peuvent être impliqués - i.e. .:

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

(pas besoin de copier ce __dict__, BTW). La différence est subtile mais importante: plutôt que d'utiliser directement le formulaire 3-args de type, nous utilisons la forme 1-arg pour récupérer le type (ie, la métaclasse) de la classe de formulaire, puis appelons cette métaclasse dans le 3- forme d'args.

Blackly magicallish en effet, mais alors c'est l'inconvénient des cadres qui font une telle utilisation de "trucs de métaclasse fantaisie purement pour le sucre sémantique" & c: vous êtes dans le trèfle tant que vous voulez faire exactement ce que le cadre prend en charge , mais pour sortir de ce support, même un petit peu peut nécessiter une magie compensatrice (ce qui explique en partie pourquoi je préfère utiliser une configuration légère et transparente, comme werkzeug, plutôt qu'un cadre qui me donne de la magie comme Rails ou Django: ma maîtrise de la magie noire ne veut PAS dire que je suis content de l'utiliser dans un code de production ordinaire ... mais, c'est une autre discussion ;-).

+0

Je pense que la raison de dict (EmployeeForm .__ dict__) était que les formes antérieures.Formule utilisée pour retourner un DictProxy pas un dict. – agiliq

3

J'ai juste essayé ceci avec des classes droites non-django et cela a fonctionné. Ce n'est donc pas un problème de Python, mais un problème de Django.

Et dans ce cas (bien que je ne sois pas sûr à 100%), il s'agit de savoir ce que fait la classe Form pendant la création de classe. Je pense qu'il a une méta-classe, et que cette méta-classe finalisera l'initialisation du formulaire pendant la création de la classe. Cela signifie que tous les champs ajoutés après la création de la classe seront ignorés.

Par conséquent, vous devez créer une nouvelle classe, comme cela est fait avec l'instruction type(), de sorte que le code de création de classe de la méta-classe soit impliqué, maintenant avec les nouveaux champs.

1

Il est à noter que cet extrait de code est un moyen très médiocre à la fin souhaitée, et implique un malentendu commun sur les objets formulaire Django - qu'un objet Form doit mapper un à un avec un formulaire HTML. La façon correcte de faire quelque chose comme ça (qui ne nécessite pas de jouer avec une magie de métaclasse) est d'utiliser plusieurs objets Form et un inline formset. Ou, si pour une raison étrange vous voulez vraiment garder les choses dans un seul objet Form, manipulez simplement self.fields dans la méthode __init__ du Formulaire.

+0

formset_factory crée plusieurs instances de même forme et les enregistre de manière transparente. Cet exemple affiche différents champs sur le formulaire, en fonction du département employé. Je ne suis pas sûr de comprendre ce que vous voulez dire quand vous dites que l'ancien peut être utilisé pour le second. Pouvez-vous élaborer? Merci d'avance. –

+0

Si votre intention est de montrer un formulaire html qui permet d'éditer les noms de tous les employés d'un département, inline formset est ce que vous voulez. Cependant, si vous êtes un certain dépt, vous voulez montrer une commission, et pas pour d'autres, c'est la façon de le faire. Les deux sont orthogonaux, et peuvent être assortis ensemble. – agiliq

Questions connexes