daily-dev.net

React, firebase, 機械学習など

Djangoでユーザーモデルに対してOneToOneで関連したProfileモデルに向けてフォームを作る

sponsored

https://qiita-image-store.s3.amazonaws.com/0/48136/e3f38a61-bab7-8ad1-173b-a76b82ae8b78.png

最近RailsプロジェクトをDjangoに書き換えました。

理由は、

  • Ruby熟練者のコードが読みにくいことがある
  • Djangoの方が詰まった時、フレームワーク自体の元コードの確認がしやすく感じた
  • Railsよりもファイル構成がわかりやすく感じた(各モデルごとに、DB構成、ビューとURLを定義するイメージ)
  • Pythonの計算用ライブラリが利用したかった
  • 純粋にPythonを実務で使ってみたかった

からです。

今回はDjangoの組み込みのUserモデルを拡張したProfileモデルを作ってフォームを作成したので、メモとして残したいと思います。

Profile はOne-To-One の関係を用いてUserモデルと紐づけています。

デコレータ(後述)にDjango組み込み関数のpost_saveを引数として渡して、Userモデルが作成されるさいの、save() メソッ ドの処理の最後に、Profileモデルも作成されるようにしています。

models.py

from django.db.models.signals import post_save
from django.dispatch import receiver

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete = models.CASCADE, primary_key=True) # モデルが削除されても保護したい場合は、`on_delete=models.PROTECT`とします。

    bio = models.TextField(max_length=500, blank=True)
    image = models.ImageField(blank=True)

    def __str__(self):
        return self.user.username

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

forms.py

モデルからフォームクラスを生成するためのヘルパクラス ModelFormを用います。

DjangoにおけるModelForm定義はこちらから確認できます。

django/models.py at 0ec0e5029ca5c499eac93787c3073c3e716b5951 · django/django · GitHub

モデルから生成されるフォームクラスは、モデルの各フィールドに対応したフォー ムフィールドを持ちます。

class Meta の内部で、fieldsとしてフィールド名からなるリストを定義すると、指定されたフィールドだけを含むフォームを生成され、表示の順番が指定した通りに尊重されます(exclude 属性の指定で、表示しないフィールドを指定することもできます)。

from django.contrib.auth.models import User
from django import forms

class ProfileForm(forms.ModelForm):
    username = forms.CharField(help_text='必須項目です。')
    image = forms.ImageField(required=False)

    class Meta:
        model = User
        fields = ('username', 'email', 'image' ) 

templates/accout_edit.html

<form method="post" accept-charset="utf-8" enctype="multipart/form-data">

            {% csrf_token %}
            {% for field in form %}

                <div class="form-field">
                <label>{{ field.label }}</label>
                {{ field }}

                {% if field.help_text %}
                    <p>
                        <small style="color: grey">{{ field.help_text }}</small>
                    </p>
                {% endif %}

                {% if field.errors %}
                    {{ field.errors }}
                {% endif %}

            {% endfor %}

            {% if user.profile.image %}
                <img src="{{ request.user.profile.image_thumbnail.url }}"/>
            {% endif %}


            <div class="form-action">
                <input class="btn-shadow btn-shadow-primary" type="submit" value="Save"/>
            </div>
        </form>

views.py

def account_edit(request , account_id=None):
    """ プロフィールの編集"""

    user = get_object_or_404(User, pk=account_id)

    if request.method == 'POST':

        form = ProfileForm(request.POST, request.FILES, instance=user)

        if form.is_valid():
            user = form.save()
            user.refresh_from_db()
            image = request.FILES.get('image')
            user.profile.image = image

            user.save()
            return redirect('home')
    else:
        form = ProfileForm(
            initial={
                               'username': request.user.username,
                               'image': request.user.profile.image,
                               'email': request.user.email,
                           }
        )
    return render(request, 'account_edit.html', {'form': form})

urls.py

        url(r'^(?P<account_id>\d+)/edit$', views.account_edit, name='account_edit'),

デコレータについて

参考記事がたくさんあるため簡潔に書くと、

そのあとに書かれた定義(def)をラップする関数の呼び出しという理解をしています。

ある関数の前後処理をするとき便利です。

たとえば、

@wrap
def test():
    pass

なら

def test():
    pass
test = wrap(test)

という意味になります。

ネストすることもできる

@wrap1
@wrap2
def test():
   // ...

し、引数を渡すことも可能です。

Djangoフォームについて

便利なメソッドをメモしておきます。

動的にモデルから選択肢を選ばせたい時:ModelChoiceField

Railsのcollection_selectのような機能。

    product = forms.ModelChoiceField(models.Product.objects, label='商品')

<form>
    <div>
        <label>商品</label>
        <select>
            <option value="">---------</option>
            <option value="1">みかん</option>
            <option value="2">りんご</option>
            <option value="3">バナナ</option>
        </select>
    </div>
    <div>
        <label>個数</label>
        <input type="number">
    </div>
</form>

のようなビューが生成されます。

DjangoのFormを活用する (1) ModelChoiceField:株式会社サブスレッド

参考

モデルからフォームを生成する — Django 1.4 documentation

www17.atpages.jp

シグナル — Django 1.4 documentation

simpleisbetterthancomplex.com