2016-07-01 13 views
6

Sto cercando di creare un mixin generico per i campi modello (al contrario dei campi modulo), l'init per il mixin accetta argomenti con nome. Mi trovo nei guai creando un'istanza per mixare con un'altra classe.Come creare un mix di campi modello django

Ecco il codice

class MyMixin(object): 
    def __init__(self, new_arg=None, *args, **kwargs): 
     super(MyMixin, self).__init__(*args, **kwargs) 
     print self.__class__, new_arg 


class MyMixinCharField(MyMixin, models.CharField): 
    pass 

... 

class MyMixinModelTest(models.Model): 
    myfield = MyMixinCharField(max_length=512,new_arg="myarg") 

Fare la migrazione per questo modello produce il seguente output:

<class 'myapp.mixintest.fields.MyMixinCharField'> myarg 
<class 'myapp.mixintest.fields.MyMixinCharField'> None 
<class 'myapp.mixintest.fields.MyMixinCharField'> None 
Migrations for 'mixintest': 
    0001_initial.py: 
     - Create model MyMixinModelTest 

In primo luogo, il motivo per cui è in esecuzione init 3 volte? Da dove viene il kwarg 'new_arg' nel secondo due? Come posso creare un mixin di campi per django?

EDIT: Al contrario another question, questa domanda riguarda campo mixins, il problema legato riferisce a modello mixins.

+0

Quale versione di django stai usando? – Aya

+0

Sto usando la versione 1.9 – jmerkow

+0

@solarissmoke, non è la stessa domanda. –

risposta

2

In primo luogo, il motivo per cui è in esecuzione init 3 volte?

Anche se il models.py viene importato solo una volta, gli Field oggetti in esso creati, come ad esempio ...

myfield = MyMixinCharField(max_length=512, new_arg="myarg") 

... sono clonati più volte, che coinvolge chiamando il costruttore campo utilizzando la parola chiave args sono stati originariamente creati con. È possibile utilizzare il modulo traceback per vedere dove sta accadendo ...

import traceback 

class MyMixin(object): 
    def __init__(self, new_arg=None, *args, **kwargs): 
     super(MyMixin, self).__init__(*args, **kwargs) 
     print self.__class__, new_arg 
     traceback.print_stack() 

... che presentino i seguenti diverse volte in uscita ...

File "django/db/migrations/state.py", line 393, in from_model 
    fields.append((name, field.clone())) 
    File "django/db/models/fields/__init__.py", line 464, in clone 
    return self.__class__(*args, **kwargs) 
    File "myproj/myapp/models.py", line 11, in __init__ 
    traceback.print_stack() 

Dove si trova il kwarg 'new_arg' nel secondo due?

Quando si originariamente chiamato ...

myfield = MyMixinCharField(max_length=512, new_arg="myarg") 

... "myarg" viene passato come parametro new_arg a ...

def __init__(self, new_arg=None, *args, **kwargs): 

... ma perché don passare quel parametro al costruttore sottostante Field ...

super(MyMixin, self).__init__(*args, **kwargs) 

...non è memorizzato da nessuna parte nell'oggetto sottostante Field, quindi quando il campo è clonato, il parametro new_arg non viene passato al costruttore.

Tuttavia, passando l'opzione per il costruttore della superclasse non funziona, perché il CharField non supporta tale arg parola chiave, così avrai ...

File "myproj/myapp/models.py", line 29, in MyMixinModelTest 
    myfield = MyMixinCharField(max_length=512, new_arg="myarg") 
    File "myproj/myapp/models.py", line 25, in __init__ 
    super(MyMixinCharField, self).__init__(*args, **kwargs) 
    File "django/db/models/fields/__init__.py", line 1072, in __init__ 
    super(CharField, self).__init__(*args, **kwargs) 
TypeError: __init__() got an unexpected keyword argument 'new_arg' 

Come creo un mixin di campo per il django?

A causa di questo comportamento la clonazione, se si desidera aggiungere opzioni dei campi personalizzati, è necessario definire un metodo personalizzato deconstruct() in modo che Django può serializzare la nuova opzione ...

class MyMixin(object): 
    def __init__(self, new_arg=None, *args, **kwargs): 
     super(MyMixin, self).__init__(*args, **kwargs) 
     self.new_arg = new_arg 
     print self.__class__, new_arg 

    def deconstruct(self): 
     name, path, args, kwargs = super(MyMixin, self).deconstruct() 
     kwargs['new_arg'] = self.new_arg 
     return name, path, args, kwargs 


class MyMixinCharField(MyMixin, models.CharField): 
    pass 


class MyMixinModelTest(models.Model): 
    myfield = MyMixinCharField(max_length=512, new_arg="myarg") 

... quali uscite ...

<class 'myapp.models.MyMixinCharField'> myarg 
<class 'myapp.models.MyMixinCharField'> myarg 
<class 'myapp.models.MyMixinCharField'> myarg 
+0

Non ho risposto a tutte le mie domande nella mia risposta, l'hai fatto. Quindi accetterò il tuo. – jmerkow

+0

Domanda veloce, nella mia risposta ho MyMixin ereditato da models.Field, questo influenza qualcosa? – jmerkow

+0

@jmerkow Non è una domanda particolarmente veloce rispondere in modo definitivo, a causa dell'uso di metaclassi in 'Modello'. Se 'MyMixin' sottoclasse' Field' direttamente, allora in alcune circostanze, le funzioni in 'CharField' che sovrascrivono quelle definite in' Field' possono essere ignorate e finiranno per chiamare la funzione sbagliata. Probabilmente è più sicuro evitare questa possibilità, e basta avere l'oggetto 'sottoclasse' MyMixin', che è ciò che i mixin dovrebbero fare comunque. – Aya

1

Così ho capito dopo un sacco di armeggiare e rileggere il django docs on custom model fields Hai bisogno di un decostruttore insieme con il tuo init. I campi di Django necessitano di un metodo deconstruct per la serializzazione.

Il mixin dovrebbe avere questo metodo così:

class MyMixin(object): 
def __init__(self, new_arg=None, *args, **kwargs): 
    self.new_arg = new_arg 
    super(MyMixin, self).__init__(*args, **kwargs) 

def deconstruct(self): 
    name, path, args, kwargs = super(MyMixin, self).deconstruct() 
    if self.new_arg is not None: 
     kwargs['new_arg'] = self.new_arg 
    return name, path, args, kwargs 
+0

Avevo quasi finito di scrivere una risposta più dettagliata quando hai postato questo. :) – Aya