2015-10-07 2 views
0

J'ai un function avec un nombre de parameters, puis un instantiation spécialisé de cette fonction, avec certains settings pour chacun des paramètres de la fonction. J'ai donc une structure comme suit:Obtenir l'ID des enregistrements enfants associés dans factory_boy

class Function(models.Model): 
    name = models.CharField() 

class FunctionParameter(models.Model): 
    function = models.ForeignKey(Function) 

class FunctionInstantiation(models.Model): 
    function = models.ForeignKey(Function) 

class ParameterSetting(models.Model): 
    function_instantiation = models.ForeignKey(FunctionInstantiation) 
    function_parameter = models.ForeignKey(FunctionParameter) 

En FunctionFactory je peux utiliser factory.RelatedFactory pour créer le parameters.

Mais FunctionInstantiationFactory je ne peux pas utiliser factory.RelatedFactory(ParameterSetting) pour créer ParameterSettings, parce que je n'ai pas accès aux parameter objets créés dans FunctionFactory, je ne peux pas mettre parameter_setting.function_parameter_id.

Comment FunctionInstantiationFactory peut-il rechercher les parameter_id de paramètres créés dans FunctionFactory? Puis-je obtenir à eux de la valeur de retour de RelatedFactory(FunctionFactory)? Ou dois-je regarder la base de données?

Répondre

-1

C'est la réponse de Xelnor, mais corrige le bug de sorte que seul un function_instantiation est créé, au lieu d'une pour chaque paire parameter/parameter_setting.

class FunctionFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.Function 
    name = factory.Sequence(lambda n: "Function %d" % n) 


class FunctionParameterFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.FunctionParameter 
    function = factory.SubFactory(FunctionFactory) 


class FunctionInstantiationFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.FunctionInstantiation 
    function = factory.SubFactory(FunctionFactory) 


class ParameterSettingFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.ParameterSetting 

    function_instantiation = factory.SubFactory(FunctionInstantiationFactory) 
    function_parameter = factory.SubFactory(FunctionParameterFactory, 
     function=factory.SelfAttribute('..function_instantiation.function')) 


class FunctionToParameterSettingsFactory(FunctionInstantiationFactory): 
    class Meta: 
     model = models.FunctionInstantiation 

    # This overrides the function_instantiation created inside 
    # ParameterSettingFactory, which then overrides the Function creation, 
    # with the SelfAttribute('..function_instantiation.function') syntax. 
    parameter_setting_1 = factory.RelatedFactory(ParameterSettingFactory, 
     'function_instantiation') 
    parameter_setting_2 = factory.RelatedFactory(ParameterSettingFactory, 
     'function_instantiation') 

Ce qui suit montre les solutions à quelques autres problèmes toute personne utilisant ce modèle rencontrera probablement, comme remplaçant les valeurs des objets liés et des liens vers d'autres tables, eux-mêmes liés. Il s'inspire largement des techniques introduites par Xelnor dans sa réponse.

class FunctionFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.Function 
    name = factory.Sequence(lambda n: "Function %d" % n) 


class FunctionParameterFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.FunctionParameter 
    name = factory.Sequence(lambda n: "Function %d" % n) 
    function = factory.SubFactory(FunctionFactory) 


class ParameterSettingFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.ParameterSetting 
    name = factory.Sequence(lambda n: "Function %d" % n) 

    function_instantiation = factory.SubFactory(FunctionInstantiationFactory) 
    function_parameter = factory.SubFactory(FunctionParameterFactory, 
     function=factory.SelfAttribute('..function_instantiation.function')) 


class DatasetAnd2ColumnsFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.Function 
    dataset = factory.SubFactory(DatasetFactory, 
     name=factory.Sequence(lambda n: "Custom dataset %d" % n)) 
    column_1 = factory.SubFactory(ColumnFactory, dataset=dataset, 
     name=factory.Sequence(lambda n: "Column 1 %d" % n)) 
    column_2 = factory.SubFactory(ColumnFactory, dataset=dataset, 
     name=factory.Sequence(lambda n: "Column 2 %d" % n)) 


# I found it neater not to inherit in the end, due to needing quite a lot of 
# additional complexity not included in my original question. 
class FunctionToParameterSettingsFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.FunctionInstantiation 

    name = factory.Sequence(lambda n: "Custom instantiation name %d" % n) 
    # You can call Sequence to pass values to SubFactories 
    function = factory.SubFactory(FunctionFactory, 
     name=factory.Sequence(lambda n: "Custom function %d" % n)) 

    parameter_setting_1 = factory.RelatedFactory(ParameterSettingFactory, 
     'function_instantiation', 
     # Note the __ syntax for override values for nested objects: 
     parameter__name='Parameter 1', 
     name='Parameter Setting 1') 
    # Possible to use Sequence here too, and makes looking at data easier 
    parameter_setting_2 = factory.RelatedFactory(ParameterSettingFactory, 
     'function_instantiation', 
     parameter__name=factory.Sequence(lambda n: "Param 1 for fn %d" % n), 
     name=factory.Sequence(lambda n: "Param Setting 1 for fn %d" % n)) 

J'ai besoin maintenant de créer un ensemble de données avec des colonnes de données, et joindre les documents parameter_setting avec ces colonnes. Pour ce faire, cela va à la fin de 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 `FunctioInstantiation` Django object just created by the `FunctionToParameterSettingsFactory` 
    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() 

C'est certes un peu hacky. J'ai essayé de passer column=column_1 dans les appels RelatedFactory, mais cela a déclenché la création de plusieurs jeux de données, chaque colonne étant liée à un autre.J'ai essayé toutes sortes d'acrobaties avec SelfAttribute et LazyAttribute, mais vous ne pouvez pas utiliser dans un appel RelatedFactory, et vous ne pouvez pas créer quelque chose avec SubFactory (SelfAttribute()), puis le passer dans RelatedFactory, car cela casse SelfAttribute (voir my other question).

Dans mon code réel j'avais plusieurs autres modèles avec une clé étrangère à l'ensemble de données et tout s'est bien passé.

+0

Mes modifications ont été rejetées ect, désolé @Xelnor, j'ai essayé de garder la réponse crédit avec vous. – Chris

+0

Il y a un bug dans le dernier exemple de code. 'dataset' est créé de façon répétée, de sorte que' column_1' et 'column_2' obtiennent des ensembles de données différents (tous deux avec le champ' name' surchargé). Si je crée un 'column_3' avec' dataset = factory.LazyAttribute (lambda col: col.factory_parent.dataset) ', cela fonctionne très bien. Si j'ajoute ensuite 'column_3' à un appel' ParameterSettingFactory', j'obtiens 'AttributeError: Le dataset du paramètre est inconnu. 'Et factory_boy ne trouve que les locals ParameterSettingFactory, et pas factory_parent. – Chris

+0

J'ai résolu le problème ci-dessus, [décrit ici] (http://stackoverflow.com/posts/33110180/edit), et mis à jour la réponse. – Chris

2

Le factory.SubFactory est destiné à suivre a ForeignKey; Si vous voulez l'utiliser dans l'autre sens, vous devez utiliser un RelatedFactory à la place.

Pour votre exemple, je partirais avec les usines suivantes:

class FunctionFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.Function 
    name = factory.Sequence(lambda n: "Function %d" % n) 


class FunctionParameterFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.FunctionParameter 
    function = factory.SubFactory(FunctionFactory) 


class FunctionInstantiationFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.FunctionInstantiation 
    function = factory.SubFactory(FunctionFactory) 


class ParameterSettingFactory(factory.django.DjangoModelFactory): 
    class Meta: 
     model = models.ParameterSetting 
     exclude = ['function'] 

    # We'll need a FunctionFactory; this field is part of 'exclude', 
    # thus available while building the factory but not passed to the 
    # target Django model 
    function = factory.SubFactory(FunctionFactory) 

    # Use the function from our Factory for both 
    # function_instantiation and function_parameter 
    function_instantiation = factory.SubFactory(FunctionInstantiationFactory, 
     function=factory.SelfAttribute('..function')) 
    function_parameter = factory.SubFactory(FunctionParameterFactory, 
     function=factory.SelfAttribute('..function')) 

Et vous pouvez ajouter une usine supplémentaire, FunctionWithParametersFactory, qui crée des paramètres le long:

class FunctionWithParametersFactory(FunctionFactory): 
    parameter1 = factory.RelatedFactory(ParameterSettingFactory, 'function') 
    parameter2 = factory.RelatedFactory(ParameterSettingFactory, 'function') 

appel de cette usine sera effectuer les opérations suivantes:

  1. Créer un objet Function (via FunctionFactory)
  2. Appel ParameterSettingFactory, pointant à l'objet créé Fonction
  3. Appel ParameterSettingFactory une seconde fois, il pointant toujours le même objet Fonction
  4. Retour cette fonction objet.
+0

Désolé, il y a quelques secondes j'ai mis à jour ma question avec 'RelatedFactory' au lieu de' SubFactory', pendant que vous étiez en train de taper. Était juste en train de lire sur 'SelfAttribute' mais était désespérément coincé sur les lignes de' function.parameter [0] .id'. Je vais prendre quelques minutes pour comprendre votre code alors prends-moi avec moi avant de marquer comme la réponse. Un grand merci, comme vous pouvez le dire, je suis nouveau sur factory_boy et ses semblables. – Chris

+0

Quelques notes, évidentes pour toute personne familière avec factory_boy. La syntaxe 'factory.SelfAttribute ('..' indique à la SubFactory ('FunctionInstantiationFactory') d'obtenir l'objet nommé (' function') de la portée de l'appel ('ParameterSettingFactory').' Exclude' arrête factory_boy de tenter d'enregistrer ' function' comme une clé étrangère sur l'objet 'ParameterSetting' (qui n'a pas ce FK).' FunctionWithParametersFactory', en héritant de 'FunctionFactory', commence par créer' Function', puis utilise 'ParameterSettingFactory' pour créer les trois autres Merci beaucoup @Xelnor cela m'a beaucoup appris – Chris

+0

De [les docs] (http://factoryboy.readthedocs.org/en/latest/reference.html#parents) je pense 'fonction = factory.LazyAttribute (lambda function_parameter: function_parameter.factory_parent.function) 'serait un moyen alternatif (inférieur) d'implémentation de la seconde' function = factory.SelfAttribute ('.. function') '. @ La solution de Xelnor est plus agréable et plus nette dans tous les sens, juste en notant une découverte connexe – Chris