1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Django でデータをプロジェクト的単位に分ける (3)

Last updated at Posted at 2020-02-29

前置き

この記事のつづきです。
「プロジェクト的単位」というのはタスク管理ツールなどで言う "プロジェクト" の事で、ここでは演劇の管理をするので「公演」がそれに当たります。

記事の内容

前回は、「公演」に属するデータの入力インターフェイスについて考えました。
今回は、自分以外のユーザを招待して、「公演」のメンバに加える仕組みについて考えてみたいと思います。

  • 使ったテクニックについては「まとめ」にまとめてあります。
  • Heroku に動くデモがあります。詳しくは GitHub Pages を御覧ください。

具体的な目標

  • 「公演」の所有者が、特定のアカウントに向けて「招待状」というものを作れるようにします。
  • 作った「招待状」を確認したり削除したり出来るようにします。
  • 招待されたアカウントでログインすると「招待状」が表示されるようにします。
  • 受け取った「招待状」から「公演」に参加したり、受け取った「招待状」を削除したり出来るようにします。
  • メール等は使わず、Web アプリ内で完結する仕組みにします。

完成イメージ

招待する方

招待された方


やりたい事の整理

この Web アプリでは "公演ユーザ" というモデルを使って、Django が管理しているアカウントと各「公演」への参加状態を関連付けています。
したがって、招待された人が「参加」を選択した時の処理は、"公演ユーザ" を新規で作成する処理になります。

「招待状」のモデルを作る

以下のフィールドを持たせます。

フィールド 内容
production 招待先の「公演」
inviter 招待する人 (Django が管理しているアカウント)
invitee 招待される人 (Django が管理しているアカウント)
exp_dt 招待状の有効期限

「招待状」を作成する仕組みを作る

  • 「メンバ一覧」という画面があるので、そこに「招待する」というボタンを作ります。
  • このボタンを押すと「招待状」を作る画面を表示するようにします。
  • 「招待状」を作る画面では、「招待される人」を指定できるようにする必要があります (それ以外のフィールドの内容はアプリ側で決められます)。

「招待状」を表示する仕組みを作る

  • 「公演」の所有者が「メンバ一覧」画面を表示した時に、自分が作った「招待状」が表示されるようにします。
    • ここで削除もできるようにします。
  • 招待されている人が「参加している公演」画面 (ログイン直後に表示される画面) を表示した時に、自分宛ての「招待状」が表示されるようにします。
    • ただし、有効期限を過ぎているものは表示されないようにします。
    • ここで「公演」への参加と、「招待状」の削除ができるようにします。

「招待状」から「公演」に参加する仕組みを作る

  • 招待された人が「参加」を選択した時の処理は、"公演ユーザ" を新規で作成する処理になります。

招待状のモデル

※コードでは「招待状」ではなく「招待」と呼んでいます。
※「公演」に参加しているメンバをまとめて「座組」と呼んでいます。

from datetime import datetime, timezone
from django.conf import settings
from django.db import models


class Invitation(models.Model):
    '''座組への招待
    '''
    production = models.ForeignKey(Production, verbose_name='公演',
        on_delete=models.CASCADE)
    inviter = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='招待する人',
        related_name='inviter', on_delete=models.CASCADE)
    invitee = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='招待される人',
        related_name='invitee', on_delete=models.CASCADE)
    exp_dt = models.DateTimeField(verbose_name='期限')
    
    class Meta:
        verbose_name = verbose_name_plural = '座組への招待'
        ordering = ['exp_dt']
    
    def __str__(self):
        return f'{self.invitee} さんへの {self.production} への招待'
    
    def expired(self):
        '''この招待が期限切れかどうかを返す
        '''
        now = datetime.now(timezone.utc)
        return now > self.exp_dt
  • Production は、この Web アプリで定義している「公演」のモデルです。
  • inviter, invitee は、どちらも Django が管理しているアカウントなので、settings.AUTH_USER_MODEL のオブジェクトへの参照となります。
  • AUTH_USER_MODEL についてはこちらを御覧ください。

招待状を作成する仕組み

以下、View や Template の説明になります。
production というアプリケーションの中で作っているので、URL の指定が 'production:usr_list' のようになっています。
呼び出しのための urls.py の書き方については、production/urls.py のソース を見てください。

View を作る

CreateView を継承して 招待状を作成するための View を作ります。

views.py
from django.views.generic.edit import CreateView
from django.contrib import messages
from django.contrib.auth import get_user_model
from .view_func import *
from .models import Production, ProdUser, Invitation


class InvtCreate(LoginRequiredMixin, CreateView):
    '''Invitation の追加ビュー
    '''
    model = Invitation
    fields = ()
  • fields を空にしているのは、ちょっとトリッキーかも知れませんが、フィールド (id 以外) の値をすべて Form ではなくコードでセットするためです。
views.py
    def get(self, request, *args, **kwargs):
        '''表示時のリクエストを受けるハンドラ
        '''
        # 所有権を検査してアクセス中の公演ユーザを取得する
        prod_user = test_owner_permission(self)
        
        # production を view の属性として持っておく
        # テンプレートで固定要素として表示するため
        self.production = prod_user.production
        
        return super().get(request, *args, **kwargs)
  • 公演の所有権を持つユーザだけにアクセスを許可するため、test_owner_permission() というメソッドを呼んでいます。
views.py
    def post(self, request, *args, **kwargs):
        '''保存時のリクエストを受けるハンドラ
        '''
        # 所有権を検査してアクセス中の公演ユーザを取得する
        prod_user = test_owner_permission(self)
        
        # フォームで入力された「招待する人の ID」
        invitee_value = request.POST['invitee_id']
        
        # それに一致するユーザを view の属性として持っておく
        user_model = get_user_model()
        invitees = user_model.objects.filter(username=invitee_value)
        if len(invitees) > 0:
            self.invitee = invitees[0]
        
        # prod_user, production を view の属性として持っておく
        # バリデーションと保存時に使うため
        self.prod_user = prod_user
        self.production = prod_user.production
        
        return super().post(request, *args, **kwargs)
  • 招待状を作成する画面では UI の文脈上、「招待される人」の事を「招待する人」と書いています。
  • 「招待される人」は ForeignKey 型なので、通常は Form で選択式の UI を表示するのですが、すべてのユーザを選択肢に出すのもどうかと思うので、テキストでアカウント ID (実際のフィールド名は username) を入力する形にしました。
  • 入力された ID を元にユーザのオブジェクトを見つける処理は post() メソッドでやっています。
    • User モデルを参照するには get_user_model() を使います (> ドキュメント)。
      settings.AUTH_USER_MODEL は文字列だからです。
  • 見つかった時だけ View の invitee 属性をセットするようにして、バリデーションの時にこれの存在チェックをするようにしています。
views.py
    def form_valid(self, form):
        '''バリデーションを通った時
        '''
        # POST ハンドラで招待するユーザを取得できていなかったら、追加失敗
        if not hasattr(self, 'invitee'):
            return self.form_invalid(form)
        
        # 公演ユーザのユーザ ID リスト
        prod_users = ProdUser.objects.filter(production=self.production)
        prod_user_user_ids = [prod_user.user.id for prod_user in prod_users]
        
        # 招待中のユーザの ID リスト
        invitations = Invitation.objects.filter(production=self.production)
        current_invitee_ids = [invt.invitee.id for invt in invitations]
        
        # 公演ユーザや招待中のユーザを招待することは出来ない。
        if self.invitee.id in prod_user_user_ids\
            or self.invitee.id in current_invitee_ids:
            return self.form_invalid(form)
        
        # 追加しようとするレコードの各フィールドをセット
        instance = form.save(commit=False)
        instance.production = self.production
        instance.inviter = self.prod_user.user
        instance.invitee = self.invitee
        # 期限は7日
        # デフォルトで UTC で保存されるが念の為 UTC を指定
        instance.exp_dt = datetime.now(timezone.utc) + timedelta(days=7)
        
        messages.success(self.request, str(instance.invitee) + " さんを招待しました。")
        return super().form_valid(form)
  • fields が空なので (Form がバリデートするものが無いので)、ユーザが招待状を作ろうとすると必ずこのメソッドを通ります。
  • 指定した ID のユーザの存在、そのユーザがすでに参加していないか、招待されていないかをチェックします。
  • すべてのフィールドをセットしてスーパーメソッドを呼べば、作成した招待状が保存されます。
views.py
    def get_success_url(self):
        '''追加に成功した時の遷移先を動的に与える
        '''
        prod_id = self.prod_user.production.id
        url = reverse_lazy('production:usr_list', kwargs={'prod_id': prod_id})
        return url
    
    def form_invalid(self, form):
        '''追加に失敗した時
        '''
        messages.warning(self.request, "招待できませんでした。")
        return super().form_invalid(form)
  • get_success_url() と、失敗した時の form_invalid() です。
  • 戻り先 (production:user_list) は、「招待する」ボタンを設置しようとしている View (「メンバ一覧」画面) です。

Template を作る

invitation_form.html
{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
<a href="{% url 'production:usr_list' prod_id=view.production.id %}"></a>
座組への招待
</h1>

<div>&nbsp;</div>

<form method="post">
    {% csrf_token %}
    <table>
    <tr><th>公演</th><td>{{ view.production }}</td></tr>
    <tr><th>招待する人の ID</th><td><input type="text" name="invitee_id" /></td></tr>
    </table>
    <input type="submit" value="招待">
</form>
{% endblock %}
  • invitee_id を入力させて「招待」ボタンで submit しているだけです。

作った招待状を表示する仕組み

自分が作った招待状を表示する画面は、「メンバ一覧」画面です。ここに「招待する」ボタンも設置します。
ユーザシナリオは以下のようになります。

  1. 公演ごとの「メンバ一覧」画面で「招待する」を押す。
  2. 招待状を作成するための (上記で作った) View が表示される。
  3. 招待する (される) ユーザの ID を入力してフォームを submit する。
  4. 「メンバ一覧」画面に戻って、自分が作った招待状を確認できる。

View を作る

views.py
from django.views.generic import ListView
from django.core.exceptions import PermissionDenied


class UsrList(LoginRequiredMixin, ListView):
    '''ProdUser のリストビュー
    '''
    model = ProdUser
    
    def get(self, request, *args, **kwargs):
        '''表示時のリクエストを受けるハンドラ
        '''
        # アクセス情報から公演ユーザを取得しアクセス権を検査する
        prod_user = accessing_prod_user(self, kwargs['prod_id'])
        if not prod_user:
            raise PermissionDenied
        
        # テンプレートから参照できるよう、ビューの属性にしておく
        self.prod_user = prod_user
        
        # 招待中のユーザを表示するため、ビューの属性にする
        self.invitations = Invitation.objects.filter(production=prod_user.production)
        
        return super().get(request, *args, **kwargs)
    
    # 以下略
  • 本来は「公演ユーザ」の一覧を表示する View ですが、同じ画面に招待状の一覧も表示できるよう、招待状を View の属性 (invitations) にして Template から参照できるようにしています。
  • 前の節で「自分が作った招待状」と書きましたが、厳密には「この公演に招待している招待状」を取得しています。理由は、「公演」の所有者が変わっても表示されるようにするためです。

Template を作る

招待状を表示している部分だけ抜粋します。
全体を見るには GitHub 上のコードにアクセスして下さい。

produser_list.html
{% if view.prod_user.is_owner and view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>招待中のユーザ</strong></p>
<table>
    <tr>
        <th>ユーザ ID</th>
        <th></th>
        <th></th>
        <th>招待の期限</th>
    </tr>
    {% for item in view.invitations %}
    <tr>
        <td>{{ item.invitee.username }}</td>
        <td>{{ item.invitee.last_name }}</td>
        <td>{{ item.invitee.first_name }}</td>
        {% if item.expired %}
            <td style="color:red;">{{ item.exp_dt }}</td>
        {% else %}
            <td>{{ item.exp_dt }}</td>
        {% endif %}
        <td><a href="{% url 'production:invt_delete' pk=item.id from='usr_list' %}" class="deletelink">削除</a></td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}
  • 最初の if 文で、アクセス中のユーザが公演の所有者である場合、かつ View に invitations 属性が存在する (そして空でない) 場合という条件で、招待状の一覧を表示するようにしています。
  • 招待の期限が切れている場合は赤く表示したいので、上で作った Invitation クラスの expired メソッドを使っています。
    • Template からメソッドを呼べることがポイントです。
  • 各招待状には「削除」ボタンをつけて、これから作る削除用の View にジャンプするようにします。
    • 招待された人も同じ View を使って削除できるようにするので、URL に from というパラメタをつけて戻り先を憶えるようにしています。

受け取った招待状を表示する仕組み

受け取った招待状を表示する画面は、「参加している公演」画面です。
本来は参加している公演の一覧を表示する画面ですが、作った招待状を表示する時と同じように、条件を満たした時だけ招待状のためのエリアを表示します。

View を作る

views.py
class ProdList(LoginRequiredMixin, ListView):
    '''Production のリストビュー
    
    ログインユーザの公演のみ表示するため、モデルは ProdUser
    '''
    model = ProdUser
    template_name = 'production/production_list.html'
    
    def get(self, request, *args, **kwargs):
        '''表示時のリクエストを受けるハンドラ
        '''
        # 座組への招待を表示するため、ビューの属性にする
        now = datetime.now(timezone.utc)
        self.invitations = Invitation.objects.filter(invitee=self.request.user,
            exp_dt__gt=now)
        
        return super().get(request, *args, **kwargs)
    
    # 以下略
  • 作った招待状を表示する時と同じように、招待状を View の属性にしています。
  • 作った招待状を表示する時との違いは、最初から期限が切れていない招待状だけを取り出している点です。
    • expired() メソッドは使わず、QuerySet の filter() で抽出しています。
    • ここでやっているような xx__gt=xx といった条件の書き方についてはこちらをご覧ください。

Template を作る

招待状を表示している部分だけ抜粋します。
全体を見るには GitHub 上のコードにアクセスして下さい。

production_list.html
{% if view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>招待されている座組</strong></p>
<table>
    <tr>
        <th>公演名</th>
        <th>招待主の ID</th>
        <th>招待の期限</th>
    </tr>
    {% for item in view.invitations %}
    <tr>
        <td>{{ item.production }}</td>
        <td>{{ item.inviter.username }}</td>
        {% if item.expired %}
            <td style="color:red;">{{ item.exp_dt }}</td>
        {% else %}
            <td>{{ item.exp_dt }}</td>
        {% endif %}
        <td>
            <a href="{% url 'production:prod_join' invt_id=item.id %}" class="addlink">参加</a>
            <a href="{% url 'production:invt_delete' pk=item.id from='prod_list' %}" class="deletelink">削除</a>
        </td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}
  • 受け取った招待状には「削除」ボタンに加えて「参加」ボタンも表示します。
  • 「参加」ボタンを押すと、参加の確認と処理をする View にジャンプするようにします。

招待状を削除する仕組み

作った招待状や受け取った招待状を表示する時、Template に「削除」ボタンを配置しました。
そのリンク (production:invt_delete) からアクセスする View を作ります。

View を作る

views.py
from django.views.generic.edit import DeleteView


class InvtDelete(LoginRequiredMixin, DeleteView):
    '''Invitation の削除ビュー
    '''
    model = Invitation
    template_name_suffix = '_delete'
  • template_name_suffix はデフォルトだと '_confirm_delete' となるようです。
    このあたりはお好みで。
views.py
    def get(self, request, *args, **kwargs):
        '''表示時のリクエストを受けるハンドラ
        '''
        # 公演の所有者または招待の invitee であることを検査する
        invt = self.get_object()
        prod_id = invt.production.id
        prod_user = accessing_prod_user(self, prod_id)
        
        is_owner = prod_user and prod_user.is_owner
        is_invitee = self.request.user == invt.invitee
        
        if not (is_owner or is_invitee):
            raise PermissionDenied
        
        return super().get(request, *args, **kwargs)
  • 削除しようとするオブジェクト (招待状) から「公演」の ID を取って、アクセス中のユーザがその公演の所有者か、または招待された人であることをチェックしています。
  • accessing_prod_user() メソッドが何をしているかについては、production/view_func.py のソース を見てください。
views.py
    def post(self, request, *args, **kwargs):
        '''保存時のリクエストを受けるハンドラ
        '''
        # 公演の所有者または招待の invitee であることを検査する
        invt = self.get_object()
        prod_id = invt.production.id
        prod_user = accessing_prod_user(self, prod_id)
        
        is_owner = prod_user and prod_user.is_owner
        is_invitee = self.request.user == invt.invitee
        
        if not (is_owner or is_invitee):
            raise PermissionDenied
        
        return super().post(request, *args, **kwargs)
  • POST ハンドラもやっていることは GET と同じです。
    重複が気持ち悪ければメソッド化しても良いと思います。
views.py
    def get_success_url(self):
        '''削除に成功した時の遷移先を動的に与える
        '''
        if self.kwargs['from'] == 'usr_list':
            prod_id = self.object.production.id
            url = reverse_lazy('production:usr_list', kwargs={'prod_id': prod_id})
        else:
            url = reverse_lazy('production:prod_list')
        return url
    
    def delete(self, request, *args, **kwargs):
        '''削除した時のメッセージ
        '''
        result = super().delete(request, *args, **kwargs)
        messages.success(
            self.request, str(self.object) + " を削除しました。")
        return result
  • 「削除」ボタンのリンク URL に from パラメタを付けたので、それを使って戻り先の振り分けをしています。

  • get()post() メソッド内でなくても、self.kwargsURLConf にアクセスできます。

  • URLConf の指定は、production/urls.py で以下のようにしています (> ソース)。

    path('invt_delete/<int:pk>/<str:from>/', views.InvtDelete.as_view(), name='invt_delete'),

Template を作る

「削除する」ボタンと「キャンセル」ボタンだけのシンプルなページです。

invitation_delete.html
{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
{% if view.kwargs.from == 'usr_list' %}
<a href="{% url 'production:usr_list' prod_id=object.production.id %}"></a>
{% else %}
<a href="{% url 'production:prod_list' %}"></a>
{% endif %}
座組への招待の削除
</h1>

<form method="post">{% csrf_token %}
    <p>この招待を削除しますか?</p>
    <input type="submit" value="削除する">
    <input type="button"
        {% if view.kwargs.from == 'usr_list' %}
        onclick="location.href='{% url 'production:usr_list' prod_id=object.production.id %}'"
        {% else %}
        onclick="location.href='{% url 'production:prod_list' %}'"
        {% endif %}
        value="キャンセル">
</form>

<table>
    <tr><th>公演</th><td>{{ object.production }}</td></tr>
    <tr><th>招待される人の ID</th><td>{{ object.invitee.username }}</td></tr>
    <tr><th></th><td>{{ object.invitee.last_name }}</td></tr>
    <tr><th></th><td>{{ object.invitee.first_name }}</td></tr>
    <tr><th>期限</th>
    {% if object.expired %}
        <td style="color:red;">{{ object.exp_dt }}</td>
    {% else %}
        <td>{{ object.exp_dt }}</td>
    {% endif %}
    </tr>
</table>
{% endblock %}
  • 「招待される人の ID」などは、from が 'prod_list' の場合 (招待されている人が開いた場合) は隠すなどしても良いかも知れません。
  • Template 内で URLConf にアクセスするには view.kwargs.from などとします ('from' がパラメタ名の場合)。

招待状から公演に参加する仕組み

View を作る

「参加」を選択した時の処理は、"公演ユーザ" を新規で作成する処理なので、以下のような CreateView を作ります。
ただし Template の名前は分かりやすく 'production_join.html' としました。

views.py
from django.http import Http404


class ProdJoin(LoginRequiredMixin, CreateView):
    '''公演に参加するビュー
    '''
    model = ProdUser
    fields = ('production', 'user')
    template_name = 'production/production_join.html'
    success_url = reverse_lazy('production:prod_list')
  • fields に指定した productionuser は、ユーザに入力してもらう訳ではないのですが、hidden にして Template に埋め込むことで Form から受け取れるようにしています。
  • もちろん「招待状を作成する仕組み」でやったように、コードでセットすることも出来ます。
views.py
    def get(self, request, *args, **kwargs):
        '''表示時のリクエストを受けるハンドラ
        '''
        # 招待されているか検査し、参加できる公演を取得
        self.production = self.production_to_join()
        
        return super().get(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        '''保存時のリクエストを受けるハンドラ
        '''
        # 招待されているか検査し、参加できる公演を取得
        self.production = self.production_to_join()
        
        return super().post(request, *args, **kwargs)
  • GET および POST ハンドラでは、URLConf の情報を元に、どの公演に参加するのかを属性として取っておきます。
  • 参加先の公演 (Production) のオブジェクトを取得する production_to_join() メソッドは、以下のようにしてあります。
views.py
    def production_to_join(self):
        '''招待されているか検査し、参加できる公演を返す
        '''
        # 招待がなければ 404 エラーを投げる
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            raise Http404
        invt = invts[0]
        
        # 招待が期限切れなら 404 エラーを投げる
        now = datetime.now(timezone.utc)
        if now > invt.exp_dt:
            raise Http404
        
        # アクセス中のユーザが invitee でなければ PermissionDenied
        user = self.request.user
        if user != invt.invitee:
            raise PermissionDenied
        
        return invt.production
  • 「受け取った招待状を表示する仕組み」のところで、「参加」ボタンのリンクに invt_id というパラメタを埋め込んであるので、これを使って招待状 (Invitation) のオブジェクトを取得しています。

  • ここ (公演に参加するビュー) へ来るためのパスは、urls.py で以下のようにしています (> ソース)。

    path('prod_join/<int:invt_id>/', views.ProdJoin.as_view(), name='prod_join'),

  • 期限切れについてはちゃんとメッセージを表示した方が良いですが、ここでは単に 404 を返しています。

views.py
    def form_valid(self, form):
        '''バリデーションを通った時
        '''
        # 招待を検査
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            return self.form_invalid(form)
        invt = invts[0]
        
        # 保存するレコードを取得する
        new_prod_user = form.save(commit=False)
        
        # 正しい公演がセットされているか
        if new_prod_user.production != invt.production:
            return self.form_invalid(form)
        
        # 正しいユーザがセットされているか
        if new_prod_user.user != invt.invitee:
            return self.form_invalid(form)
        
        # 招待を削除
        invt.delete()
        
        messages.success(self.request, str(invt.production) + " に参加しました。")
        return super().form_valid(form)
    
    def form_invalid(self, form):
        '''参加に失敗した時
        '''
        messages.warning(self.request, "参加できませんでした。")
        return super().form_invalid(form)
  • Form から正しいフィールド値 (この場合は productionuser) を受け取った後に View の form_valid() メソッドが呼ばれるので、改めて「招待状」の内容と突き合わせます。
  • このようなチェックが必要な理由は、フィールド値が一旦 Invitation オブジェクトを離れて HTML に晒されているからです。
  • チェックを通過したらスーパーメソッドを呼べば "公演ユーザ" が作成されますが、その前に「招待状」を削除しておきます。

Template を作る

「参加」ボタンと「あとで」ボタンだけのシンプルなページです。

production_join.html
{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
<a href="{% url 'production:prod_list' %}"></a>
公演に参加
</h1>

<div>&nbsp;</div>

<p>
{{ view.production }} に招待されています。参加しますか?
</p>

<form method="post">
    {% csrf_token %}
    <input type="hidden" name="production" value="{{ view.production.id }}">
    <input type="hidden" name="user" value="{{ view.request.user.id }}">
    <input type="submit" value="参加">
    <input type="button"
        onclick="location.href='{% url 'production:prod_list' %}'"
        value="あとで">
</form>
{% endblock %}
  • POST ハンドラ以降の処理に渡したいフィールド値を、hidden にして直に書き込んでいます。
  • ForeignKey 型のフィールド値を直に書く場合は、オブジェクトの id を使います。

まとめ

「公演」という "プロジェクト的単位" (グループ) にユーザを招待したり、招待を受けたユーザがそこに参加したりする機能の実装を紹介しました。
使ったテクニックについて以下にまとめます。

  • モデルのフィールドとして User 型を指定する時は settings.AUTH_USER_MODEL を使い、View のメソッドで User モデルを参照するには get_user_model() を使いました。
    • 実はモデルのフィールドとして指定する時も get_user_model() は使えるそうです。
  • オブジェクトを作成する View でフィールド値をすべて (id 以外) コードでセットする場合でも、fields を空にすれば CreateView が使えました。
  • Template で選択式の UI を使いたくない場合は、オブジェクトを見つけるためのフィールド (username など) の値を入力するようにして、コード (post() メソッド) で見つけるようにしました。
  • Python ではオブジェクトの属性を好きな時に追加できるので、有効な値が見つかった時だけ View の属性として持っておいて、あとで属性の有無を調べて (hasattr() メソッドを使って) バリデートする事ができました。
  • 表示したいデータを View の属性にしておけば、Tamplate 側で属性の有無 (あっても空かどうか) を調べて、必要に応じて表示する事ができました。
  • Template からは、View のメソッドを呼んで返り値を使う事もできました。
  • 同じ View に複数のページからジャンプして戻り先を動的に決めたい場合、その View への URL に from 等といったパラメタを持たせれば可能でした。
  • get()post() メソッドで受け取った kwargs は、あとからでも self.kwargs として参照可能でした。
    • View の属性なので Template からも view.kwargs.パラメタ名 として参照可能でした。
  • GET の時点で作ろうとしているオブジェクトのフィールド値が分かっている場合は、Template に hidden で書いてしまうという方法もありました。
  • ForeignKey 型のフィールド値を (hidden 等として) Template に直に書く場合は、そのオブジェクトの id を使えば、Form で正しく処理されました。
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?