python3.doc_61
R e i - D r e a m
for Laravel
TOP > Python3 > Django(プロフィールフォーム)
Guest
login

最終投稿日:2022年6月18日

Django(プロフィールフォーム)
設定関連
※ DBは、PostgreSQL を使用する
※ prof アプリを想定
● settings.py
INSTALLED_APPS に prof アプリの登録
抜粋)
    INSTALLED_APPS = [
        ~ 省略 ~
        'prof.apps.ProfConfig',
    ]
● urls.py( プロジェクト側 )
from django.contrib import admin
from django.conf.urls import include, url
from django.views.generic import RedirectView
urlpatterns = [
    url(r'^$', RedirectView.as_view(url='prof/')),
    url(r'^prof/', include('prof.urls', namespace='prof')),
    url('admin/' , admin.site.urls),
]
● urls.py( アプリ側 )
from django.conf.urls import url
from django.views.generic import RedirectView
from .views import IndexView, DataSaveView
app_name = 'prof'
urlpatterns = [
    url(r'^$', RedirectView.as_view(url='index/')),
    url(r'^index/$', IndexView.as_view(), name='index'),
    url(r'^data_save/$', DataSaveView.as_view(), name='data_save'),
]
モデル関連
● models.py
from django.db import models
class MPref(models.Model):
    class Meta:
        db_table = 'm_pref'
    tdk = models.CharField(max_length = 20)
    tdk_k = models.CharField(max_length = 20)
    tks = models.CharField(max_length = 1, default =' ')
class MSyumi(models.Model):
    class Meta:
        db_table = 'm_syumi'
    sname = models.CharField(max_length = 50)
    sname_k = models.CharField(max_length = 50)
    tks = models.CharField(max_length = 1, default =' ')
class Profile(models.Model):
    class Meta:
        db_table = 'profile'
    mail = models.CharField(primary_key = True, max_length = 50)
    fname = models.CharField(max_length = 10)
    lname = models.CharField(max_length = 10)
    fname_k = models.CharField(max_length = 10)
    lname_k = models.CharField(max_length = 10)
    sex = models.CharField(max_length = 1)
    birth = models.CharField(max_length = 8)
    zipn = models.CharField(max_length = 8)
    m_pref = models.ForeignKey(MPref, to_field = 'id', on_delete=models.PROTECT)
    pref1 = models.CharField(max_length = 100)
    pref2 = models.CharField(max_length = 100, null = True, default = '')
    tel = models.CharField(max_length = 13)
    syumi = models.TextField()
    bikou = models.TextField(null = True, default = '')
    insert_date = models.DateTimeField(null = True)
    update_date = models.DateTimeField(null = True)
    tks = models.CharField(max_length = 1, default =' ')
● サンプルテーブルのSQLy
[都道府県TBL]
    create table M_pref(
        -- id
         id serial PRIMARY KEY NOT NULL
        -- 都道府県名
        ,tdk VARCHAR(20) NOT NULL
        -- 都道府県名カナ
        ,tdk_k VARCHAR(20) NOT NULL
        -- 取り消しフラグ
        ,tks char(1) default ' '
    );
        insert into M_pref (tdk, tdk_k) values('東京都', 'トウキョウト');
        insert into M_pref (tdk, tdk_k) values('埼玉県', 'サイタマケン');
        insert into M_pref (tdk, tdk_k) values('千葉県', 'チバケン');
[趣味TBL]
    create table M_syumi(
        -- id
         id serial PRIMARY KEY NOT NULL
        -- 趣味名
        ,sname VARCHAR(50) NOT NULL
        -- 趣味名カナ
        ,sname_k VARCHAR(50) NOT NULL
        -- 取り消しフラグ
        ,tks char(1) default ' '
    );
        insert into M_syumi (sname, sname_k) values('釣り', 'ツリ');
        insert into M_syumi (sname, sname_k) values('ドライブ', 'ドライブ');
        insert into M_syumi (sname, sname_k) values('ギャンブル', 'ギャンブル');
        insert into M_syumi (sname, sname_k) values('貯金', 'チョキン');
        insert into M_syumi (sname, sname_k) values('旅行', 'リョコウ');
        insert into M_syumi (sname, sname_k) values('お酒', 'オサケ');
[プロフィールTBL]
    create table profile(
        -- メールアドレス
         mail VARCHAR(50) PRIMARY KEY NOT NULL
        -- 名
        ,fname VARCHAR(10) NOT NULL
        -- 姓
        ,lname VARCHAR(10) NOT NULL
        -- 名カナ
        ,fname_k VARCHAR(10) NOT NULL
        -- 姓カナ
        ,lname_k VARCHAR(10) NOT NULL
        -- 性別
        ,sex char(1) NOT NULL
        -- 誕生日
        ,birth VARCHAR(8) NOT NULL
        -- 郵便番号
        ,zipn VARCHAR(8) NOT NULL
        -- 都道府県
        ,m_pref_id integer NOT NULL
            references M_pref(id)
        -- 住所1
        ,pref1 VARCHAR(100) NOT NULL
        -- マンション名等
        ,pref2 VARCHAR(100)
        -- 電話番号
        ,tel VARCHAR(13)
        -- 趣味
        ,syumi text NOT NULL
        -- 備考
        ,bikou text
        -- 登録日時
        ,insert_date timestamp
        -- 更新日時
        ,update_date timestamp
        -- 取り消しフラグ
        ,tks char(1) default ' '
    );
フォーム関連
● forms.py
from django import forms
from .models import Profile, MPref, MSyumi
from mycommon import *
import re
from datetime import date, time, datetime
class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        exclude = ('tks', 'insert_date', 'update_date', 'm_pref')
    #初期設定(全てのカラムに適用する)
    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        for field in self.fields.values():
            #html5の必須を解除
            field.required = False
    #都道府県取得
    __PREF_option = [(s.id,s.tdk) for s in MPref.objects.all().exclude(tks='1').order_by('tdk_k')]
    __PREF_option.insert(0,(0,'--'))
    #趣味取得
    __SYUMI_option = [(s.id,s.sname) for s in MSyumi.objects.all().exclude(tks='1').order_by('sname_k')]
    mail = forms.CharField(
        label = 'E-Mail',
        max_length = 50,
        widget = forms.TextInput(
            attrs = {'size':'30'}
        ),
    )
    fname = forms.CharField(
        label ='名',
        max_length = 10,
        widget = forms.TextInput(
            attrs = {'size':'15'}
        ),
    )
    lname = forms.CharField(
        label = '姓',
        max_length = 10,
        widget = forms.TextInput(
            attrs = {'size':'15'}
        ),
    )
    fname_k = forms.CharField(
        label = 'メイ',
        max_length = 10,
        widget = forms.TextInput(
            attrs = {'size':'15'}
        ),
    )
    lname_k = forms.CharField(
        label = 'セイ',
        max_length = 10,
        widget = forms.TextInput(
         attrs = {'size':'15'}
        ),
    )
    sex = forms.ChoiceField(
        label = '性別',
        widget = forms.RadioSelect(),
        choices=(('1','男'),('2','女')),
    )
    #モデルにはない項目(年月日を3つのプルダウンにわける)
    birth_y = forms.ChoiceField(
        label = '生年月日',
        widget = forms.Select(),
        choices = yearNowToXXX(100,'---'),
    )
    birth_m = forms.ChoiceField(
        widget = forms.Select(),
        choices = intToXX(1, 12, '--')
    )
    birth_d = forms.ChoiceField(
        widget = forms.Select(),
        choices = intToXX(1, 31, '--')
    )
    zipn = forms.CharField(
        label = '郵便番号',
        max_length = 8,
        widget = forms.TextInput(
            attrs = {'size':'10'}
        ),
    )
    m_pref_id = forms.ChoiceField(
        label = '都道府県',
        widget = forms.Select(),
        choices = __PREF_option,
    )
    pref1 = forms.CharField(
        label = '住所1',
        max_length = 100,
        widget = forms.TextInput(
            attrs = {'size':'40'}
        ),
    )
    pref2 = forms.CharField(
        label = '住所2',
        max_length = 100,
        widget = forms.TextInput(
            attrs = {'size':'40'}
        ),
    )
    tel = forms.CharField(
        label = '電話番号',
        max_length = 13,
        widget = forms.TextInput(
            attrs = {'size':'15'}
        ),
    )
    syumi = forms.MultipleChoiceField(
        label = '趣味',
        widget = forms.CheckboxSelectMultiple(),
        choices = __SYUMI_option,
    )
    bikou = forms.CharField(
        label = '備考',
        widget = forms.Textarea(attrs={'cols': 50, 'rows': 4}),
        required = False,
    )
    """
        ------------ ここからバリデーション ------------
    """
    def clean(self):
        """ メールチェック """
        s = self.cleaned_data['mail']
        if s == '':
            self.add_error('mail', 'メールアドレスは必須項目です')
        else:
            m = re.search(r'^[^0-9][a-zA-Z0-9_-]+([.][a-zA-Z0-9_-]+)*[@][a-zA-Z0-9_-]+([.][a-zA-Z0-9_-]+)*[.][a-zA-Z]{2,4}$', s)
            if m == None:
                self.add_error('mail', 'メールアドレスの型式ではありません!')
        """ 名前 """
        name1 = self.cleaned_data['fname']
        name2 = self.cleaned_data['lname']
        if name1 =='' or name2 == '':
            self.add_error('lname', '名前は必須項目です')
        """ 名前カナ """
        name1 = self.cleaned_data['fname_k']
        name2 = self.cleaned_data['lname_k']
        if name1 =='' or name2 == '':
            self.add_error('lname_k', '名前(カナ)は必須項目です')
        else:
            m1 = re.search(r'^[ァ-ヶー]+$', name1)
            m2 = re.search(r'^[ァ-ヶー]+$', name2)
            if m1 == None or m2 == None:
                self.add_error('lname_k', '名前(カナ)は全角カタカナで入力してください')
        """ 性別 """
        s = self.cleaned_data['sex']
        if s == '':
            self.add_error('sex', '性別を選択してください')
        """ 生年月日 """
        y = self.cleaned_data['birth_y']
        m = self.cleaned_data['birth_m']
        d = self.cleaned_data['birth_d']
        if y == '0' or m == '0' or d == '0':
            self.add_error('birth_y', '生年月日を選択してください')
        else:
            try:
                datetime(int(y), int(m), int(d))
            except:
                self.add_error('birth_y', '選択された生年月日は存在しません')
        """ 郵便番号 """
        s = self.cleaned_data['zipn']
        if s == '':
            self.add_error('zipn', '郵便番号を記入してください')
        else:
            m = re.search(r'^\d{3}[-]\d{4}$', s)
            if m == None:
                self.add_error('zipn', '郵便番号の形式ではありません')
        """ 住所 """
        v1 = self.cleaned_data['m_pref_id']
        v2 = self.cleaned_data['pref1']
        if v1 == '0' or v2 == '':
            self.add_error('pref1', '住所を選択、記入してください')
        """ 電話 """
        s = self.cleaned_data['tel']
        if s == '':
            self.add_error('tel', '電話番号は必須です')
        else:
            m = re.search(r'^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$', s)
            if m == None:
                self.add_error('tel', '電話番号の形式ではありません')
        """ 趣味 """
        s = self.cleaned_data['syumi']
        c = len(s)
        if c < 2:
            self.add_error('syumi', '趣味は最低2つ以上選択してください')
        """ 備考 """
        s = self.cleaned_data['bikou']
        i = len(s) - (s.count('\n') * 2)
        if i > 50:
            self.add_error('bikou', '備考は50文字以内で記入してください')
return self.cleaned_data
コントローラ関連
● views.py
from django.shortcuts import render
from django.http.response import HttpResponse
from django.views.generic import View
from . import forms
from .models import MPref, MSyumi, Profile
from datetime import date, time, datetime, timedelta, timezone
from mycommon import *
class IndexView(View):
    def get(self,request):
        form = forms.ProfileForm()
        d = {'form':form}
        return render(request, 'prof/index.html', d)
    def post(self,request):
        form = forms.ProfileForm(request.POST)
        if form.is_valid():
            #エラーなし
            ken = MPref.objects.get(id = request.POST.get(key='m_pref_id'))
            res = MSyumi.objects.all().filter(id__in=request.POST.getlist('syumi'))
            syumi = ''
            for s in res:
                syumi += s.sname + ' '
            d = {'form':form,'ken':ken.tdk,'syumi':syumi}
            return render(request, 'prof/confirm.html', d)
        else:
            #エラー
            d = {'form':form}
            return render(request, 'prof/index.html', d)
class DataSaveView(View):
    def post(self,request):
        form = forms.ProfileForm(request.POST)
        #戻る押下の場合
        if request.POST.get('form_back'):
            d = {'form':form}
            return render(request, 'prof/index.html', d)
        #保存押下の場合
        else:
            #一応バリデーションチェックをする
            if form.is_valid():
                #仮のインスタンス作成
                instance = form.save(commit=False)
                #生年月日作成
                birth = request.POST['birth_y'] + request.POST['birth_m'].zfill(2) + request.POST['birth_d'].zfill(2)
                #誕生日カラムに代入
                instance.birth = birth
                #都道府県カラムに代入
                instance.m_pref_id = request.POST['m_pref_id']
                #主キーが存在するか検索
                pk = Profile.objects.all().filter(mail = instance.mail)
                #タイムゾーンの設定(ワーニング回避)
                myJST = timezone(timedelta(hours = 9), 'Japan')
                if len(pk) > 0:
                    #主キーで検索して結果が0件以外の場合は更新処理とする
                    uf = my_exclude_fields(Profile,True, ['insert_date'])
                    instance.update_date = datetime.now(tz = myJST)
                    instance.save(update_fields = uf)
                else:
                    instance.insert_date = datetime.now(tz = myJST)
                    instance.save()
                return render(request, 'prof/thanks.html')
            else:
                #異常系(本来はスローしてエラーにする)
                print('異常系エラー')
独自ライブラリ関連
● my_lib/mycommon.py (共通処理ファイル)
from datetime import date, time, datetime
"""
現在年からマイナス第1引数分の年を以下の型式で返却する
[(2018,2018),(2017,2017)...]
また、第2引数に文字列があれば初期値のタプルを先頭に追加する
※年のプルダウン用として使用する
"""
def yearNowToXXX(i = 0, s = ''):
    nen = int(datetime.now().strftime('%Y'))
    l = [(y,y) for y in range(nen, nen - i, -1)]
    if s != '':
        l.insert(0, (0, s))
    return l
"""
メソッドyearNowToXXXの数字バージョン
"""
def intToXX(st=0, ed=0, s=''):
    l = [(i,i) for i in range(st, ed + 1)]
    if s != '':
        l.insert(0, (0, s))
    return l
"""
任意のモデルインスタンスを受け取り、除外カラム名を除いたカラム名の配列を返却する
 引数1:モデルインスタンス
 引数2:主キーを除外の対象とするか(True:する)
 引数3:除外カラム名リスト
"""
def my_exclude_fields(MyModel,pkey, l):
    res = []
    for f in MyModel._meta.get_fields():
        if pkey == True:
            #主キーも除隊対象とする
            if f.primary_key != True and f.name not in l:
                res.append(f.name)
        else:
            #主キーは除外対象としない
            if f.name not in l:
                res.append(f.name)
    return res
HTML関連
● layout/base.html
<html>
    <head>
        <title>{{my_title}}</title>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
    </head>
    <body>
        {% block my_body %}{% endblock %}
    </body>
</html>
● prof/index.html
{% extends 'layout/base.html' %}
{% block my_body %}
いんでっくす<br>
<form method='POST' action="{% url 'prof:index' %}">
    <table border='0' width='600'>
        <tr>
            <th width='200' style='vertical-align:bottom;'>{{form.mail.label}}</th>
            <td width='400'>
                <div style='color:red;'>
                    {% if form.mail.errors.as_text != '' %}
                        {{form.mail.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.mail}}
            </td>
        </tr>
        <tr>
            <th style='vertical-align:bottom;'>お名前</th>
            <td>
                <div style='color:red;'>
                    {% if form.lname.errors.as_text != '' %}
                        {{form.lname.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.lname.label}} {{form.lname}} {{form.fname.label}} {{form.fname}}
            </td>
        </tr>
        <tr>
            <th style='vertical-align:bottom;'>オナマエ</th>
            <td>
                <div style='color:red;'>
                    {% if form.lname_k.errors.as_text != '' %}
                        {{form.lname_k.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.lname_k.label}} {{form.lname_k}} {{form.fname_k.label}} {{form.fname_k}}
            </td>
        </tr>
        <tr>
            <th style='vertical-align:bottom;'>{{form.sex.label}}</th>
            <td>
                <div style='color:red;'>
                    {% if form.sex.errors.as_text != '' %}
                        {{form.sex.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.sex.0}} {{form.sex.1}}
            </td>
        </tr>
        <tr>
            <th style='vertical-align:bottom;'>{{form.birth_y.label}}</th>
            <td>
                <div style='color:red;'>
                    {% if form.birth_y.errors.as_text != '' %}
                        {{form.birth_y.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.birth_y}}年 {{form.birth_m}}月 {{form.birth_d}}日
            </td>
        </tr>
        <tr>
            <th>ご住所</th>
            <td>
                <div style='color:red;'>
                    {% if form.zipn.errors.as_text != '' %}
                        {{form.zipn.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                〒 {{form.zipn}}
                <div style='color:red;'>
                    {% if form.pref1.errors.as_text != '' %}
                        {{form.pref1.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                 {{form.m_pref_id}}<br> {{form.pref1}}<br> {{form.pref2}}
            </td>
        </tr>
        <tr>
            <th style='vertical-align:bottom;'>{{form.tel.label}}</th>
            <td>
                <div style='color:red;'>
                    {% if form.tel.errors.as_text != '' %}
                        {{form.tel.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.tel}}
            </td>
        </tr>
        <tr>
            <th>{{form.syumi.label}}</th>
            <td>
                <div style='color:red;'>
                    {% if form.syumi.errors.as_text != '' %}
                        {{form.syumi.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {% for field in form.syumi %}
                    <nobr>{{field}}</nobr>
                {% endfor %}
            </td>
        </tr>
        <tr>
            <th>{{form.bikou.label}}</th>
            <td>
                <div style='color:red;'>
                    {% if form.bikou.errors.as_text != '' %}
                        {{form.bikou.errors.as_text|cut:'* '}}
                    {% endif %}
                </div>
                {{form.bikou}}
            </td>
        </tr>
        <tr>
            <td colspan='2' align='center' height='100'>
                {% csrf_token %}
                <input type='submit' value=' 登 録 '>
            </td>
        </tr>
    </table>
</form>
{% endblock %}
● prof/confirm.html
{% extends 'layout/base.html' %}
{% block my_body %}
こんふぁ~む
<form method='POST' action="{% url 'prof:data_save' %}">
    <table width='600'>
        <tr>
            <th width='200'>{{form.mail.label}}</th>
            <td width='400'>{{form.mail.value}}</td>
        </tr>
        <tr>
            <th>お名前</th>
            <td>{{form.lname.value}} {{form.fname.value}}<br>{{form.lname_k.value}} {{form.fname_k.value}}</td>
        </tr>
        <tr>
            <th>{{form.sex.label}}</th>
            <td>
                {% if form.sex.value == '1' %}
                    男性
                {% else %}
                    女性
                {% endif %}
            </td>
        </tr>
        <tr>
            <th>{{form.birth_y.label}}</th>
            <td>{{form.birth_y.value}} 年 {{form.birth_m.value}} 月 {{form.birth_d.value}} 日</td>
        </tr>
        <tr>
            <th>ご住所</th>
            <td>
                {{ken}} {{form.pref1.value}} {{form.pref2.value}}<br>
            </td>
        </tr>
        <tr>
            <th>{{form.tel.label}}</th>
            <td>{{form.tel.value}}</td>
        </tr>
        <tr>
            <th>{{form.syumi.label}}</th>
            <td>{{syumi}}</td>
        </tr>
        <tr>
            <th>{{form.bikou.label}}</th>
            <td>{{form.bikou.value|linebreaks}}</td>
        </tr>
        <tr>
            <td colspan='2'>
                <input type='submit' name='form_back' value='戻る'>
                <input type='submit' name='form_save' value='登録'>
            </td>
        </tr>
    </table>
    {% for field in form.visible_fields %}
        {{ field.as_hidden }}
    {% endfor %}
    {% csrf_token %}
</form>
{% endblock %}
● prof/thanks.html
{% extends 'layout/base.html' %}
{% block my_body %}
    あんがとん。。。
{% endblock %}
ログインしてコメントを残そう!!


きっぷる