2016-06-17 10 views
1

私は食材のリストを含むデータベースを持っています。私はこのテーブルの重複したエントリを避けたいと思います。私は2つの理由のためにuniqueキーワードを使用したくない: Djangoでエントリの一意性をカスタマイズする

  • 私の一意性制約

    もう少し洗練された単なる私は例外を発生させたくない=
  • とき、既存の成分よりもモデルを作成する代わりに、そのモデルを返すだけで、Ingredient(ingredient_name='tomato')と書くことができ、そのすべてをtry節にカプセル化するのではなく、私の一日を続けることができます。これにより、レシピテーブルに食材をすぐに追加することができます。

一つの解決策は、create_ingredientのようなラッパー関数を持っているだけであるが、私はそれが特にエレガントであることを見つけていない、より具体的に、それは単にラッパーを使用するのを忘れるラインの下のいくつかの他の開発者に堅牢ではありません。代わりに、私はpre_initとpost_initシグナルで遊んでいます。

は、ここで私がこれまで持っているものです。

class Ingredient(models.Model): 
    ingredient_name = models.CharField(max_length=200) 
    recipes = models.ManyToManyField(Recipe,related_name='ingredients') 

    def __str__(self): 
     return self.ingredient_name 

class Name(models.Model): 
    main_name = models.CharField(max_length=200, default=None) 
    equivalent_name = models.CharField(max_length=200, primary_key=True, default=None) 

def _add_ingredient(sender, args, **kwargs): 
    if 'ingredient_name' not in kwargs['kwargs'] : 
     return 
    kwargs['kwargs']['ingredient_name'] = kwargs['kwargs']['ingredient_name'].lower() 
    # check if equivalent name exists, make this one the main one otherwise 
    try: 
     kwargs['kwargs']['ingredient_name'] = Name.objects.filter(
      equivalent_name=kwargs['kwargs']['ingredient_name'] 
     )[0].main_name 
    except IndexError: 
     name = Name(main_name=kwargs['kwargs']['ingredient_name'], 
       equivalent_name=kwargs['kwargs']['ingredient_name']) 
     name.save() 

pre_init.connect(_add_ingredient, Ingredient) 

これまでのところは良いです。これは実際に動作し、モデルが初期化される前に必要に応じてingredient_nameを置き換えます。今私がしたいのは、問題の成分がすでに存在しているかどうかを調べ、イニシャライザがそれを返すようにすることです。私はこれを行うにはpost_initと遊ぶ必要があると思うが、私は作成されている特定のインスタンスを変更する方法を知らない。私がコメントしてきたようにinstance = ...が実際にinstanceを変更しないので、私はこれが動作するように期待していない、それだけで変数名を再割り当て

def _finalize_ingredient(sender, instance, **kwargs): 
    try: 
     # doesn't work because of python's "pass arguments in python's super unique way of doing things" thing 
     instance = Ingredient.objects.filter(ingredient_name=instance.ingredient_name)[0] 
    except IndexError: 
     pass 

post_init.connect(_finalize_ingredient, Ingredient) 

(あなたが実行しようついでに場合:ここで私はそれによって何を意味するかです私はこれが間違っていないことを知っているので、私は理解しても気にしない、あらゆる種類の恐ろしいことが起こります。だから私は実際にこれをやりますか?私は本当にラッパー関数がここで最もクリーンなオプションではないことを願っています。私はOOPの大ファンであり、これはOOPの解決策を望んでいる(私が言ったように、長期的にはラッパーよりはるかに頑強で安全だと思う)。

レシピに私にはadd_ingredientメソッドを追加することができますが、このすべてを私のために行うことができますが、Ingredientクラスにこのすべてを含めるというのが本当に好きです。どんな状況でも。私はまた、post_initメソッドを使用して、特定の状況で作成されたオブジェクトを完全にオーバーライドすることができるかどうかについて知りたいと思っています。私はIngredientテーブルにNameテーブルを接続します私のNameクラスでForeignKeyエントリを持っていない理由ところで

は、あなたのいくつかは不思議に思われるかもしれません。結局のところ、私のチェックは私の_add_ingredientメソッドで本質的に達成されているものではありませんか?その理由の1つは、私がこれを行うと、私はここで解決しようとしているのと同じ問題に終わるということです:私はレシピにそれを加えるために素早く原料を作りたいと思ったら、単にNameオブジェクトIngredientオブジェクトを作成したときに、既に使用されているmain_nameに対応する場合は例外が発生します(必要なオブジェクトを単に返すのではなく)。

答えて

0

私はget_or_create()を探していると思いますが、これはすでにDjangoに組み込まれています。

あなたが言及:

一つの解決策は、create_ingredientのようなラッパー関数を持っているだけであるが、私はそれが特にエレガントであることを見つけていない、より具体的に、それは単にラインの下のいくつかの他の開発者に堅牢ではありませんラッパーの使用を忘れる。

まあ、それを逆に見てください。実際に「重複」成分を作成する必要がある場合はどうすればよいですか?その可能性を持つことはいいですね。

0

私は、私が後にしているものを与えることが可能であると思うほどエレガントで頑丈なものを考え出しました。私はまだadd_ingredientメソッドを定義しなければなりませんでしたが、私はまだ必要な堅牢性を持っています。それは主キーで任意のクラスに一般化することができるように、私はそれを作った、とNameテーブルは、任意のテーブルの名前の一意性を定義する情報が含まれています:

class Name(models.Model): 
    main_name = models.CharField(max_length=200, default=None) 
    equivalent_name = models.CharField(max_length=200, primary_key=True, default=None) 

def _pre_init_unique_fetcher(sender, args, **kwargs): 
    pk_name = sender._meta.pk.name 
    if pk_name not in kwargs['kwargs'] : 
     return 
    kwargs['kwargs'][pk_name] = kwargs['kwargs'][pk_name].lower() 
    # check if equivalent name exists, make this one the main one otherwise 
    try: 
     kwargs['kwargs'][pk_name] = Name.objects.filter(
      equivalent_name=kwargs['kwargs'][pk_name] 
     )[0].main_name 
    except IndexError: 
     name = Name(main_name=kwargs['kwargs'][pk_name], 
        equivalent_name=kwargs['kwargs'][pk_name]) 
     name.save() 
    sender._input_dict = kwargs['kwargs'] 

def _post_init_unique_fetcher(sender, instance, **kwargs): 
    pk_name = sender._meta.pk.name 
    pk_instance = instance.__dict__[pk_name] 
    filter_dict = {} 
    filter_dict[pk_name] = pk_instance 
    try: 
     post_init.disconnect(_post_init_unique_fetcher,sender) 
     instance.__dict__ = sender.objects.filter(**filter_dict)[0].__dict__ 
     post_init.connect(_post_init_unique_fetcher, sender) 
     for key in sender._input_dict: 
      instance.__dict__[key] = sender._input_dict[key] 
    del sender._input_dict 
    except IndexError: 
     post_init.connect(_post_init_unique_fetcher, sender) 
    except: 
     post_init.connect(_post_init_unique_fetcher, sender) 
     raise 

unique_fetch_models = [Ingredient, Recipe, WeekPlan] 
for unique_fetch_model in unique_fetch_models : 
    pre_init.connect(_pre_init_unique_fetcher, unique_fetch_model) 
    post_init.connect(_post_init_unique_fetcher, unique_fetch_model) 

は今、これが何をするかです同じ名前のモデルが存在する場合は、以前のモデルの既存のデータ(デフォルト値ではなく)を持つ新しいモデルをロードします。 Recipeクラスにまだadd_ingredientメソッドが必要な理由は、モデルを作成してすぐに保存できるという事実にもかかわらず、例外を発生させずに既存の成分にIngredient.objects.create()を呼び出すことができないためです。これはDjangoがprimary_keyという名前をどのように扱うかと関係があります。saveというモデルを作成すると、そのキーが既に存在していればエントリを更新しているに過ぎず、createというエントリを追加しようとしますそしてそれはprimary_key指定と矛盾します。だから私はrecipe.add_ingredient(Ingredient(ingredient_name='tomato', vegetarian=True))のようなことをすることができます。

関連する問題