mizzsugar’s blog

日々感じていることや学んだことを書きます。エンジニアリング以外にも書くかもしれません。

PyConに初めて参加してみて学んだこと

初めてPyConに参加しましたので、参加したセッションで学んだことをまとめました! 普段考えなくても動くようなプログラムの裏側を学ぶことができて、 よりたくさんのことができるようにするための糧となりました^^

何より、PyConで未経験から転職したというLTが最も勇気づけられて・・・ 私もPyConでエンジニア転職したい!!!!!!!(本気です!笑)


●印象に残ったセッションと学んだこと●

-あなたとPython今すぐパッケージン

-トイル撲滅記

-Pythonで学ぶUnixプロセス


1日目

あなたとPython今すぐパッケージン(pipenvの話)

今まで何気なくpipを使っていたけれども、pipの仕組みを学ぶきっかけになりました。

(あとから調べた) pipとは パッケージイントールするためのツール。パッケージは、PyPIPython Package Index)というPythonパッケージを管理する団体で管理しているものを利用する。

virtualenvとは

パッケージを切り分ける仮想環境。 プロジェクトごとにパッケージのバージョンを依存関係を切り替えるために構築する。

pipfileとは

依存ライブラリのフォーマット。 requirement.txtの置き換え。

pipenv登場前:virtualenvを構築後、pipでvirtualenvにパッケージをインストール ↓

pipenvたるものが登場

※pipenvとは、「pip+virtualenv+pipfileを包括的に取り扱うツール」

pipenv:「pip install pipenv」のコマンドだけで環境構築もパッケージインストールもできる

(けど遅い)

疑問:個人的には、pipenvじゃなくてrequirement.txtでも間に合うのですが、

pipenvの方が良いのは具体的にはどんな開発をするとき?


2日目

トイル撲滅記

普段保守運用で課題感を感じているからか、やはりSRE好きだなと思いました。

「SREはトイル撲滅がメイン」

トイルとは・・・「本番サービスに関する作業で手作業で繰り返し行われ自動化することが可能であり戦術的で長期的な価値を持たず作業量がサービスの成長に比例する」


Case

環境構築作業(職人技になっている、機能開発開始までに時間がかかる)

ー>Docker Compose

Docker-composeのみで開発できるようになるし、環境差異によるバグがおさえられる (やりたい!!!)


Case

デプロイ作業(手順書作成ー>1代ずつ縮退・更新、チェッカーが必要)

ー>Amazon ECSの利用

コンテナオーケストレーション、サーバー管理不要、スケジュール不要


ー>CI/CDの利用

※CI(Continuous Intergration) / CD(Continuous Delivery, Deploy)

プッシュするとー>Unitテストー>Flake8ー>Radon(コードの複雑さを図るツール)

走る。

レビューの手間が省ける。 書き方が統一化されて保守しやすくなる。 ( やりたい!!!)


Case 管理サーバにログインできない。ログを見てわかる状態が望ましい。

Effective Python->Noneを返すよりもExceptionを返そう

Exceptionでloggingするべき。

特別な例外を出すことでやりたいことの明確化。


AWS X-Ray(setting.pyに設定する)

・リクエストに関するデータ収集

SQLがどれだけ発行されているかもわかる

非エンジニアでも調査できるようになった。 (素晴らしい!)


Pythonで学ぶUnixプロセス

普段何気なく使っていたbashの仕組みを学べて良かった。

大前提:すべてのプログラムはプロセスによって動いている。

プログラムの実行時にプロセスも生まれ、終了したらプロセスも死ぬ


システムコール

アプリケーションプログラムに対して提供する機能を呼び出すこと。

プログラムが動作するときの2つの処理空間

カーネルとユーザーランド

カーネル:ハードウェアとやりとり

ユーザーランド:カーネルとやり取りする空間。ユーザーランドの仕組みをシステムコールという


プロセス:

あらゆるコードはプロセスで実行される

プログラムの実行時にプロセスも生まれ、終了したらプロセスも死ぬ

プロセスにはIDがある

省略されてpidと呼ばれる

os.getpid()で現在実行しているプロセスを確認できる


プロセスにはリソースの制限がある

カーネルによって1プロセス毎にリソースが制限される

(本題)プロセスは子プロセスを作れる:

fork(2)システムコールで子プロセスを作れる

fork(2)は親プロセスのコピー

fork(2)からifが分岐する

親が死んでも子は生き残る。

生き残った子プロセスを孤児プロセスという

親プロセスは、子プロセスが終わるまで待てる

os.wait()でできる


ゾンビプロセス:

子プロセスのほうが先に終了

親プロセスはwaitで子プロセスの終了ステータスを要求しない

孤児とゾンビのち外

先に自分が死んでwait待ちなのがゾンビ


デーモンプロセス:

端末から制御されるのではなく、バックグラウンドから制御

デーモン化はプロセスを完全に制御端末やシグナルから切り離せる

add_error(field, error)で「Pythonではfieldじゃなくてattributeじゃないの!?」と慌てた話

Djangoドキュメントを読んで慌てたけれども、ドキュメントは間違えていなかったという話です。

add_errorとは

エラーを引数にもつフォーム型の変数に、エラーを追加する関数です。

混乱

fieldってなんだ!? Pythonでは、フィールドのことを attributeというのではないのか!? と焦りました:(;゙゚'ω゚'):

Djangoドキュメントはこう書いていた

The field argument is the name of the field to which the errors should be added.

引用 https://docs.djangoproject.com/en/2.0/ref/forms/api/

引数「field」には、エラーが追加されるフィールドの名前を入れてください

とのこと。

こういうことだった

add_error(field, error)

のfieldは、field型のattributeだった。

たとえば、

add_error(‘password’, error)

こちらは、forms.pyで定義しているクラスの

‘password’ というフィールドを引数にしています。

forms.py

from django import forms

class RegistrationForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField() #ここのこと
    email = forms.EmailField()

add_error(password, error)じゃダメなの?

ダメです。

というのも、 fieldという引数をpasswordという名前の引数に変えたので別物 になってしまうからです。

【Python・Django入門】ログイン画面、ユーザー登録画面を作る②

mizzsugar.hatenablog.com

こちらの記事の続きです。 ①の記事では、ユーザー登録機能とログイン機能を作りました。

この記事では、パスワードの変更機能とログアウト機能を作ります。

パスワード変更機能

urls.pyにパスワード変更画面のURLを追加します。

urls.py

from django.contrib import admin
from django.urls import path
import myApp.forms
import myApp.views

# 他にもありますが、今回はパスワード追加URLのみ
urlpatterns = [
    path("change_password", myApp.views.change_password)
]

forms.pyにパスワード変更用のフォームを定義します。

forms.py

from django import forms
from django.forms import PasswordInput

class ChangePasswordForm(forms.Form):
    new_password = forms.CharField(label='新しいパスワード', widget=PasswordInput())  # label = ''で画面に表示されるラベルを設定します。widget=PasswordInput()で、入力した値が●●●で表示されます。PasswordInput()を利用するには、PasswordInputをインポートする必要があります。

views.py

import myApp.forms

def change_password(request):
    if not request.user.is_authenticated: # このURLに移動した時点でログイン認証されていなかった場合
        return django.http.HttpResponseRedirect('/login') # ログイン画面に移動します
    if request.method == 'POST': # ログイン認証がされていて、POSTメソッドが実行された場合
        change_password_form = myApp.forms.ChangePasswordForm(request.POST) 
        new_password = request.POST['new_password']
        if len(new_password) < 8:
            change_password_form.add_error('new_password', "文字数が8文字未満です。")
        if not has_digit(new_password):
            change_password_form.add_error('new_password',"数字が含まれていません")
        if not has_alphabet(new_password):
            change_password_form.add_error('new_password',"アルファベットが含まれていません")
        if change_password_form.has_error('new_password'):
            return render(request, 'change_password.html', {'change_password_form': change_password_form}) # 新しいパスワードが8文字以上で英字数字含むことを満たしていない場合、パスワード変更画面に戻ります。
        user = request.user # userにリクエストを送ったユーザーの情報を代入します。ログインされているユーザーです。
        user.set_password(request.POST['new_password']) # set_password(password)でそのユーザーのパスワードを引数のパスワードに変更します。
        user.save() # save()でそのユーザーの情報の変更を保存します。
        # なお、set_password(password)でパスワードを変えると、自動的にログアウトされる動きをする
        return django.http.HttpResponseRedirect('/changed_password') # 変更したら、changed_passwordに移動します。
    else:
        change_password_form = myApp.forms.ChangePasswordForm()
    return render(request, 'change_password.html', {'change_password_form': change_password_form})

def changed_password(request):
    return render(request, 'changed_password.html') # このページに移動されている時にはログアウトされています。

templatesフォルダに、パスワード変更画面と、パスワードが変更された後に表示する画面のhtmlファイルを配置します。

changed_password.html(パスワードを変更する画面)

<form action="/change_password" enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ change_password_form }}
    <input type="submit" value="submit">
</form>

f:id:mizzsugar:20180704230907p:plain

changed_password.html(パスワードが変更された後の画面)

<p>パスワードの変更に成功しました。</p>

<a href="/login">ログイン画面で再度ログインする</a>
<-- ログアウトされた状態なので、ログイン画面へのリンクを追加します-->

f:id:mizzsugar:20180704231915p:plain

ログインしたら入れる、投稿画面と投稿一覧画面に、 ログアウト画面のリンクを追加します。

new_post.html

<a href="/change_password">パスワードを変更する</a>

<p>こんにちは、{{ user }}さん</p>
<form action="/post_new_post" enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="submit">
</form>

list.html

<p>こんにちは、{{ user }}さん</p>

<h2>POSTS</h2>


<a href="/change_password">パスワードを変更する</a>

{% load static %}
  <img src="{% static "coffee.jpg" %}">

{% for post in posts %}
    <img src = {% static post.file_path %}>
    <p>{{ post.comment }}</p>
    <p>{{ post.posted_at }}</p>
{% endfor %}

ログアウト機能

パスワード変更機能と比べて、ログアウト機能はすぐに終わります笑

urls.pyにパスワード変更画面のURLを追加します。

urls.py

from django.contrib import admin
from django.urls import path
import myApp.forms
import myApp.views

# 他にもありますが、今回はパスワード追加URLのみ
urlpatterns = [
    path("logout", myApp.views.logout)
]

templatesフォルダ内にログアウト画面のhtmlファイルを配置します。

logout.html

<p>ログアウトしました。</p>

<a href="/login">ログイン画面に移動する</a>

views.py

import django.http
from django.contrib.auth import authenticate, logout as django_logout # ここでは、def logout(request)内で利用するlogoutはdiango_logoutと定義します。同じ名前だとエラーになりました。


def logout(request):
    django_logout(request) # logoutモジュールのlogoutでログイン認証を解除します。
    return django.http.HttpResponseRedirect('/logout') # logout画面へ移動します。

ログインしたら入れる、投稿画面と投稿一覧画面に、 ログアウト画面のリンクを追加します。

new_post.html

<a href="/logout">ログアウト</a>
<a href="/change_password">パスワードを変更する</a>

<p>こんにちは、{{ user }}さん</p>
<form action="/post_new_post" enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="submit">
</form>

list.html

<p>こんにちは、{{ user }}さん</p>

<h2>POSTS</h2>


<a href="/logout">ログアウト</a>
<a href="/change_password">パスワードを変更する</a>

{% load static %}
  <img src="{% static "coffee.jpg" %}">

{% for post in posts %}
    <img src = {% static post.file_path %}>
    <p>{{ post.comment }}</p>
    <p>{{ post.posted_at }}</p>
{% endfor %}

後々、パスワードを忘れた時のための機能を追加します!

try/except(try/catch)の処理順序を理解する

try/exceptの処理の順序がよくわかっていないことがわかったので、自分の理解のために書きます。

今回は、Djangoでユーザー登録する機能を例にします。

パスワードが以下の条件を満たしていなかったらエラーとなり、

  • 8文字以上

  • 半角英字と半角数字の両方から成り立っている

を満たしていたら、データベースにユーザーの情報が登録されて、ログインした状態でブログの投稿画面に移動します。

f:id:mizzsugar:20180628210859p:plain
ユーザー登録画面です。


urls.pyで URLを指定します。 今回は、必要な分だけ設定します。

urls.py

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

import myApp.views

urlpatterns = [
    path("post_new_post", myApp.views.post_new_post),
    path('admin/', admin.site.urls),
    path("registration", myApp.views.registation_user)
]
# post_new_postは、ブログ投稿用のURL
# adminは、アプリ管理者しか入れない、管理用のURL
# registrationは、ユーザー登録用の URL

forms.py

from django import forms

class RegistrationForm(forms.Form): # 登録画面で入力するフォームのフィールドを定義します。
    username = forms.CharField()
    password = forms.CharField()
    email = forms.EmailField()

exceptions.pyでは、例外の設定をします。

exceptions.py

class MyAppError(Exception): # Exceptionクラスを継承するエラークラスを定義します。
    pass

class RegistrationError(MyAppError): # Exceptionクラスを継承するエラークラスを引数とします。
    def __init__(self, form): # Registration classのインスタンスを生成するために、__init__メソッドを定義します。例外クラスのインスタンスに、フォームを引数に持たせます。
        self.form = form # エラーになったフォームを例外に持たせて、エラーの詳細としています。

domain.pyでは、アプリのコアとなる処理を設定します。

domain.py

import myApp.exceptions
import uuid
import re
import myApp.forms
from django.contrib.auth.models import User


def has_digit(text): # 文字列に数字を含むかどうかを判定する関数です。
    if re.search("\d", text): 
        return True
    return False

def has_alphabet(text): # 文字列に英字(小文字または大文字)を含むかどうかを判定する関数です。
    if re.search("[a-zA-Z]", text):
        return True
    return False


def register(user_profile): # ユーザー情報を登録する関数です。
    registration_form = myApp.forms.RegistrationForm(user_profile)
    password = registration_form.password
    if len(password) < 8:
        registration_form.add_error('password', "文字数が8文字未満です。") # まず、エラーがあるか判定します。文字列が8文字未満なら、「文字数が8文字未満です。」というエラーをフォームに追加します。
    if not has_digit(password):
        registration_form.add_error('password',"数字が含まれていません") # has_digitがfalseなら、「数字が含まれていません」というエラーをフォームに追加します。
    if not has_alphabet(password): # has_alphabetがfalseなら、「アルファベットが含まれていません」というエラーをフォームに追加します。
        registration_form.add_error('password',"アルファベットが含まれていません")
    if registration_form.has_error('password'): # パスワードがエラーの条件に合致するか判定し、エラーがある場合フォームに追加した後、フォームがエラーを持っているか判定します。
        raise myApp.exceptions.RegistrationError(registration_form) # エラーを持っている場合、RegistrationErrorを発生させます。RegistrationErrorのコンストラクタの引数は、registration_formとします。
    user = User.objects.create_user(
        username=registration_form.username, password=password, email=registration_form.email) # エラーを持っていないと判定された場合、まずデータベースにユーザー情報を登録します。
    return user # 登録した後、登録した内容を代入された変数userを返します。

views.pyでは、http通信する際の処理を設定します。

views.py

import django.http
from django.shortcuts import render
import re
from django.contrib.auth import authenticate, login as django_login
import myApp.exceptions
import myApp.domain


def get_registration_user(request):# URL'registration'でgetメソッドが発生した時に利用する関数
    registration_form = minsta.forms.RegistrationForm()
    return render(request, 'registration.html', {'registration_form': registration_form})

def post_registration_user(request): #  URL'registration'でpostメソッドが発生した時に利用する関数
    try:
        user = myApp.domain.register(request.POST) #まず、tryでdomain.pyで設定したregister関数を実行します。引数user_profileには、HTMLフォームで送信された内容(request.POST)を入れます。
    except myApp.exceptions.RegistrationError as e: # try文でRegistrationErrorという例外が発生した場合
        return render(request, 'registration.html', {'registration_form': e.registration_form, 'user':request.user}) # registration.htmlに移動します。RegistrationErrorのインスタンスに含まれているフォームを利用します。
    django_login(request, user) # 例外が発生せずユーザー情報が登録されたら、userにはユーザー情報が含まれます。それを利用してlogin関数でログインします。
    return django.http.HttpResponseRedirect('/post_new_post') # 例外が発生しなかった場合、ログインした後に、'post_new_post'に移動します。

def registration_user(request):
    if request.method == 'POST': #リクエストメソッドにPOSTが利用されたら、post_registration_userの結果を返します。
        return post_registration_user(request)
    return get_registration_user(request) #リクエストメソッドにGEETが利用されたら、get_registration_userの結果を返します。

def post_new_post(request): # post_new_postが呼び出された時の関数です。
    return render(request, 'new_post.html', {'form': form, 'user':request.user})
# 今回は、投稿画面に移動するだけとします。

try/exceptを利用しているメソッドを見てみます。

# views.pyの一部抜粋
def post_registration_user(request):
    try:
        user = myApp.domain.register(request.POST) # この処理が1番目に実行する処理。
    except myApp.exceptions.RegistrationError as e: # 「except 例外」に、tryの実行結果で「except 例外」で指定した例外が発生されている場合の処理を書きます。例外が発生されているかどうかを判定するのが2番目の処理。
        return render(request, 'registration.html', {'registration_form': e.registration_form, 'user':request.user}) # tryの実行結果にRegistrationErrorが発生していたら、これが3番目の処理。
    django_login(request, user) # 例外が発生していなかったら、これが3番目の処理。domain.pyですでに例外が発生していなかったらユーザー情報をデータベースに登録する処理をするよう設定しているので、views.pyで設定する必要はありません。
    return django.http.HttpResponseRedirect('/post_new_post') # 例外が発生しなかった場合、これが4番目の処理

ーーー勘違いしていたことーーー

最初、try節の文はif文のように条件を示すだけで実行していないかと思っていました。

if文とは違って、try文は実行されているのですが・・・

try文の中にある、

user = ....

は、ただ単にuserという変数を代入しているだけで

評価していないかと思っていました。

しかし、実際は

except以下で、まずエラーをキャッチするか判断し

エラーがなければ

user = ...

でユーザーがデータベースに登録され、かつ代入もされる動きでした。

これからは、

tryは実行されるということと、

exceptと例外が発生しなかった時の処理に関して

上から判定・処理されることを意識してプログラムを書きます。

【Python・Django入門】ログイン画面、ユーザー登録画面を作る①

ブログのユーザー登録画面と、ログイン画面を作成します。

f:id:mizzsugar:20180628205546p:plain

f:id:mizzsugar:20180628205615p:plain

投稿画面と投稿一覧画面はこの記事のものを利用します。

mizzsugar.hatenablog.com


forms.pyでログインフォームとユーザー登録フォームを定義するクラスを作成します。

ユーザー登録では、

  • ユーザー名
  • パスワード
  • メールアドレス

を入力してもらいます。

ログイン画面では、

  • ユーザー名
  • パスワード

を入力してもらいます。

forms.py

from django import forms

class RegistrationForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField()
    email = forms.EmailField()

class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField()

ログインフォームとユーザー登録フォーム画面のhtmlは下記になります。 htmlファイルを保存する、templateフォルダに作成します。(templateフォルダについては、上の記事をご参照ください)

login.html

<form action="/login" enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ login_form }}
    <input type="submit" value="submit">
</form>

<a href="/registration">新規登録はこちら</a>

registration.html

<form action="/registration" enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ registration_form }}
    <input type="submit" value="submit">
</form>

ユーザー情報について、models.pyは作成しません。 views.pyにUserクラスをインポートすれば、自動でテーブルが生成されます。

import django.http
import myApp.models
import myApp.forms
from django.shortcuts import render
import uuid
from django.contrib.auth.models import User
import re
from django.contrib.auth import authenticate, login as django_login

def has_digit(text):
    if re.search("\d", text):
        return True
    return False

def has_alphabet(text):
    if re.search("[a-zA-Z]", text):
        return True
    return False


def post_new_post(request):
    if request.method == 'POST':
        form = myApp.forms.InputForm(request.POST)
        if form.is_valid():
            myApp.models.Post.objects.create(name=request.POST['name'], age=request.POST['age'], comment=request.POST['comment'])
            return django.http.HttpResponseRedirect('/list')
    else:
        form = myApp.forms.InputForm()
    return render(request, 'post_new_post.html', {'form': form})

def list(request):
    posts = myApp.models.Post.objects.all()
    return render(request, 'list.html', {'posts': posts}) 

def login_user(request):
    if request.method == 'POST':
        login_form = myApp.forms.LoginForm(request.POST)
        username = login_form.username
        password = login_form.password
        user = authenticate(request, username=username, password=password)
        if user is not None:
            django_login(request, user)
            return django.http.HttpResponseRedirect('/new_post')
        else:
            login_form.add_error(None, "ユーザー名またはパスワードが異なります。")
            return render(request, 'login.html', {'login_form': login_form})
        return render(request, 'login.html', {'login_form': login_form})
    else:
        login_form = myApp.forms.LoginForm()
    return render(request, 'login.html', {'login_form': login_form})
    #アカウントとパスワードが合致したら、その人専用の投稿画面に遷移する
    #アカウントとパスワードが合致しなかったら、エラーメッセージ付きのログイン画面に遷移する
    

def registation_user(request):
    if request.method == 'POST':
        registration_form = myApp.forms.RegistrationForm(request.POST)
        password = request.POST['password']
        if len(password) < 8:
            registration_form.add_error('password', "文字数が8文字未満です。")
        if not has_digit(password):
            registration_form.add_error('password',"数字が含まれていません")
        if not has_alphabet(password):
            registration_form.add_error('password',"アルファベットが含まれていません")
        if registration_form.has_error('password'):
            return render(request, 'registration.html', {'registration_form': registration_form})
        user = User.objects.create_user(username=request.POST['username'], password=password, email=request.POST['email'])
        return django.http.HttpResponseRedirect('/login')
    else:
        registration_form = myApp.forms.RegistrationForm()
    return render(request, 'registration.html', {'registration_form': registration_form})

authenticate(request, username=username, password=password)で、 送られたユーザー名とパスワードが登録されているものと合致するか認証します。

認証に失敗すると、Noneが返されます。

django.contrib.authのlogin(request, user)で、

user = authenticate(request, username=username, password=password)

のuserの情報を保持したまま、他のページへ移動することができます。


re.search では正規表現を使用して文字列を取得します。

re.search(正規表現,文字列)では、第1引数に正規表現パターンを指定し、第2引数に検索させる文字列を指定します。

文字列の中に、正規表現の表現が存在するか判定します。

"\d"は、0-9のいずれか一文字にマッチする正規表現です。

アルファベットが存在するかどうかの正規表現のメタ文字がなかったので、自分で作成します。

"[a-zA-Z]"で、小文字のa-zと大文字のA-Zのいずれかを含むか判定します。"[パターン内容]"で、検索するパターンを作成できます。


Form.add_error(フィールド型のattribute, エラー)で、フィールドにエラーを追加します。

例えば、

 if len(password) < 8:
            registration_form.add_error('password', "文字数が8文字未満です。")

len(password) < 8を満たすと、フォームに入力されたpassword型のattributeに「文字列が8文字未満です」というエラーが追加されます。

なお、add_errorはattributeに複数のエラーを保持させることができます。

「文字数が8文字未満」で「数字がない」パスワードを登録すると、 f:id:mizzsugar:20180628210859p:plain

「文字数が8文字未満です。」と「数字が含まれていません」というエラーが表示されます。 f:id:mizzsugar:20180628211129p:plain


urls.pyに、ユーザー登録画面とログイン画面のurlを設定します。

urls.py

urlpatterns = [
    path("list", myApp.views.list),
    path("post_new_post", myApp.views.post_new_post),
    path('admin/', admin.site.urls),
    path("login", myApp.views.login_user),
    path("registration", myApp.views.registation_user)
]

python3 manage.py runserver myApp

でブラウザで作成した画面を動かせます。

次回は、ログアウトする方法、パスワードを変更する方法を書きます。 レイアウトの整え方も・・・!

【Python・Django入門】フォームに入力して送信したら、入力した値が表示される方法

1.プロジェクトを作る Djangoはプロジェクトを作成して、その中にWebアプリケーションを作成します。 プロジェクトを作りたいディレクトリに移動して、以下のコマンドを実行します。

django-admin startproject myProject

次に、myProjectのディレクトリに移動した後、 以下のコマンドでmyProjectというプロジェクトの中に myAppというWebアプリケーションを作成します。

python3 manage.py startapp myApp

次に、myProject/settings.pyのINSTALLED_APPSを編集します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myApp',
]

models.pyを編集。 入力した値をDBに保存するため。

models.py

from django.db import models

class InputForm(models.Model):
    name = models.CharField()
    age = models.IntegerFiels();
    comment = models.TextField()

コマンドラインにて、以下のコマンドを実行します。

python3 manage.py makemigrations  myApp
python3 manage.py migrate myApp

makemigrationsでmodels.pyで設定したクラスを元にSQLを発行し、 migrat絵でそのSQLを実行し、DBにテーブルを生成します。

DBを確認すると、テーブル生成されていることがわかります。

f:id:mizzsugar:20180625223118p:plain

次に、templateフォルダを配置します。 その中に、htmlファイルを配置します。

入力フォームのhtmlファイルを作成します。

post_new_post.html

<form action="/output" enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="submit">
</form>

CSRF(ログイン済みの別のWebサイト上で、意図しない何らかの操作を行わせる攻撃手法のこと)対策として、 {% csrf_token %}を入力します。 コンテキスト変数 form を使ってテンプレートに inputForm インス タンスを渡しています。

list.html

<h2>POSTS</h2>


{% for post in posts %}
    <p>{{ post.name }}は{{ post.age }}です</p>
    <p>{{ post.comment }}</p>
    <hr>
{% endfor %}

入力フォームのフィールドを定義する、 forms.pyはmyApp内に作成します。

forms.py

from django import forms
#formsというフォーム処理をするライブラリをインポートします。

class InputForm(forms.Form):
    name = forms.CharField(max_length=30)
    age = forms.IntegerField()
    comment = forms.CharField(widget=forms.Textarea)

クラス内に入力するデータを、フィールドを持って定義します。 名前は文字数が比較的少ないのでCharFieldを用います。 TextFieldを利用すると、「module 'django.forms' has no attribute 'TextField'」というエラーが出力されます。 FormクラスはTextFieldが存在しないクラスだからです。 代わりにCharField(widget=forms.Textarea)を利用します。

views.pyを編集します。

import django.http
import myApp.forms
import myApp.models
from django.shortcuts import render


def post_new_post(request):
    if request.method == 'POST':
        form = myApp.forms.InputForm(request.POST)
        if form.is_valid():
            myApp.models.Post.objects.create(name=request.POST['name'], age=request.POST['age'], comment=request.POST['comment'])
            return django.http.HttpResponseRedirect('/list')
    else:
        form = myApp.forms.InputForm()
    return render(request, 'post_new_post.html', {'form': form})

def list(request):
    posts = myApp.models.Post.objects.all()
    return render(request, 'list.html', {'posts': posts}) 

myApp.models.Post.objects.create(name=request.POST['name'], age=request.POST['age'], comment=request.POST['comment']) こちらで、Postクラスを元に生成されたテーブルにレコードを挿入します。 ()内の引数は、models.pyで登録したクラスのフィールドです。

name=request.POST['name'] の左のnameはmodels.pyのPOSTクラスのフィールドのnameです。 左のnameはforms.pyで登録したフィールド名のnameです。

django.http.HttpResponseRedirect('/output')で、出力先のURLに移動します。 POSTメソッドで、フォームに入力した内容がvalidなら 必ずHttpResponseRedirect(url)にします。

HttpResponseRedirect(url)は、POSTで返って来たデータを安全に破棄して違うviewに遷移させるからです。 render(request,'表示させたいhtmlファイル名',{'html上で利用する変数名':右で定義したhtml上の変数を指す変数}は 単純にページを表示させる時に利用します。

return render(request, 'output.html', {'posts': posts}) の場合、 posts = myApp.models.Post.objects.all() で定義しているpostが{}内の右のpostsになります。

urls.pyに各画面の URLを設定します。

urls.py

urlpatterns = [
    path("list", myApp.views.list),
    path("new_post", myApp.views.post_new_post),
    path('admin/', admin.site.urls),
]

コマンドラインで以下のコマンドを実行すると、コマンドラインにURLが表示されます。 その URLをブラウザで開き、urls.pyで設定したURLに移動しましょう。

python3 manage.py runserver

【Java入門】データベースの情報にデータを挿入、データを削除、データを修正する

mizzsugar.hatenablog.com

↑こちらの記事と同じデータを利用しています。

今回は、この生徒一覧表テーブルから、情報を取得します。

f:id:mizzsugar:20180608083541p:plain

SQL文にすると

CREATE TABLE `students` (
    `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    `name`  TEXT NOT NULL,
    `grade` INTEGER NOT NULL
);

Studentクラス

public class Student {

    private int id;
    private String name;
    private int schoolId;
    private int grade;

    public Student(int id, String name, int schoolId, int grade) {
        this.id = id;
        this.name = name;
        this.schoolId = schoolId;
        this.grade = grade;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getSchoolId() {
        return schoolId;
    }

    public int getGrade() {
        return grade;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    private static String getSqlite3Path(){
        return System.getProperty("user.dir") + File.separator + "sql" + File.separator + "juku.sqlite3";
    }


//データを挿入するメソッド。引数は挿入する内容となる値。
    public static void insert(String name, int schoolId, int grade) {
        try {
            Connection connection = DriverManager.getConnection("jdbc:sqlite:" + getSqlite3Path());
            PreparedStatement prepareStatement = connection
                    .prepareStatement("INSERT INTO students(name, school_id, grade) VALUES(?, ?, ?);");
            prepareStatement.setString(1, name);
            prepareStatement.setInt(2, schoolId);
            prepareStatement.setInt(3, grade);
            prepareStatement.execute();

        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
    
//削除するメソッド。
    public void delete() {
        try {
            Connection connection = DriverManager.getConnection("jdbc:sqlite:" + getSqlite3Path());
            PreparedStatement prepareStatement = connection
                    .prepareStatement("DELETE FROM students WHERE id = ?;"); 
            prepareStatement.setInt(1, id);
            prepareStatement.execute();

        } catch (SQLException e) {
            e.printStackTrace();
        }
        
    }

//修正するメソッド。
    public void update() {
        try {
            Connection connection = DriverManager.getConnection("jdbc:sqlite:" + getSqlite3Path());
            PreparedStatement prepareStatement = connection
                    .prepareStatement("UPDATE students SET name = ? WHERE id = ?;");
            prepareStatement.setString(1, name);
            prepareStatement.setInt(2, id);
            prepareStatement.execute();

        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

}