[django] 基本メモ(単語帳を作る)

#django単語帳（備忘録）
djangoの基本的なコマンドを使って単語帳を作る。

##プロジェクト生成
```text:command
django-admin startproject prj
cd prj
python manage.py startapp vocaApp
```
次のような構造になる。

```text:tree
C:.
│  manage.py
│
├─prj
│  │  settings.py
│  │  urls.py
│  │  wsgi.py
│  │  __init__.py
│  │
│  └─__pycache__
│          settings.cpython-36.pyc
│          __init__.cpython-36.pyc
│
└─vocaApp
    │  admin.py
    │  apps.py
    │  models.py
    │  tests.py
    │  views.py
    │  __init__.py
    │
    └─migrations
            __init__.py
```

##設定
settings.pyを変更。
defaultはsqliteになっているがpostgresqlを使う。
postgresqlなかったらインストールし、
pip install psycopg2でpsycopg2を手に入れる。

```python:settings.py
INSTALLED_APPS = [
    'vocaApp.apps.VocaappConfig',
...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': '...',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
...
TIME_ZONE = 'Asia/Tokyo'
```

```python:prj/urls.py
...
from django.urls import path, include

urlpatterns = [
    path('', include('vocaApp.urls')),
...
```

##モデル
モデルー＞DB
migrateするとvocaApp_prjのようなテーブルが生成される。

```python:models.py
class Vocabulary(models.Model):
    # 自動増加id
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=50)
    mean = models.TextField(max_length=100)
    example = models.TextField(max_length=300, blank=True, null=True)
    mem_count = models.IntegerField(blank=True, null=True, default=0)
    check = models.NullBooleanField(blank=True, null=True, default=False)
    # 生成時のtimestamp
    created_date = models.DateTimeField(auto_now_add=True)
    # 変更時のtimestamp
    modified_date = models.DateTimeField(auto_now=True)
```

DB－＞モデル（djangoを使うならモデルー＞DBの方が良い、既存のテーブルからdjangoにするとき使う。）
先にpgadminなどでテーブルを作った後inspectdbでmodelスクリプトを得てmodels.pyにコピー

```text:command
python manage.py inspectdb vocaApp_prj
```

##フォーム
データを受けるModelFormを作成

```python:forms.py
from django import forms
from .models import *

class VocaForm(forms.ModelForm):
    class Meta:
        model = Vocabulary
        fields = '__all__'
```

##ビュー
ビジネスロジックとhtmlにrenderするviews.py

```python:views.py
from django.views import generic
from django.shortcuts import render, redirect
from django.views.decorators.http import require_http_methods
from .forms import *
from .models import *

# Create your views here.
class Voca(generic.FormView):
    template_name = "index.html"
    form_class = VocaForm
    success_url = '/'

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['vocabulary'] = Vocabulary.objects.all().order_by("created_date")
        return ctx

    def form_valid(self, form):
        form.save()
        return super().form_valid(form)

@require_http_methods(["POST"])
def update(req):
    form = VocaForm(req.POST)
    name = form["name"].value()
    result = Vocabulary.objects.filter(name=name)
    if result:
        try:
            if form.is_valid():
                result[0].name = name
                result[0].mean = form["mean"].value()
                result[0].example = form["example"].value()
                result[0].mem_count = int(form["mem_count"].value())
                result[0].check = bool(form["check"].value())
                result[0].save()
                return redirect("/")
            else:
                raise Exception("Form validation failed")
        except Exception as e:
            return render(req, 'index.html', {"form" : form,
                                              "vocabulary" : Vocabulary.objects.all().order_by("created_date")})
    else:
        error_msg = "There is no vocabulary that name is %s"%name
        return render(req, 'error.html', {"error_msg" : error_msg})

@require_http_methods(["GET"])
def delete(req):
    id = req.GET["id"]
    Vocabulary.objects.filter(pk=id).delete()
    return redirect("/")
```

##URL
```python:urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.Voca.as_view()),
    path('update', views.update),
    path('delete', views.delete)
]
```

##テンプレート
html templateの作成

```html:templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Voca</title>
    {% load static %}
    <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
</head>
<body>
<table border="1" style="border-collapse: collapse;">
    <thead>
        <tr>
           <th id="name">単語</th>
           <th id="mean">意味</th>
           <th id="example">例文</th>
           <th>記憶カウント</th>
           <th>チェック</th>
           <th>削除ボタン</th>
        </tr>
    </thead>
    <tbody>
    <!-- None処理してないのでvocabularyを何も登録しないとエラー発生 -->
    {% for voca in vocabulary %}
        <tr>
            <td><span class="c1">{{ voca.name }}</span></td>
            <td><span class="c2">{{ voca.mean }}</span></td>
            <td><span class="c3">{{ voca.example }}</span></td>
            <td><span class="c4">{{ voca.mem_count }}</span></td>
            <td><span class="c5">{{ voca.check }}</span></td>
            <td><input type="button" value="削除" onclick="deleteById({{ voca.id }})"></td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="reset" value="リセット">
    <input type="submit" value="挿入">
    <input type="button" value="修正" onclick="update()">
</form>
<script src="{% static 'js/local.js' %}"></script>
</body>
</html>
```

```html:error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Error</title>
</head>
<body>
{{ error_msg }}
</body>
</html>
```

##Javascript
static javascript

```javascript:static/js/local.js
// thを押して単語の意味などを隠す
var nameVisable = true
var meanVisable = true
var exampleVisable = true

$("#name, #mean, #example").click(function() {
    if ($(this).attr("id") === "name") {
        if (nameVisable) {
            $(".c1").hide()
            nameVisable = false
        } else {
            $(".c1").show()
            nameVisable = true
        }
    } else if ($(this).attr("id") === "mean") {
        if (meanVisable) {
            $(".c2").hide()
            meanVisable = false
        } else {
            $(".c2").show()
            meanVisable = true
        }
    } else if ($(this).attr("id") === "example") {
        if (exampleVisable) {
            $(".c3").hide()
            exampleVisable = false
        } else {
            $(".c3").show()
            exampleVisable = true
        }
    };
})

// 単語の名前を押すとFormに値をセット
$(".c1").click(function() {
    $("input[name='name']").val($(this).html())
    var i = 0
    $.each($(this).parent().siblings(), function() {
        if (i === 0) {
            $("textarea[name='mean']").val($(this).find("span").html())
        } else if (i === 1) {
            $("textarea[name='example']").val($(this).find("span").html())
        } else if (i === 2) {
            $("input[name='mem_count']").val($(this).find("span").html())
        } else if (i === 3) {
            if ($(this).find("span").html() === "True") {
                $("select[name='check'] option[value='2']").attr('selected','selected');
            } else if ($(this).find("span").html() === "False") {
                $("select[name='check'] option[value='3']").attr('selected','selected');
            }
        }
        i++
    })
})

// delete 単語
function deleteById(id) {
    $.get("/delete", {"id" : id})
    location.href = "/"
}

// update 単語
function update() {
    $("form").attr("action", "/update").submit()
}
```

##最終フォルダーのtree構造
最後にprjのtree構造は以下のようになる。

```text:tree構造
C:.
│  manage.py
│  run.py
│
├─vocaApp
│  │  admin.py
│  │  apps.py
│  │  forms.py
│  │  models.py
│  │  tests.py
│  │  urls.py
│  │  views.py
│  │  __init__.py
│  │
│  ├─migrations
│  │  │  0001_initial.py
│  │  │  __init__.py
│  │  │
│  │  └─__pycache__
│  │          0001_initial.cpython-36.pyc
│  │          __init__.cpython-36.pyc
│  │
│  ├─static
│  │  └─js
│  │          jquery-3.3.1.min.js
│  │          local.js
│  │
│  ├─templates
│  │      error.html
│  │      index.html
│  │      __init__.py
│  │
│  └─__pycache__
│          admin.cpython-36.pyc
│          apps.cpython-36.pyc
│          forms.cpython-36.pyc
│          models.cpython-36.pyc
│          urls.cpython-36.pyc
│          views.cpython-36.pyc
│          __init__.cpython-36.pyc
│
└─vocabulary
    │  settings.py
    │  urls.py
    │  wsgi.py
    │  __init__.py
    │
    └─__pycache__
            settings.cpython-36.pyc
            urls.cpython-36.pyc
            wsgi.cpython-36.pyc
            __init__.cpython-36.pyc
```

##Build & Run
build & run

```text:command
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
```

##Fake Migrate 
テーブルがおかしくなりやり直したいとき

```text:command
python manage.py migrate --fake vocaApp zero
```