utamaro’s blog

誰かの役に立つ情報を発信するブログ

シンプルなカレンダーフォームを作る方法

日付選択をする際にでてくるあのカレンダーを作る方法を紹介します。

documentではdomに属性をつけてオプションを指定しているのがほとんどですが、属性をつけるとごちゃごちゃするので、jsに書きます。

準備

以下のライブラリが必要です。

リンクを付けましたので、ダウンロードしてください。

もしくは、npmで環境を整えても良いです。

ダウンロードしてサンプルを作っているので、npmで用意した場合はそれぞれの環境に置き換えてください。

おそらく、node_modules以下にファイルがあるはずです。

inputをクリックするとカレンダーが表示されるサンプル

sample.html

<html>
    <head>
        <link href="../dist/css/datepicker.min.css" rel="stylesheet" type="text/css">
        <script src="./jquery-3.2.1.min.js"></script>
        <script src="../dist/js/datepicker.min.js"></script>

        <!-- Include English language -->
        <script src="../dist/js/i18n/datepicker.en.js"></script>
        <script src="./sample.js"></script>
    </head>

    <body>
        <div>
            <input type='text' id="jsDatePicker"/>
        </div>
    </body>
</html>

sample.js

(function() {
    // docs
    // http://t1m0n.name/air-datepicker/docs/
    function sample1() {
        console.log("called init function.");
        $('#jsDatePicker').datepicker({
            position: "right top",
            language: 'en',
            minDate: new Date() // Now can select only dates, which goes after today
        });
    }

    function init() {
        sample1();
    }

    document.addEventListener("DOMContentLoaded", function() {
        init();
    }, false)
})()

カレンダーの日付をクリックするとアラートが表示されるサンプル

onSelectオプションを使用するとできます。

callbackで渡される引数には以下の値が入ります。

  1. fd: フォーマットされた日付
  2. date: date object
  3. inst: datepickerのインスタンス

これを使うと、選択された日付のタスクを横に出すとかできます。

multipleDates: trueをオプションに追加した場合は、dateが配列になります。

range: trueに設定した場合は、開始日と終了日が入った配列になります。

function sample2() {
    console.log("called init function.");
    $('#jsDatePicker').datepicker({
        position: "right top",
        language: 'en',
        multipleDates: false,
        minDate: new Date(), // Now can select only dates, which goes after today
        onSelect: function(fd, date, inst) {
            alert('selected date.');
            console.log(fd);  // 10/17/2018
            console.log(date);
            // date.getFullYear()
            // date.getMonth()
            // date.getDate()
        }
    });
}

最後に

air-datepickerのドキュメントはとても読みやすいので参照してみてください。

今回紹介したサンプルもドキュメントに書かれているサンプルから取っています。

http://t1m0n.name/air-datepicker/docs/

Django+herokuでS3を使わない画像の保存方法

cloudinaryというアドオンを使って、画像を保存します。

画像の保存というとamazon s3が思い浮かぶと思いますが、あれほんの少しお金かかりますよね?

スタートということで、完全無料でやるためにcloudinaryを使ってみます。

cloudinaryのアドオンは↓です。

https://elements.heroku.com/addons/cloudinary

無料の範囲は↓です。

Storage: 10 GB
Monthly Bandwidth: 20 GB
Total Images & Videos: 300,000
Monthly Transformations: 20,000

準備

ライブラリをインストールする

pip install cloudinary

herokuでアドオンを追加して、画面の流れに沿って操作してください。

画像をアップロードする

import cloudinary
import cloudinary.uploader
import cloudinary.api

cloudinary.config(
  cloud_name="xxxxx",
  api_key="xxxxxxxxxx",
  api_secret="xxxxxxxxxxxxxxxx"
)

def upload(image):
    result = cloudinary.uploader.upload(image)

if __name__ == '__main__':
    upload('sample.jpg')

ちなみに、urlを指定しても保存してくれます。

upload('http://sample.com/sample.jpg)

Djangoのtemplateで画像を表示する

settingsにcloudinaryを追加する

INSTALLED_APPS = [
    ...
    'cloudinary',
    ...
]

tempalteで以下のようにする

<!DOCTYPE html>
<html>
<head>
    {% load cloudinary %}
</head>
    <body>
        {% cloudinary "public_id" crop="fill" %}
    </body>
</html>

もちろん、view内でpublic_id部分を置換しても良いです。

置換する場合は↓のようにします。

{% cloudinary public_id crop="fill" %}
context = {
    'public_id': 'xxxxxxxxxxxxxxx',
}
return self.render_to_response(context)

public_idを自分で設定する場合

アップロードする際にpublic_idを指定します。

result = cloudinary.uploader.upload(image, public_id='kyaro_kyawawa')

画像のアップロードは↓にもっと詳しいドキュメントが書かれています。

参考にすると役に立つはずです。

https://cloudinary.com/documentation/django_image_upload

できるだけ早く、無料でwebサービスを開発するまで

無料でwebサービスを作り、公開するまでの流れを紹介したいと思います。

今回BookStackerhttp://www.book-stacker.comというサービスを作ったので、その際の開発の流れを紹介します。

以下の流れで開発を行いました。

  1. タスクを洗い出す(3h)
  2. 仕様を書き出す(3h)
  3. 開発をする(4day)
  4. リリースする(3h)
  5. 運用する(...)

合計で一週間ぐらいです。

タスクを洗い出す

タスクには以下の3つ書き出しました。

  1. やらなければならないこと
  2. やったほうがよいこと
  3. やらなくてもよいこと

それぞれについて解説します。

やらなければならないこと

これは作ろうとしているサービスの必須機能です。

「これを作れば公開できる」といったものです。

BookStackerなら以下の項目です。(一部抜粋)

  • 本の一覧を表示する
  • 本の情報をDBに保存する

やったほうがよいこと

必須ではないけど、やっておくと何かとメリットがあるものです。

  • レスポンシブデザイン
  • ドメイン登録
  • グーグルアナリティクス

やらなくてもよいこと

やってもいいけど、やらなくてもいいといった少し迷う定義です。

仕様を書き出す

タスクができたら今度は仕様を書き出しました。

仕様を書く際はエクセルやスプレッドシートなどを使って作成することもあると思います。

これはおすすめしません。必要なのは文字であって、表ではないからです。

というわけで、マークダウンで書きました。

タスク管理なら、こんな感じです。

# タスク管理

## /task/create
タスクを登録する

method: post
params:
    title:
        type: str
        valid:
            required: true
    body:
        type: str
        valid:
            required: false
            max length: 2000

viewの仕様も、apiの仕様もこのように書くことができ、またgitで管理できるので表計算ソフトより効率が良いです。

画面のレイアウトは紙に書いて、壁に貼り付けました。開発が終わったら取ってファイリングします。

開発をする

タスクを出して、仕様を書き出したらいよいよ開発です。

開発に使う言語は何でも良いです。作れればなんでも良いのです。一番慣れている言語が一番はやく開発できるでしょう。

BookStackerではpythonを使って開発をしました。

またフレームワークDjangoを使いました。

無料で運用することを決めているので、herokuの無料枠を使いました。

herokuの無料枠ではmysqlよりもpostgresqlのほうがデータが入るので、postgresqlを使用しました。

(ClearDB MySQLのfreeプランの場合は5MBまでしかデータが入りません。それに対して、postgresqlなら10000行データが入ります。)

できる限りテーブル数を削減すればかなりのデータが入ることでしょう。

実装する順番について

「やらなければならない」タスクから取り掛かります。

そして、それが終わったらherokuにデプロイします。

「やったほうがよいこと」「やらなくてもよいこと」については運用しつつ開発するのが良いでしょう。

リリースする

git push heroku master!

以上が開発の流れです。

これから残りの機能を実装しつつ、運用したいと考えています。

注意

herokuの無料プランを使うと、無料でサービスを公開できます。

ですが、無料枠から離れた場合結構お金がかかります。

最低で7ドル(120円*7=840円)ぐらいです。

また、postgresも入れると+9ドル(120円*9=1080円)です。

合計しておよそ2000円です。

これならvps借りて運用したほうがまだ安いです。

javascriptのreduceを使って配列を更新する方法

reduceを使用すると、ある配列を使って単一の値を計算したい場合に使えます。

いくつかreduceを使った例を紹介します。

配列内のデータを加算する

var datas = [1,2,3,4,5]
var result = datas.reduce(function(num, item) {
    // numの初期値は0
    return num + item;
}, 0);
// result == 15

配列内のデータを逆順にする

reverseを使ったほうが良いけども

var datas = [1,2,3,4,5,6]
var result = datas.reduce(function(table, item) {
    console.log(table, item);
    table.unshift(item);
    return table;
}, []);
// result = [6,5,4,3,2,1]

// ↓ console.log(table, item)
// [] 1
// [1] 2
// [2, 1] 3
// [3, 2, 1] 4
// [4, 3, 2, 1] 5
// [5, 4, 3, 2, 1] 6

一次元の配列を3つ区切りの二次元配列にしたい場合

↓の例の場合、[1,2,3,4,5,6][[1,2,3],[4,5,6]]に変換されます。

var datas = [1,2,3,4,5,6];
var result = datas.reduce(function(table, item) {
    // tableが更新されていく
    // itemには要素が入っている
    console.log(table, item);
    const last = table[table.length - 1];
    // ↑ [[1,2,3][4,]] の場合、[4]を取得している
    if (last.length === 3) {
        table.push([item]);
    } else {
        last.push(item);
    }
    return table;
}, [[]]);
// result == [[1,2,3],[4,5,6]]

二次元配列を一次元配列にしたい場合

var datas = [[1,2,3], [4,5,6]];
var result = datas.reduce(function(table, item) {
    return table.concat(item);
}, []);
// result == [1,2,3,4,5,6]

Djangoでcssファイルから画像ファイルを読み込む方法

Djangocssファイルから画像ファイルをurlで指定する方法について紹介します。

やることは簡単です。

Djangoの設定ファイルで以下のような設定をしていると思います。

STATIC_URL = '/static/'
STATIC_ROOT = 'staticfiles'

この設定をしている場合、staticファイルは/static/がurlのルートとなります。

また、staticのルートディレクトリはstaticfilesになります。

つまり、staticfiles/img/sample.pngというファイルをcssファイルから読み込みたい場合以下のようになります。

background-image: url("/static/img/sample.png");

ただ、staticファイルのurlルートを変更した場合にはcssの中を修正する必要が出てきます。

そのときはとても面倒になるので、scssで変数として管理しておくと良いでしょう。

おまけ

staticファイルをhtmlファイルから使用する方法も紹介します。

{% load static %}を入れると、{% static "img/sample.png" %}のように指定できます。

/static/img/sample.pngに置換されます。

{% load static %}

<script type="text/javascript" src="{% static 'js/jquery.min.js' %}"></script>
<img src="{% static "img/sample.png" %}" alt="sample" >

javascriptでカレンダーを作成する

html、cssjavascriptを使ってカレンダーを作成します。

この記事では、htmlとcssの部分についてはcodepenで見つけたデザインを使用しています。

なので、javascriptをメインで紹介したいと思います。

参考にしたデザインはこちらです。

https://codepen.io/jamiemggs/pen/xdvaJv

最終的に、このようなカレンダーができます。

f:id:miyaji-y26:20181007110243p:plain

成果物について

表示する項目

  • 今月のカレンダーを表示する
  • 月の移動はしない
  • 先月、来月の日付を表示する
  • 先月、来月の日付を表示しないこともできる

html

jsCalendarに対してdomを追加します。

javascriptを使って、jsCalendarにhandlebars.jsで生成したdomを追加します。

<div id="jsCalendar" class="calendar"></div>

css

cssについてはcodepenのをそのまま使用します。

handlebar

この例ではテンプレートにhandlebars.jsを使用しています。

handlebars.jsについてはこちらを確認してください。

https://github.com/wycats/handlebars.js/

<div class="jzdbox1 jzdcal">
    <div class="jzdcalt">{{year}}年{{month}}月</div>

    <span>Su</span>
    <span>Mo</span>
    <span>Tu</span>
    <span>We</span>
    <span>Th</span>
    <span>Fr</span>
    <span>Sa</span>

    {{#each dates}}
        {{#if this }}
            <span>{{date}}</span>
        {{else}}
            <span class="jzdb"><!--BLANK--></span>
        {{/if}}
    {{/each}}
</div>

{{#if this }}に気がつくまで時間がかかりました。

このテンプレートに渡されるデータの構造について。

year: 2018
month: 10
dates: [
    {
        month: 10
        date: 1
    }
]

datesの配列にはnullが入ることもある。

nullの項目が日付の非表示に対応する形です。

javascript

ES2015の書き方をしている箇所があります。

import $ from 'jquery';
import hb_calendar from 'HandleBars/calendar';

(function() {
    class Calendar {
        constructor() {
            this.element = {
                template_target: {
                    calendar: "#jsCalendar",
                }
            }
        }

        render(calendar_data) {
            // handlebarsは入れるtemplateを入れるtargetを作って
            let target = $(this.element.template_target.calendar);
            // templateからhtml文字列を作って
            let html = hb_calendar({
                year: calendar_data.year,
                month: calendar_data.month,
                dates: calendar_data.dates,
            });
            // targetにhtmlをappendする。これだけ!
            target.append(html);
        }

        createCalendarData(allowBlank) {
            allowBlank = allowBlank || false;
            let today = new Date();
            let year = today.getFullYear();
            let month = today.getMonth();

            let lastMonthDateObj = new Date(year, month, 0);  // 先月の最終日
            let nextMonthDateObj = new Date(year, month + 1, 1);  // 来月の初日
            let startDate = new Date(year, month, 1); // 今月の初日
            let endDate  = new Date(year, month + 1 , 0); // 今月の最終日

            // Sunday is 0, Monday is 1, and so on.
            let firstDateNum = startDate.getDate();
            let lastDateNum = endDate.getDate();

            let dates = []
            // まずは、先月の日付を入れる。
            for (let di = 0; di < startDate.getDay(); di++) {
                if (allowBlank) {
                    dates.push(null)
                } else {
                    dates.push(
                        {
                            month: lastMonthDateObj.getMonth() + 1,
                            date: lastMonthDateObj.getDate() - di,
                        }
                    );
                }
            }
            
            // 次に日付を入れる
            for (var di = firstDateNum; di <= lastDateNum; di++) {
                dates.push(
                    {
                        month: month + 1,
                        date: di,
                    }
                )
            }

            // 最後に来月の日付をいれる
            for (let di = 0; di < endDate.getDay(); di++) {
                if (allowBlank) {
                    dates.push(null);
                } else {
                    dates.push(
                        {
                            month: nextMonthDateObj.getMonth() + 1,
                            date: 1 + di,
                        }
                    )
                }
            }
            // datesにカレンダーの情報が入っている。
            // あとはレンダリングするだけ
            console.log(dates);
            return {
                year: year,
                month: month + 1,
                dates: dates,
            };
        }
    }

    function init() {
        let calendar = new Calendar();
        let datas = calendar.createCalendarData(false);
        calendar.render(datas);
    }

    $(window).on('load', function() {
        console.log("called ready function");
        init();
    })

})();

pythonのファイルをherokuで定期的に実行する方法

この記事ではpythonのファイルに書かれたprint()を定期的に実行する方法を紹介します。

↓のページを参考にしています。

https://devcenter.heroku.com/articles/clock-processes-python

herokuで適当なアプリケーションを作成していて、remoteへpushできるまで準備が整っている状態を想定しています。

また、custom clock processesを対象にしています。

pythonファイルを作成する

スケジュール用のライブラリをインストールします。

pip install apscheduler

1分間隔でログをだすようにコードを書きます。

batch.py

from apscheduler.schedulers.blocking import BlockingScheduler

sched = BlockingScheduler()

@sched.scheduled_job('interval', minutes=1)
def timed_job():
    print('This job is run every three minutes.')

sched.start()

apschedulerではなく、scheduleライブラリを使ってもよいです。

pip install schedule
import schedule
import time

def job():
    print("hello every 10 seconds.")

#10秒毎にjobを実行
schedule.every(10).seconds.do(job)

while True:
    schedule.run_pending()
    time.sleep(1)

Procfileを作成する

clock: python batch.py

requirements.txtを用意する

pip freeze > requirements.txt

herokuにpushする

git add ./
git commit -m "first commit"
git push heroku master

↓のようにログが確認できます。

heroku[clock.1]: Starting process with command `python batch.py`
heroku[clock.1]: State changed from starting to up
app[api]: Build succeeded
app[clock.1]: This job is run every three minutes.
app[clock.1]: This job is run every three minutes.

注意

custom clock processesの場合起動中はdynoを消費し続けます。

なので、web1, clock1でscaleしていると、webのみの場合の2倍早く消費されます。

無料内で定期バッチを実行する場合は、add-onを使ったほうが良いでしょう。

(ただし、10分、1時間、1日ごとのみ指定可能です)

add-onの場合は、実行時のみ消費されるので低コストです。

1日ごとに論理削除されたレコードを削除したいといった場合はadd-onを使ったほうがよさそうです。