進捗をGitHub風に可視化するアプリを作った話。

## 具体的に作ったもの

毎日の進捗日記を投稿、閲覧するアプリ。Webアプリ処女作クオリティ:disappointed_relieved:
進捗(投降した日記の文字数)が芝の色に反映される。
(画像にある日記タイトルはテストで投稿してるのでお気になさらず)

![mydiary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351891/f200dc11-a9aa-0e74-03c9-df8be206110c.png)

投稿する日記はmarkdown形式に対応していて閲覧するとhtml形式に変換されて表示される。
![mydiary_admin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351891/b479a929-c667-1048-533b-1b2ccd825517.png)



## バージョン
python 3.6.5
Django 2.2.1

## 作り方

以下、プロジェクト名はmydiary、アプリの名前はblogとします。

まず、モデルを作成していきます。[djangogirls](https://tutorial.djangogirls.org/ja/installation/)というチュートリアルを参考にさせていただきました。

日記をmarkdown形式に対応させたいので、models.pyはdjangogirlsのものに以下のように少し手を加えます。
具体的には、markdownをDjangoで扱うためのライブラリ、django-markdownxを使用します。
([こちらのサイト](https://narito.ninja/blog/detail/102/)を参考にさせていただきました。)

```python:blog\models.py
from django.db import models
from django.utils import timezone
from markdownx.models import MarkdownxField
from markdownx.utils import markdownify


class Post(models.Model):
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    # text = models.TextField()
    text = MarkdownxField('本文', help_text='Markdown形式で書いてください. ')
    created_date = models.DateTimeField(
            default=timezone.now)
    published_date = models.DateTimeField(
            blank=True, null=True)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def text_to_markdown(self):
        return markdownify(self.text)

    def __str__(self):
        return self.title
```

この日記モデルを管理サイトから投稿するようにします。

```python:blog\admin.py

from django.contrib import admin
from .models import Post

admin.site.register(Post)
```

urls.pyはそれぞれ以下のように指定しました。


```python:mydiary\urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('blog.urls')),
    path('admin/', admin.site.urls),
    path('markdownx/', include('markdownx.urls')),
]
```

```python:blog\urls.py
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('add/', views.post_edit, name='post_add'),
    path('browse/<int:post_id>/', views.post_browse, name='post_browse')
]
```
次に、viewを作成していきます。
簡単に、ページは以下の2つだけにします。

- 投降した進捗日記のタイトル一覧と、github風の芝があるページ (post_list)
- 選択した進捗日記を読むページ (post_browse)

まずtemplateからですが、base.htmlを作ります。
これは@kaki_k様の[Python Django入門(4)](https://qiita.com/kaki_k/items/6e17597804437ef170ae)から拝借させていただきました。

そのbase.htmlを使用して以下のようにpost_list.htmlを作成します。
これも[上記のサイト](https://qiita.com/kaki_k/items/6e17597804437ef170ae)を参考にさせていただいています。

```python:blog\templates\blog\post_list.html
{% extends "blog/base.html" %}

{% block title %}日記の一覧{% endblock title %}

{% block content %}
    <h4 class="mt-4 border-bottom">日記の一覧</h4>
    <table class="table table-striped table-bordered">
      <thead>
        <tr>
            <th scope="col">日記タイトル</th>
            <th scope="col">書いた日時</th>
          <th scope="col"></th>
        </tr>
      </thead>
      <tbody>
        {% for post in posts %}
        <tr>
          <th scope="row">{{ post.title }}</th>
          <td>{{ post.published_date }}</td>
          <td align="center">
              <a href="{% url 'blog:post_browse' post_id=post.id %}" class="btn btn-outline-primary btn-sm">読む</a>
          </td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
{% endblock content %}

{% block extra_css %}
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css" />
{% endblock extra_css %}

{% block extra_js %}
    <script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js"></script>
    <script type="text/javascript">
    var datas = {{ data }};
        var cal = new CalHeatMap();
        var now = new Date();
        cal.init({
            itemSelector: '#sample-heatmap',
            domain: "month",
            data: datas,
            domainLabelFormat: '%Y-%m',
            start: new Date(now.getFullYear(), now.getMonth() - 11),
            cellSize: 10,
            range: 12,
            legend: [10, 25, 50, 75, 150],
        });
    </script>

{% endblock extra_js %}
```

github風の芝を実装するライブラリは[Cal-Heatmap](https://cal-heatmap.com/)を使用しています。
[こちら](http://undersourcecode.hatenablog.com/entry/2019/01/19/183852)を参考にさせていただきました。

{{  }}で囲まれている部分にはviewから渡されたデータが入ります。

post_browse.htmlは以下のように書きます。シンプルすぎて味気ないですね・・・

```html:blog\templates\blog\post_browse.html
<h1>{{ post.title }}</h1>
<p>{{ post.text_to_markdown | safe }}</p>
<p>published: {{ post.published_date }}</p>
```

最後にviews.pyです。

```py:blog\templates\views.py
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Post


def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    unix_time = [post.published_date.timestamp() for post in posts]
    char_count = [len(post.text) for post in posts]
    data = dict(zip(unix_time, char_count))
    return render(request, 'blog/post_list.html', {
        'posts': posts,
        'data': data,
    })


def post_browse(request, post_id):
    post = get_object_or_404(Post, pk=post_id)
    return render(request, 'blog/post_browse.html', {
        'post': post,
    })
```
post_listでは投稿された日時をUNIX時間に直したもの（`unix_time`）と
進捗日記の文字数（`char_count`）をタプルとしてまとめたリスト（`data`）をtemplateに渡しています。
これがcal-heatmapのデータになっています。`posts`と`post`は投降した日記のオブジェクトです。

以上です。

改良の余地は多々ありますが実現したいことはできたので「これで完成」と言わせてください。

## このアプリを作った理由

- 実験ノートを毎日markdown形式で書き溜めていて、活用したかったから。
- 進捗を可視化してモチベーションを保ちたかったから
- 10連休のGWに新しいことを始めたかったから。

## 感想

タイトルの割にしょうもない内容ですみません。
初めてのWebアプリ製作はわからないことだらけでしたがそれを一つずつ解決して
自分の思う完成に近づいていく過程がとてもたのしかったです。
たくさんのサイトにお世話になりました。感謝申し上げます。
また、ここまで読んでくださった方、ありがとうございました。