Pythonメモ torinaブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

Djangoで、自分自身に対するリレーション

プログラミング関連 Django
約171日前 2016年10月7日7:08
以下のようなモデルがあったとします。何の変哲もない、シンプルな記事的なモデルです。
from django.db import models


class Post(models.Model):
    """ブログのポスト"""

    title = models.CharField(max_length=255)
    text = models.TextField()

    def __str__(self):
        return self.title



関連記事を自分で編集したい、という場合があります。
その場合、以下のように自分自身にリレーションを張ると便利でしょう。
from django.db import models


class Post(models.Model):
    """ブログのポスト"""

    title = models.CharField("タイトル", max_length=255)
    text = models.TextField("中身")
    friend_posts = models.ManyToManyField("self", verbose_name="関連記事", blank=True)

    def __str__(self):
        return self.title


するとこのように、関連記事としてPost自身が選べますね。


しかし、ちょっと困ったことがあります。気づいた人はいるでしょうか。
そう、記事4の関連記事で記事4が選べますね。これは少しいやです。


Djangoで、limit_choices_toを使った絞込み
https://torina.top/main/276/
で、表示されるものを絞り込めると以前に書きました。しかし、今回は少し難しそうです。別の方法を考えます。


forms.py
これは、何の変哲もないModelFormに、少し細工をしました。__init__内です。
excludeで、自分を除いています。自分以外の、全ての記事をquerysetに入れているということです。
from django import forms
from .models import Post


class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = "__all__"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['friend_posts'].queryset = Post.objects.exclude(pk=self.instance.pk)


admin.py
ModelAdminですが、こいつを色々することで管理画面の機能をカスタマイズできます。
今回は、さっき作ったformを管理画面でも使うようにお願いしました。
from django.contrib import admin
from .forms import PostForm
from .models import Post


class PostAdmin(admin.ModelAdmin):
    form = PostForm

admin.site.register(Post, PostAdmin)



すると、ちゃんと動作していますね。記事4の関連記事リストに、記事4がありませんね。


管理画面でも適用するためにadmin.pyを編集しましたが、adminを使わないなら不要です。
自分で投稿画面等を作った場合は、単純にPostFormを使うだけで大丈夫です。

ちなみにですが、以下のように関連記事を指定...記事4から、1、2、3、5全て指定したとします。


すると、1、2、3、5でも関連記事に4が自動で設定されます。。
記事4で全ての関連記事を外すと、同様に外れます。
例えば記事5で記事4を関連記事から外しても、記事4から自動で外れます。ManyToManyは相思相愛と覚えましょう。


ForeignKeyでもちゃんと動きます。

from django.db import models


class Post(models.Model):
    """ブログのポスト"""

    title = models.CharField("タイトル", max_length=255)
    text = models.TextField("中身")
    friend_posts = models.ForeignKey("self", verbose_name="関連記事", blank=True, null=True)

    def __str__(self):
        return self.title



この場合は、記事1から記事2を指定しても、記事2の関連記事が自動で記事1にはなりません。