Django しょほ

Macです。



####環境
venvで環境作ってdjangoインストールします。
`$python -m venv <environmentName>`
`$cd <environmentName>`
`$source bin/activate`
`$pip install --upgrade pip`
`$pip install django==2.0.1`
`$django-admin startproject <projectName> .`
`$python manage.py startapp <applicationName>`
2019年末でdjangoはバージョン３系です。何もしないでインストールするとそれになり、adminにログインできなくなる場合があるので、2系にしとくのが無難です。

以下で行きます。
environmentName = reh
projectName = rehabili
applicationName = reha
<br />
<br />


###settings.pyでDjangoの環境設定をいじります。

```python:rehabili/python:setting.py
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'
```

<br />
###データベースへの作成
`python manage.py migrate`




<br />
###スーパーユーザの追加
`python manage.py createsuperuser`
適当に指示に従います。




<br />
###開発サーバの起動
`python manage.py runserver`

 http://127.0.0.1:8000/ or http://localhost:8000/ をブラウザで開いてロケットが出てれば成功




<br />
###モデルの作成


```python:reh/models.py
from django.db import models


class SamplePost(models.Model):
    postname = models.CharField('name', max_length=999)
    postfrom = models.CharField('from', max_length=999, blank=True)
    postlength = models.IntegerField('length', blank=True, default=0)

    def __str__(self):
        return self.postname


class SamplePost2(models.Model):
    postname2 = models.ForeignKey(SamplePost, verbose_name='postname', related_name='samplepost2', on_delete=models.CASCADE)
    comment = models.TextField('comment', blank=True)

    def __str__(self):
        return self.comment

```
####管理画面に追加
```python:reh/admin.py
from django.contrib import admin
from reh.models import SamplePost, SamplePost2

admin.site.register(SamplePost)
admin.site.register(SamplePost2)
````


####アプリの追加
```python:rehabili/python:setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'reh', #ここに作成したアプリ名を追加
]
```
<br />
makemigratoinsでデータベースに追加するための前処理をします。最後のアプリケーション名はなくても大丈夫ですが、プロジェクトが大きくなると多分長くなります。
`python manage.py makemigrations reh`
<br />
データベースに追加
`python manage.py migrate`


####管理画面へログイン
先ほど作ったスーパーユーザーでログイン
http://localhost:8000/admin/
![Screen Shot 2019-12-31 at 13.51.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212730/b8b2e4d6-c209-5faa-6ae9-0da85a435fbd.png)








####管理画面を修正
ここを参考にしました。管理画面の一覧から修正できる項目が増えます。
https://qiita.com/kaki_k/items/7b178ad39394a031b50d

```python:reh/admin.py
from django.contrib import admin
from reh.models import SamplePost, SamplePost2


class SamplePostAdmin(admin.ModelAdmin):
    list_display = ('id', 'postname', 'postfrom', 'postlength',)  # 一覧に出したい項目
    list_display_links = ('id', 'postname',)  # 修正リンクでクリックできる項目

admin.site.register(SamplePost, SamplePostAdmin)


class SamplePost2Admin(admin.ModelAdmin):
    list_display = ('id', 'comment',)
    list_display_links = ('id', 'comment',)
    raw_id_fields = ('postname2',)   # 外部キーをプルダウンにしない（データ件数が増加時のタイムアウトを予防）

admin.site.register(SamplePost2, SamplePost2Admin)
````



####URL スキームの設計
アプリフォルダ内にurls.pyを作成し、プロジェクトのurls.pyからインクルードします。

```python:reh/views.py
from django.shortcuts import render
from django.http import HttpResponse


def reh_list(request):
    return HttpResponse('reh一覧')

```


```python:reh/urls.py
from django.urls import path
from reh import views

app_name = 'reh'
urlpatterns = [
    path('list/', views.reh_list, name='reh_list'),
]

 ````


```python:rehabili/urls.py
from django.contrib import admin
from django.urls import path, include

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

以下のURLが生きてきます
http://127.0.0.1:8000/reh/list/



####フォームを作る

少し横道にそれて、普通にフォームを作ります。ただしこの作り方だとDBに反映できません。

````python:reh/views.py

### 追記です
def formInit(request):
    params = {
        'title':'Hello World',
        'msg':'名前を入力してください',
    }
    return render(request,'reh/hello.html', params)



def formTest(request):
    msg = request.POST['msg']
    params = {
        'title':'Hello World',
        'msg':'hello '+msg+'!',
    }
    return render(request,'reh/hello.html', params)
````

```python:reh/urls.py
from django.urls import path
from reh import views

app_name = 'reh'
urlpatterns = [
    path('list/', views.reh_list, name='reh_list'),
    path('formInit/', views.formInit, name='formInit'),
    path('formTest/', views.formTest, name='formTest'),
]
````


```html:reh/template/reh/hello.html
{% load static %}
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
    </head>
    <body>
        <h1>{{ title }}</h1>　　　
        <p>{{ msg }}</p>
        <!--普通のHTMLと同様にactionを指定します-->
        <form action="{% url 'reh:formTest' %}" method = "post"> 
            {% csrf_token %} <!--CSRF対策-->
            <label for = "msg">何か入れて</label>
            <input id = "msg" type="text" name ="msg">
            <input type="submit" value="input">
        </form>
    </body>
</html>
````


![dddd.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212730/1f907547-16ee-a0c8-e352-d41516d2bbd3.jpeg)

ブラウザ上のみでは普通に動きます。
このままじゃDBに反映できないので、Djangoのforms.py、Formクラスを使います。
差分の部分のみ書きます。


````python:reh/views.py
from .forms import formInitForm

def formInit(request):
    # params = {
    #     'title':'Hello World',
    #     'msg':'名前を入力してください',
    # }
    # return render(request,'reh/hello.html', params)

    params = {
        'title':'Hello World',
        'msg':'名前を入力してください',
        'form': formInitForm(),
    }
    if (request.method=='POST'):
        params['msg'] = 'こんにちは!'+request.POST['name']+'さん!<br>'+request.POST['area']+'にお住まいで<br>年齢は'+request.POST['age']+'歳なんですね!<br>よろしくお願いします。'
        params['form']= formInitForm(request.POST)

    return render(request,'reh/hello.html', params)    

# def formTest(request):
#     msg = request.POST['msg']
#     params = {
#         'title':'Hello World',
#         'msg':'hello '+msg+'!',
#     }
#     return render(request,'reh/hello.html', params)


````

```python:reh/urls.py
    path('formInit/', views.formInit, name='formInit'),
    # path('formTest/', views.formTest, name='formTest'),
````


テンプレート内はコメントアウトでも中のpython実行されちゃうんで、差分は各自確認ください。

```html:reh/template/reh/hello.html
{% load static %}
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
    </head>
    <body>
        <h1>{{ title }}</h1>　　　

        <p>{{msg|safe}}</p>     
        <form action= "{% url 'reh:formInit' %}" method = "post"> 
            {% csrf_token %} 
            <ul>
            {{form.as_ul}} <!--ここがforms.pyの項目-->    
            </ul>
            <input type="submit" value="input"> 
        </form>

    </body>
</html>
````

![aaa.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212730/ee408175-027c-9bb4-fbd2-347dae173bd4.jpeg)



####CRUD/ create, read, update, delete
さて、横道から戻して、一覧から修正や削除、追加ができるようにします。
まずは一覧を作ります。

いくつかテンプレートhtml作るので、baseを作ります。適当にcss当ててます。

```html:reh/template/reh/base.html
{% load i18n static %}
<!DOCTYPE html>{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE|default:'en-us' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
h4{
  font-size: 20px;
  display: inline-block;
  margin-right: 30px;
}
.container{
    width: 100%;
    max-width: 800px;
    margin: 60px auto;
}
.btn{
  text-decoration: none;
    padding: 5px 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}
button.btn{
    font-size: 16px;
    display: inline-block;
    line-height: 1;
}
th,td{
  min-width: 100px;
  text-align: center;
}
table{
  border-collapse:collapse;
  margin:0 auto;
}
th{
  color:#005ab3;
  min-width: 120px;
}
td{
  border-bottom:1px dashed #999;
}
th,tr:last-child td{
  border-bottom:2px solid #005ab3;
}
td,th{
  padding:10px;
}
form input{
  padding: 5px;
    font-size: 20px;
    margin-bottom: 20px;
}
form label{
    padding: 5px;
    font-size: 20px;
    margin-bottom: 20px;
    min-width: 90px;
    display: inline-block;
}
.flex{
  display: flex;
}
</style>
{% block extra_css %}{% endblock %}
<title>{% block title %}My rehs{% endblock %}</title>
</head>
<body>
  <div class="container">
    {% block content %}
      {{ content }}
    {% endblock %}
  </div>
{% block extra_js %}{% endblock %}
</body>
</html>
````



```html:reh/template/reh/reh_list.html
{% extends "reh/base.html" %}

{% block title %}一覧{% endblock title %}

{% block content %}
    <h4>一覧</h4>
    <table class="table table-striped table-bordered">
      <thead>
        <tr>
          <th scope="col">ID</th>
          <th scope="col">NAME</th>
          <th scope="col">FROM</th>
          <th scope="col">LENGTH</th>
        </tr>
      </thead>
      <tbody>
        {% for reh in rehs %} <!-- ① -->
        <tr>
          <th scope="row">{{ reh.id }}</th>
          <td>{{ reh.postname }}</td>
          <td>{{ reh.postfrom }}</td>
          <td>{{ reh.postlength }}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
{% endblock content %}
````

① {% for reh in rehs %} の部分で一覧を読み込みますので、views.pyを修正します。

```python:reh/views.py
def reh_list(request):
#    return HttpResponse('一覧')

    #ここでSamplePostをid順に全て持ってきてrehsに格納
    rehs = SamplePost.objects.all().order_by('id')

    #renderメソッドの第3引数に辞書を入れられるので、上で持ってきたrehsという名前でrehsを入れる。
    return render(request, 'reh/reh_list.html', {'rehs': rehs})

```

これで http://localhost:8000/reh/list/ に一覧が表示されます。

次に追加ボタンを作成します。


```html:reh/template/reh/reh_list.html
{% extends "reh/base.html" %}

{% block title %}一覧{% endblock title %}

{% block content %}
    <h4>一覧</h4>
    <!-- ここに追加 -->
    <a href="{% url 'reh:reh_add' %}" class="btn">追加</a>
    <table class="table table-striped table-bordered">
      <thead>
        <tr>
          <th scope="col">ID</th>
          <th scope="col">NAME</th>
          <th scope="col">FROM</th>
          <th scope="col">LENGTH</th>
        </tr>
      </thead>
      <tbody>
        {% for reh in rehs %} <!-- ① -->
        <tr>
          <th scope="row">{{ reh.id }}</th>
          <td>{{ reh.postname }}</td>
          <td>{{ reh.postfrom }}</td>
          <td>{{ reh.postlength }}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
{% endblock content %}
````

{% url 'reh:reh_add' %} reh_add をurls.pyに追加します。
reh_edit というviewを追加します。views.pyのreh_editを呼び出すようにします。

```python:reh/urls.py
path('list/add/', views.reh_edit, name='reh_add'),
```

```python:reh/views.py
def reh_edit(request, reh_id=None):
    reh_temp = SamplePost()

    if request.method == 'POST':
        #追加ページでフォームをPOSTする場合の処理
        form = SamplePostForm(request.POST, instance=reh_temp)
        #POSTされたformのバリデーション
        if form.is_valid():
            reh_temp = form.save(commit=False)
            reh_temp.save()
            #formの保存が終わったら一覧に戻ります
            return redirect('reh:reh_list')

    else: #GET時、一覧から追加ボタンを押した時はこちらを表示
        #SamplePostインスタンスからフォームを作成
        form = SamplePostForm(instance=reh_temp)

    #辞書にformとreh_id=Noneを入れてrenderメソッドで編集用ページ遷移させます
    return render(request, 'reh/reh_edit.html', dict(form=form, reh_id=reh_id))

```

上でのformのモデルをforms.pyで作ります。

```python:reh/forms.py
from django.forms import ModelForm
from reh.models import SamplePost

class SamplePostForm(ModelForm):
    #すでにSamplePostで定義してあるプロパティを使用する場合はMetaクラスで
    class Meta:
        model = SamplePost
        #　明示的もしくはモデルの一部のプロパティだけ使う場合
        fields = ('postname', 'postfrom', 'postlength', )
```



編集用ページテンプレートを作成します。

```html:reh/template/reh/reh_edit.html
{% extends "reh/base.html" %}

{% block title %}タイトル{% endblock title %}

{% block content %}
  <!-- formのアクションとして、formの値をreh_editビューに送ります -->
  <form action="{% url 'reh:reh_add' %}" method="post">      
    {% csrf_token %}
    #後述するforms.pyを呼びます
    {{ form.as_ul }}
    <div>
      <button type="submit" class="btn">送信</button>
    </div>
  </form>
  <a href="{% url 'reh:reh_list' %}" class="btn">戻る</a>
{% endblock content %}
````


<br />

次に簡単な削除を（確認画面とかなしでいきなり消えるので簡単）

reh_idを取得してそれを消します。

templateにボタンを追加し、id付きで次に作るviewに飛ばします。

```html:reh/template/reh/reh_list.html
{% extends "reh/base.html" %}

{% block title %}一覧{% endblock title %}

{% block content %}
    <h4>一覧</h4>
    <!-- ここに追加 -->
    <a href="{% url 'reh:reh_add' %}" class="btn">追加</a>
    <table class="table table-striped table-bordered">
      <thead>
        <tr>
          <th scope="col">ID</th>
          <th scope="col">NAME</th>
          <th scope="col">FROM</th>
          <th scope="col">LENGTH</th>
          <th scope="col">操作</th>
        </tr>
      </thead>
      <tbody>
        {% for reh in rehs %} <!-- ① -->
        <tr>
          <th scope="row">{{ reh.id }}</th>
          <td>{{ reh.postname }}</td>
          <td>{{ reh.postfrom }}</td>
          <td>{{ reh.postlength }}</td>
　　　　　　<td>
            <!-- aタグのリンク先にreh_del viewにして、引数にIDつける -->
            <a href="{% url 'reh:reh_del' reh_id=reh.id %}" class="btn">削除</a>
          </td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
{% endblock content %}
````


```python:reh/views.py

def reh_del(request, reh_id):
    #指定したIDのインスタンスがある場合はそれを、ない場合は404(django.http.Http404)呼び出し、
    #その後deleteして一覧にリダイレクト。
    reh = get_object_or_404(SamplePost, pk=reh_id)
    reh.delete()
    return redirect('reh:reh_list')
```

URLはこれですね。

```python:reh/urls.py
path('list/del/<int:reh_id>/', views.reh_del, name='reh_del'),
```



<br />


最後に修正ボタンを。
一覧ページにボタンをつけましょう。

```html:reh/template/reh/reh_list.html
{% extends "reh/base.html" %}

{% block title %}一覧{% endblock title %}

{% block content %}
    <h4>一覧</h4>
    <!-- ここに追加 -->
    <a href="{% url 'reh:reh_add' %}" class="btn">追加</a>
    <table class="table table-striped table-bordered">
      <thead>
        <tr>
          <th scope="col">ID</th>
          <th scope="col">NAME</th>
          <th scope="col">FROM</th>
          <th scope="col">LENGTH</th>
          <th scope="col">操作</th>
        </tr>
      </thead>
      <tbody>
        {% for reh in rehs %} <!-- ① -->
        <tr>
          <th scope="row">{{ reh.id }}</th>
          <td>{{ reh.postname }}</td>
          <td>{{ reh.postfrom }}</td>
          <td>{{ reh.postlength }}</td>
　　　　　　<td>
            <!-- 修正ボタン追加 これも引数にidを。追加を同じviewを使います。 -->
            <a href="{% url 'reh:reh_mod' reh_id=reh.id %}" class="btn ">修正</a>
            <a href="{% url 'reh:reh_del' reh_id=reh.id %}" class="btn">削除</a>
          </td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
{% endblock content %}
````

views.pyのreh_editを修正します。


```python:reh/views.py
def reh_edit(request, reh_id=None):
    #追加の場合との場合わけは、idを引数に必要とするかどうかなのでそれを利用
    #reh_tempに値が入っているかであとは追加と同じ処理
    if reh_id:
        #修正場合の処理。インスタンスは既存の値
        reh_temp = get_object_or_404(SamplePost, pk=reh_id)
    else:
        #こっちが新規で追加する場合の処理。インスタンスは初期値になっている
        reh_temp = SamplePost()

    if request.method == 'POST':
        form = SamplePostForm(request.POST, instance=reh_temp)
        if form.is_valid():
            reh_temp = form.save(commit=False)
            reh_temp.save()
            return redirect('reh:reh_list')

    else:
        form = SamplePostForm(instance=reh_temp)

    return render(request, 'reh/reh_edit.html', dict(form=form, reh_id=reh_id))

```

```python:reh/urls.py
path('list/mod/<int:reh_id>/', views.reh_edit, name='reh_mod'),
```


編集用ページテンプレートも追加のみでなく修正にも対応させます。

```html:reh/template/reh/reh_edit.html
{% extends "reh/base.html" %}

{% block title %}タイトル{% endblock title %}

{% block content %}
    <!-- views.pyと同様に、reh_idの有無で判断します -->
    {% if reh_id %}
    <form action="{% url 'reh:reh_mod' reh_id=reh_id %}" method="post">
    {% else %}
    <form action="{% url 'reh:reh_add' %}" method="post">
    {% endif %}
      {% csrf_token %}
      <ul>
      {{ form.as_ul }}
      </ul>
      <div class="flex">
          <button type="submit" class="btn">送信</button>
          <a href="{% url 'reh:reh_list' %}" class="btn">戻る</a>
      </div>
    </form>
{% endblock content %}
````


以下のようなものができたと思います。
<img width="940" alt="Screen Shot 2020-01-02 at 16.52.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212730/b0364f3c-bb34-28bb-407f-255715a999cc.png">


<img width="716" alt="Screen Shot 2020-01-02 at 16.52.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212730/362fad26-8f81-6fa7-441a-580ca116b253.png">