naritoブログ

【お知らせ】
・コメントで質問等をしたが返事が返ってこない場合、私はそれを見落としています。
その場合は再度コメントをするかメールをしてください(toritoritorina@gmail.com)。

・近いうちに新しいブログが作成されます。わーお!

Djangoで、Userモデルのカスタマイズ(OneToOneで、別モデルと紐づける)

約742日前 2016年9月11日19:38
プログラミング関連
Django Python
今回は、Userモデルをカスタマイズします。
DjangoのUserモデルは便利ですが、物足りなく感じることもあります。
例えば、電話番号等や住所等の情報が欲しいときです。これらはUserモデルにはありません。
今回は、シンプルにUserモデルと紐づくProfileモデルを作成し、Userモデルにない情報を紐づけます。

まず、トップ画面!


そして、登録画面。


するとログインされ、トップ画面に戻ります。ログイン中は、各種情報が表示されます。


models.py
プロフィール情報のモデルです。見たまんまですね。
from django.contrib.auth.models import User
from django.db import models


GENDER_CHOICES = (
    ("女性", "女性"),
    ("男性", "男性"),
)


class Profile(models.Model):

    name = models.CharField("ハンドルネーム", max_length=255)
    phone = models.CharField("電話番号", max_length=255, blank=True)
    gendar = models.CharField("性別", max_length=2, choices=GENDER_CHOICES, blank=True)
    user = models.OneToOneField(User)

    def __str__(self):
        return self.name


ここでOneToOneにしています。これにより、profile.userやuser.profileとすることで、相互に呼び出すことができます。
user = models.OneToOneField(User)


forms.py
User用フォームと、Profile用フォームです。
UserCreationFormは、Userモデルを使ったユーザ登録の際に強い威力を発揮します。
Profileモデルは、ただのModelFormです。fieldsにuserが含まれていないことに、注意です。
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import Profile


class UserCreateForm(UserCreationForm):
    pass


class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = (
            "name", "gendar", "phone"
        )



views.py
from django.contrib.auth import login
from django.shortcuts import render, redirect
from .forms import ProfileForm, UserCreateForm


def index(request):
    """トップ画面!"""

    return render(request, 'testapp/index.html')


def regist_user(request):
    """ユーザ作成ページへ"""

    user_form = UserCreateForm(request.POST or None)
    profile_form = ProfileForm(request.POST or None)
    if request.method == "POST" and user_form.is_valid() and profile_form.is_valid():

        # Userモデルの処理。ログインできるようis_activeをTrueにし保存
        user = user_form.save(commit=False)
        user.is_active = True
        user.save()

        # Profileモデルの処理。↑のUserモデルと紐づけましょう。
        profile = profile_form.save(commit=False)
        profile.user = user
        profile.save()

        login(
            request, user, backend="django.contrib.auth.backends.ModelBackend")

        return redirect("testapp:index")

    context = {
        "user_form": user_form,
        "profile_form": profile_form,
    }
    return render(request, 'testapp/regist.html', context)



templateには、複数のフォームを渡すことができますし、それを受け取ることも可能です。
    user_form = UserCreateForm(request.POST or None)
    profile_form = ProfileForm(request.POST or None)

...
...
    context = {
        "user_form": user_form,
        "profile_form": profile_form,
    }
    return render(request, 'testapp/regist.html', context)



Userモデルの作成です。
このcommit=Falseですが、データベースには保存せず、保存する前のモデルオブジェクトを返します。
なので、user.is_active等の属性へのアクセスと保存が可能になります。
        # Userモデルの処理。ログインできるようis_activeをTrueにし保存
        user = user_form.save(commit=False)
        user.is_active = True
        user.save()


Profileモデルの作成です。
Userモデルと紐づけています。
        # Profileモデルの処理。↑のUserモデルと紐づけましょう。
        profile = profile_form.save(commit=False)
        profile.user = user
        profile.save()


そして、ログインさせています。
authenticate()を使わずにログインさせる場合はbackendの指定が必要です。
いつのまにか、login関数にbackendという引数が追加されていたため、使用しました。指定しているのは、Djangoのデフォルトのバックエンドです。
        login(
            request, user, backend="django.contrib.auth.backends.ModelBackend")



templates/testapp/base.html
{% load staticfiles %}
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
    {% block content %} {% endblock %}
</body>

</html>


index.html
templateでは、無条件で{{ user }}が使えます。
なので、それに紐づくprofileも使えます。
{% extends "testapp/base.html" %}
{% block content %}

<h1>
{% if user.is_authenticated %}
ユーザ名:{{ user.username }}<br>
ハンドルネーム:{{ user.profile.name }}<br>
性別:{{ user.profile.gendar }}<br>
電話:{{ user.profile.phone }}<br>
{% else %}
ようこそ、ゲスト
{% endif %}
</h1>
<hr>
<a href="{% url 'testapp:regist_user' %}">ユーザ作成</a>
{% endblock %}



regist.html
2種類のフォームを、<form>タグの中に書きます。
{% extends "testapp/base.html" %}
{% block content %}
<form action="" method="POST">
{{ user_form.as_p }}
<hr>
{{ profile_form.as_p }}
{% csrf_token %}
<input type="submit" value="送信">
</form>
{% endblock %}




こうなると、/adminの管理画面でもUserとProfileを一緒に登録したくなるものです。
これはadmin.pyを以下のように変更するだけでできます。
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from .models import Profile


class ProfileInline(admin.StackedInline):
    model = Profile
    max_num = 1
    can_delete = False


class UserAdmin(AuthUserAdmin):
    inlines = [ProfileInline]


admin.site.unregister(User)
admin.site.register(User, UserAdmin)

名無し 約165日前 2018年4月12日7:11 返信する
いつも参考にさせていただいています。Udemyの方も買わせていただきました。
ビジネス向けWebアプリを開発しようと、悪戦苦闘しています。

この、OneToOneを実装しているのですが、
UnboundLocalError at /signup/
local variable 'user_form' referenced before assignment

Request Method: GET
Request URL: http://13.250.223.224:8000/signup/
Django Version: 2.0.3
Exception Type: UnboundLocalError
Exception Value:
local variable 'user_form' referenced before assignment
Exception Location: /home/ec2-user/environment/myapp2/inout/views.py in signup, line 55
Python Executable: /home/ec2-user/.pyenv/versions/anaconda3-4.0.0/bin/python3
Python Version: 3.5.1
Python Path:
['/home/ec2-user/environment/myapp2',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python35.zip',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python3.5',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python3.5/plat-linux',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python3.5/lib-dynload',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python3.5/site-packages/Sphinx-1.3.5-py3.5.egg',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python3.5/site-packages/setuptools-20.3-py3.5.egg',
'/home/ec2-user/.pyenv/versions/anaconda3-4.0.0/lib/python3.5/site-packages']
Server time: 木, 12 4月 2018 07:05:24 +0900

というエラーになります。
環境はこちらです。
Python 3.5.1
Django 2.0.3

対処の勘所だけで構いませんので、教えていただけると幸いです。
なりと 約164日前 2018年4月12日14:08
local variable 'user_form' referenced before assignment
というエラーメッセージがあるので、「user_form」変数が定義されていない、もしくは、そこからは見えない場所でuser_form変数を定義していると思われます。
生徒 約156日前 2018年4月20日19:09 返信する
お世話になっております。いつも楽しく拝見しています。
Userモデルの拡張について質問します。
現在、簡単なwebアプリを制作中で、Userの拡張モデルを使っています。
拡張先のmodelをデータベースブラウザーで確認すると、user_idが表示されますが、そのmodelをadmin上で確認すると、usernameが表示されます。
この状況において、データベース上で確認するのにuser_idだけだと分かりづらいので、user_idと同時に、拡張先のmodelにUser model のusernameを加えたいです。
どのような方法で加えるのが望ましいでしょうか。
なりと 約155日前 2018年4月21日18:07
添付ファイルダウンロード(models.py)
データベースブラウザーというのは専用のツールのことでしょうか。
データベース内におけるテーブルのレベルは、Django側で自動的に作成されます。ForeignKey等のリレーションを貼った場合は、リレーション先のid等プライマリキーを保持する仕組みです。
拡張先のモデルにuser_usernameといったフィールドを作り、モデルのsave後にそのuser_usernameフィールドの値に対してデータをセットする、というアプローチはできます。
添付しているmodels.pyのような感じです。
生徒 約154日前 2018年4月22日20:41 返信する
お世話になっております。
先生から頂いたコードを使って、自分のアプリの拡張先のmodelで試してみました。
結果、拡張先modelにusernameのフィールドはできたのですが、その中に実際のusernameが入りません。
データベースを見ると、auth.userモデルの中にはusernameが入っているのですが・・・。
何か自分が勘違いをしている可能性もあるので、もうちょっと調べてみます。
なりと 約154日前 2018年4月22日20:58
saveメソッドを上書きしているので、一度保存する必要がありますので、それを試してみてください。
名無し 約123日前 2018年5月23日15:58 返信する
こんにちは、毎日このサイトで勉強させていただいています。
私が今作成しているウェブアプリは、ユーザーがコメントを投稿することができるのですが、そのコメントがだれによって投稿されたのか判別するためのフィールドを、Commentモデルに持たせたいと思っています。
そこで
class Comment(models.Model):
........
........
username = models.OneToOneField(User)

と定義してみたのですが、どのタイミングでそのユーザーと紐づけたら良いのかが分かりません。
ご教授いただけないでしょうか?
なりと 約123日前 2018年5月23日17:22
form_validメソッドにて、
comment = form.save(commit=False)
comment.username = request.user
comment.save()
return リダイレクト...
のようにすると良いと思います。

form.save(commit=False)は、DBに保存せずにモデルインスタンスを返します。
モデルインスタンスがかえるので、.username = ...のようにして編集できるようになります。
request.userで、(ログイン済みならば)コメントを送信したユーザーのモデルインスタンスが取得できます。
ユーザーを設定したら、saveでDBに保存します。

models.OneToOneFieldですが、これは1対1で紐づくために、そのユーザーは他のコメントが行えなくなりますので注意してください。それが厳しい場合は、ForeighKeyを指定してください。
名無し 約121日前 2018年5月25日16:07 返信する
ありがとうございました。
ForeignKeyを使って思っていた通りの機能を実装することができました。
名無し 約88日前 2018年6月27日17:04 返信する
こんにちは。
いつも拝見させていただいております。

OneToOneFieldで紐づけたデータをDeteilViewで表示させるにはどのようにしたら良いのでしょうか。
宜しくお願いします。
なりと 約88日前 2018年6月27日18:39
OneToOneで紐付いている場合、その紐付いた先にあるオブジェクトには単純に.フィールド名 でアクセスできます。
Profileというモデル内で
user = models.OneToOneField...としているならば、profile.user としてアクセスでき、テンプレートでも同様にアクセス可能です。逆に、user.profile のようにしてアクセスすることもできます。
名無しの人 約42日前 2018年8月12日11:44 返信する
ログインできるかの判定は.is_staffではなく.is_activeだと思いますが、どうなんでしょうか?
なりと 約42日前 2018年8月12日13:23
ありがとうございます、おっしゃるとおりログインはis_activeです。修正しました。