Djangoでニコ生風のプレゼンテーションシステムを作る

最近[Djangoのチュートリアル](https://docs.djangoproject.com/ja/2.0/intro/tutorial01/)をやり終わったので、続いて自分で考えたシステムを作ってみようと思いました。

私自身も初心者なので、チュートリアルは出来たけどこれから何をしていいか分からない人とかの参考になるんじゃないかなー、と思います。
あと、上級者の人にとっても「初心者ってこんな考え方すんの！？」みたいな感じでエンターテイメント的に楽しめるのではないでしょうか。

私が作ろうとしているのは、聴衆がニコ生風にコメントを投稿できるプレゼンテーションシステムです。
https://github.com/junki5088/ricefield
まだ未完成なのですが、現状は下のgifみたいな感じです。
![ricefield.gif](https://qiita-image-store.s3.amazonaws.com/0/182999/b9580603-f28d-03a1-a6ab-aa4189649307.gif)

#作っているもの
上にも書いた通り、ニコ生風プレゼンシステムを作ろうとしています。必要な機能は
1. ブラウザ上でのスライド表示
2. 聴衆からのコメント取得
3. コメント表示
です。

今のところ 1. と 3. が出来ていて、固定のコメントをスライド上に表示することができます。

#作ろうとした動機
LTとかで発表している時に、聴衆の反応が静かだと寂しくないですか？発表している側にとっては、賑やかな方がいいですよね。
でも聴いてる側としては、発表中に考えたことを声に出すのは難しいです。

そこで、発表中にニコ生みたいにコメントを投稿できるシステムを作れば、この問題を解決できるんじゃないかと思ったわけです。

#開発環境
macOS上のvagrantで開発を行なっています。
詳しいバージョンは
```macOS High Sierra  10.13.2
vagrant 2.0.1
virtual box 5.0.40
CentOS  7.4.1708
Python 3.5.2
Django 2.0
```です。

PDFを画像に変換する際にImageMagickを使用しています。
CentOSでは
`sudo yum install ImageMagick`
で入ります。

#本題
それでは詳細にソースコードを解説していきます。
Djangoのチュートリアルでいうと、アプリケーションを作成してurls.pyを追加した状態から始めます。
DBはSQLiteを使用していますが、他のものでも問題ないと思います。
##ディレクトリ構成
プロジェクト名 gasshuku、アプリケーション名 ricefieldで作成しています。
プロジェクト名は会社の開発合宿で作り始めたからこうなってます。
アプリケーション名は、コメント→米→田んぼ→ライスフィールド という安直な思考で決めました。

名前は好きに変えても大丈夫だと思います。
##Model
まずは gasshuku/ricefield/models.py から変更していきます。

スライドのファイル名とページ数を保持するSlidesクラスを用意しています。
また、送信されたPDFファイルを保存するためのsave_fileメソッドも置いています。が、これは別にここに置かなくてもよかったのかもしれません。
上級者の皆様、ここはツッコミどころですよ。

```
import os

from django.db import models

class Slides(models.Model):
    name = models.TextField()
    pages_num = models.IntegerField()

def save_file(filepath, file):
    if os.path.exists(filepath):
        os.remove(filepath)

    with open(filepath, 'ab') as f:
        for chunk in file.chunks():
            f.write(chunk)
```
##View
PDFファイルをアップロードするupload_formページと、スライドを表示するslideページを用意しています。

upload_formページでの処理の流れは以下の通りです。
1. POSTされてきたPDFファイルを保存する
2. PDFファイルを画像ファイルに変換
3. 変換された画像ファイルの枚数を数える
4. ファイル名とページ数を保存する
5. スライドのidをslideページに渡して遷移

slideページの流れは以下の通りです
1. upload_formページからスライドのidを受け取る
2. 受け取ったidに一致するスライド画像を取得する
3. 先頭の画像以外はhidden()する
4. 表示する画像はキーイベントで変更する
5. クリックされた時は固定のコメントを表示する

```
import os
import re
import subprocess 

from django.shortcuts import render, redirect
from django.http import HttpResponse

from .models import Slides
from .forms import UploadFileForm
from .models import save_file

def index(request):
    return render(request, 'ricefield/index.html')

def upload_form(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            # postされたPDFファイルと部屋の名前を取得する
            file_name = request.FILES['file'].name
            save_file_name = file_name + '.pdf'
            save_path = os.path.join('ricefield/static/pdf_slides', save_file_name)
            # PDFファイルは一旦保存（Python上で画像に変換したかった）
            save_file(save_path, request.FILES['file'])
            # 保存したPDFファイルをPNGに変換
            os.system(
                "convert -density 100 -geometry 960x540 {0} /home/vagrant/gasshuku/ricefield/static/image_slides/{1}".format(save_path, file_name + '_%04d.png')
            )
            # スライドの枚数を数える（ファイル名は重複しない前提
            filelist = os.listdir('/home/vagrant/gasshuku/ricefield/static/image_slides/')
            pages_num = 0
            for f in filelist:
                if file_name in f:
                    pages_num += 1
            # スライド情報を保存
            slide = Slides(name=file_name, pages_num=pages_num)
            slide.save()

            return redirect('/ricefield/{}/slide'.format(slide.id))
        else:
            return HttpResponse('Invalid request')
    else:
        form = UploadFileForm()
        return render(request, 'ricefield/upload_form.html', {'form':form})


def slide(request, slide_id):
    slide = Slides.objects.get(id=int(slide_id))
    file_name_list = [os.path.join('image_slides', slide.name + '_000' + str(i) + '.png') for i in range(slide.pages_num)]
    context = {
        'file_name_list': file_name_list,
        'pages_num': slide.pages_num,
    }
    return render(request, 'ricefield/slide.html', context)
```
indexページはテスト用です。
また、コメントを表示する時はnicoJSというライブラリを使用しています。
https://github.com/yuki540net/nicoJS
##今後の課題
コメントを投稿する機能がまだできていないので、早めに作りたいです。
twitter APIを使って、特定のハッシュタグが付いたツイートを取得するようにするのがいいかなと思ってます。

あと、POSTするためにいちいちPDFでエクスポートするのが面倒なので、パワポ形式で投稿できるようにしたいと思っています。

以上、進展あればまた投稿します。