【Django】校内2ちゃんねるをつくる【Pythonanywhere】

# はじめに
<!-- 発端や概要を記載 -->
「Webアプリをつくりたい！」と思い、JSPとサーブレットを勉強していたところ、プロから「PHPの方が流行ってるよ」と言われ撃沈。
もちろん、Java開発のメリットも多いが、スクリプト言語の攻撃力はとても高い。

ゴリゴリC言語プログラマーとして生きてきたが、時代には巻かれなければと思いPythonのWebアプリケーションフレームワークであるDjangoに手を出した。

チュートリアルが終わったため、次のアイデアを考えていると「2ちゃんねるなら手軽に作れそう！」と思い立つ。
課題研究にこじつけて、「みんなの交流場」的な感じで校内2ちゃんねるを作ることにした。

##お前誰やねん

しがない学生
電子回路とマイコン制御ならプロ
Pythonはディープラーニングを実装する時に少し触った程度
HTML・CSSはコピペでなんとかしていく

##開発環境

OS : Linux mint 20
言語 : Python 3.9
エディタ : VSC
フレームワーク : Django
Webサーバー : Pythonanywhere.com

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [まずは完成品](#Chapter1)
1. [Djangoについて](#Chapter2)
1. [2ちゃんねるづくり](#Chapter3)
1. [Pythonanywhereについて](#Chapter4)
1. [参考](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# まずは完成品

出来上がったものがこちら！

<iframe width="560" height="315" src="https://www.youtube.com/embed/H33eacPWYns" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<a id="#Chapter2"></a>
# Djangoについて

Djangoは公式ドキュメントがめちゃくちゃわかりやすいので、Docsを読んだほうが良い

https://docs.djangoproject.com/ja/3.2/

実践形式で、「まずはこうしよう」→「実はこんな便利なやり方もある」というパターンが多い。
急いで飛ばし飛ばしにやると間違いなく途中で詰むので素直にひとつひとつこなすのがおすすめ。

<a id="#Chapter3"></a>
# 2ちゃんねるづくり

##ビュー

``` python:views.py
....

def create_thread(request):
    try:
        thread = Thread(
            thread_text=request.POST['thread_str'],
            pub_date=timezone.now(),
            latest_date=timezone.now(),
        )
    except (KeyError):
        # Redisplay the thread voting form.
        return render(request, 'board/index.html', {
            'error_message': "Error",
        })
    else:
        thread.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('board:index'))


def tweet(request, thread_id):
    thread = get_object_or_404(Thread, pk=thread_id)
    try:
        tweet = thread.response_set.create(
            response_text=request.POST['tweet_str'],
            name_text=request.POST['name_str'],
            tweet_date=datetime.now()
        )
    except (KeyError):
        # Redisplay the thread voting form.
        return render(request, 'board/detail.html', {
            'thread': thread,
            'error_message': "You didn't have a tweet.",
        })
    else:
        tweet.save()
        thread.update_date()
        thread.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('board:results', args=(thread.id,)))

```

チュートリアルを改造し、新規スレッド作成と書き込みを実装した。
エラー処理はかなり雑

##モデル

```Python:models.py

...

class Thread(models.Model):
    thread_text = models.CharField(max_length=255)
    pub_date = models.DateTimeField('date published')
    latest_date = models.DateTimeField('date published')

    def __str__(self):
        return self.thread_text

    def print_pub_date(self):
        return (self.pub_date+datetime.timedelta(hours=9)).strftime('%Y-%m-%d %H:%M:%S')#wrong


    def update_date(self):
        self.latest_date = timezone.now()

    def print_latest_date(self):
        return (self.latest_date+datetime.timedelta(hours=9)).strftime('%Y-%m-%d %H:%M:%S')#wrong

    def print_count_response(self):
        return self.response_set.count()

    def print_title(self):
        if(len(self.thread_text) > 16):
            return self.thread_text[0:16]+'...'
        else:
            return self.thread_text

    @ admin.display(
        boolean=True,
        ordering='pub_date',
        description='Published recently?',
    )
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now


class Response(models.Model):
    thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
    response_text = models.CharField(max_length=255)
    name_text = models.CharField(max_length=255)
    tweet_date = models.DateTimeField('date published')

    def __str__(self):
        return self.response_text

    def print_tweet_date(self):
        return (self.tweet_date+datetime.timedelta(hours=9)).strftime('%Y-%m-%d %H:%M:%S')#wrong

```

タイムゾーン設定はDjango.settingで行っているため、標準出力なら綺麗に出るが内部でテキスト変換するとタイムゾーンがずれる。
関数化しようとも思ったがめんどくさくなりゴリ押しした。

| 関係 |    内容     |    備考    |
|---:|-------------|------------|
| 親 |スレッド|タイトル、作成日、最終更新日|
| 子 | レスポンス      | レス内容、投稿者、レス日|

##トップページ

``` HTML:index.html

...

{% if latest_thread_list %}
{% for thread in latest_thread_list %}
<a href="{% url 'board:detail' thread.id %}" class="btn-flat-double-border">
    {{ thread.print_title }} <br>
    スレ数 : {{ thread.print_count_response}} <br>
    更新日時 : {{ thread.print_latest_date}}<br>
    作成日時 : {{ thread.print_pub_date}}</a>
</a>
{% endfor %}
{% else %}
<p>そして誰も居なくなった...</p>
<p>新規スレを立ててね！</p>
{% endif %}

<div class="box17">
    <form action="{% url 'board:create_thread' %}" method="post">
        {% csrf_token %}
        新規スレ:<br>
        <textarea name="thread_str" rows="4" class="textarea_width"></textarea> <br>
        <input type="submit" value="新規">
        <input type="reset" value="リセット">
    </form>
</div>

```

チュートリアルを改造して、2ちゃんねるっぽく作り変えた。
フォーム類はDocsを読まないとよくわからないため、DjangoとHTMLのしっかりとした知識が必要。

ロゴはこのサイトだと適当にオシャンティーなものが作れるのでおすすめ

https://www.designevo.com/jp/logo-maker/

##スレッドページ

``` HTML:detail.html

...

<ul>
    {% for response in thread.response_set.all %}
    <li>
        {{ forloop.counter }}.{{ response.name_text }} -- {{ response.print_tweet_date }}<br>
        {{ response.response_text }}
    </li>
    {% endfor %}
</ul>

<div class="box17">
    <form action="{% url 'board:tweet' thread.id %}" method="post">
        {% csrf_token %}
        <a name="jump">書き込み</a><br>
        <input type="text" value="ななしちゃん" name="name_str" required minlength="1" maxlength="10"> <br>
        つぶやき : <br>
        <textarea name="tweet_str" rows="4" class="textarea_width"></textarea> <br>
        <input type="submit" value="書き込み">
        <input type="reset" value="リセット">
    </form>
</div>

...

```

スレッド内のレスを表示し、書き込みを可能にしている。

#CSS

こちらのサイトからほぼ全てコピペさせていただいた。

https://saruwakakun.com/html-css/reference/css-sample

スマホ表示とPC表示の切り替えは苦労した。

https://www.sejuku.net/blog/99964

HTMLとCSSの知識が不足していて、よくサイトが崩れたのでしっかりと学び直そうと思う

https://www.amazon.co.jp/dp/4797398892/ref=cm_sw_r_tw_dp_53426J817QCREZHGX8F6