djangoをdockerコンテナで利用(7) – djangoでリストボックス(choices)使いながらデータベースに保存

◆◆◆

前回までで、データベースにある業務データを読み書きして、.pyファイルの分割と整理をした。

(1.dockerコンテナ準備onSSL)(2.pip利用とhelloWorldとdb接続)(3.グラフ表示練習)では環境を作り、処理を作っていくための習作みたいなものまで作れた。

その後(4.ログイン画面)もでき、(5.crud)もできるようになってmariaDBの読み書き削除と登録ができるようになった。

ここまでで肥大化しそうになってきたから、(6.分割)をやってみた。

データベースに入っている文字や数字が、きちっと画面で登録・更新できるのかそれぞれ試した。

日付でjavascript使うとこと、decimalのリストボックス化は悩んだな。

数字

普通はinteger使う。パーセンテージとかレートみたいなのを扱いたいときは、floatじゃなくdecimalを使う。
floatは誤差が出そうやから使わない。

学術に使うときはfloatのほうがいいのかもしれないけど、お金を扱う場合に誤差が出たら困る。

個数とか通し番号はmodelsの中で「IntegerField」で定義。
重量とかだったら「DecimalField」で定義。

    kazu = models.IntegerField(verbose_name='数量',db_column='Kazu', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.kazu

    weight = models.DecimalField(verbose_name='重量-kg',max_digits=8, decimal_places=4, blank=True, null=True)
    def __str__(self):
        return self.weight

自分はdjango起点で考えているわけじゃなく、mariadbに業務データがありき、それをdjangoにinspectさせてmodelsを自動生成させたので、型について深く考えて選んだわけじゃない。

むしろmariadbでどう入れるか考えたものをdjangoに解釈してもらった。
データを先に考えて作る。

データベースの型との対照表を書いておられる方がいてわかりやすかった。
mariadbはmysqlのところ参照させてもらった。
作者さんありがとう。

Django モデルフィールド:データベースフィールド 型対応表 - Qiita
#この記事についてDjangoでデータベースマイグレーションを実行した時、モデルフィールドがデータベース上のどの型に対応付けられるのかを調査環境:Django2.0 Postgres9.6 My…

数字の入力項目は、入力フィールドに自動で上下ボタンがつく。

django-type

なるほど、マウスだけでカチカチ入力できるってわけか。
「100」って入れたいとき、100回クリックせなあかんのかな。
便利かどうか疑問。

django-type

decimalで小数点以下を4桁持たせてると、この矢印ボタンは0.0001単位で値が上下してくれる。

当たり前っちゃ当たり前やけど、こんなん使い物にならへん。

電卓みたいなの表示させて値入力できたらいいのに。
jraで馬券買うときにそんなのが表示されてたっけ。

javascriptで使う部品探したらできるかもしれないけど、今はもうこのままでいい。javascript苦手やし。

文字

普通に扱う。バリデーションで「全角文字だけ」とかできるんかもしれんけど、今はやらん。

    tehai = models.CharField(verbose_name='手配',db_column='Tehai', max_length=1000, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.tehai

日付

mariadbでdatetimeで作ってある。
create文はこんな感じ。

`Toroku` datetime DEFAULT NULL COMMENT '資産登録日',

日付関連は、実際の登録日とか更新日とかもそのうちつける。
資産関連のテーブルじゃなくて帳簿やったら入金日とか、支払日みたいなのも入れる。

datetimeじゃなくてdateとtimeもあるけど、統一したかったからdatetime。
だから日付しか使わなくてもdatetimeにして時間は「00:00:00」にして扱う。

inspectした結果の日時の入る項目はこう生成されてた。

toroku = models.DateTimeField(db_column='Toroku', blank=True, null=True)  # Field name made lowercase.

djangoでは入力フィールドが「0001-01-01 00:00:00」って形式になる。

日付の取り扱い解説書いておられる方がおられて参考にさせてもらった。
作者さんありがとう。

Pythonで現在時刻・日付・日時を取得 | note.nkmk.me
Pythonで現在時刻を取得するには標準ライブラリのtimeモジュールかdatetimeモジュールを使う。UNIX時間(エポック秒)で取得したい場合はtimeモジュール、日時(日付と時刻)を表すdatetimeオブジェクトなどで取得したい場...
from datetime import datetime     ## テーブルの日付フィールドを扱うため

    :(中略)

    dt_year = datetime.now().year
    dt_mon = datetime.now().month
    dt_day = datetime.now().day
    dt_defdate = str(dt_year) + '-' + str(dt_mon) + '-' + str(dt_day) + ' ' + '00:00:00'

    :(中略)

    toroku = models.DateTimeField(verbose_name='登録日',db_column='Toroku', blank=True, null=True, 
        default=datetime.strptime(dt_defdate,'%Y-%m-%d %H:%M:%S'))  # Field name made lowercase.
    def __str__(self):
        return self.toroku.strftime('%c')

mariadbにはタイムゾーンがないらしい。
だから保管日付は、sqlで参照するとJSTで入ったものが表示される。
テストしても「今」の時間が入ってくれるからわかりやすい。

djangoはタイムゾーンがあるらしい。
グローバルなサイトを扱うとき、UTCベースでデータを取り扱いし、表示はJSTにしてくれる。

これを自分のデータベースに照らし合わせると、表示が9時間ズレる!!!
なんちゅーことしてくれるねん。

昔見たIISのログの日付みたいなことになってくれる。
これはイカン。
事実が発生した日時がUTCで保存されると、直観的にデータが読めなくなる。

表示のときだけ9時間ズラすことも考えたけど、実際の格納時間は自分にとってJSTで扱う方針は変えられない。

ということで、非推奨と言われるが自分にとってわかりやすさを優先したいため、settings.pyのUSE_TZ記述をfalseに変えた。

# Trueが推奨項目かもしれないが、日時をmariaDBで扱うときにutcになってしまう。
# mariaDBはdatetime型にタイムゾーンを持たない。
# 日時はJSTで維持している。
# 格納されているままを扱いたいのでTrueにはしない。
USE_TZ = False

これで時間表示が自分にとって正しくなる。

テンプレートで手動生成する

updateviewで更新画面なんかを表示させるとき、djangoでは少ないコードで一発表示ができる。

    <table border=4 align=center>
    {{ form.as_table }}
    </table>

「あー、便利、ありがたい、ありがたい」って思ったのは一瞬。

modelsで書いた項目をテンプレートで表示させられるようになった後、「このまま表示させるわけにはいかない」ってなった。

表示させ方がそのままじゃ気に入らない。というか、使えない。

  • 必要のない項目は表示させたくない
  • できるだけリストボックスで選択入力できるようにしたい
  • 日付はカレンダー入力させたい
  • URLの入ったテキストボックスの横に「開く」ってボタンをつけて別タブで開くようにしたい

このへんのために、必要な項目だけをmodelsに書いて、テンプレートでは表示項目の画面IDによって、表示展開を手動展開する。

手動展開っていっても、自動展開されたhtmlをマネして好きなように書き直すだけ。

更新画面で文字や数字、リストボックスを扱う

扱うmodelsはGvisZaiko.py。

資産管理するために存在するmariadbのテーブルからinspectdbさせて作ったものに肉付けしてある。

choicesは「値、表示」を列挙して作った。

choicesDBって名前にある箇所は、テーブルのマスタデータから「値、名前」で抜き出してchoicesとして生成させてる。

from django.db import models
from django.utils import timezone
from pymysql import NULL

from datetime import datetime   ## テーブルの日付フィールドを扱うため

## マスターテーブル使うため、別ファイル「gvisWebApp/models/GvisMaster.py」から取り込む
from gvisWebApp.models.GvisMaster import GvisMstKshisanshokyaku
from gvisWebApp.models.GvisMaster import GvisMstKshisanshokyakurit

class GvisZaiko(models.Model):

    serialshubetsu_choices = (
            ('GVIS','GVIS-(製品登録資産したもの)'),
            ('GVI','GVI-私財投入資産'),
            ('GVH','GVH-ハードウェア資産'),
            ('GVS','GVS-ソフトウェア資産'),
            ('GVO','GVO-オープンソース資産'),
        )

    ## 参考URL https://note.nkmk.me/python-datetime-now-today/
    dt_year = datetime.now().year
    dt_mon = datetime.now().month
    dt_day = datetime.now().day
    dt_defdate = str(dt_year) + '-' + str(dt_mon) + '-' + str(dt_day) + ' ' + '00:00:00'

    alwayson_choices = (
        (0,'0-Off'),
        (1,'1-On'),
    )

    shokyakuhou_choicesDB=[(i.houname, i.houname) for i in GvisMstKshisanshokyaku.objects.all().order_by('hyojiorder')]
    '''
    shokyakuhou_choices = (
        ('定額法','定額法'),
        ('定率法','定率法'),
    )
    '''

    koteishisan_choices = (
        (0,'0-しない'),
        (1,'1-する'),
    )

    ## DBから取得して文字列書式設定するときの参考
    ## 参考URL  https://www.javadrive.jp/python/string/index23.html

    ## 国税庁の償却率
    ## 参考URL  https://www.nta.go.jp/law/joho-zeikaishaku/shotoku/shinkoku/070412/pdf/3.pdf

    shokyakurit_choicesDB=[(i.shokyakurit, "%02d" % i.taiyonen + "年-" + str(i.shokyakurit) ) for i in GvisMstKshisanshokyakurit.objects.all().order_by('hyojiorder')]

    serialseq1_choices = (
            (2014,'2014'),
            (2015,'2015'),
            (2016,'2016'),
            (2017,'2017'),
            (2018,'2018'),
            (2019,'2019'),
            (2020,'2020'),
            (2021,'2021'),
            (2022,'2022'),
            (2023,'2023'),
            (2024,'2024'),
            (2025,'2025'),
            (2026,'2026'),
            (2027,'2027'),
            (2028,'2028'),
            (2029,'2029'),
            (2030,'2030'),
            (2031,'2031'),
            (2032,'2032'),
            (2033,'2033'),
            (2034,'2034'),
            (2035,'2035'),
            (2036,'2036'),
            (2037,'2037'),
            (2038,'2038'),
            (2039,'2039'),
            (2040,'2040'),
        )

    serialshubetsu = models.CharField(verbose_name='資産種別',db_column='SerialShubetsu', max_length=4,
        choices=serialshubetsu_choices , default='GVH')  # Field name made lowercase.
    def __str__(self):
        return self.serialshubetsu

    serialseq1 = models.IntegerField(verbose_name='seq1',db_column='SerialSeq1', 
        choices=serialseq1_choices, default=dt_year )  # Field name made lowercase.
    def __str__(self):
        return self.serialseq1

    serialseq2 = models.IntegerField(verbose_name='seq2',db_column='SerialSeq2',default=1 )  # Field name made lowercase.
    def __str__(self):
        return self.serialseq2

    gvis_no = models.CharField(verbose_name='GVIS_No',db_column='GVIS_No', max_length=13, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.gvis_no

    toroku = models.DateTimeField(verbose_name='登録日',db_column='Toroku', blank=True, null=True, 
        default=datetime.strptime(dt_defdate,'%Y-%m-%d %H:%M:%S'))  # Field name made lowercase.
    def __str__(self):
        return self.toroku.strftime('%c')

    massho = models.DateTimeField(verbose_name='抹消日',db_column='Massho', blank=True, null=True, 
        default=timezone.datetime.min)  # Field name made lowercase.
    def __str__(self):
        return self.massho.strftime('%c')

    category1 = models.CharField(verbose_name='カテゴリ1',db_column='Category1', max_length=100, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.category1

    category2 = models.CharField(verbose_name='カテゴリ2',db_column='Category2', max_length=100, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.category2

    tehai = models.CharField(verbose_name='手配',db_column='Tehai', max_length=1000, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.tehai

    makerurl = models.CharField(verbose_name='製品URL',db_column='MakerURL', max_length=5000, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.makerurl

    shanaiurl = models.CharField(verbose_name='社内URL',db_column='ShanaiURL', max_length=5000, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.shanaiurl

    kazu = models.IntegerField(verbose_name='数量',db_column='Kazu', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.kazu

    yoteitnk = models.IntegerField(verbose_name='予定単価',db_column='YoteiTnk', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.yoteitnk

    shutokukng = models.IntegerField(verbose_name='取得金額',db_column='ShutokuKng', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.shutokukng

    koteishisan = models.IntegerField(verbose_name='固定資産台帳対象',db_column='KoteiShisan', 
        choices=koteishisan_choices ,blank=True, null=True, default=0)  # Field name made lowercase.
    def __str__(self):
        return self.koteishisan

    shokyakuhou = models.CharField(verbose_name='償却方法',db_column='ShokyakuHou', max_length=100, 
        choices=shokyakuhou_choicesDB ,blank=True, null=True, default='定額法')  # Field name made lowercase.
    def __str__(self):
        return self.shokyakuhou

    taiyonen = models.IntegerField(verbose_name='耐用年数',db_column='TaiyoNen', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.taiyonen

    shokyakurit = models.DecimalField(verbose_name='償却率',db_column='ShokyakuRit', max_digits=8, decimal_places=4, 
        choices=shokyakurit_choicesDB, blank=True, null=True, default=0)  # Field name made lowercase.
    def __str__(self):
        return self.shokyakurit

    jigyowariai = models.DecimalField(verbose_name='事業使用割合-%',db_column='JigyoWariai', max_digits=8, decimal_places=4, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.jigyowariai

    weight = models.DecimalField(verbose_name='重量-kg',max_digits=8, decimal_places=4, blank=True, null=True)
    def __str__(self):
        return self.weight

    electricity = models.DecimalField(verbose_name='電力-w',max_digits=8, decimal_places=4, blank=True, null=True)
    def __str__(self):
        return self.electricity

    alwayson = models.IntegerField(verbose_name='常時ON(0-Off,1-On)',blank=True, null=True,
        choices=alwayson_choices,default=0)
    def __str__(self):
        return self.alwayson

    class Meta:
        managed = False
        db_table = 'GVIS_zaiko'

マスターテーブルの定義はこんな感じ。「hyojiorder」を好きなように入れておき、とりこむときにソートしたらリストボックスの中での表示位置を好きなようにできる。

from django.db import models
from pymysql import NULL

from datetime import datetime   ## テーブルの日付フィールドを扱うため

class GvisMstKshisanshokyaku(models.Model):
    houname = models.CharField(db_column='HouName', max_length=20, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.houname

    hyojiorder = models.IntegerField(db_column='hyojiOrder', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.hyojiorder

    class Meta:
        managed = False
        db_table = 'GVIS_mst_kshisanShokyaku'

class GvisMstKshisanshokyakurit(models.Model):
    taiyonen = models.IntegerField(db_column='TaiyoNen')  # Field name made lowercase.
    def __str__(self):
        return self.taiyonen

    shokyakurit = models.DecimalField(db_column='ShokyakuRit', max_digits=8, decimal_places=4, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.shokyakurit

    hyojiorder = models.IntegerField(db_column='hyojiOrder', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.hyojiorder

    class Meta:
        managed = False
        db_table = 'GVIS_mst_kshisanShokyakuRit'

今回表示させた結果はこうなった。わかりにくいけど、最初の3つの項目と「登録日」はupdateviewのためのテンプレート内で値変更できないようにしてる。

django-type

次はテンプレート。抜き出すとこんな感じ。
最初に自動生成させたときのhtmlをテキストファイルに残しておいて、特定のidが来たら、そのフィールドごとに展開したい内容を書いていった。
表示のために参考にさせてもらったページもあるけど、基本は自動生成内容をベースに改変して書いた。

「★」のついたあたりを作るとき、それぞれ参考にさせてもらったURLがある。コメントとして入れてある。
それぞれの作者さんありがとう。

<h1>資産更新</h1>
<form method="post" onsubmit="return kakuninPopup()">
    {% csrf_token %}

    {# 「{{ form.as_table }}」と書く代わりに自分で書いて変更させたくない項目はlabelに設定する #}
    {# 参考URL #}
    {# https://blog.narito.ninja/detail/98/ #}
    {# https://django.kurodigi.com/form_html/ #}
    {# https://arakan-pgm-ai.hatenablog.com/entry/2020/01/22/000000 #}
    {# https://tech.pjin.jp/blog/2017/03/15/python-primer-23-howto-django/ #}

    {{ form.non_field_errors }} ★1 バリデーション用のエラーメッセージ
    <table border=4 align=center>
    <tr>
        <th> 項目 </th>
        <th> 値 </th>
    </tr>
    {% for field in form %}
        <tr>
        {# 資産種別はリストボックス入力させてるけどinput typeがtextで変更はできないようにしとく #}
        {% if field.id_for_label == "id_serialshubetsu" %}
            <th><label for={{field.id_for_label}}>{{field.label}}:{{ field.errors }}</label></th>
            <td>
            <input type="text" name="{{field.html_name}}"  ★2 inputでは"text"とか"number"と書き、更新できない項目を展開
            value="{{ field.value }}" 
            id="{{field.id_for_label}}" readonly>
            </td>

        {# Seq1とSeq2はinput typeがnumberで変更はできないようにしとく #}
        {# 自動展開されたときのページソースはこんな内容だった#}
        {# <tr><th><label for="id_serialseq2">Seq2:</label></th><td><input type="number" name="serialseq2" value="3" required id="id_serialseq2"></td></tr> #}
        {% elif field.id_for_label == "id_serialseq1" or field.id_for_label == "id_serialseq2" %}
            <th><label for={{field.id_for_label}}>{{field.label}}:{{ field.errors }}</label>{{ field.errors }}</th>
            <td>
            <input type="number" name="{{field.html_name}}" 
            value="{{ field.value }}" id="{{field.id_for_label}}" readonly>
            </td>

        {# 登録日はdatetime型でそのままでは「xx年xx月xx日」が設定されるので、保存時の必要書式に加工。input typeがtextで変更はできないようにしとく #}
        {# 自動展開されたときのページソースはこんな内容だった#}
        {# <tr><th><label for="id_toroku">登録日:</label></th><td><input type="text" name="toroku" value="2021年11月25日 00:00:00" id="id_toroku"></td></tr> #}
        {% elif field.id_for_label == "id_toroku" %}
            <th><label for={{field.id_for_label}}>{{field.label}}:</label>{{ field.errors }}</th>
            <td>
            <input type="text" name="{{field.html_name}}" 
            value="{{ field.value.year }}-{{ field.value.month }}-{{ field.value.day }} 00:00:00" ★3 日付の書式指定しとく
            id="{{field.id_for_label}}" readonly>
            </td>

        {# URLの入ったフィールドは「開く」ボタンをつけとく #}
        {% elif field.id_for_label == "id_makerurl" %}
            <td>
            {{ field.label_tag }}
            {{ field.errors }}
            </td>
            <td>
            {{ field }}
            <input class="darkmode-ignore" type="button" name="MakerURL_btn" ★4 URL参照させる
            onclick="openURL(this.form.makerurl.value)"  value="開く">
            </td>

:(中略)

        {# 表示を自動でさせるときはこれだけ #}
        {% else %}
            <td>{{ field.label_tag }}{{ field.errors }}</td> ★5 そのまま改変させずに表示
            <td>{{ field }}</td>
        {% endif %} 
        </tr>
    {% endfor %}
    </table>

    <br>
    <input class="darkmode-ignore" type="submit" value=" 保 存 ">
</form>

<br>
<input class="darkmode-ignore" type="button" name="close_btn" onclick="history.back();" value=" 戻 る " > ★6 「戻る」ボタンの設置

<script type="text/javascript">
  {# formのonclickに渡すとキャンセルボタンを押してもtrueが戻ってしまうので注意 #}
  function kakuninPopup() {
    if(window.confirm('この内容で保存しますか?')) {
      return true;
    } else {
      return false;
    }
  }

  function openURL(p) {
    window.open(p);
  }

</script>

★1 バリデーション用のエラーメッセージ

たとえばデータ全体に関わるようなバリデーションメッセージの表示で{{ form.non_field_errors }}って使うらしい。
1つ1つの項目のバリデーションには、{{ field.errors }}って書いておく。

<td>{{ field.label_tag }}{{ field.errors }}</td>

意地悪して、更新画面開いているときにmariadbのdockerコンテナ停止したら「保存に失敗しました」とか表示されるのかも。
今度やってみるかな。

★2 inputでは”text”とか”number”と書き、更新できない項目を展開

テーブルの項目は{% if field.id_for_label == "id_serialshubetsu" %}みたいにして頭にid_をつけてひっかけるんだと。
手動展開では、field.xxxを使ってname/id/valueの右辺に書く。

htmlのinputにtypeを書くところがあって、全部”text”って書けばいいと思ってたら、そうじゃない。
試しに、数字が入る項目に”text”って書くとバリデーションにひっかかった。
項目にあわせた属性を書く必要があるらしい。

    <input type="text" name="{{field.html_name}}"  ★2 inputでは"text"とか"number"と書く
    value="{{ field.value }}" 
    id="{{field.id_for_label}}" readonly>

最後にreadonlyってつけておくと、画面上で値を変更できなくなる。
これで自動発番した項目とか、データベースでキーにしてる項目とかは更新できなくなってくれる。

★3 日付の書式指定しとく

mariadbではdatetime使ってるので、その書式にあわせるようにする。

settigs.pyに書いてるからか、そのままにしておくと「xxxx年xx月xx日」ってなって、登録するときにバリデーションでひっかかる。

なんやねん、めんどくさいわー。
表示を勝手に変えるんやったら、登録するときも読み替えろやー。

    <input type="text" name="{{field.html_name}}" 
    value="{{ field.value.year }}-{{ field.value.month }}-{{ field.value.day }} 00:00:00" ★3 日付の書式指定しとく
    id="{{field.id_for_label}}" readonly>

ひっかからんようにハイフン区切りで年月日を入れておき、時間記録は必要ないなら「00:00:00」って入れておく。
更新画面では登録日を改変させたくないのでreadonlyをつけてる。

「抹消日」って項目は、javascriptでjqueryのdatepickerを使った。いろんなデザインがあるし、パラメータもたくさん。
datepickerはjqueryのサイトで試せるし、「view source」ってサイトにあるからその場でソースも見れてめっちゃ便利。作った人すげー。

画面上ではid_masshoって識別名になっているので、functionの中でひっかけるように書いている。
定義は画面上の項目1つ1つについてひっかける必要があるから、デザインもそれぞれ変えられる。

    <!-- for datepicker -->
    {# 参考URL #}
    {# https://ore-nikki.tokyo/jquery/datepicker/ #}
    {# https://blog.narito.ninja/detail/83/ #}
    {# https://www.site-convert.com/archives/2190#outline__2 #}
    {# https://jqueryui.com/themeroller/ #}

    <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/trontastic/jquery-ui.css">  {# テーマを設定 #}
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <script src="https://rawgit.com/jquery/jquery-ui/master/ui/i18n/datepicker-ja.js"></script>       {# 日本語化 #}

    <script>
        $(function () {
            let dateFormat = 'yy-mm-dd';
            $('#id_massho').datepicker({
                dateFormat: 'yy-mm-dd 00:00:00',showButtonPanel: true,changeYear: true, changeMonth: true,firstDay: 1,showAnim: 'slideDown'
            });
        });
    </script>
    <!-- end for datepicker -->

表示させるとこんな感じ。おお、かっけー。

django-type

もちろんiphoneでも表示できてる。

django-type

普段はダークモード使うし、黒い画面に黒いカレンダーになるので、緑の蛍光色っぽいのを選んで月曜日始まりにした。

★4 URL参照させる

djangoにはurlfieldってのがあるらしい。

って、フィールドの型の一覧を書いてくれてた人がいた。
作者さんありがとう。

Django 4.0 のモデルフィールドリファレンスまとめ - Qiita
Django 4.0 のモデルフィールドリファレンスを、まとめました。※分かりやすくするために、文章を編集したり、例文を追加している箇所がありますモデルフィールドの型とオプションの関係は、以下の…

inspectdbさせたとき、url格納するフィールドはそのままCharFieldって解釈してくれてた。まぁええか。

    makerurl = models.CharField(verbose_name='製品URL',db_column='MakerURL', max_length=5000, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.makerurl

このURLの入ったフィールドはリンク切れてないか確認するために、別窓で開いてくれるボタンをつけた。

        {% elif field.id_for_label == "id_makerurl" %}
            <td>
            {{ field.label_tag }}
            {{ field.errors }}
            </td>
            <td>
            {{ field }}
            <input class="darkmode-ignore" type="button" name="MakerURL_btn" ★4 URL参照させる
            onclick="openURL(this.form.makerurl.value)"  value="開く">
            </td>

見た目はこんな感じ。

django-type

★5 そのまま改変させずに表示

テンプレートの中で{{ field.xxx }}って書き方すると展開してくれる。
elseで一番最初に書いた。

特に表示をいじらずdjangoに任せる箇所はこの書き方。

    <td>{{ field.label_tag }}{{ field.errors }}</td> ★5 そのまま改変させずに表示
    <td>{{ field }}</td>

★6 「戻る」ボタンの設置

最初のclass="darkmode-ignore"は自分用のcssでダークモード使うときのおまじない。もちろんcss定義しとく必要がある。

<input class="darkmode-ignore" type="button" name="close_btn" onclick="history.back();" value=" 戻 る " > ★6 「戻る」ボタンの設置

「戻る」ボタンを置いておき、javascript使って前の画面に戻らせる。
具体的にはlistviewに戻る。

ボタンとかの挙動を作るときは、javascriptをテンプレートに書いておいて呼び出す。
最初のほうでform method="post" onsubmit="return kakuninPopup()"って書いた箇所から呼ばれる確認ポップアップと、後でURLを「開く」ボタンも置くのでついでに「openURL」も近くに入れておいた。

リストボックス化

リストボックスはdjangoではmodelsの中でタプルを書いておいて、choicesっていう定義に書いて使う。

    shokyakuhou_choices = (
        ('定額法','定額法'),
        ('定率法','定率法'),
    )

    shokyakuhou = models.CharField(verbose_name='償却方法',db_column='ShokyakuHou', max_length=100, 
        choices=shokyakuhou_choices ,blank=True, null=True, default='定額法')  # Field name made lowercase.
    def __str__(self):
        return self.shokyakuhou

値も表示も同じにするだけなら、めっちゃ簡単。ただ、同じにするなんてほぼないはず。

django-type

データベースに書き込むときの値と、リストに表示させたい値なんかをいじりたいとき、たとえば自分はdeimalのカラムに入れたいときがあった。

データベースに値をこんな感じで入れておく。

django-type

これをマスタデータみたいな感じで使えるようにmodelsを書く。
idはdjangoには必要かもしれんけど、表示には不要なのでmodelsには書かない。

class GvisMstKshisanshokyakurit(models.Model):
    taiyonen = models.IntegerField(db_column='TaiyoNen')  # Field name made lowercase.
    def __str__(self):
        return self.taiyonen

    shokyakurit = models.DecimalField(db_column='ShokyakuRit', max_digits=8, decimal_places=4, blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.shokyakurit

    hyojiorder = models.IntegerField(db_column='hyojiOrder', blank=True, null=True)  # Field name made lowercase.
    def __str__(self):
        return self.hyojiorder

    class Meta:
        managed = False
        db_table = 'GVIS_mst_kshisanShokyakuRit'

さらに選択できるように書いておく。c言語のsprintfみたいな書き方できるやん。

    ## DBから取得して文字列書式設定するときの参考
    ## 参考URL  https://www.javadrive.jp/python/string/index23.html

    ## 国税庁の償却率
    ## 参考URL  https://www.nta.go.jp/law/joho-zeikaishaku/shotoku/shinkoku/070412/pdf/3.pdf

    shokyakurit_choicesDB=[(i.shokyakurit, "%02d" % i.taiyonen + "年-" + str(i.shokyakurit) ) for i in GvisMstKshisanshokyakurit.objects.all().order_by('hyojiorder')]

    :(中略)

    shokyakurit = models.DecimalField(verbose_name='償却率',db_column='ShokyakuRit', max_digits=8, decimal_places=4, 
        choices=shokyakurit_choicesDB, blank=True, null=True, default=0)  # Field name made lowercase.
    def __str__(self):
        return self.shokyakurit

するとこんな感じ。

django-type

このdecimalの箇所、最初は手動で書こうとしたら、”0.5000″ってhtmlのoption valueに設定して表示はできるけど、バリデーションで文字列として扱うらしくてデータ保存ができんかった。

実際にテーブルに入れる値と、表示させる値をマスタデータに入れてるの思い出したから、sprintfみたいなやり方探して書いたら”0.5000″って自動展開させても同じように入ってるのに、なぜかバリデーションでエラーにならんかった。

なんでかわからん。ま、リストボックス作れたし今はよしとしましょ。

djangoというかフロントエンドへの思い

確かにdjangoはlistview/createview/updateview/deleteviewを使って楽に書くことができる。
それはそれで素晴らしい。

ただし、それだけでは実際の利用には耐えない。
いつも使いやすい画面ができるわけじゃないから、工夫が必要。

phpで作った機能を移植するため、今は資産管理だけ作ってて、次は帳簿管理も作っていくし、この画面は自分でしか使わないけど、「こう動いてほしい」っていう機能リクエストが実現できるのかっていうのは作り手の努力でなんとかしなきゃいけない。

こういう努力が簡単にできれば、フレームワークやシステムとしてとても優れていると思う。

大昔に自分がまだプログラムを書き始めたとき、N60basicで簡単な演算ができたときは「おー」って驚いた。

めっちゃ合成音やったけど、pc6001mk2が言葉を発したときはビビったし、デモのプログラムでビバルディの「春」が鳴ったときも驚いた。

お金をもらってプログラムを書く喜びを感じ始めたとき、実務でvisual basic2使ってフォーム画面を初めて作ったときも、マウスでボタン押してモーダル画面開けたときは嬉しかった。

やり方忘れたけど、あのときはbtrieveのデータベースも読み込んだっけ。

来年でエンジニア30年目。
djangoのおかげで昔味わった喜びを思い出せた。

この「おー」を味わいたいために、まだまだ書き方探すんやろなぁ。
やめられまへん。

タイトルとURLをコピーしました