【Python】初級者がDjangoのWebアプリケーションを勉強中にトラブルシューティングしたこと


#1.この記事について
##概要

DjangoのWebアプリケーション開発を勉強しているときに引っ掛かった部分を忘備録的にメモしました。

##内容
こちらのチュートリアルに従って勉強しました。
[https://eiry.bitbucket.io/](https://eiry.bitbucket.io/)


##※※
内容が「初級者が～」ということのなので、ある程度Djangoやその他Webアプリになれた方にとっては響かない記事かもしれません。

#2.使用したツール・環境
環境
・Windows10
・Python3.8.0
・Webフレームワーク：django2.2.5
・DB：sqlite3.30.1

ツール
・Pycharm

<hr>

#3.トラブルシューティング
##(A)__HTMLのinputタグとtextareaタグ内の文字色が白色になってしまう
###(1)現象
####設定内容
forms.pyに次のようにフォームを指定


```py:forms.py
from django import forms

from .models import Posting


class PostingForm(forms.ModelForm):
    class Meta:
        model = Posting
        fields = ('name', 'message')
        widgets = {
            'name': forms.TextInput(attrs={'size': 40, 'required': True}),
            'message': forms.Textarea(attrs={'cols': 80, 'rows': 20, 'required': False})
        }

```

views.pyで次のようにindex.htmlにフォーム情報を渡す。


```py:viewsの一部

def index(request):
    """表示・投稿を処理する"""
    # ModelFormもFormもインスタンスを作るタイミングでの使い方は同じ
    form = PostingForm(request.POST or None)
    if request.method == 'POST':
        # NoneならばFalse
        if form.is_valid():
            # save()メソッドを呼ぶだけでModelを使ってDBに登録される。
            form.save()
            # メッセージフレームワークを使い、処理が成功したことをユーザーに通知する
            messages.success(request, '投稿を受付しました。')
            return redirect('guestboard:index')
        else:
            # メッセージフレームワークを使い、処理が失敗したことをユーザーに通知する
            messages.error(request, '入力内容に誤りがあります。')
    page = _get_page(
        Posting.objects.order_by('-id'),  # 投稿を新しい順に並び替えて取得する
        request.GET.get('page'),  # GETクエリからページ番号を取得する
        count=3
    )
    contexts = {
        'form': form,
        'page': page,
    }
    return render(request, 'guestboard/index.html', contexts)
```

index.htmlの内容はこのよう。
{{ field.label_tag }}と{{ field }}でそれぞれフォーム上のデータを展開する。


```html:indexの一部
    <form action="{% url 'guestboard:index' %}" method="post">
        <div class="row">
            {% for field in form %}
                <div class="form-group">
                    {% if field.errors %}
                        <div class="col-sm-10">
                            <span class="col-sm-10">{{ field.errors }}</span>
                        </div>
                    {% endif %}
                    <div class="col-sm-10 ">
                        <label class="col-sm-3 control-label ">{{ field.label_tag }}</label>
                        <label class="label col-sm-7 ">{{ field }}</label>
                    </div>
                </div>
            {% endfor %}
            <div class="col-sm-10">
                <div class="form-group">
                    <label class="col-sm-2 control-label"><input type="submit" class="btn btn-primary"
                                                                 value="登録"></label>
                    {% csrf_token %}
                </div>
            </div>
        </div>
    </form>
```

cssは、bootstrap 3.3.5と自作シートを用いている。


```html
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
        <link rel="stylesheet" href="{% static 'css/common.css' %}">
```

####トラブル
テストサーバを立ててみる。
**すると、次のように入力ボックスの文字が白色になって見えない状態になる。**

下では、名前とメッセージ欄に書き込んでいる。
![コメント 2020-01-19 145606.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/6f37326e-a9dc-7b6f-60d8-ad897dcc4316.png)

反転させてようやく見える状態
![コメント 2020-01-19 145620.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/ab1f862f-56bc-5f4b-c5a1-561610242bfa.png)

###(2)結論
結論から先に言うと、次の二点。

・bootstrap 3.3.5のデフォルト設定が悪さ（？）をしていた。
・自作CSSの変更後、「スーパーリロード」がひつようだった。

###(3)トラブルシューティング
####3-1
まずchromeの開発者ツールを開いて調査してみた。

![コメント 2020-01-19 151440.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/ae4e652a-809c-51f4-eb9e-178ca454cc08.png)

スタイルシート欄を注視。

![コメント 2020-01-19 151549.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/2cf09dd3-eed5-bde1-42b3-9602754e2a53.png)

すると、bootstrapのtextareaに対するデフォルト設定が適用されていることを確認できた。
検証のためにこれをオフにすると文字が出現。
![コメント 2020-01-19 151813.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/1a758d24-e01e-9215-731f-0fd137f02ff1.png)

なので、自作のCSSファイルで上書きしてやろうという方針にした。

####3-2
Djangoのstaticファイル格納フォルダに次のように定義。
text_change_colorセレクタを定義し、子要素にも適用できるようにした。

```css:common.cssの一部

.text_change_color {
    color: black !important;
}

.text_change_color * {
    color: black !important;
}

```

index.htmlを改変。

```html:index.htmlの一部
                    <div class="col-sm-10 text_change_color">
                        <label class="col-sm-3 control-label ">{{ field.label_tag }}</label>
                        <label class="label col-sm-7 ">{{ field }}</label>
                    </div>
```

これでWebアプリを立ち上げたが、
まだ文字が白色のままだった。

![コメント 2020-01-19 145606.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/f6b38b86-05bf-d52b-54a8-a85ef1df1b21.png)

####3-3
怪しく思って開発者ツールでcssの中身を見てみると、
変更が反映されていない。
![コメント 2020-01-19 152507.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/c71e1ba3-7b66-527a-bf27-4bd08bfca823.png)

アプリをリブートしても、状況は変わらない。

色々調べたところ、キャッシュを初期化のために「スーパーリロード」をしなければいけないらしいとわかった。
[参考：https://qiita.com/shati-taro/items/3946d3962071a26ebcb6](https://qiita.com/shati-taro/items/3946d3962071a26ebcb6)

**スーパーリロードをしたところ、無事反映し、入力ボックスの文字がちゃんと黒色になったことを確認できた。**


<hr>

##(B)__auto_now_addで登録したDBの時刻値がTIMEZONEを反映していない
~~###(1)現象~~
~~####設定内容~~
~~次のようにPostingモデルを定義。~~
~~登録日時の指定にはDateTimeFieldの「auto_now_add」引数をTrueにして~~
~~現在のシステム日時が登録されるように設定する。~~
~~~~

```py:models.pyの一部
from django.db import models


class Posting(models.Model):
    name = models.CharField(
        max_length=64,
        verbose_name='名前',
        help_text='<b>あなたの名前を入力してください</b>',
    )
    message = models.CharField(
        max_length=255,
        verbose_name='メッセージ',
        help_text='<u>メッセージを入力してください</u>',
    )
    created_at = models.DateTimeField(
        auto_now_add=True,
        verbose_name='登録日時',
    )

```



~~また、setting,pyでTIMEZONEをAsia/Tokyo（日本標準時）に指定。~~
~~USE_TZ = TrueとしてTIMEZONEを有効化済み。~~
~~~~

```py

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

```


~~indexページで登録ボタンを押下するとDBにモデルの各データがレコードとして登録される。~~
~~~~
~~####トラブル~~
~~上記の条件で、テストサーバを立て~~
~~自分のPC時間で【2020-01-18 17:35ごろ】に~~
~~「７６７６７５４６７」とメッセージを打って~~
~~画面上の登録ボタンを押す。~~
~~~~
~~データが登録されたことを確かめるために、~~
~~Pycharm上でデータベースのツールウィンドウを開いて~~
~~内容を確認。~~
~~~~
~~**すると、UTC時刻で登録されてしまっている。**~~
~~~~
~~![コメント 2020-01-19 155649.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/9f2cd4b5-c8d8-0b5d-0d79-d08c5cd6ea99.png)~~
~~~~
~~TIMEZONEが有効化されているはずなのに、なぜ？？~~
~~（カラム名が謎の文字化けを起こしているのもかなり気になるけど）~~
~~~~
~~###(2)結論~~
~~結論から言うと、~~
~~auto_now_add指定時のモデルデータ生成に関するDjangoの仕様だった。~~
~~タイムゾーン云々はDjango 側で勝手にやってくれる。~~
~~~~
~~###(3)トラブルシューティング~~
~~####3-1~~
~~とりあえず画面上のデータもデータベースの登録日時と整合性があっているか確認した。~~
~~すると、画面上では日本標準時で表示できている。~~
~~~~
~~![コメント 2020-01-19 160228.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541790/34ec29c9-3849-457a-5b0b-d157531506bf.png)~~
~~~~
~~どうやらDateTimeField内のコードを読む必要がありそう。~~
~~~~
~~####3-2~~
~~DateField(DateTimeFieldの親クラス)のコンストラクタはこのよう。~~
~~~~

```py:

class DateField(DateTimeCheckMixin, Field):
    empty_strings_allowed = False
    default_error_messages = {
        'invalid': _("'%(value)s' value has an invalid date format. It must be "
                     "in YYYY-MM-DD format."),
        'invalid_date': _("'%(value)s' value has the correct format (YYYY-MM-DD) "
                          "but it is an invalid date."),
    }
    description = _("Date (without time)")

    def __init__(self, verbose_name=None, name=None, auto_now=False,
                 auto_now_add=False, **kwargs):
        self.auto_now, self.auto_now_add = auto_now, auto_now_add
        if auto_now or auto_now_add:
            kwargs['editable'] = False
            kwargs['blank'] = True
        super().__init__(verbose_name, name, **kwargs)
```

~~auto_now_addがTrueだとeditableをFalseとしてさらに親クラスに渡すらしい。~~
~~ここを調べても有用な情報は手に入らなさそうだったので、~~
~~pre_saveメソッド（モデルのsave時に実行されるメソッド）を見てみた。~~
~~~~
~~~~


```py:pre_save
    def pre_save(self, model_instance, add):
        if self.auto_now or (self.auto_now_add and add):
            value = timezone.now()
            setattr(model_instance, self.attname, value)
            return value
        else:
            return super().pre_save(model_instance, add)

```



~~auto_now_addがTrueだったときはtimezone.now()を呼んでいる。~~
~~その中身は......~~
~~~~
~~~~


```py
def now():
    """
    Return an aware or naive datetime.datetime, depending on settings.USE_TZ.
    """
    if settings.USE_TZ:
        # timeit shows that datetime.now(tz=utc) is 24% slower
        return datetime.utcnow().replace(tzinfo=utc)
    else:
        return datetime.now()

```


~~settings.pyの指定タイムゾーン利用が有効であろうとなかろうと、~~
~~returnする値はUTCのものになっているように見える。~~
~~~~
~~ん？~~
~~でも画面上では日本標準時が取得できている。~~
~~なぜだろう。~~
~~~~
~~####3-3~~
~~手に負えなかったので調べたら、~~
~~次のような記事を見つけた。~~
~~~~
~~[https://djangobrothers.com/blogs/django_datetime_localtime/](https://djangobrothers.com/blogs/django_datetime_localtime/)~~
~~~~
~~>Djangoのdjango.utils.timezoneを利用する際の注意点として、コード内でdatetimeを利用する時にはまだローカライズされていないという点が挙げられます。~~
~~少し分かりづらいですが、例えば、テンプレートで表示されるまではUTCを基準にしたawareなdatetimeオブジェクトを扱っており、それが表示されるタイミングで適切なローカルタイムに変換されているということです。~~
~~~~
~~この仕様に準拠すると、上記の挙動も理解できる。~~
~~~~
~~画面にアウトプットする際にDjango側でTIMEZONEの自動変換をしているようだ。~~
~~これがソースコードのどこの部分の処理なのかは以前不明だけど。~~


<font color="Red">公式ドキュメントにUTCでDBに登録と書いてありますね</font>
<font color="Red">先にそちらを読めばよかったです</font>
[https://docs.djangoproject.com/ja/2.1/topics/i18n/timezones/](https://docs.djangoproject.com/ja/2.1/topics/i18n/timezones/)



#5.終わりに

以上、初級者のトラブル解決までの道筋でした。
なにか補足がありましたらコメントください。
あと、なにか個人情報もれてるよってところがあったら、
コソッと教えていただけると助かります。（mailto:1044adad@gmail.com）