Ho risolto questo problema semplicemente chiamando le fabbriche entro @factory.post_generation
. A rigor di termini questa non è una soluzione al problema specifico posto, ma spiego di seguito in grande dettaglio perché questa risultò essere un'architettura migliore. @ La soluzione di rhunwick passa autenticamente da SubFactory(LazyAttribute(''))
a RelatedFactory
, tuttavia restavano delle restrizioni che significavano che non era giusto per la mia situazione.
spostiamo la creazione di sub_object
e object
ProblemFactory
-ObjectWithSubObjectsFactory
(e rimuovere la clausola exclude
), e aggiungere il seguente codice alla fine del ProblemFactory
.
@factory.post_generation
def post(self, create, extracted, **kwargs):
if not create:
return # No IDs, so wouldn't work anyway
object = ObjectWithSubObjectsFactory()
sub_object_ids_by_code = dict((sbj.name, sbj.id) for sbj in object.subobject_set.all())
# self is the `Foo` Django object just created by the `ProblemFactory` that contains this code.
for another_obj in self.anotherobject_set.all():
if another_obj.name == 'age_in':
another_obj.attribute_id = sub_object_ids_by_code['Age']
another_obj.save()
elif another_obj.name == 'income_in':
another_obj.attribute_id = sub_object_ids_by_code['Income']
another_obj.save()
così sembra RelatedFactory
chiamate vengono eseguite prima PostGeneration
chiamate.
La denominazione in this question è più facile da capire, ecco lo stesso codice di soluzione per questo problema del campione:
La creazione di dataset
, column_1
e column_2
vengono spostati in una nuova fabbrica DatasetAnd2ColumnsFactory
, e il codice qui sotto è quindi aggiunto alla fine di FunctionToParameterSettingsFactory
.
@factory.post_generation
def post(self, create, extracted, **kwargs):
if not create:
return
dataset = DatasetAnd2ColumnsFactory()
column_ids_by_name =
dict((column.name, column.id) for column in dataset.column_set.all())
# self is the `FunctionInstantiation` Django object just created by the `FunctionToParameterSettingsFactory` that contains this code.
for parameter_setting in self.parametersetting_set.all():
if parameter_setting.name == 'age_in':
parameter_setting.column_id = column_ids_by_name['Age']
parameter_setting.save()
elif parameter_setting.name == 'income_in':
parameter_setting.column_id = column_ids_by_name['Income']
parameter_setting.save()
Ho poi esteso questo approccio passando opzioni per configurare la fabbrica, in questo modo:
whatever = WhateverFactory(options__an_option=True, options__another_option=True)
Allora questo codice di fabbrica rilevato le opzioni e ha generato i dati di test richiesti (notare il metodo viene rinominato options
per abbinare il prefisso sui nomi dei parametri):
@factory.post_generation
def options(self, create, not_used, **kwargs):
# The standard code as above
if kwargs.get('an_option', None):
# code for custom option 'an_option'
if kwargs.get('another_option', None):
# code for custom option 'another_option'
ho poi ulteriormente esteso questo. Poiché i miei modelli desiderati contenevano auto join, la mia fabbrica è ricorsiva. Così, per una chiamata come ad esempio:
whatever = WhateverFactory(options__an_option='xyz',
options__an_option_for_a_nested_whatever='abc')
Entro @factory.post_generation
ho:
class Meta:
model = Whatever
# self is the top level object being generated
@factory.post_generation
def options(self, create, not_used, **kwargs):
# This generates the nested object
nested_object = WhateverFactory(
options__an_option=kwargs.get('an_option_for_a_nested_whatever', None))
# then join nested_object to self via the self join
self.nested_whatever_id = nested_object.id
Alcune note non è necessario leggere il motivo per cui sono andato con questa opzione invece di @ corretta soluzione di rhunwicks alla mia domanda di cui sopra. C'erano due ragioni.
La cosa che mi ha impedito di sperimentarlo era che l'ordine di RelatedFactory e di post-generazione non è affidabile: i fattori apparentemente non correlati lo influenzano, presumibilmente una conseguenza della valutazione pigra. Avevo degli errori in cui un insieme di fabbriche avrebbe improvvisamente smesso di funzionare senza una ragione apparente. Una volta era perché ho rinominato le variabili a cui RelatedFactory era assegnato. Sembra ridicolo, ma l'ho testato fino alla morte (e pubblicato here) ma non c'è dubbio: la modifica delle variabili ha commutato in modo affidabile la sequenza di RelatedFactory e l'esecuzione post-gen. Ho sempre pensato che si trattasse di una supervisione a mio nome fino a quando non è successo di nuovo per qualche altro motivo (che non sono mai riuscito a diagnosticare).
In secondo luogo ho trovato il codice dichiarativo confuso, inflessibile e difficile da ri-fattore. Non è semplice passare diverse configurazioni durante l'istanziazione in modo che lo stesso factory possa essere utilizzato per diverse variazioni dei dati di test, ovvero ho dovuto ripetere il codice, object
deve essere aggiunto a un elenco di Factory Meta.exclude
- sembra banale ma quando hai pagine di codice che genera dati è stato un errore affidabile. Come sviluppatore dovresti passare diverse fabbriche diverse volte per capire il flusso di controllo. Il codice di generazione sarebbe stato distribuito tra il corpo dichiarativo, fino a quando non avresti esaurito questi trucchi, poi il resto sarebbe andato in post-generazione o sarebbe diventato molto contorto. Un esempio comune per me è una triade di modelli interdipendenti (ad esempio, una struttura di categoria genitore-figlio o dataset/attributi/entità) come chiave esterna di un'altra triade di oggetti interdipendenti (ad es. Modelli, valori dei parametri, ecc. ai valori dei parametri di altri modelli). Alcuni di questi tipi di strutture, soprattutto se nidificate, diventano rapidamente ingestibili.
Mi rendo conto che non è proprio nello spirito di factory_boy, ma mettere tutto in post-generazione ha risolto tutti questi problemi. Posso passare i parametri, quindi la stessa singola fabbrica serve tutti i miei requisiti di dati di test del modello composito e nessun codice viene ripetuto. La sequenza di creazione è facile da vedere immediatamente, ovvia e completamente affidabile, piuttosto che dipendere da catene confuse di ereditarietà e di sovrapposizione e soggette a qualche bug. Le interazioni sono ovvie quindi non è necessario digerire tutto per aggiungere funzionalità, e le diverse aree di funzionalità sono raggruppate nelle clausole if post generazione. Non è necessario escludere variabili di lavoro e puoi farvi riferimento per la durata del codice di fabbrica. Il codice di test unitario è semplificato, in quanto la descrizione della funzionalità viene eseguita nei nomi dei parametri anziché in quelli della classe Factory, pertanto è possibile creare dati con una chiamata come WhateverFactory(options__create_xyz=True, options__create_abc=True..
anziché WhateverCreateXYZCreateABC..()
. Ciò rende abbastanza chiara la codifica delle responsabilità.
Penso che se specifichi lo stesso pk, risolverà il problema. –
Vuoi dire "sub_object_id = sub_object_id'? O 'LazyAttribute (lambda obj: obj.factory_parent.sub_object.id)'? – Chris
Le risposte a [questa domanda] (http://stackoverflow.com/questions/32995158/getting-id-of-associated-child-records-in-factory-boy/33043428#33043428) sembrano indicare un approccio alternativo, ma non riesco a farlo per soddisfare questo requisito. (Questa domanda è per correggere un bug in una di quelle risposte.) – Chris