3

ユーザーが自分でユーザー グループを作成および管理できるようにするためのアプリに取り組んでいます。

問題は、どのユーザーが新しいメンバーをグループに追加したかを保存したいということです。

これらは現時点での私のモデルです:

class UserManagedGroup(Group):

    leader = models.ForeignKey(User, verbose_name=_('group leader'), related_name='leaded_groups')
    members = models.ManyToManyField(User, verbose_name=_('members'), through='Membership',
                                     related_name='managed_groups')

class Membership(models.Model):

    user = models.ForeignKey(User, related_name='memberships')
    group = models.ForeignKey(UserManagedGroup, related_name='memberships')
    info = models.OneToOneField('MembershipInfo', verbose_name=_('membership information'))

    class Meta:
        unique_together = ['user', 'group']

class MembershipInfo(models.Model):

    date_added = models.DateField(_('date added'), auto_now_add=True)
    made_member_by = models.ForeignKey(User, verbose_name=_('user who made him a member'))
    membership_justification = models.TextField(_('membership justification'), blank=True, default='')


@receiver(signals.post_delete, sender=Membership)
def delete_membership_info(sender, instance, *args, **kwargs):
    if instance.info.pk:
        instance.info.delete()

ご覧のとおり、フィールドの性質上、MembershipInfoマージした方がはるかに適したばかげたモデルがあります。Membershipまた、MembershipInfos の寿命はそれにバインドされていますMembership(これが、この post_delete シグナル接続を作成する必要があった理由です)。

このため、それらをマージできません:

中間モデルには、ターゲット モデルへの外部キーが 1 つだけ含まれている必要があります (この例では Person になります)。複数の外部キーがある場合、検証エラーが発生します。

(私の場合、ユーザーに2つの外部キーを使用できません)

さて、これは実際に機能しますが、私は好きではありません。Membership常に最初にインスタンスを作成する必要があるため、インスタンスの作成が面倒になりますMembershipInfo。また、1 つではなく 2 つのクエリ。

質問User私のメンバー関係にバインドされた同じモデル ( ) に 2 つの外部キーを格納する最良の方法。

4

2 に答える 2

5

同じターゲットへの2つの外部キーを持つ中間モデルを含む同様の問題を解決しました。これは私のシステムがどのように見えるかです:

class Node(models.Model):
    receivers = models.ManyToManyField('self', through='Connection',  related_name='senders',  symmetrical=False)

class Connection(models.Model):
    sender = models.ForeignKey(Node, related_name='outgoing')
    receiver = models.ForeignKey(Node, related_name='incoming')

これは、中間モデルで同じターゲットに対して 2 つの外部キーを使用するための主な要件を示していると思います。つまり、モデルにはManyToManyField、ターゲット'self'(再帰的な ManyToMany) とthrough中間モデルを指す属性を持つ が必要です。また、各外部キーに一意の を割り当てる必要があると思いますrelated_name。このsymmetrical=False引数は、再帰関係を一方向にしたい場合に適用されます。たとえば、Node1 は Node2 にシグナルを送信しますが、Node2 は必ずしも Node1 にシグナルを送信するとは限りません。symmetrical=False再帰的な ManyToMany でカスタムの「スルー」モデルを使用するには、関係を定義する必要があります。カスタムの「スルー」モデルを使用して対称的な再帰的 ManyToMany を作成する場合は、ここでアドバイスを見つけることができます。

これらの相互関係はすべてかなり紛らわしいことがわかったので、コードが何をしているかを実際に捉える適切なモデル属性と related_name を選択するのにしばらく時間がかかりました。これがどのように機能するかを明確にするために、ノード オブジェクト N がある場合、N からデータを受信するか、N にデータを送信する他のノードのセットをそれぞれ呼び出すN.receivers.all()か、返します。related_names を使用して、Connection オブジェクト自体をN.senders.all()呼び出しN.outgoing.all()たり、アクセスしたりします。N.incoming.all()それにはまだあいまいさがsendersありreceivers、ManyToManyField で交換することができ、コードは同様にうまく機能しますが、方向が逆になることに注意してください。「送信者」が実際に「受信者」に送信しているかどうか、またはその逆かどうかについてテストケースを確認することで、上記にたどり着きました。

あなたの場合、再帰的な ManyToManyField を User に直接追加する方法が明らかではないため、両方の外部キーを User にターゲットすると複雑になります。User モデルをカスタマイズする好ましい方法は、OneToOneField を介して User に接続されたプロキシを介して拡張することだと思います。これは、MembershipInfo を使用して Membership を拡張するのと同じように満足できないかもしれませんが、少なくとも User モデルにさらにカスタマイズを簡単に追加できるようになります。

したがって、あなたのシステムでは、次のようなことを試します(テストされていません):

class Member(models.Model):
    user = models.OneToOneField(User, related_name='member')
    recruiters = models.ManyToManyField('self', through = 'Membership',  related_name = 'recruits',  symmetrical=False)
    other_custom_info = ... 

class UserManagedGroup(Group):
    leader = models.ForeignKey(Member, related_name='leaded_groups')
    members = models.ManyToManyField(Member, through='Membership', related_name='managed_groups')

class Membership(models.Model):
    member = models.ForeignKey(Member, related_name='memberships')
    made_member_by = models.ForeignKey(Member, related_name='recruitments')
    group = models.ForeignKey(UserManagedGroup, related_name='memberships')

    date_added = ...
    membership_justification = ...

Member1 が Member2 をリクルートしても、Member2 が Member1 をリクルートしたことを意味しないため、再帰フィールドは非対称である必要があります。関係をより明確に伝えるために、いくつかの属性を変更しました。ユーザー オブジェクトにアクセスする必要がある場合はいつでも Member.user にアクセスできるため、ユーザーを使用する場合はどこでもプロキシ メンバーを使用できます。これが意図したとおりに機能する場合、特定のメンバー M で次のことができるはずです。

M.recruiters.all() -> set of other members that have recruited M to groups
M.recruits.all() -> set of other members that M has recruited to groups
M.leaded_groups.all() -> set of groups M leads
M.managed_groups.all() -> set of groups of which M is a member
M.memberships.all() -> set of Membership objects in which M has been recruited
M.recruitments.all() -> set of Membership objects in which M has recruited someone

グループGの場合、

G.memberships.all() -> set of Memberships associated with the group

これは機能し、別の MembershipInfo モデルよりも「クリーンな」ソリューションを提供するはずだと思いますが、たとえば、再帰フィールドの方向をチェックして、採用担当者が新兵を募集していることを確認し、その逆ではないことを確認するなど、微調整が必​​要になる場合があります。

編集: Member モデルを User モデルにリンクするのを忘れていました。それは次のように行われます:

def create_member(member, instance, created, **kwargs):
    if created:
        member, created = Member.objects.get_or_create(user=instance)

post_save.connect(create_member, member=User)

create_member は Member のメソッドではなく、Member が定義された後に呼び出されることに注意してください。これにより、User が作成されるたびに Member オブジェクトが自動的に作成されます (Member フィールドを初期化せずにユーザーを追加する場合は、メンバー フィールドを null=True および/または blank=True に設定する必要がある場合があります)。

于 2013-05-01T04:02:19.370 に答える