20200211のdockerに関する記事は30件です。

dockerでCentOSのイメージを使ってみる

環境情報

  • OS: Linux (centos 7.7.1908)
  • docker: 19.03.5

手順

centos7のイメージを取得

docker pull centos:7

実行結果

[root@centos-sample aky100200]# docker pull centos:7
7: Pulling from library/centos
ab5ef0e58194: Pull complete
Digest: sha256:4a701376d03f6b39b8c2a8f4a8e499441b0d567f9ab9d58e4991de4472fb813c
Status: Downloaded newer image for centos:7
docker.io/library/centos:7
[root@centos-sample aky100200]#

イメージの確認

docker images

実行結果

[root@centos-sample aky100200]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              7                   5e35e350aded        3 months ago        203MB
hello-world         latest              fce289e99eb9        13 months ago       1.84kB
[root@centos-sample aky100200]#

起動

docker run -it -d --name centos7 centos:7

実行結果

[root@centos-sample aky100200]# docker run -it -d --name centos7 centos:7
fdc5ffc971420c60228330c1b9f7b4030878836e74f5590614774e8d35518694
[root@centos-sample aky100200]#

プロセスの起動を確認

docker ps

実行結果

[root@centos-sample aky100200]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES
fdc5ffc97142        centos:7            "/bin/bash"         About a minute ago   Up About a minute                       centos7
[root@centos-sample aky100200]#

コンテナ内コマンドの実行

docker exec -it centos7 /bin/bash

実行結果

[root@centos-sample aky100200]# docker exec -it centos7 /bin/bash
[root@fdc5ffc97142 /]#

まとめ

イメージの作成ができた。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker概要

Dockerについて

コンテナ型の仮想環境を提供するソフトウェア。

image.png

主要な3つの機能

Dockerイメージを作る機能(build)

DockerFileというファイルに、インフラ情報(ライブラリや、ミドルウェアのインストールが記述されたファイル)を1つにまとめて、Dockerイメージを作ることができる。
もしくは、Dockerコマンドを使って、手動で作ることも可能。

Dockerイメージを共有する機能(ship)

Dockerのイメージは、Dockerレジストリで共有することができる。
Docker Hubと呼ばれる、公式レジストリがある。
DockerHubには、Nginxや、ruby,pythonなどの公式イメージが置いてある。それらをコマンド1つでローカルPCのDocker上に構築することができる。
自作したイメージも、自由にDocker Hubに公開することができる。

Dockerコンテナを動かす機能(run)

Dockerイメージを元にして、コンテナを作成することができる。
コンテナの起動/停止/削除は、Dockerコマンドを使うことによって可能となる。

メリット:

  • Dockerをインストールしている環境であれば、どこでも動くので、「開発、テスト環境では動いていたが、本番環境で動かない」というリスクを減らすことができる。
  • 動作がとても軽い
  • 自分自身も簡単に環境構築できるし、Dockerイメージを共有することにより、複数人での開発環境を簡単に揃えることができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑦ユーザーごとのデータ登録できるようにする〜削除編

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう
 ⑤ユーザー登録(サインイン)機能を作ろう
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編
 ⑦ユーザーごとのデータ登録できるようにする〜削除編<--今ここ

Goal

ユーザーごとのデータを登録できるようにする。残りのDの部分
削除するだけなら簡単なのですが、、、

View

kids_profile_delete関数を追加。
IDを受け取って、delete()するだけ。簡単。

views.py
from django.contrib.auth import login as dj_login
from .forms import SignUpForm, LoginForm, KidsProfileForm
from django.shortcuts import render, redirect
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.decorators import login_required
from .models import KidsProfile

# サインアップ画面
def signup(request):
(略)

#ログインページ
class login_mypage(LoginView):
(略)

#ログアウトページ
class logout(LogoutView):
(略)

#マイページページ
@login_required
def mypage(request):
(略)

#子供情報追加
@login_required
def kids_profile_add(request):
(略)

#子供情報編集
@login_required
def kids_profile_edit(request, kidsProfileId):
(略)

#子供情報削除
@login_required
def kids_profile_delete(request, kidsProfileId):
    KidsProfile.objects.get(pk=kidsProfileId).delete()
    return redirect('users:mypage')

HTML

削除前に「削除してもいいですか?」と確認するポップアップを実装。

ポップアップはbootstrapのmodalで実現。
modalを表示するのは難しくないが、「一覧上にある削除ボタンが持っているID情報」を
modal内の削除ボタンに引き継ぐのが難しい。

modalを起動させるbuttonにdata-**形式(今回はdata-pkとdata-url)で持ち回らせ、
modal側はjsのonclickイベントにて受け取る形で実装。

このjsはココでしか使わないので、blockコメント化。
extra_jsという名前でbase.htmlで待ち受けておく。

mypage.html
{% extends 'base.html' %}

{% block extra_js %}
<script>
$(function() {
  $('.del_confirm').on('click', function () {
     $("#del_pk").text($(this).data("pk"));
     $('#del_url').attr('href', $(this).data("url"));

     $(this).attr('href', href);
  });
});
</script>
{% endblock extra_js %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>My Page</h2>

    <br>
    <br>

    <table class = "table">
      <tr>
        <td>ユーザー名</td>
        <td>{{ user }}</td>
      </tr>
    </table>

    <table class = "table table-hover">
      <thead class = "thead-dark">
        <tr>
          <th>名前</th>
          <th>性別</th>
          <th>誕生日</th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      {% for kids_profile in kidsProfiles %}
      <tr>
        <td>{{ kids_profile.name }}</td>
        <td>{{ kids_profile.get_gender_display }}</td>
        <td>{{ kids_profile.birthday }}</td>
        <td><a href="{% url 'users:kids_profile_edit' kids_profile.id %}" class="btn btn-success btn-sm">編集</a></td>
        <td><button type="button" class="btn btn-danger btn-sm del_confirm" data-toggle="modal" data-target="#delete_modal"
                    data-pk="{{ kids_profile.pk }}" data-url="{% url 'users:kids_profile_delete' kids_profile.pk %}">削除</button></td>
      </tr>

      {% endfor%}
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td><a href="{% url 'users:kids_profile_add' %}" class="btn btn-primary btn-sm" role="button">追加</a></td>
      </tr>
    </table>


    <div class="modal fade" id="delete_modal" tabindex="-1" role="dialog" aria-labelledby="label1" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-body">
                    削除しても良いですか?
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">キャンセル</button>
                    <a href="#" id="del_url" class="btn btn-danger">削除</a>
                </div>
             </div>
         </div>
    </div>
</div>

{% endblock content %}


base.html
{% load static %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}

<html>
    <head>
        <title>kids Growth</title>
        <link rel="stylesheet" href="{% static 'css/kidsGrowth.css' %}">
        {% block extra_js %}{% endblock %}
       </head>
    <body>
        <!-- Navigation -->
        <nav class="navbar navbar-expand-sm navbar-dark bg-dark mt-3 mb-3">
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav4" aria-controls="navbarNav4" aria-expanded="false" aria-label="Toggle navigation">
              <span class="sr-only">メニュー</span>  
              <span class="navbar-toggler-icon"></span>
            </button>
            <a class="navbar-brand" href="">Kids Growth</a>
            <div class="collapse navbar-collapse" id="navbarNav4">
                <ul class="navbar-nav">
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー1</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー2</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー3</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="{% url 'users:mypage'%}">マイページ</a>
                  </li>
                  {% if user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:logout'%}">ログアウト</a>
                    </li>
                  {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:login'%}">ログイン</a>
                    </li>
                  {% endif %}
                </ul>
            </div>
        </nav>    

        <div class="content container">
          {% block content %}
          {% endblock %}
        </div>
    </body>
</html>

動かすよ〜

2つ目のレコードを消します。
スクリーンショット 2020-02-11 22.59.47.png
modalはポワワーンと出ます。bootstrapの機能です。
(これを消すよ、と書いてあげてもよかったかも。その場合は、引き渡し項目を増やす必要あり。)
スクリーンショット 2020-02-11 22.59.57.png
modal内の「削除」を押すと、データを消してからmypageにリダイレクトします。
スクリーンショット 2020-02-11 23.00.18.png

参考

http://www.tohoho-web.com/bootstrap/index.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerでHelloWorldする

dockerでHelloWorldする

環境情報

  • OS: Linux (centos 7.7.1908)
  • docker: 19.03.5

手順

イメージの一覧を確認する

docker images

まだ何もないことを確認

実行結果

[root@centos-sample aky100200]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
[root@centos-sample aky100200]#

Hello-Worldの準備

hello-worldイメージをpullする

docker pull hello-world

実行結果

[root@centos-sample aky100200]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
[root@centos-sample aky100200]#

イメージが取得できているか確認

docker images

hello-worldが追加されている

[root@centos-sample aky100200]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        13 months ago       1.84kB
[root@centos-sample aky100200]#

Hello-World

実行

docker run hello-world

実行結果

[root@centos-sample aky100200]# docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

[root@centos-sample aky100200]#

「Hello from Docker!」が出力されている。

まとめ

次はlinuxイメージを作りたい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑥

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう
 ⑤ユーザーごとのデータ登録できるようにする〜CRU編

Goal

ユーザーごとのデータを登録できるようにする。CRUDのうちCRUまでいきます。
作るのは、ユーザーごとに子供の情報を登録できるようにします。

Model

名前、性別に加えて、あとで身長体重の成長曲線が書けるよう、生年月日を持ちます。
Modelを直したら、マイグレーションを実施。(コマンドは省略)

models.py
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.utils import timezone

class UserManager(BaseUserManager):
(略)

class User(AbstractBaseUser, PermissionsMixin):
(略)

class KidsProfile(models.Model):
    GENDER_CHOICES = (
        ('1', '女性'),
        ('2', '男性'),
    )
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    name = models.CharField(max_length=20, blank=False)
    gender = gender = models.CharField(max_length=2, choices=GENDER_CHOICES, blank=False)
    birthday = models.DateField(null=False, blank=False)

    def __str__(self):
        title = self.name
        return title

モデルを追加したら、adminで操作できるようにしましょう。

admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User, KidsProfile #モデル追加


class MyUserChangeForm(UserChangeForm):
(略)


class MyUserCreationForm(UserCreationForm):
(略)

class MyUserAdmin(UserAdmin):
(略)

admin.site.register(User, MyUserAdmin)
admin.site.register(KidsProfile) #追加

Form

pythonのDateFieldはyyyy-mm-dd形式なので、入力時のバリデーションが面倒。
GUI的に入力しやすくするため、datetimepickerを入れます。

このフォームを入力するのはログインしてからなので、
Modelでは保有しているuser情報はFormでは見せないようにしてます。
(user情報はviewの中でシステム的に持ち回る)

forms.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm
from .models import KidsProfile
import bootstrap_datepicker_plus as datetimepicker

User = get_user_model()

class SignUpForm(UserCreationForm):
(略)

class LoginForm(AuthenticationForm):
(略)

class KidsProfileForm(forms.ModelForm):
    class Meta:
        model = KidsProfile
        fields = ('name', 'gender', 'birthday')
        widgets = {
            'birthday': datetimepicker.DatePickerInput(
                           format='%Y-%m-%d'),
        }

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', '')
        super(KidsProfileForm, self).__init__(*args, **kwargs)
requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17
django-bootstrap4==1.1.1
django-bootstrap-datepicker-plus==3.0.5 #追加
setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'bootstrap4',
    'bootstrap_datepicker_plus', #追加
]

View

kids_profile_add関数では、POSTで受け取ったformに加えて
request.userで取得したユーザー情報を付与して保存してます。

kids_profile_edit関数では、それに加えて変更したい情報を事前に表示しておきたいので、
データのIDをURL経由で受け取ります。

users/views.py
#子供情報追加
@login_required
def kids_profile_add(request):
    user_name = request.user
    if request.method == 'POST':
      form = KidsProfileForm(request.POST, user = user_name)
      if form.is_valid():
        data = form.save(commit=False)
        data.user = user_name
        data.save()
        return redirect('users:mypage')
    else:
      form = KidsProfileForm(user = user_name)
    return render(request, 'users/kids_profile_add.html', {'form': form})

#子供情報編集
@login_required
def kids_profile_edit(request, kidsProfileId):
    kidsProfileData = KidsProfile.objects.get(pk=kidsProfileId)
    if request.method == 'POST':
      form = KidsProfileForm(request.POST)
      if form.is_valid():
        data = form.save(commit=False)
        data.id = kidsProfileData.id
        data.user = request.user
        data.save()
        return redirect('users:mypage')
    else:
      form = KidsProfileForm(
        {
          'name' : kidsProfileData.name ,
          'gender' : kidsProfileData.gender ,
          'birthday' : kidsProfileData.birthday
        },
      )
    return render(request, 'users/kids_profile_edit.html', {'form': form})

HTML

_addも_editも、テンプレートはほぼ一緒。
{{ form.media }}を書かないと、datetimepickerが動かないので書く。

kids_profile_add.html
{% extends 'base.html' %}

{% block content %}

<!--日付入力フォーマット用-->
{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>New data</h2>
    <form method="POST" class="post-form">{% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="save btn btn-primary">Save</button>
    </form>
</div>
{% endblock %}

kids_profile_edit.html
{% extends 'base.html' %}

{% block content %}

<!--日付入力フォーマット用-->
{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>Data Edit</h2>
    <form method="POST" class="post-form">{% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="save btn btn-primary">Save</button>
    </form>
</div>
{% endblock %}

ユーザーに紐づく子供の情報をTable形式で表示します。
子供の情報は複数人分が登録されるので、for文でレコード分だけ表示します。

mypage.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>My Page</h2>

    <br>
    <br>

    <table class = "table">
      <tr>
        <td>ユーザー名</td>
        <td>{{ user }}</td>
      </tr>
    </table>

    <table class = "table table-hover">
      <thead class = "thead-dark">
        <tr>
          <th>名前</th>
          <th>性別</th>
          <th>誕生日</th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      {% for kids_profile in kidsProfiles %}
      <tr>
        <td>{{ kids_profile.name }}</td>
        <td>{{ kids_profile.get_gender_display }}</td>
        <td>{{ kids_profile.birthday }}</td>
        <td><a href="{% url 'users:kids_profile_edit' kids_profile.id %}" class="btn btn-success btn-sm">編集</a></td>
        <td><button type="button" class="btn btn-danger btn-sm del_confirm">削除</button></td>
      </tr>

      {% endfor%}
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td><a href="{% url 'users:kids_profile_add' %}" class="btn btn-primary btn-sm" role="button">追加</a></td>
      </tr>
    </table>

</div>

{% endblock content %}

urls

新しいページを作ったので、URLのパスを通す。
kidsProfileIdをurlsで指定することで、URLを使ってHTMLとPythonで変数を受け渡す。

urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('signup/', views.signup, name='signup'),
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
    path('kids_profile_add/', views.kids_profile_add, name='kids_profile_add'), #追加
    path('kids_profile_edit/<kidsProfileId>', views.kids_profile_edit, name='kids_profile_edit'),#追加
]

動かす!

登録がない状態。
スクリーンショット 2020-02-11 22.20.53.png
登録画面。datetimepickerだと、こんな感じで表示される。
ちなみにスマホでやると、ちょっとやり辛い。解消方法はあるようだが、試してはいない。
スクリーンショット 2020-02-11 22.21.55.png
編集すると、こんな感じ。各項目、元々の情報が入った状態で、変更可能なフォームが表示される。
スクリーンショット 2020-02-11 22.22.27.png
登録されると、こんな感じ。この時点では削除ボタンはダミー。
スクリーンショット 2020-02-11 22.22.33.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑥ユーザーごとのデータ登録できるようにする〜CRU編

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう
 ⑤ユーザー登録(サインイン)機能を作ろう
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編<--今ここ
 ⑦ユーザーごとのデータ登録できるようにする〜削除編

Goal

ユーザーごとのデータを登録できるようにする。CRUDのうちCRUまでいきます。
作るのは、ユーザーごとに子供の情報を登録できるようにします。

Model

名前、性別に加えて、あとで身長体重の成長曲線が書けるよう、生年月日を持ちます。
Modelを直したら、マイグレーションを実施。(コマンドは省略)

models.py
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.utils import timezone

class UserManager(BaseUserManager):
(略)

class User(AbstractBaseUser, PermissionsMixin):
(略)

class KidsProfile(models.Model):
    GENDER_CHOICES = (
        ('1', '女性'),
        ('2', '男性'),
    )
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    name = models.CharField(max_length=20, blank=False)
    gender = gender = models.CharField(max_length=2, choices=GENDER_CHOICES, blank=False)
    birthday = models.DateField(null=False, blank=False)

    def __str__(self):
        title = self.name
        return title

モデルを追加したら、adminで操作できるようにしましょう。

admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User, KidsProfile #モデル追加


class MyUserChangeForm(UserChangeForm):
(略)


class MyUserCreationForm(UserCreationForm):
(略)

class MyUserAdmin(UserAdmin):
(略)

admin.site.register(User, MyUserAdmin)
admin.site.register(KidsProfile) #追加

Form

pythonのDateFieldはyyyy-mm-dd形式なので、入力時のバリデーションが面倒。
GUI的に入力しやすくするため、datetimepickerを入れます。

このフォームを入力するのはログインしてからなので、
Modelでは保有しているuser情報はFormでは見せないようにしてます。
(user情報はviewの中でシステム的に持ち回る)

forms.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm
from .models import KidsProfile
import bootstrap_datepicker_plus as datetimepicker

User = get_user_model()

class SignUpForm(UserCreationForm):
(略)

class LoginForm(AuthenticationForm):
(略)

class KidsProfileForm(forms.ModelForm):
    class Meta:
        model = KidsProfile
        fields = ('name', 'gender', 'birthday')
        widgets = {
            'birthday': datetimepicker.DatePickerInput(
                           format='%Y-%m-%d'),
        }

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', '')
        super(KidsProfileForm, self).__init__(*args, **kwargs)
requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17
django-bootstrap4==1.1.1
django-bootstrap-datepicker-plus==3.0.5 #追加
setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'bootstrap4',
    'bootstrap_datepicker_plus', #追加
]

View

kids_profile_add関数では、POSTで受け取ったformに加えて
request.userで取得したユーザー情報を付与して保存してます。

kids_profile_edit関数では、それに加えて変更したい情報を事前に表示しておきたいので、
データのIDをURL経由で受け取ります。

users/views.py
#子供情報追加
@login_required
def kids_profile_add(request):
    user_name = request.user
    if request.method == 'POST':
      form = KidsProfileForm(request.POST, user = user_name)
      if form.is_valid():
        data = form.save(commit=False)
        data.user = user_name
        data.save()
        return redirect('users:mypage')
    else:
      form = KidsProfileForm(user = user_name)
    return render(request, 'users/kids_profile_add.html', {'form': form})

#子供情報編集
@login_required
def kids_profile_edit(request, kidsProfileId):
    kidsProfileData = KidsProfile.objects.get(pk=kidsProfileId)
    if request.method == 'POST':
      form = KidsProfileForm(request.POST)
      if form.is_valid():
        data = form.save(commit=False)
        data.id = kidsProfileData.id
        data.user = request.user
        data.save()
        return redirect('users:mypage')
    else:
      form = KidsProfileForm(
        {
          'name' : kidsProfileData.name ,
          'gender' : kidsProfileData.gender ,
          'birthday' : kidsProfileData.birthday
        },
      )
    return render(request, 'users/kids_profile_edit.html', {'form': form})

HTML

_addも_editも、テンプレートはほぼ一緒。
{{ form.media }}を書かないと、datetimepickerが動かないので書く。

kids_profile_add.html
{% extends 'base.html' %}

{% block content %}

<!--日付入力フォーマット用-->
{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>New data</h2>
    <form method="POST" class="post-form">{% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="save btn btn-primary">Save</button>
    </form>
</div>
{% endblock %}

kids_profile_edit.html
{% extends 'base.html' %}

{% block content %}

<!--日付入力フォーマット用-->
{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>Data Edit</h2>
    <form method="POST" class="post-form">{% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="save btn btn-primary">Save</button>
    </form>
</div>
{% endblock %}

ユーザーに紐づく子供の情報をTable形式で表示します。
子供の情報は複数人分が登録されるので、for文でレコード分だけ表示します。

mypage.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>My Page</h2>

    <br>
    <br>

    <table class = "table">
      <tr>
        <td>ユーザー名</td>
        <td>{{ user }}</td>
      </tr>
    </table>

    <table class = "table table-hover">
      <thead class = "thead-dark">
        <tr>
          <th>名前</th>
          <th>性別</th>
          <th>誕生日</th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      {% for kids_profile in kidsProfiles %}
      <tr>
        <td>{{ kids_profile.name }}</td>
        <td>{{ kids_profile.get_gender_display }}</td>
        <td>{{ kids_profile.birthday }}</td>
        <td><a href="{% url 'users:kids_profile_edit' kids_profile.id %}" class="btn btn-success btn-sm">編集</a></td>
        <td><button type="button" class="btn btn-danger btn-sm del_confirm">削除</button></td>
      </tr>

      {% endfor%}
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td><a href="{% url 'users:kids_profile_add' %}" class="btn btn-primary btn-sm" role="button">追加</a></td>
      </tr>
    </table>

</div>

{% endblock content %}

urls

新しいページを作ったので、URLのパスを通す。
kidsProfileIdをurlsで指定することで、URLを使ってHTMLとPythonで変数を受け渡す。

urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('signup/', views.signup, name='signup'),
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
    path('kids_profile_add/', views.kids_profile_add, name='kids_profile_add'), #追加
    path('kids_profile_edit/<kidsProfileId>', views.kids_profile_edit, name='kids_profile_edit'),#追加
]

動かす!

登録がない状態。
スクリーンショット 2020-02-11 22.20.53.png
登録画面。datetimepickerだと、こんな感じで表示される。
ちなみにスマホでやると、ちょっとやり辛い。解消方法はあるようだが、試してはいない。
スクリーンショット 2020-02-11 22.21.55.png
編集すると、こんな感じ。各項目、元々の情報が入った状態で、変更可能なフォームが表示される。
スクリーンショット 2020-02-11 22.22.27.png
登録されると、こんな感じ。この時点では削除ボタンはダミー。
スクリーンショット 2020-02-11 22.22.33.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS7にDockerをインストールする

CentOSにDockerをインストールする

事前準備

AzureでCentOSのVMを作成しておく。

作成したマシン情報

  • OS: Linux (centos 7.7.1908)
  • マシン名: centos-sample

手順

クライアントのWindowsからteratermで接続する。

rootユーザに変更

sudo su

実行結果

[aky100200@centos-sample ~]$ sudo su

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for aky100200:
[root@centos-sample aky100200]#

パッケージのインストール

  • yum-utils
  • device-mapper-persistent-data
  • lvm2
yum install -y yum-utils device-mapper-persistent-data lvm2

実行結果

[root@centos-sample aky100200]# yum install -y yum-utils device-mapper-persistent-data lvm2
Loaded plugins: fastestmirror, langpacks
Determining fastest mirrors
base                                                     | 3.1 kB     00:00
extras                                                   | 2.5 kB     00:00
openlogic                                                | 2.9 kB     00:00
updates                                                  | 2.6 kB     00:00
(1/5): openlogic/7/x86_64/primary_db                       |  33 kB   00:00
(2/5): base/7/x86_64/group_gz                              | 165 kB   00:00
(3/5): extras/7/x86_64/primary_db                          | 159 kB   00:00
(4/5): updates/7/x86_64/primary_db                         | 6.7 MB   00:00
(5/5): base/7/x86_64/primary_db                            | 6.0 MB   00:00
Package yum-utils-1.1.31-52.el7.noarch already installed and latest version
Package device-mapper-persistent-data-0.8.5-1.el7.x86_64 already installed and latest version
Package 7:lvm2-2.02.185-2.el7_7.2.x86_64 already installed and latest version
Nothing to do
[root@centos-sample aky100200]#

Dockerリポジトリのインストール

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

実行結果

root@centos-sample aky100200]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Loaded plugins: fastestmirror, langpacks
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
repo saved to /etc/yum.repos.d/docker-ce.repo
[root@centos-sample aky100200]#

docker-ceのインストール

yum -y install docker-ce docker-ce-cli containerd.io

実行結果

[root@centos-sample aky100200]# yum -y install docker-ce docker-ce-cli containerd.io
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
Resolving Dependencies
--> Running transaction check
---> Package containerd.io.x86_64 0:1.2.10-3.2.el7 will be installed
--> Processing Dependency: container-selinux >= 2:2.74 for package: containerd.io-1.2.10-3.2.el7.x86_64
---> Package docker-ce.x86_64 3:19.03.5-3.el7 will be installed
--> Processing Dependency: libcgroup for package: 3:docker-ce-19.03.5-3.el7.x86_64
---> Package docker-ce-cli.x86_64 1:19.03.5-3.el7 will be installed
--> Running transaction check
---> Package container-selinux.noarch 2:2.107-3.el7 will be installed
--> Processing Dependency: policycoreutils-python for package: 2:container-selinux-2.107-3.el7.noarch
---> Package libcgroup.x86_64 0:0.41-21.el7 will be installed
--> Running transaction check
---> Package policycoreutils-python.x86_64 0:2.5-33.el7 will be installed
--> Processing Dependency: setools-libs >= 3.3.8-4 for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: libsemanage-python >= 2.5-14 for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: audit-libs-python >= 2.1.3-4 for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: python-IPy for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: libqpol.so.1(VERS_1.4)(64bit) for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: libqpol.so.1(VERS_1.2)(64bit) for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: libapol.so.4(VERS_4.0)(64bit) for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: checkpolicy for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: libqpol.so.1()(64bit) for package: policycoreutils-python-2.5-33.el7.x86_64
--> Processing Dependency: libapol.so.4()(64bit) for package: policycoreutils-python-2.5-33.el7.x86_64
--> Running transaction check
---> Package audit-libs-python.x86_64 0:2.8.5-4.el7 will be installed
---> Package checkpolicy.x86_64 0:2.5-8.el7 will be installed
---> Package libsemanage-python.x86_64 0:2.5-14.el7 will be installed
---> Package python-IPy.noarch 0:0.75-6.el7 will be installed
---> Package setools-libs.x86_64 0:3.3.8-4.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package                  Arch     Version             Repository          Size
================================================================================
Installing:
 containerd.io            x86_64   1.2.10-3.2.el7      docker-ce-stable    23 M
 docker-ce                x86_64   3:19.03.5-3.el7     docker-ce-stable    24 M
 docker-ce-cli            x86_64   1:19.03.5-3.el7     docker-ce-stable    39 M
Installing for dependencies:
 audit-libs-python        x86_64   2.8.5-4.el7         base                76 k
 checkpolicy              x86_64   2.5-8.el7           base               295 k
 container-selinux        noarch   2:2.107-3.el7       extras              39 k
 libcgroup                x86_64   0.41-21.el7         base                66 k
 libsemanage-python       x86_64   2.5-14.el7          base               113 k
 policycoreutils-python   x86_64   2.5-33.el7          base               457 k
 python-IPy               noarch   0.75-6.el7          base                32 k
 setools-libs             x86_64   3.3.8-4.el7         base               620 k

Transaction Summary
================================================================================
Install  3 Packages (+8 Dependent packages)

Total download size: 89 M
Installed size: 368 M
Downloading packages:
(1/11): container-selinux-2.107-3.el7.noarch.rpm           |  39 kB   00:00
(2/11): audit-libs-python-2.8.5-4.el7.x86_64.rpm           |  76 kB   00:00
(3/11): checkpolicy-2.5-8.el7.x86_64.rpm                   | 295 kB   00:00
warning: /var/cache/yum/x86_64/7/docker-ce-stable/packages/docker-ce-19.03.5-3.el7.x86_64.rpm: Header V4 RSA/SHA512 Signature, key ID 621e9f35: NOKEY
Public key for docker-ce-19.03.5-3.el7.x86_64.rpm is not installed
(4/11): docker-ce-19.03.5-3.el7.x86_64.rpm                 |  24 MB   00:00
(5/11): libcgroup-0.41-21.el7.x86_64.rpm                   |  66 kB   00:00
(6/11): libsemanage-python-2.5-14.el7.x86_64.rpm           | 113 kB   00:00
(7/11): policycoreutils-python-2.5-33.el7.x86_64.rpm       | 457 kB   00:00
(8/11): python-IPy-0.75-6.el7.noarch.rpm                   |  32 kB   00:00
(9/11): setools-libs-3.3.8-4.el7.x86_64.rpm                | 620 kB   00:00
(10/11): docker-ce-cli-19.03.5-3.el7.x86_64.rpm            |  39 MB   00:01
(11/11): containerd.io-1.2.10-3.2.el7.x86_64.rpm           |  23 MB   00:02
--------------------------------------------------------------------------------
Total                                               38 MB/s |  89 MB  00:02
Retrieving key from https://download.docker.com/linux/centos/gpg
Importing GPG key 0x621E9F35:
 Userid     : "Docker Release (CE rpm) <docker@docker.com>"
 Fingerprint: 060a 61c5 1b55 8a7f 742b 77aa c52f eb6b 621e 9f35
 From       : https://download.docker.com/linux/centos/gpg
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : libcgroup-0.41-21.el7.x86_64                                1/11
  Installing : setools-libs-3.3.8-4.el7.x86_64                             2/11
  Installing : 1:docker-ce-cli-19.03.5-3.el7.x86_64                        3/11
  Installing : audit-libs-python-2.8.5-4.el7.x86_64                        4/11
  Installing : python-IPy-0.75-6.el7.noarch                                5/11
  Installing : libsemanage-python-2.5-14.el7.x86_64                        6/11
  Installing : checkpolicy-2.5-8.el7.x86_64                                7/11
  Installing : policycoreutils-python-2.5-33.el7.x86_64                    8/11
  Installing : 2:container-selinux-2.107-3.el7.noarch                      9/11
  Installing : containerd.io-1.2.10-3.2.el7.x86_64                        10/11
  Installing : 3:docker-ce-19.03.5-3.el7.x86_64                           11/11
  Verifying  : checkpolicy-2.5-8.el7.x86_64                                1/11
  Verifying  : policycoreutils-python-2.5-33.el7.x86_64                    2/11
  Verifying  : 3:docker-ce-19.03.5-3.el7.x86_64                            3/11
  Verifying  : libsemanage-python-2.5-14.el7.x86_64                        4/11
  Verifying  : 2:container-selinux-2.107-3.el7.noarch                      5/11
  Verifying  : python-IPy-0.75-6.el7.noarch                                6/11
  Verifying  : audit-libs-python-2.8.5-4.el7.x86_64                        7/11
  Verifying  : containerd.io-1.2.10-3.2.el7.x86_64                         8/11
  Verifying  : 1:docker-ce-cli-19.03.5-3.el7.x86_64                        9/11
  Verifying  : setools-libs-3.3.8-4.el7.x86_64                            10/11
  Verifying  : libcgroup-0.41-21.el7.x86_64                               11/11

Installed:
  containerd.io.x86_64 0:1.2.10-3.2.el7     docker-ce.x86_64 3:19.03.5-3.el7
  docker-ce-cli.x86_64 1:19.03.5-3.el7

Dependency Installed:
  audit-libs-python.x86_64 0:2.8.5-4.el7
  checkpolicy.x86_64 0:2.5-8.el7
  container-selinux.noarch 2:2.107-3.el7
  libcgroup.x86_64 0:0.41-21.el7
  libsemanage-python.x86_64 0:2.5-14.el7
  policycoreutils-python.x86_64 0:2.5-33.el7
  python-IPy.noarch 0:0.75-6.el7
  setools-libs.x86_64 0:3.3.8-4.el7

Complete!
[root@centos-sample aky100200]#

インストールの確認

dockerコマンドのパスを確認する。

パスが確認できればOK。

which docker

実行結果

[root@centos-sample aky100200]# which docker
/bin/docker
[root@centos-sample aky100200]#

dockerdの起動

実行してエラーが出なければOK。

systemctl start docker

実行結果

[root@centos-sample aky100200]# systemctl start docker
[root@centos-sample aky100200]#

自動起動設定

systemctl enable docker

実行結果

root@centos-sample aky100200]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@centos-sample aky100200]#

これでdockerが使えるようになった。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分のDockerコンテナIDをホスト名に設定する

ホスト名をキーとして扱うサービスに対応するために、Docker コンテナ内から、自分のコンテナ ID を取得して docker-0b2e383f のようなホスト名を設定する。

#!/bin/bash

docker="$(egrep ':/docker/[0-9a-f]+$' /proc/self/cgroup | head -1)"
docker="${docker##*/}"
hostname "docker-${docker:0:8}"

https://gist.github.com/kawanet/a44493c0c3942b4b8f5d768f5a304564

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js + Docker Compose で ホットリロードしながら開発する環境をつくる

はじめに

個人の開発用に Vue.js の動作環境を Docker に移行してみたら、とても捗ったのでメモがてら投稿します。

手順

前提

  • Vue.js のプロジェクトはできている
  • npm run serve で、 Vue.js が立ち上がるように設定されている

1. Dockerfile を作成

プロジェクトの直下に作成します。

FROM node:lts-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 8080

CMD ["npm", "run", "serve"]

これで、 Vue.js を動作させる Docker Image を作成することができます。

2. docker-compose.yml を作成

こちらもプロジェクト直下に作成します。

docker-compose.yml
version: "3"
services:
  app:
    container_name: web
    build: 
      context: .
      dockerfile: Dockerfile
    ports:
        - 8080:8080
    volumes: 
        - .:/app

volues でプロジェクトを Docker コンテナにマウントさせることで、 npm run serve の Hot Road が利くようになります。

起動する

プロジェクト直下で下記コマンドを実行します。

docker-compose up

localhost:8080 へアクセスすると、 Vue.js が起動しているのが確認できると思います。

終わりに

Dockerはありがたいですね。
少しでもあなたの為になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑤

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう

Goal

サインイン機能を作ろう

Form

Djangoはログインログアウトは簡単に実装できる。(あと簡単なCRUDも)
サインインも機能は実装されているが、Templateはないので自分たちで作る。
カスタムユーザーを推奨しているし、ユーザーを管理するために欲しい情報は
ビジネスロジック寄りであるということだろう。

UserCreationFormを継承することでサインインの基本機能を継承。
Userはget_user_model()をしないとエラーになる。
カスタムユーザーにしているので、明示しないと見つからないのだと思われる。

forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm

User = get_user_model()

class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

View

HTMLからPOSTで受け取って、フォームを返す・・・という基本形。

users/view.py
from django.contrib.auth import login as dj_login
from .forms import SignUpForm
from django.shortcuts import render, redirect

# サインアップ画面
def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            dj_login(request, user)
            return redirect(to='/')
    else:
        form = SignUpForm()
    return render(request, 'users/signup.html', {'form': form})

HTML

ログインページとあまり変わらない。Formとbootstrapに任せる。

users/signup.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>ユーザー登録</h2>
    <form method="POST" class="post-form">{% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="save btn btn-primary">登録</button>
    </form>
</div>
{% endblock %}

urls

signupページのページを追加。
signupにはLoginページからリンクを貼っている。詳細は1つ前の回を参照。

users/urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('signup/', views.signup, name='signup'), #追加
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
]

確認

Signup画面。入力のためのバリデーションコメントも付いてる。
(邪魔な場合は消せる。)
スクリーンショット 2020-02-11 21.05.33.png

入力するユーザー名(メールアドレス)が重複していると、検知する。
スクリーンショット 2020-02-11 21.06.08.png

登録が成立すると、マイページに飛ぶ。
スクリーンショット 2020-02-11 21.06.44.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑤ユーザー登録(サインイン)機能を作ろう

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう
 ⑤ユーザー登録(サインイン)機能を作ろう<--今ここ
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編
 ⑦ユーザーごとのデータ登録できるようにする〜削除編

Goal

サインイン機能を作ろう

Form

Djangoはログインログアウトは簡単に実装できる。(あと簡単なCRUDも)
サインインも機能は実装されているが、Templateはないので自分たちで作る。
カスタムユーザーを推奨しているし、ユーザーを管理するために欲しい情報は
ビジネスロジック寄りであるということだろう。

UserCreationFormを継承することでサインインの基本機能を継承。
Userはget_user_model()をしないとエラーになる。
カスタムユーザーにしているので、明示しないと見つからないのだと思われる。

forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm

User = get_user_model()

class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

View

HTMLからPOSTで受け取って、フォームを返す・・・という基本形。

users/view.py
from django.contrib.auth import login as dj_login
from .forms import SignUpForm
from django.shortcuts import render, redirect

# サインアップ画面
def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            dj_login(request, user)
            return redirect(to='/')
    else:
        form = SignUpForm()
    return render(request, 'users/signup.html', {'form': form})

HTML

ログインページとあまり変わらない。Formとbootstrapに任せる。

users/signup.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>ユーザー登録</h2>
    <form method="POST" class="post-form">{% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="save btn btn-primary">登録</button>
    </form>
</div>
{% endblock %}

urls

signupページのページを追加。
signupにはLoginページからリンクを貼っている。詳細は1つ前の回を参照。

users/urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('signup/', views.signup, name='signup'), #追加
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
]

確認

Signup画面。入力のためのバリデーションコメントも付いてる。
(邪魔な場合は消せる。)
スクリーンショット 2020-02-11 21.05.33.png

入力するユーザー名(メールアドレス)が重複していると、検知する。
スクリーンショット 2020-02-11 21.06.08.png

登録が成立すると、マイページに飛ぶ。
スクリーンショット 2020-02-11 21.06.44.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで④

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く

Goal

ログインログオフ機能を作ろう

HTML(下準備)

Djangoのtemplateをアプリごとに分けるのが面倒になりそうなので、
テンプレートは一箇所に置くことにする。最終形は以下(細かいものは省略)。

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── templates
│   │   ├── base.html
│   │   └── users
│   │       ├── login.html
│   │       ├── mypage.html
│   │       └── signup.html
│   └── users
│       ├── admin.py
│       ├── apps.py
│       ├── forms.py
│       ├── models.py
│       ├── urls.py
│       └── views.py
├── static
│   └── 略
└── web
    ├── Dockerfile
    └── requirements.txt

templateの場所を教えてあげるために、setting.pyを修正する。

setting.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #変更
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

CSSはbootstrapが手っ取り早くて良い。
bootstrapはdjango用のものがあるので、それを使う。

requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17
django-bootstrap4==1.1.1 #追加

settingはAPPSに追加するのと、Templateの指定にも追加する。

setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'bootstrap4', #追加
]
()
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            'builtins':[ #追加
                'bootstrap4.templatetags.bootstrap4',
            ],
        },
    },
]

HTML(本番)

ベースのHTML。bootstrapでレスポンシブ対応ぽくしている。

base.html
{% load static %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}

<html>
    <head>
        <title>kids Growth</title>
        <link rel="stylesheet" href="{% static 'css/kidsGrowth.css' %}">
        {% block extra_js %}{% endblock %}
       </head>
    <body>
        <!-- Navigation -->
        <nav class="navbar navbar-expand-sm navbar-dark bg-dark mt-3 mb-3">
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav4" aria-controls="navbarNav4" aria-expanded="false" aria-label="Toggle navigation">
              <span class="sr-only">メニュー</span>  
              <span class="navbar-toggler-icon"></span>
            </button>
            <a class="navbar-brand" href="">Kids Growth</a>
            <div class="collapse navbar-collapse" id="navbarNav4">
                <ul class="navbar-nav">
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー1</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー2</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー3</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="{% url 'users:mypage'%}">マイページ</a>
                  </li>
                  {% if user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:logout'%}">ログアウト</a>
                    </li>
                  {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:login'%}">ログイン</a>
                    </li>
                  {% endif %}
                </ul>
            </div>
        </nav>    

        <div class="content container">
          {% block content %}
          {% endblock %}
        </div>
    </body>
</html>

マイページ。(ユーザー名が出るだけ)
ログイン後に飛んでくるところとして指定。
{{ user }}はあとでViewから引き渡します。

users/mypage.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>My Page</h2>

    <br>
    <br>

    <table class = "table">
      <tr>
        <td>ユーザー名</td>
        <td>{{ user }}</td>
      </tr>
    </table>


</div>

{% endblock content %}

formという名前でフォームを引き渡して、
bootstrap_formで綺麗に整形するだけ。
ユーザー登録のリンクを用意してますが、その機能は次で。

users/login.html
{% extends 'base.html' %}

{% block content %}

{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>Login</h2>
        <form action="" method="POST">
                {{ form.non_field_errors }}
                {% bootstrap_form form %}
                <hr>
                <button type="submit" class="btn btn-success btn-lg btn-block" >ログイン</button>
                {% csrf_token %}
        </form>


        <a href="{% url 'users:signup' %}">ユーザー登録</a>

</div>
{% endblock %}

Form

FormとしてAuthenticationFormを継承することで、
ログイン機能を実装します。これだけ。便利。

users/form.py
class LoginForm(AuthenticationForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

View

ログインとログアウトは専用のViewがあるので継承する。
@login_requiredを指定した関数はログインした状態じゃないと呼ばれない。
ひとまずはログインしているユーザーの名前を取得して、templateに返すだけのお仕事。

ちなみにログインのクラスをloginにすると、このあとSignup関数を作るときに
規定のネーミングとバッティングするのかエラーが外れず、ややカッコ悪いネーミング。

users/view.py
from .forms import LoginForm
from django.shortcuts import render, redirect
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.decorators import login_required

#ログイン
class login_mypage(LoginView):
    form_class = LoginForm
    template_name = 'users/login.html'

#ログアウト
class logout(LogoutView):
    template_name = 'users/login.html'

#マイページ
@login_required
def mypage(request):
    user_name = request.user
    params = {
        'user' : user_name,
    }
    return render(request, 'users/mypage.html',params)

urls

プロジェクトのurlsで、アプリごとのurlsを読み込む。

mysite/urls.py
from django.contrib import admin
from django.urls import path, include

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

アプリごとに定義したURLパターンごとにViewを決める。

users/urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
]

Setting.py

以下を追加する。ログインするときのURLはここだよ〜
ログインしたらここに最初にアクセスしてね〜
ログアウトしたらなんの画面を見せるよ〜
ということを決める。

setting.py
LOGIN_URL = 'users:login'
LOGIN_REDIRECT_URL = 'users:mypage'
LOGOUT_REDIRECT_URL = 'users:login'

出来上がり

ログイン画面と、ログインした後の画面。

スクリーンショット 2020-02-11 20.45.05.png
スクリーンショット 2020-02-11 20.45.49.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで④ログインログアウトをしよう

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう<--今ここ
 ⑤ユーザー登録(サインイン)機能を作ろう
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編
 ⑦ユーザーごとのデータ登録できるようにする〜削除編

Goal

ログインログオフ機能を作ろう

HTML(下準備)

Djangoのtemplateをアプリごとに分けるのが面倒になりそうなので、
テンプレートは一箇所に置くことにする。最終形は以下(細かいものは省略)。

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── templates
│   │   ├── base.html
│   │   └── users
│   │       ├── login.html
│   │       ├── mypage.html
│   │       └── signup.html
│   └── users
│       ├── admin.py
│       ├── apps.py
│       ├── forms.py
│       ├── models.py
│       ├── urls.py
│       └── views.py
├── static
│   └── 略
└── web
    ├── Dockerfile
    └── requirements.txt

templateの場所を教えてあげるために、setting.pyを修正する。

setting.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #変更
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

CSSはbootstrapが手っ取り早くて良い。
bootstrapはdjango用のものがあるので、それを使う。

requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17
django-bootstrap4==1.1.1 #追加

settingはAPPSに追加するのと、Templateの指定にも追加する。

setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'bootstrap4', #追加
]
()
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            'builtins':[ #追加
                'bootstrap4.templatetags.bootstrap4',
            ],
        },
    },
]

HTML(本番)

ベースのHTML。bootstrapでレスポンシブ対応ぽくしている。

base.html
{% load static %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}

<html>
    <head>
        <title>kids Growth</title>
        <link rel="stylesheet" href="{% static 'css/kidsGrowth.css' %}">
        {% block extra_js %}{% endblock %}
       </head>
    <body>
        <!-- Navigation -->
        <nav class="navbar navbar-expand-sm navbar-dark bg-dark mt-3 mb-3">
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav4" aria-controls="navbarNav4" aria-expanded="false" aria-label="Toggle navigation">
              <span class="sr-only">メニュー</span>  
              <span class="navbar-toggler-icon"></span>
            </button>
            <a class="navbar-brand" href="">Kids Growth</a>
            <div class="collapse navbar-collapse" id="navbarNav4">
                <ul class="navbar-nav">
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー1</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー2</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="">メニュー3</a>
                  </li>
                  <li class="nav-item">
                      <a class="nav-link" href="{% url 'users:mypage'%}">マイページ</a>
                  </li>
                  {% if user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:logout'%}">ログアウト</a>
                    </li>
                  {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'users:login'%}">ログイン</a>
                    </li>
                  {% endif %}
                </ul>
            </div>
        </nav>    

        <div class="content container">
          {% block content %}
          {% endblock %}
        </div>
    </body>
</html>

マイページ。(ユーザー名が出るだけ)
ログイン後に飛んでくるところとして指定。
{{ user }}はあとでViewから引き渡します。

users/mypage.html
{% extends 'base.html' %}

{% block content %}

<div class="col-md-12 col-lg-5">
    <h2>My Page</h2>

    <br>
    <br>

    <table class = "table">
      <tr>
        <td>ユーザー名</td>
        <td>{{ user }}</td>
      </tr>
    </table>


</div>

{% endblock content %}

formという名前でフォームを引き渡して、
bootstrap_formで綺麗に整形するだけ。
ユーザー登録のリンクを用意してますが、その機能は次で。

users/login.html
{% extends 'base.html' %}

{% block content %}

{{ form.media }}

<div class="col-md-12 col-lg-5">
    <h2>Login</h2>
        <form action="" method="POST">
                {{ form.non_field_errors }}
                {% bootstrap_form form %}
                <hr>
                <button type="submit" class="btn btn-success btn-lg btn-block" >ログイン</button>
                {% csrf_token %}
        </form>


        <a href="{% url 'users:signup' %}">ユーザー登録</a>

</div>
{% endblock %}

Form

FormとしてAuthenticationFormを継承することで、
ログイン機能を実装します。これだけ。便利。

users/form.py
class LoginForm(AuthenticationForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

View

ログインとログアウトは専用のViewがあるので継承する。
@login_requiredを指定した関数はログインした状態じゃないと呼ばれない。
ひとまずはログインしているユーザーの名前を取得して、templateに返すだけのお仕事。

ちなみにログインのクラスをloginにすると、このあとSignup関数を作るときに
規定のネーミングとバッティングするのかエラーが外れず、ややカッコ悪いネーミング。

users/view.py
from .forms import LoginForm
from django.shortcuts import render, redirect
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.decorators import login_required

#ログイン
class login_mypage(LoginView):
    form_class = LoginForm
    template_name = 'users/login.html'

#ログアウト
class logout(LogoutView):
    template_name = 'users/login.html'

#マイページ
@login_required
def mypage(request):
    user_name = request.user
    params = {
        'user' : user_name,
    }
    return render(request, 'users/mypage.html',params)

urls

プロジェクトのurlsで、アプリごとのurlsを読み込む。

mysite/urls.py
from django.contrib import admin
from django.urls import path, include

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

アプリごとに定義したURLパターンごとにViewを決める。

users/urls.py
app_name = 'users'

urlpatterns = [
    path('', views.mypage, name='mypage'),
    path('mypage/', views.mypage, name='mypage'),
    path('login/', views.login_mypage.as_view(), name='login'),
    path('logout/', views.logout.as_view(), name='logout'),
]

Setting.py

以下を追加する。ログインするときのURLはここだよ〜
ログインしたらここに最初にアクセスしてね〜
ログアウトしたらなんの画面を見せるよ〜
ということを決める。

setting.py
LOGIN_URL = 'users:login'
LOGIN_REDIRECT_URL = 'users:mypage'
LOGOUT_REDIRECT_URL = 'users:login'

出来上がり

ログイン画面と、ログインした後の画面。

スクリーンショット 2020-02-11 20.45.05.png
スクリーンショット 2020-02-11 20.45.49.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでRailsの環境構築

docker-composeでrails環境を構築します。データベースはpostgresqlを使用します。

Setup

以下のような構成からスタートします。

./
|- docker-compose.yml
|- rails-app/
  |- Dockerfile

Dockerfileの作成

Dockerfile
FROM ruby:2.5.0

RUN apt-get update && apt-get install -y build-essential libpq-dev postgresql-client vim less
RUN gem install rails -v '5.2.1'
RUN mkdir /app
WORKDIR /app

railsのバージョンは5.2.1を指定しています。
マウント先のディレクトリとしてappディレクトリを作成しておきます。

docker-compose.ymlの作成

docker-compose.yml
version: "3"

services:
  rails-app:
    build: rails-app
    ports:
      - "3001:3001"
    environment:
      - "DATABASE_HOST=db"
      - "DATABASE_PORT=5432"
      - "DATABASE_USER=postgres"
      - "DATABASE_PASSWORD=<パスワード>"
    links:
      - db
    volumes:
      - "./rails-app:/app"
    stdin_open: true

  db:
    image: postgres:10.1
    ports:
      - "5432:5432"
    environment:
      - "POSTGRES_USER=postgres"
      - "POSTGRES_PASSWORD=<パスワード>"

ポートは3001番を指定していますが、3000番でもOKです。

コンテナの起動

docker-compose build
docker-compose up -d

コンテナにログインします。

docker-compose exec rails-app bash

以降はコンテナ内で操作を行います。

Railsプロジェクトの作成

cd /app
rails new . -d postgresql -BT

-Bでbundle installをスキップし、-Tでテストファイルの作成をスキップしています。
Gemfileにtherubyracerを追加します。

Gemfile
gem 'therubyracer', platforms: :ruby

この状態でbundle installします。

bundle install —-path vendor/bundle

これでプロジェクトが作成されました。

データベースの設定

config/database.ymlを編集します。

  • defaultにhost, port, username, passwordを追加
  • データベース名を rails_app_development に変更
config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ host: <%= ENV.fetch("DATABASE_HOST") { "localhost" } %>
+ port: <%= ENV.fetch("DATABASE_PORT") { 5432 } %>
+ username: <%= ENV.fetch("DATABASE_USER") { "root" } %>
+ password: <%= ENV.fetch("DATABASE_PASSWORD") { "password" } %>


development:
  <<: *default
- database: app_development
+ database: rails_app_development

データベースを作成します。

bundle exec rails db:create

起動

IPアドレスとポートを指定してサーバを起動します。

rails s -b 0.0.0.0 -p 3001

ブラウザでhttp://localhost:3001/を開いて、正しく起動できているか確認します。
スクリーンショット 2020-02-11 18.49.22.png

ホストPCとdockerコンテナ間でプロジェクトディレクトリを共有しているので(./rails-app:/app)、編集はホストPCで(vscode等で!)、実行は環境構築したコンテナで、という開発ができます。
幸せになりましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CLIでECRにログインする時はget-loginではなくget-login-passwordを使おう

aws ecr get-login は非推奨に

コマンドリファレンスに記載されています。

Note: This command is deprecated. Use get-login-password instead.

AWS CLI Command Reference - get-login
https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html

aws ecr get-login コマンドの出力結果は以下のようになります。

$ aws ecr get-login --no-include-email
docker login -u AWS -p <password> https://<aws_account_id>.dkr.ecr.<region>.amazonaws.com

docker login コマンド全体が標準出力されるため便利ではあるのですが
そのまま実行してしまうと、パスワードがシェルのhistoryやログファイルに
残ってしまうというリスクがあります。

aws ecr get-login-password を使う

AWS CLI Command Reference - get-login-password
https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login-password.html

aws ecr get-login-password コマンドはログイン用のパスワードのみが標準出力されます。

$ aws ecr get-login-password
<password>

docker login コマンドは --password-stdin というオプションが利用可能で
パスワードを標準入力から読み込ませることができます。
以下のように aws ecr get-login-password の結果を渡すことで、
履歴やログにパスワードを残さず済みます。

$ aws ecr get-login-password | docker login --username AWS --password-stdin https://<aws_account_id>.dkr.ecr.<region>.amazonaws.com
Login Succeeded

Provide a password using STDIN
https://docs.docker.com/engine/reference/commandline/login/#provide-a-password-using-stdin

利用可能なAWS CLIのバージョン

get-login-password コマンドは AWS CLI Version 1.17.10 以降、
または2020/2/10にGAとなった Version 2で利用することができます。

注意点としては AWS CLI Version 2では get-loginコマンドは削除されているため利用できません。
Version 1.17.10 以降では 下位互換性のため、引き続き利用可能です。

The older aws ecr get-login command is still available in the AWS CLI version 1 for backward compatibility.

Breaking Changes – Migrating from AWS CLI version 1 to version 2
AWS CLI version 2 replaces ecr get-login with ecr get-login-password
https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html#cliv2-migration-ecr-get-login

以上です。
参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laradockでの環境構築からSeleniumでお手軽スクレイピング

概要

タイトル通りです。

対象者

スクレイピングするのに、Seleniumで 妥協していいや やりたいんだ!って人。

Laradockとは

Laravel + Dockerです。

Laravelとは

Laravelです。

Dockerとは

Dockerです。

Seleniumとは

ウェブアプリケーションの自動化を目的としたブラウザ用のテストツール群です。
しかしながら本記事で解説するようにスクレイピングを行うにあたっても利用できます。
指定したウェブサイトへアクセスし、指定したページに遷移し、指定した情報を読み取ってくるなどの動きが出来ます。
あくまでテスト用ツールなので、スクレイピングを主目的とするならイケてるPythonとかのそういうツールを使った方がいいです。
Selenium最高です。Selenium使いましょう。

今回スクレイピングで利用するツールの概要

  • Selenium Server
    クライアントとドライバーの中継役として働くサーバーです。Javaのコードで実装されています。
  • Chrome Driver
    ブラウザ操作に関する命令を仲介してくれます。 Chromeじゃなくてもいいですが、今回はChromeを使用します。他にもFirefox、IEなどがあります。
  • php-webdriver
    Chrome Driverを動かすためのライブラリ。PHPでは公式にドライバーを動かせるツールが無い為、Facebookが作ったらしいです。

Seleniumを使用したスクレイピングのしくみ

ざっくり説明すると、
プログラム => PHP WeDriver => Selenium Server => Chrome Driver => Chrome => スクレイピング対象Webサイト

みたいな感じですかね!知らんけど。

環境構築

LaravelとDocker

LaravelとDockerの環境構築については私の書いたズボラなこちらの記事を参考にしてください。
https://qiita.com/heiheiyoyo/items/0ff035d018a1ce268c69

Selenium

LaravelとDockerの環境構築はできたと思うので、早速SeleniumのコンテナをDockerで立ち上げましょう!

Seleniumコンテナの立ち上げについて必要な記述はLaradockのdocker-compose.ymlとDockerfileに既に記載されてますので、以下のコマンドを叩くだけでOKです。

$ docker-compose up -d selenium

立ち上がったか確認しましょう。
以下のようにSeleniumコンテナが立ち上がっていればOKです。

$ docker ps
d05e612e2baf        laradock_selenium    "/opt/bin/entry_poin…"   3 minutes ago       Up 3 minutes        0.0.0.0:4444->4444/tcp                           laradock_selenium_1
以下省略...

ちなみLaradock内のSeleniumのDockerfileを見てみると、以下のようになっており、デフォルトでベースのイメージがselenium/standalone-chromeにとされているので、今回の環境構築の手間が省けました。

FROM selenium/standalone-chrome

LABEL maintainer="Edmund Luong <edmundvmluong@gmail.com>"

EXPOSE 4444

php-webdriver

php-webdriverはまだインストールされていないので、ここで準備しておきます。
プロジェクト直下にあるcomposer.jsonというファイルに追記していきます。
この、composer.jsonというのはインストールしたいパッケージを記述しておき、後からコマンドを叩いた時に、まとめてインストールする為のファイルです。json形式で記述します。
ちなみに、require-devというのは本番に必要のない開発用のパッケージを記述する場所です。
本番でも必要な場合はrequireに記述しましょう。
今回はとりあえず、require-devに記述しておきます。
バージョンの書き方など詳しく知りたければ調べてください。
以下のリンクが参考になります。
Access Japan
https://access-jp.co.jp/blogs/development/256

とりあえず今回は最新版でインストールします。

追記前

"require-dev": {
        "facade/ignition": "^1.4",
        "fzaninotto/faker": "^1.4",
        "mockery/mockery": "^1.0",
        "nunomaduro/collision": "^3.0",
        "phpunit/phpunit": "^8.0"
    },

追記後

"require-dev": {
        "facade/ignition": "^1.4",
        "fzaninotto/faker": "^1.4",
        "mockery/mockery": "^1.0",
        "nunomaduro/collision": "^3.0",
        "phpunit/phpunit": "^8.0",
        "php-webdriver/webdriver": "*"
    },

composer updateコマンドを実行します。

composer updateはcomposer.jsonの情報を元に各ファイルを最新版にアップデートするらしいです。
これでプロジェクト内のvenderディレクトリにphp-webdriverというディレクトリが出来たと思います。
この中に実際にブラウザを操作するためのファイルがいろいろと入っています。

この中にChromeDriver.phpに定義されているChromeDriverクラスというものがドライバーの起動等を担ってくれるのですが、今回はコイツを使わず、継承元であるRemoteWebDriverというクラスを直接使っていきます。このRemoteWebDriverクラスにはスクレイピングに必要な処理(どのURLにアクセスするとか、どのページのどの要素を読み込むか等々)が色々と定義されています。(正確にはRemoteWebDriverはWebDriverインターフェースを実装しているので、定義自体はインターフェースで行われています。)

$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 1 removal
  - Removing facebook/webdriver (1.7.1)
  - Installing php-webdriver/webdriver (1.7.1): Downloading (100%)         
php-webdriver/webdriver suggests installing ext-SimpleXML (For Firefox profile creation)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.

ここまでくれば環境構築は終了ですが、最後にプロジェクト内にvendor/autoload.phpのファイルが存在するかをチェックしておいて下さい。なければcomposer install等を実行し、作っておいて下さい。
あとで使いますが、autoload.phpをrequireするだけで、vendor配下のファイルを自動で読み込めるので、便利です。

実装

クローラの作成

さあ実装に入っていきますが、まずはスクレイピングを行うにあたっての本体的な役割にあたるクローラというプログラムを作っていきましょう。

参考

クローラ(Crawler)とは、ウェブ上の文書や画像などを周期的に取得し、自動的にデータベース化するプログラムである。「ボット(Bot)」、「スパイダー」、「ロボット」などとも呼ばれる。(Wikipedia)

今回は手軽に実装する為、スクレイピングの開始から終了までを一つのファイルにまとめたものを作っていきます。(Laravelのコマンドクラスを使います。)

本格的にクローラを運用するなら、クローラの構成は、クローリングの処理はコマンドクラスとジョブクラスに分け、非同期処理を行う。さらに他クラスでDBの永続化処理を行うというような実装ができますが、それはまた別の機会にでもやりたいと思います。(やらんけど)

コマンドクラスの作成

以下のコマンドでコマンドクラスを作りましょう。「php artisan hogehoge(例)」 の「hogehoge」に当たる部分を自作していきます。自作したコマンドを叩けば、クローラの処理が実行されるという感じにしていきたいと思います。

デフォルトでapp/Console/Commands/に作成されます。

$ php artisan make:command SeleniumTestCommand
Console command created successfully.

作成されたコマンドクラスは以下のようになっていると思います。

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class SeleniumTestCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:name';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
    }
}

このコマンドクラスを書き換えていきましょう。

ソースコード

先ほど作成したコマンドクラスを適当に以下のように書き換えて下さい。

<?php

namespace App\Console\Commands;

require base_path(). '/vendor/autoload.php';

use Illuminate\Console\Command;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\WebDriverExpectedCondition;

class SeleniumTestCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'scrape:selenium-test';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Seleniumでスクレイピングをするよ';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // クロームの機能を管理するクラスのインスタンス化
        $options = new ChromeOptions();
        // クローム起動時のオプション格納
        $options->addArguments([
            '--no-sandbox',
            '--headless'
        ]);

        // Chromeブラウザを起動
        $caps = DesiredCapabilities::chrome();
        $caps->setCapability(ChromeOptions::CAPABILITY, $options);
        // ブラウザを実行するプラットフォームを指定。クロームとのセッションがスムーズになる??
        $caps->setPlatform("LINUX");

        // これはSelenium Serverの置いてあるURLなのかな
        $host = 'http://localhost:4444/wd/hub';

        try {
            // なんかよく起動できずに落ちたので、retry()でくくる
            $driver = retry(3, function () use ($host, $caps) {
                // chrome ドライバーの起動、ウイーーーーーーーーーーーン
                return RemoteWebDriver::create($host, $caps, 60000, 60000);
            }, 1000);


            // Y◯hoo!さんのニュースサイトに潜入します
            $driver->get('https://news.yahoo.co.jp');

            dump($driver->getCurrentUrl());

            // ページタイトル「Yahoo!ニュース」が現れるまで待ちます
            $driver->wait()->until(
                WebDriverExpectedCondition::titleIs('Yahoo!ニュース')
            );

            // トップページのトピックをリンクを取得していきます
            $topics_counts = count($driver->findElement(WebDriverBy::className('topicsList_main'))
                                          ->findElements(WebDriverBy::className('topicsListItem')));

            // リンク集
            $links = [];

            for ($i=0; $i <= $topics_counts -1; $i++) {
                $links[] = $driver->findElement(WebDriverBy::className('topicsList_main'))
                                  ->findElements(WebDriverBy::className('topicsListItem'))[$i]
                                  ->findElement(WebDriverBy::tagName('a'))
                                  ->getAttribute('href');
            }

            // リンクの数だけアクセス
            foreach ($links as $link) {

                // リンクが取得できているか
                dump($link);

                // URLにアクセス
                $driver->get($link);

                // ページタイトルに「Yahoo!ニュース」が含まれるものが現れるまで待ちます
                $driver->wait()->until(WebDriverExpectedCondition::titleContains('Yahoo!ニュース'));

                // 記事のタイトルをクローリングします
                $article_title = $driver->findElement(WebDriverBy::className('pickupMain_articleTitle'))
                                        ->getText();

                // 記事のタイトルが取得できているか
                dump($article_title);
            }

            // 処理終了
            return;
        } catch (\Exception $e) {
            echo 'エラーによりスクレイピングが失敗しました。ERROR MESSAGE : '.$e->getErrorMessage().' TRACE : '.$e->getTraceAsString();
        } finally {
            $driver->quit();
        }
    }
}

実行

以下のコマンドで実行しましょう。

$ php artisan scrape:selenium-test

うまくいけば、ターミナル常にdumpの出力がされて

サイトトップページURL
記事のページのURL
記事のタイトル
記事のページのURL
記事のタイトル
記事のページのURL
記事のタイトル
...

みたいになると思います。

うまくいかなければ、、、
てへぺろ。

解説

※後半から畳み掛けるように雑になると思います。

ChromeOptionsクラスのインスタンス化

ChromeOptionsクラスはChromeの機能を管理するクラスです。

$options = new ChromeOptions();

Chrome起動時に渡すコマンド引数の設定

以下のように指定します。headlessはヘッドレスモードで起動する設定で、no-sandboxはセキュリティを下げ、スリルを味わってスクレイピングしたい人向けの設定です。
addArguments()でChromeOptionsクラスのクラス変数argumentsにarrayで引数を渡しています。
argumentsはChromeの起動時に適用されます。

$options->addArguments([
            '--no-sandbox',
            '--headless'
        ]);

参考オプション一覧

  • user-data-dir... ユーザープロファイルの設定。 Chromeは複数のユーザーを使い分けられるらしいので、ユーザー毎にブックマーク、履歴、パスワードなどをプロファイル毎に管理できるのだと思います。
  • --proxy-server... プロキシサーバーの設定。 プロキシサーバーはクライアントとWebサイトの中継役を行います。セキュリティの向上などが見込まれます。
  • --headless... ヘッドレスモード。ブラウザのUIなしで起動できます。
  • --no-sandbox... サンドボックスの外でプロセスを動作させる。
  • window-size... 画面幅指定。
  • --start-maximized... 画面幅を最大化して起動します。
  • --user-agent... 他のブラウザや端末に偽装できます。(なりすませます。)
  • --incognito... シークレットモードで起動。履歴とかが残らない。アダルトサイトのスクレイピングとかにいいんじゃないっすかね。(誰がやんねん)
  • --single-process... シングルプロセスで起動します。通常はタブ、サイト毎のマルチプロセスで起動するっぽいのですが、場合によってはメモリの使用量とか負担になるので使用するといいかもしれません。
  • --disable-javascript... JavaScriptを無効にします。
  • --disable-popup-blocking... ポップアップブロックを無効にします。コード内でアラート等に対応する必要がなくなりそうなので、便利ですね。
  • --enable-logging... ログ出力を有効化します。ファイルはChrome\Application\chrome_debug.logで作成されるみたいです。
  • --log-level... ログレベルを設定できます。
  • --dump-histograms-on-exit... 終了時に各種統計情報をログファイルへ出力するという設定です。

オプションについては以下のサイトがよくまとめられており、参考になります。
起動オプション - Google Chrome まとめWiki
http://chrome.half-moon.org/43.html#y3a4f50e

その他Chromeの設定

今回は設定していませんが、例えば何かしらのファイルをスクレイピングで取得したいとします。
その場合はダウンロードしたファイルの保存先を以下のように設定します。download.default_directoryはデフォルトのダウンロードディレクトリを指定するオプションです。
ちなみにsetExperimentalOption()はChromeOptions APIを介してまだ公開されていないChromeDriverオプションを試す為に使用します。(実験的なオプションを指定する的な。)
prefは起動時の引数とは異なり、起動後?の設定画面の項目のことを指しています。

$options->setExperimentalOption('prefs', [
            'download.default_directory' => "任意のパス",
        ]);

設定の反映〜ドライバー立ち上げ

まずは、ドライバーを起動させる際の設定をしています。

// Chromeブラウザを起動
$caps = DesiredCapabilities::chrome();
$caps->setCapability(ChromeOptions::CAPABILITY, $options);
// ブラウザを実行するプラットフォームを指定。クロームとのセッションがスムーズになる??
$caps->setPlatform("LINUX");

// これはSelenium Serverの置いてあるURLなのかな
$host = 'http://localhost:4444/wd/hub';

次にドライバーを起動させます。
このタイミングで設定情報を渡してあげて下さい。
ちなみに原因不明ですが、Curl error thrown for http POST to...みたいなエラーを吐き捨てられることがよくあったので、リトライでくくっています。(要らないかも)

// なんかよく起動できずに落ちたので、retry()でくくる
$driver = retry(3, function () use ($host, $caps) {
    // chrome ドライバーの起動、ウイーーーーーーーーーーーン
    return RemoteWebDriver::create($host, $caps, 60000, 60000);
}, 1000);

いざ、クローリング開始

ドライバーの立ち上げに成功したら、Webサイトへアクセスします。
今回は天下のY◯hoo!さんのニュースサイトにお邪魔しようと思います。
トップページにアクセスして話題になっているトピックのページに遷移し、記事のタイトルを読み取ってくるという流れでいきます。

// Y◯hoo!さんのニュースサイトに潜入します
$driver->get('https://news.yahoo.co.jp');

// ページタイトル「Yahoo!ニュース」が現れるまで待ちます
$driver->wait()->until(
    WebDriverExpectedCondition::titleIs('Yahoo!ニュース')
);

トップページの左上部あたりに注目のニュース?的なトピックがあるので、その数をカウントしています。

// トップページのトピックをリンクを取得していきます
$topics_counts = count($driver->findElement(WebDriverBy::className('topicsList_main'))
                              ->findElements(WebDriverBy::className('topicsListItem')));

classNameの名前を指定し、トピックの数だけ該当するaタグのhrefを配列に格納していきます。

// リンク集
$links = [];

for ($i=0; $i <= $topics_counts -1; $i++) {

$links[] = $driver->findElement(WebDriverBy::className('topicsList_main'))
                  ->findElements(WebDriverBy::className('topicsListItem'))[$i]
                  ->findElement(WebDriverBy::tagName('a'))
                  ->getAttribute('href');
}

リンクが集まれば、リンクの数だけそのページにアクセスしていきましょう。
さらにリンク先に記事のタイトル文章が記載されているので、それを読み取ってきます。

// リンクの数だけアクセス
foreach ($links as $link) {

// リンクが取得できているか
dump($link);

// URLにアクセス
$driver->get($link);

// ページタイトルに「Yahoo!ニュース」が含まれるものが現れるまで待ちます
$driver->wait()->until(WebDriverExpectedCondition::titleContains('Yahoo!ニュース'));

// 記事のタイトルをクローリングします
$article_title = $driver->findElement(WebDriverBy::className('pickupMain_articleTitle'))
                        ->getText();

// 記事のタイトルが取得できているか
dump($article_title);
}

大部分の処理はこれで終了です。

補足

ChromeDriverを起動させましたが、場合によっては処理が終了したにも関わらず、プロセスが残り続けることがあるみたいです。

なんか嫌なので、quit()で終了させるようにしましょう。
finallyの中に組み込めば必ず実行されると思います。

} finally {
    $driver->quit();
}

はい!
ということでお手軽にスクレイピングができました!
現場からは以上です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで③

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで

Goal

カスタムユーザー設定を入れて、admin画面までたどり着く

その前に・・・

Djangoの設定を日本版にする。

setting.py
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'

まずはユーザー管理用のアプリを作る

startappでusersアプリを作る。

docker-compose run web python ./manage.py startapp users

すると、こんな感じになる。

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── static
│   └── users
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   └── __init__.py
│       ├── models.py
│       ├── tests.py
│       └── views.py
└── web
    ├── Dockerfile
    └── requirements.txt

Djangoでアプリを作ったら、settingに作ったことを教えてあげる。

settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig', #追加
]

カスタムユーザーを作ったので、そのことを教えてあげる。

settings.py
AUTH_USER_MODEL = 'users.User' #追加

ユーザーカスタマイズ

AbstractBaseUserでデフォルトのユーザークラスをオーバーライド。
デフォルトだとID&PASSだが、mailアドレスをID扱いにしている。

users/model.py
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.contrib.auth.base_user import BaseUserManager

class UserManager(BaseUserManager):
    """ユーザーマネージャー."""

    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """Create and save a user with the given username, email, and
        password."""
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)

        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    """カスタムユーザーモデル."""

    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        """Return the first_name plus the last_name, with a space in
        between."""
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)

モデルを追加したら、adminを修正する。

users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'


class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)


class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)


admin.site.register(User, MyUserAdmin)

モデルを作ったら、migrationする。

docker-compose run web python ./manage.py makemigrations
docker-compose run web python ./manage.py migrate

superユーザー作成

docker-compose run web python ./manage.py createsuperuser

createsuperuserを入れると、ID (今回だとメールアドレス)とPasswordを求められる。
管理者権限のユーザーなので、秘密にする。

メールアドレス: admin@example.com
Password:
Password (again):
Superuser created successfully.

Dockerで立ち上げよう①

Dockerを起動

docker-compose up

localhost:8000/adminへアクセスする。
表示されるけど、CSSが効いてない…
スクリーンショット 2020-02-11 14.39.45.png

静的ファイルの設定

Djangoで参照するSTATIC_ROOTを指定する。

setting.py
STATIC_ROOT = '/static'

DockerでStaticフォルダを指定する。

docker-compose.yml
version: "3"

services:

  nginx:
    image: nginx:1.13
    ports:
      - "8000:8000"
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./static:/static
    depends_on:
      - web

  db-postgres:
    image: postgres

  web:
    build: ./web
    volumes:
      - ./src:/code
      - ./static:/static #ここを追加
    expose:
     - "8000"
    depends_on:
      - db-postgres
    command: uwsgi --socket :8000 --module mysite.wsgi

collectstaticでプロジェクト内に入っているSTATICファイルの中身を
最上位のSTATICに集約する。

docker-compose run web python ./manage.py collectstatic

Dockerで立ち上げよう②

localhost:8000/adminへアクセスする。
ちゃんとCSSが効いた!

スクリーンショット 2020-02-11 15.01.09.png

最後はこんな感じ。

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── static
│   └── users
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-37.pyc
│       │   ├── admin.cpython-37.pyc
│       │   ├── apps.cpython-37.pyc
│       │   └── models.cpython-37.pyc
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   ├── 0001_initial.py
│       │   ├── __init__.py
│       │   └── __pycache__
│       │       ├── 0001_initial.cpython-37.pyc
│       │       └── __init__.cpython-37.pyc
│       ├── models.py
│       ├── tests.py
│       └── views.py
├── static
│   └── admin
│       ├── css
│       │   ├── autocomplete.css
│       │   ├── base.css
│   〜〜〜この中にいっぱい入っている。省略。
└── web
    ├── Dockerfile
    └── requirements.txt


参考

https://narito.ninja/blog/detail/39/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで③カスタムユーザーを作ってadminにたどり着く

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く<--今ここ
 ④ログインログアウトをしよう
 ⑤ユーザー登録(サインイン)機能を作ろう
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編
 ⑦ユーザーごとのデータ登録できるようにする〜削除編

Goal

カスタムユーザー設定を入れて、admin画面までたどり着く

その前に・・・

Djangoの設定を日本版にする。

setting.py
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'

まずはユーザー管理用のアプリを作る

startappでusersアプリを作る。

docker-compose run web python ./manage.py startapp users

すると、こんな感じになる。

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── static
│   └── users
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   └── __init__.py
│       ├── models.py
│       ├── tests.py
│       └── views.py
└── web
    ├── Dockerfile
    └── requirements.txt

Djangoでアプリを作ったら、settingに作ったことを教えてあげる。

settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig', #追加
]

カスタムユーザーを作ったので、そのことを教えてあげる。

settings.py
AUTH_USER_MODEL = 'users.User' #追加

ユーザーカスタマイズ

AbstractBaseUserでデフォルトのユーザークラスをオーバーライド。
デフォルトだとID&PASSだが、mailアドレスをID扱いにしている。

users/model.py
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.contrib.auth.base_user import BaseUserManager

class UserManager(BaseUserManager):
    """ユーザーマネージャー."""

    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """Create and save a user with the given username, email, and
        password."""
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)

        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    """カスタムユーザーモデル."""

    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        """Return the first_name plus the last_name, with a space in
        between."""
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)

モデルを追加したら、adminを修正する。

users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'


class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)


class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)


admin.site.register(User, MyUserAdmin)

モデルを作ったら、migrationする。

docker-compose run web python ./manage.py makemigrations
docker-compose run web python ./manage.py migrate

superユーザー作成

docker-compose run web python ./manage.py createsuperuser

createsuperuserを入れると、ID (今回だとメールアドレス)とPasswordを求められる。
管理者権限のユーザーなので、秘密にする。

メールアドレス: admin@example.com
Password:
Password (again):
Superuser created successfully.

Dockerで立ち上げよう①

Dockerを起動

docker-compose up

localhost:8000/adminへアクセスする。
表示されるけど、CSSが効いてない…
スクリーンショット 2020-02-11 14.39.45.png

静的ファイルの設定

Djangoで参照するSTATIC_ROOTを指定する。

setting.py
STATIC_ROOT = '/static'

DockerでStaticフォルダを指定する。

docker-compose.yml
version: "3"

services:

  nginx:
    image: nginx:1.13
    ports:
      - "8000:8000"
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./static:/static
    depends_on:
      - web

  db-postgres:
    image: postgres

  web:
    build: ./web
    volumes:
      - ./src:/code
      - ./static:/static #ここを追加
    expose:
     - "8000"
    depends_on:
      - db-postgres
    command: uwsgi --socket :8000 --module mysite.wsgi

collectstaticでプロジェクト内に入っているSTATICファイルの中身を
最上位のSTATICに集約する。

docker-compose run web python ./manage.py collectstatic

Dockerで立ち上げよう②

localhost:8000/adminへアクセスする。
ちゃんとCSSが効いた!

スクリーンショット 2020-02-11 15.01.09.png

最後はこんな感じ。

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── static
│   └── users
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-37.pyc
│       │   ├── admin.cpython-37.pyc
│       │   ├── apps.cpython-37.pyc
│       │   └── models.cpython-37.pyc
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   ├── 0001_initial.py
│       │   ├── __init__.py
│       │   └── __pycache__
│       │       ├── 0001_initial.cpython-37.pyc
│       │       └── __init__.cpython-37.pyc
│       ├── models.py
│       ├── tests.py
│       └── views.py
├── static
│   └── admin
│       ├── css
│       │   ├── autocomplete.css
│       │   ├── base.css
│   〜〜〜この中にいっぱい入っている。省略。
└── web
    ├── Dockerfile
    └── requirements.txt


参考

https://narito.ninja/blog/detail/39/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RaspiにUbuntu入れてその上にDocker入れてコンテナからpythonでGPIO制御

はじめに

めちゃくちゃややこしいタイトルになってしまいましたがこんなイメージです
スクリーンショット 2020-02-11 14.56.17.png

環境

開発PC:Mac Mojave
RaspBerry Pi4 メモリ4GB
SDカード16GB

Ubuntu19.10をSDに書き込む

https://ubuntu.com/download/raspberry-pi
から64bit版をダウンロードしてbalenaEtcharを使って書き込みました。

dockerをUbuntuにインストール

Ubuntuにsshかなんかで入って

sudo apt-get install docker-ce

でインストールできます。

sudo usermod -aG docker ubuntu

でsudo使わなくてもdockerできます。

ベースイメージを作る

開発PCでpython scriptを書いてからDockerfileでdockerImageにコピーしてもよいのですが、わかりづらかったので一度Dockerfileでベースを作ってから、コンテナに入りそのコンテナからイメージを作ります。

Dockerfileです

Raspberry Pi で TensorFlow する Docker 環境構築
を参考にさせていただきました、この記事ほぼほぼ上を参考にしました。
rpi.gpioを入れたかったのですが入れ方よくわかんなかったのでそのまま記述してます

FROM resin/rpi-raspbian:stretch

RUN echo "deb http://mirrordirector.raspbian.org/raspbian/ stretch main contrib non-free rpi firmware" > /etc/apt/sources.list

RUN apt-get update -y
RUN apt-get install -y --no-install-recommends \
        vim git less wget \
        build-essential \
        libatlas-base-dev \
        python3-pip python3-dev python3-setuptools\
        python3-scipy python3-h5py \
        libraspberrypi-bin \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

RUN pip3 install --upgrade pip setuptools
RUN pip3 install rpi.gpio

あとはbuildします

docker build -t rpi/rpi:0.1 .

docker imagesでできてることを確認しましょう!できてるはずです。

コンテナを起動してpythonスクリプトを書く

先ほど作ったイメージからコンテナを起動します。
bash
docker run --name rpi_test -ti --privileged rpi/rpi:0.1 /bin/bash

で、コンテナの中に入るので、欲しいものをインストールします。nano派なので

sudo apt-get update
sudo apt-get install nano

homeに移動してscriptを作ります。
この辺ほんとは作業ディレクトリとか設定するのがよいのだろうな・・・

cd home
nano test.py

GPIO17にLEDをさして適当にLチカするプログラムです。

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BOARD)
GPIO.setup(11, GPIO.OUT)
while True:
    GPIO.output(11, True)
    time.sleep(2)
    GPIO.output(11, False)
    time.sleep(2)

スクリプトを保存したら

exit

でログアウトしてください

docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS               NAMES
e700d8490be8        rpi/rpi:0.1           "/usr/bin/entry.sh /…"   11 minutes ago      Exited (0) 6 seconds ago                       rpi_test

コンテナが止まっています。このコンテナからイメージを作るには

docker commit rpi_test rpi/rpi:0.2
docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
rpi/rpi              0.2                 50f7f2a406e7        2 minutes ago       622MB

scriptが保存してあるイメージができました。

起動

sudo docker run --privileged -it -d --name rpi -w /home rpi/rpi:0.2 python3 test.py

バックグラウンドでコンテナが動いてLちかし続けます!
止めたい時は docker stop rpiでとまります

まとめ

センサーデータとかもそのうちとりたい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで②

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで

Goal

NginxからDjangoのようこそページへたどり着く

uWsgiの設定

NginxとDjangoを繋ぐためのuwsgiを入れる。

requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17

docker-composeも変更する。
nginxのコンテナ設定追加と、webコンテナのコマンド・ポート設定を変更する。

docker-compose.yml
version: "3"

services:

  nginx: #ここを追加する
    image: nginx:1.13
    ports:
      - "8000:8000"
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./static:/static #staticも繋ぐ
    depends_on:
      - web

  db-postgres:
    image: postgres

  web:
    build: ./web
    volumes:
      - ./src:/code
    expose:
     - "8000" #ポートフォワードじゃなくて普通にport空ける
    depends_on:
      - db-postgres
    command: uwsgi --socket :8000 --module mysite.wsgi #uwsgi起動に変更

Nginxの設定

こんなファイル体系になるように、nginx配下を作っていく

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── static
└── web
    ├── Dockerfile
    └── requirements.txt

confファイルで、静的レスポンスと動的レスポンスを振り分けてるイメージ。

conf/mysite_nginx.conf
upstream django {
    ip_hash;
    server web:8000;
}

server {
    listen      8000;
    server_name 127.0.0.1;
    charset     utf-8;

    client_max_body_size 75M;

    location /static {
        alias /static;
    }

    location / {
        uwsgi_pass  django;
        include     /etc/nginx/uwsgi_params;
    }
}

uwsgi_paramsは、そのまま。

uwsgi_params
uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

Dockerで立ち上げよう

requirement.txtを直したら、buildしておく。

docker-compose build

Dockerを起動

docker-compose up

localhost:8000へアクセスする
Djangoのrunserverと同じように、初期画面が表示されたら成功!
スクリーンショット 2020-02-11 14.01.47.png

参考

https://www.macky-studio.com/entry/2019/07/01/132337

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで②NginxでDjangoのようこそページへたどり着く

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
 ①Djangoのようこそページへたどり着くまで
 ②NginxでDjangoのようこそページへたどり着くまで<--今ここ
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう
 ⑤ユーザー登録(サインイン)機能を作ろう
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編
 ⑦ユーザーごとのデータ登録できるようにする〜削除編

Goal

NginxからDjangoのようこそページへたどり着く

uWsgiの設定

NginxとDjangoを繋ぐためのuwsgiを入れる。

requirement.txt
Django==2.2.2
psycopg2==2.8.4
uwsgi==2.0.17

docker-composeも変更する。
nginxのコンテナ設定追加と、webコンテナのコマンド・ポート設定を変更する。

docker-compose.yml
version: "3"

services:

  nginx: #ここを追加する
    image: nginx:1.13
    ports:
      - "8000:8000"
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./static:/static #staticも繋ぐ
    depends_on:
      - web

  db-postgres:
    image: postgres

  web:
    build: ./web
    volumes:
      - ./src:/code
    expose:
     - "8000" #ポートフォワードじゃなくて普通にport空ける
    depends_on:
      - db-postgres
    command: uwsgi --socket :8000 --module mysite.wsgi #uwsgi起動に変更

Nginxの設定

こんなファイル体系になるように、nginx配下を作っていく

.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── static
└── web
    ├── Dockerfile
    └── requirements.txt

confファイルで、静的レスポンスと動的レスポンスを振り分けてるイメージ。

conf/mysite_nginx.conf
upstream django {
    ip_hash;
    server web:8000;
}

server {
    listen      8000;
    server_name 127.0.0.1;
    charset     utf-8;

    client_max_body_size 75M;

    location /static {
        alias /static;
    }

    location / {
        uwsgi_pass  django;
        include     /etc/nginx/uwsgi_params;
    }
}

uwsgi_paramsは、そのまま。

uwsgi_params
uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

Dockerで立ち上げよう

requirement.txtを直したら、buildしておく。

docker-compose build

Dockerを起動

docker-compose up

localhost:8000へアクセスする
Djangoのrunserverと同じように、初期画面が表示されたら成功!
スクリーンショット 2020-02-11 14.01.47.png

参考

https://www.macky-studio.com/entry/2019/07/01/132337

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで①

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
開発環境はMac。DBはRDSで Postgresを使います。
間違っているところなどあれば、ご連絡お願いします。

Goal

Djangoのようこそページへたどり着く

前提

DockerとDocker-composeは開発環境に入っていること。
MacだとHomebrewを使えば簡単に入る。

Dockerの設定

たぶん、あまり捻りは無い。

Dockerfile
FROM python:3.7

ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/
requirement.txt
Django==2.2.2
psycopg2==2.8.4
docker-compose.yml
version: "3"

services:
  db-postgres:
    image: postgres

  web:
    container_name: web
    build: ./web #webの中のmanage.pyを見に行くよ
    volumes:
      - ./src:/code #srcの中をコンテナ内の/codeに繋げるよ
    ports:
      - "8000:8000" #portフォワード
    depends_on:
      - db-postgres #dbは上で指定したやつ
    command: python manage.py runserver 0.0.0.0:8000 #Django開発用サーバ起動

この時点のファイルはこんな感じ。

.
├── docker-compose.yml
├── src
└── web
    ├── Dockerfile
    └── requirements.txt

Djangoの設定

まずはプロジェクトを作る。
docker-compose.ymlがあるとこで実行。
最後のピリオドを忘れないこと。

docker-compose run web django-admin startproject mysite .

すると、以下のようにDjangoの各ファイルが作られる。

.
├── docker-compose.yml
├── src
│   ├── manage.py
│   └── mysite
│       ├── __init__.py
│       ├── settings.py
│       ├── urls.py
│       └── wsgi.py
└── web
    ├── Dockerfile
    └── requirements.txt

Django上でDBの設定をする。
今回はAWSにPostgresのRDSを作ってあるので、そこに繋げる。
ローカルからAWSのRDSへアクセスする場合は、RDSの設定で
パブリックアクセシビリティを「はい」にしないとダメなので注意。

setting.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': '*****',
        'PASSWORD': '*****',
        'HOST': '*****',
        'PORT': 5432,
    }
}

さあ確認

Dockerを起動

docker-compose up

localhost:8000へアクセスする
スクリーンショット 2020-02-11 13.02.47.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Docker+Nginx+Django+RDS】WEBアプリができるまで①Djangoのようこそページへたどり着くまで

前置き

独学で、子供の成長アプリを作った時のことを、記録として残していきます。
開発環境はMac。DBはRDSで Postgresを使います。
間違っているところなどあれば、ご連絡お願いします。

 ①Djangoのようこそページへたどり着くまで<--今ここ
 ②NginxでDjangoのようこそページへたどり着くまで
 ③カスタムユーザーを作ってadminにたどり着く
 ④ログインログアウトをしよう
 ⑤ユーザー登録(サインイン)機能を作ろう
 ⑥ユーザーごとのデータ登録できるようにする〜CRU編
 ⑦ユーザーごとのデータ登録できるようにする〜削除編

Goal

Djangoのようこそページへたどり着く

前提

DockerとDocker-composeは開発環境に入っていること。
MacだとHomebrewを使えば簡単に入る。

Dockerの設定

たぶん、あまり捻りは無い。

Dockerfile
FROM python:3.7

ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/
requirement.txt
Django==2.2.2
psycopg2==2.8.4
docker-compose.yml
version: "3"

services:
  db-postgres:
    image: postgres

  web:
    container_name: web
    build: ./web #webの中のmanage.pyを見に行くよ
    volumes:
      - ./src:/code #srcの中をコンテナ内の/codeに繋げるよ
    ports:
      - "8000:8000" #portフォワード
    depends_on:
      - db-postgres #dbは上で指定したやつ
    command: python manage.py runserver 0.0.0.0:8000 #Django開発用サーバ起動

この時点のファイルはこんな感じ。

.
├── docker-compose.yml
├── src
└── web
    ├── Dockerfile
    └── requirements.txt

Djangoの設定

まずはプロジェクトを作る。
docker-compose.ymlがあるとこで実行。
最後のピリオドを忘れないこと。

docker-compose run web django-admin startproject mysite .

すると、以下のようにDjangoの各ファイルが作られる。

.
├── docker-compose.yml
├── src
│   ├── manage.py
│   └── mysite
│       ├── __init__.py
│       ├── settings.py
│       ├── urls.py
│       └── wsgi.py
└── web
    ├── Dockerfile
    └── requirements.txt

Django上でDBの設定をする。
今回はAWSにPostgresのRDSを作ってあるので、そこに繋げる。
ローカルからAWSのRDSへアクセスする場合は、RDSの設定で
パブリックアクセシビリティを「はい」にしないとダメなので注意。

setting.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': '*****',
        'PASSWORD': '*****',
        'HOST': '*****',
        'PORT': 5432,
    }
}

さあ確認

Dockerを起動

docker-compose up

localhost:8000へアクセスする
スクリーンショット 2020-02-11 13.02.47.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あんまり頭を使わないで構築するRailsAPI on Docker

すでに多くの記事がありますが、APIモードのRailsがDocker上で動くところまで、必要最小限の情報でまとめてみます。RubyMineでの設定も最後にちょっと書いてあります。

参考: 公式 Quickstart: Compose and Rails

ファイル準備

アプリケーションを作成したいディレクトリで設定ファイルを作成します。

 $ touch Gemfile Gemfile.lock Dockerfile docker-compose.yml entrypoint.sh 

各ファイルの内容を埋めます。

Gemfile

source 'https://rubygems.org'
gem 'rails', '~>6'

Dockerfile

FROM ruby:2.6
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    ports:
      - 5438:5432
  app:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3008:3000"
    depends_on:
      - db
    environment:
      LANG: C.UTF-8
      BUNDLE_PATH: vendor/bundle
      RAILS_ENV: development
      DB_USER: postgres
      DB_NAME: app_db
      DB_PASSWORD: password
      DB_HOST: db

ファイルの内容ちょっと補足

  • Rubyは2.6、Railsは6系です
  • 公開するポートはrailsが3008、postgresqlが5438です
  • 後述するdatabase.ymlではwebサービスに設定した環境変数を読んで設定を行います

作る

$ docker-compose run app rails new . --force --no-deps --database=postgresql --api 
$ docker-compose build 

したら一通りRailsが必要なファイルを作ってくれます。自動で作成された database.yml を編集します。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  pool: 5

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test
$ docker-compose run app rails db:create

動作確認

$ docker-compose up

http://localhost:3008/で動いてるはずです。

補足: RubyMine使ってる方、ちょろっと設定編

インタプリタの設定

Preferences > Languages & Frameworks > Ruby SDK and Gems

で、追加 > Docker Composeをクリック > OK

読み込みが完了したら、該当のSDKをチェックし、APPLYして完了です。補完がいい感じに効くようになります。

DB設定

Database > 追加 > Data Source > PostgreSQL

  • Host: localhost
  • Port: 5438
  • User: postgres
  • Password: password

で接続ができるはずです。うまくいかない場合は、タブから適切なスキーマを選択したり、DBが起動しているか確かめたりしてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-composeでLaravel環境構築

はじめに

dockerによってLaravelの環境構築を行います。laradockは使いません。
以前はlaradockを使っていましたが、
・プロジェクトが必要以上に大きくなる
・dockerの知識がほとんどなくても環境構築できでしまう
これらの点からlaradockを使わない方法を試していきます。

laradockはとりあえずdockerでLaravelを立ち上げたい方、知識が全くない方は使うとよいかもしれません。

以前にlaradockによるlaravelの環境構築の記事を書きましたので、laradockを使いたい方はこちらを→https://qiita.com/rope19181/items/da31dc2cd6097315fa10

環境・仕様

・nginx上でPHP実行環境を構築し、Laravel新規プロジェクト作成。
・mysqlとwebGUIとしてphpmyadminも使えるようにします。

環境、仕様は以下の通りです。
Docker version 19.03.5
docker-compose version 1.24.1
php 7.2-fpm
mysql 5.7

構成

最終的にこのような構成となります。

project
├ docker
│  ├ db
│  │ ├ data
│  │ ├ my.conf
│  │ └ sql
│  │
│  ├ nginx
│  │  └ default.conf
│  │  
│  └ php
│   ├ Dockerfile
│   └ php.ini  
├ myapp
└ docker-compose.yml

今回はprojectをルートディレクトリとしています。

./myappはlaravelのプロジェクトを置くディレクトリとなります。

docker-compose.ymlの作成

docker-composeでは複数のコンテナを管理、構築するためにdocker-compose.ymlに各コンテナの環境設定を定義します。

docker-compose.yml
version: '3'

services:
  php:
    container_name: php
    build: ./docker/php
    volumes:
      - ./myapp/:/var/www

  nginx:
    image: nginx
    container_name: nginx
    ports:
    - 80:80
    volumes:
    - ./myapp/:/var/www
    - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
    - php

  db:
    image: mysql:5.7
    container_name: db-host
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: database
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
    - ./docker/db/data:/var/lib/mysql
    - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
    - 3306:3306

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=db-host
      - PMA_USER=docker
      - PMA_PASSWORD=docker
    links:
      - db
    ports:
      - 8080:80
    volumes:
      - /sessions

各項目について簡単に説明します。
詳しくは公式リファレンスを見てみましょう。https://docs.docker.com/compose/compose-file/

image

ローカルやリモートにあるイメージを指定します。ローカルにない場合リモートからpullされます。

今回はnginx、mysql、phpmyadminはローカルのイメージを指定しています。

build

イメージをbuildする際に参照するファイルを指定します。
要はイメージを構築する場合はこちらを使うわけです。またbuildとimageを両方指定することはできません。

今回はphpコンテナの構築でDockerfileを使うため、そのディレクトリのパスを指定しています。

volumes

ボリュームとしてマウントするパスを指定します。ホスト:コンテナまたアクセスモードを読み取り専用する場合ホスト:コンテナ:roとします。

今回はアクセスモードを指定しないためrwとなり、書き込みもできるボリュームです。

ports

公開(expose)するポートをします。ホスト:コンテナとします。

ポートが被る場合はここを変更します。

links

コンテナにリンクさせるサービス名を指定します。
注)コンテナ名(container_name)ではなくサービス名

今回はphpmyadminのコンテナにてdb-hostコンテナのサービス名であるdbを指定しています。

phpのDockerfileの作成

phpコンテナではLaravelの実行のためにcomposerのインストールします。
公式(https://getcomposer.org/download/) から下の部分を#Composer installのところに貼り付けます。

スクリーンショット 2020-02-10 18.55.21.png

Dockerfile
FROM php:7.2-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
  && apt-get install -y zlib1g-dev mariadb-client \
  && docker-php-ext-install zip pdo_mysql

#Composer install
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php -r "if (hash_file('sha384', 'composer-setup.php') === 'c5b9b6d368201a9db6f74e2611495f369991b72d9c8cbd3ffbc63edff210eb73d46ffbfce88669ad33695ef77dc76976') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin


WORKDIR /var/www

RUN composer global require "laravel/installer"

PHP設定ファイルの作成

docker/php/にphp.iniを作成します。

php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

nginxの設定ファイルの作成

docker/nginxにdefault.confを作成します。

default.conf
server {
  listen 80;
    index index.php index.html;
    root /var/www/public;

  location / {
    root /var/www/public;
    index  index.html index.php;
    }

  location ~ \.php$ {

    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass php:9000;
    fastcgi_index index.php;
    include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PATH_INFO $fastcgi_path_info;
  }
 }

dockerの起動

下記コマンドでdockerを起動させます。初回は結構時間がかかります。
docker-composeではupによってイメージの構築、コンテナの起動を行いますが、初回はキャッシュがないため--buildをつけます。-dオプションによってバックグラウンドで起動させます。

$ docker-compose up -d --build

laravelプロジェクトの作成

まずはphpコンテナに入ります。コンテナに入る場合はexecコマンドを実行します。

$ docker-compose exec php bash

laravelの新規プロジェクトを作成します。

composer create-project --prefer-dist "laravel/laravel myapp

laravelプロジェクトの環境設定

laravelプロジェクト内の.envやconfig/detabase.phpのデータベースの設定を変更します。
DB名、ユーザ名、パスワードなどを先ほどdocker-compose.ymlに設定した値に変更しましょう。

動作確認

nginx、php

http://localhost にアクセスしlaravelのホームページが表示されればOK。

スクリーンショット 2020-02-11 00.24.57.png

mysql、phpmyadmin

phpコンテナに入り、migrateします。

$ docker-compose exec php bash

$ php artisan migrate

http://localhost:8080 にアクセスし、phpmyadminが立ち上がりDB内に2つのテーブルが作成されていれば成功。

スクリーンショット 2020-02-11 00.23.37.png

最後に

laradockも簡単にできますが、少しの知識があればこちらの方法のほうがシンプルで分かりやすいと思います。

laradockを使いたい方はこちら→https://qiita.com/rope19181/items/da31dc2cd6097315fa10

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerでLaravel環境構築

はじめに

dockerによってLaravelの環境構築を行います。できるだけシンプルな構成を目指します。laradockは使いません。

自分は以前laradockを使っていましたが、
・プロジェクトが必要以上に大きくなる
・dockerの知識がほとんどなくても環境構築できでしまう
これらの点からlaradockを使わない方法を試していきます。

laradockはとりあえずdockerでLaravelを立ち上げたい方、知識が全くない方は使うとよいかもしれません。

以前にlaradockによるlaravelの環境構築の記事を書きましたので、laradockを使いたい方はこちらを→https://qiita.com/rope19181/items/da31dc2cd6097315fa10

環境・仕様

・nginx上でPHP実行環境を構築し、Laravel新規プロジェクト作成。
・mysqlとwebGUIとしてphpmyadminも使えるようにします。

・環境、仕様は以下の通りです。
Docker version 19.03.5
docker-compose version 1.24.1
php 7.2-fpm
mysql 5.7

構成

最終的にこのような構成となります。

project
├ docker
│  ├ db
│  │ ├ data
│  │ ├ my.conf
│  │ └ sql
│  │
│  ├ nginx
│  │  └ default.conf
│  │  
│  └ php
│   ├ Dockerfile
│   └ php.ini  
├ myapp
└ docker-compose.yml

今回はprojectをルートディレクトリとしています。

./myappはlaravelのプロジェクトを置くディレクトリとなります。

docker-compose.ymlの作成

docker-composeでは複数のコンテナを管理、構築するためにdocker-compose.ymlに各コンテナの環境設定を定義します。

docker-compose.yml
version: '3'

services:
  php:
    container_name: php
    build: ./docker/php
    volumes:
      - ./myapp/:/var/www

  nginx:
    image: nginx
    container_name: nginx
    ports:
    - 80:80
    volumes:
    - ./myapp/:/var/www
    - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
    - php

  db:
    image: mysql:5.7
    container_name: db-host
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: database
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
    - ./docker/db/data:/var/lib/mysql
    - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
    - 3306:3306

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=db-host
      - PMA_USER=docker
      - PMA_PASSWORD=docker
    links:
      - db
    ports:
      - 8080:80
    volumes:
      - /sessions

各項目について簡単に説明します。
詳しくは公式リファレンスを見てみましょう。https://docs.docker.com/compose/compose-file/

image

ローカルやリモートにあるイメージを指定します。ローカルにない場合リモートからpullされます。

今回はnginx、mysql、phpmyadminはリモートのイメージを指定しています。

build

イメージをbuildする際に参照するファイルを指定します。
要はイメージを構築する場合はこちらを使うわけです。またbuildとimageを両方指定することはできません。

今回はphpコンテナの構築でDockerfileを使うため、そのディレクトリのパスを指定しています。

volumes

ボリュームとしてマウントするパスを指定します。ホスト:コンテナまたアクセスモードを読み取り専用する場合ホスト:コンテナ:roとします。

今回はアクセスモードを指定しないためrwとなり、書き込みもできるボリュームです。

ports

公開(expose)するポートをします。ホスト:コンテナとします。

ポートが被る場合はここを変更します。

links

コンテナにリンクさせるサービス名を指定します。
注)コンテナ名(container_name)ではなくサービス名

今回はphpmyadminのコンテナにてdb-hostコンテナのサービス名であるdbを指定しています。

phpのDockerfileの作成

phpコンテナではLaravelの実行のためにcomposerのインストールします。
公式(https://getcomposer.org/download/) から下の部分を#Composer installのところに貼り付けます。

スクリーンショット 2020-02-10 18.55.21.png

Dockerfile
FROM php:7.2-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
  && apt-get install -y zlib1g-dev mariadb-client \
  && docker-php-ext-install zip pdo_mysql

#Composer install
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php -r "if (hash_file('sha384', 'composer-setup.php') === 'c5b9b6d368201a9db6f74e2611495f369991b72d9c8cbd3ffbc63edff210eb73d46ffbfce88669ad33695ef77dc76976') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin


WORKDIR /var/www

RUN composer global require "laravel/installer"

PHP設定ファイルの作成

docker/php/にphp.iniを作成します。

php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

nginxの設定ファイルの作成

docker/nginxにdefault.confを作成します。

default.conf
server {
  listen 80;
    index index.php index.html;
    root /var/www/public;

  location / {
    root /var/www/public;
    index  index.html index.php;
    }

  location ~ \.php$ {

    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass php:9000;
    fastcgi_index index.php;
    include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PATH_INFO $fastcgi_path_info;
  }
 }

dockerの起動

下記コマンドでdockerを起動させます。初回は結構時間がかかります。
docker-composeではupによってイメージの構築、コンテナの起動を行いますが、初回はキャッシュがないため--buildをつけます。-dオプションによってバックグラウンドで起動させます。

$ docker-compose up -d --build

laravelプロジェクトの作成

まずはphpコンテナに入ります。コンテナに入る場合はexecコマンドを実行します。

$ docker-compose exec php bash

laravelの新規プロジェクトを作成します。

composer create-project --prefer-dist "laravel/laravel myapp

laravelプロジェクトの環境設定

laravelプロジェクト内の.envやconfig/detabase.phpのデータベースの設定を変更します。
DB名、ユーザ名、パスワードなどを先ほどdocker-compose.ymlに設定した値に変更しましょう。

動作確認

nginx、php

http://localhost にアクセスしlaravelのホームページが表示されればOK。

スクリーンショット 2020-02-11 00.24.57.png

mysql、phpmyadmin

phpコンテナに入り、migrateします。

$ docker-compose exec php bash

$ php artisan migrate

http://localhost:8080 にアクセスし、phpmyadminが立ち上がりDB内に2つのテーブルが作成されていれば成功。

スクリーンショット 2020-02-11 00.23.37.png

docker-composeコマンド小まとめ

よく使うものをまとめます。

up

コンテナの構築、起動をします。

似たものとしてイメージの構築のみを行うbuild、コンテナの起動のみを行うstartなどがありますが、docker-composeではとりあえずupをしとけばいいと思います。
Dockerfileを更新した時などはbuildしましょう。

$ docker-compose up -d  
$ docker-compose up -d --build #初回の起動などbuildもしたいとき

down

upで作られたものを削除します。downだけだとコンテナとネットワークの削除を行います。オプションによってその範囲を指定します。

$ docker-compose down

コンテナ、イメージ、ボリュームそしてネットワークすべてを削除したい場合は

$ docker-compose down --rmi all --volumes

exec

起動中のコンテナでコマンドを実行

$ docker-compose exec コンテナ名 実行するコマンド

$ docker-compose exec コンテナ名 bash #コンテナに入る場合

最後に

laradockも簡単にできますが、少しの知識があればこちらの方法のほうがシンプルで分かりやすいと思います。

laradockを使いたい方はこちら→https://qiita.com/rope19181/items/da31dc2cd6097315fa10

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reCAPTCHAのサイトを毎日自動スクレイピングする(1/7)

RPAによる業務改善をするプロジェクトに参加していたのですが…
RPAツールがあまりにも高額(720万円/年)だったため、pythonで置き換えました。
※少々フェイクあり

要件

概要

  • とあるサイトにログインし、特定ワードで検索した結果をダウンロードし、データを取得します。
  • INPUTとなる特定ワードはAmazonS3に毎日置かれます。
  • 取得したデータを整形し、S3上の別位置に置くことがOUTPUTとなります。
  • なお、サイトの会社に連絡し、スクレイピングの許可はいただいています。

難点

  • その特定サイトにはログイン時にreCAPTCHAが仕込まれています(!)
  • その特定サイトではAnglarJSが使われています
  • ダウンロードするときには、クリックをしなければなりません
  • 毎日実行できるようにしなければなりません

技術選定

(かなり試行錯誤したのですが)結論だけ書くと、下記のようになりました。
なかなか大変だった…

  • pythonのseleniumを使ってデータを取得
  • 「2captcha」というサービスを使ってreCAPTCHAを突破
  • Dockerコンテナ化。Xvfbを使って仮想ディスプレイを立ち上げ、アプリを実行
  • AWS batchを使って毎日起動させる

Story

下記のような流れで開発していきました。

  1. python環境構築
  2. サイトのスクレイピング機構を作る
  3. ダウンロードしたファイル(xls)を加工し、最終成果物(csv)を作成するようにする
  4. S3からのファイルダウンロード / S3へのファイルアップロードをつくる
  5. 2captchaを実装
  6. Dockerコンテナで起動できるようにする
  7. AWS batchに登録

それぞれのページでQiitaは分けようかと思います。

1.環境構築

まずは通常のpython環境の構築です!

今回は python3.6.3 を使います。ターミナルはデフォルトの bash です。
今後 複数のバージョンのpythonを使いたくなることを考え、pyenvpyenv-virtualenv を使って専用のpython環境を作ろうと思います。

pyenvのインストール

git clone https://github.com/pyenv/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile

続いて、python3.6をインストール

pyenv install 3.6.3

pyenv-virtualenvのインストール

必要なのかどうかよくわかっていないのですが、誰かが良いと言っていたので pyenv-virtualenvも入れます。

git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile

そして今回のためのvirtualenvを作成。

pyenv virtualenv 3.6.3 myproject

ディレクトリの作成

今回のプロジェクトで使うディレクトリを準備しておきます。
(このへんも本当は試行錯誤しながら変えていったのですが)

mkdir myproject
cd myproject
pyenv local myproject

このmyproject以下に、下記のようにフォルダとファイルを準備します。

├── app
│   ├── drivers          seleniumドライバーを置く
│   └── source           
│       └── scraping.py  処理
└── tmp
    ├── files
    │   └── download     スクレイピングでダウンロードしたファイルを置く
    └── logs             ログ(seleniumログなど)

続きはまた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reCAPTCHAのサイトを毎日自動スクレイピングする (1/7: python環境構築)

RPAによる業務改善をするプロジェクトに参加していたのですが…
RPAツールがあまりにも高額(720万円/年)だったため、pythonで置き換えました。
そのときの仕事をまとめたものです。

要件

概要

  • とあるサイトにログインし、特定ワードで検索した結果をダウンロードし、データを取得します。
  • INPUTとなる特定ワードはAmazonS3に毎日置かれます。
  • 取得したデータを整形し、S3上の別位置に置くことがOUTPUTとなります。
  • なお、サイトの会社に連絡し、スクレイピングの許可はいただいています。

難点

  • その特定サイトにはログイン時にreCAPTCHAが仕込まれています(!)
  • その特定サイトではAnglarJSが使われています
  • ダウンロードするときには、クリックをしなければなりません
  • 毎日実行できるようにしなければなりません

技術選定

(かなり試行錯誤したのですが)結論だけ書くと、下記のようになりました。
なかなか大変だった…

  • pythonのseleniumを使ってデータを取得
  • 「2captcha」というサービスを使ってreCAPTCHAを突破
  • Dockerコンテナ化。Xvfbを使って仮想ディスプレイを立ち上げ、アプリを実行
  • AWS batchを使って毎日起動させる

Story

下記のような流れで開発していきました。

  1. python環境構築
  2. サイトのスクレイピング機構を作る
  3. ダウンロードしたファイル(xls)を加工し、最終成果物(csv)を作成するようにする
  4. S3からのファイルダウンロード / S3へのファイルアップロードをつくる
  5. 2captchaを実装
  6. Dockerコンテナで起動できるようにする
  7. AWS batchに登録

それぞれのページでQiitaは分けようかと思います。

1.環境構築

まずは通常のpython環境の構築です!

今回は python3.6.3 を使います。ターミナルはデフォルトの bash です。
今後 複数のバージョンのpythonを使いたくなることを考え、pyenvpyenv-virtualenv を使って専用のpython環境を作ろうと思います。

pyenvのインストール

git clone https://github.com/pyenv/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile

続いて、python3.6をインストール

pyenv install 3.6.3

pyenv-virtualenvのインストール

必要なのかどうかよくわかっていないのですが、誰かが良いと言っていたので pyenv-virtualenvも入れます。

git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile

そして今回のためのvirtualenvを作成。

pyenv virtualenv 3.6.3 myproject

ディレクトリの作成

今回のプロジェクトで使うディレクトリを準備しておきます。
(このへんも本当は試行錯誤しながら変えていったのですが)

mkdir myproject
cd myproject
pyenv local myproject

このmyproject以下に、下記のようにフォルダとファイルを準備します。

├── app
│   ├── drivers          seleniumドライバーを置く
│   └── source           
│       └── scraping.py  処理
└── tmp
    ├── files
    │   └── download     スクレイピングでダウンロードしたファイルを置く
    └── logs             ログ(seleniumログなど)

続きはこちら。
https://qiita.com/kamyu1201@github/items/a07c7d175c051b8ab4c0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsの環境構築 その1

はじめに

これからdockerで開発環境の構築からAWSデブロイまで数回に分けてまとめてみます

私が使った有料教材

Dockerについて

Dockerについては下記のサイトを見てください。
* 入門 Docker

Dockerでrailsの環境構築

参考

作業手順

ディレクトリを作成しそこで作業

mkdir ror_app

Dockerfileを作成Dockerfile内に下記を記述

touch Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

コマンド

FROM: ベースとなるDocker Image を指定します。
COPY: Docker内へホストのファイル/ディレクトリをコピーします。
  COPY は基本的に2つの引数を設定します。1つ目はホスト側のディレクトリ、2つ目はDocker側のディレクトリです。
RUN: Docker内でコマンドを実行します。
CMD: Docker起動時にデフォルトで実行されるコマンドを定義します。
WORKDIR: Dockerfileでコマンドを実行する際に基準となるディレクトリを設定します。
EXPOSE: コンテナ起動時に公開することを想定されているポートを記述します
ENTRYPOINT: 指定されたコマンドを実行します。

GemfileとGemfile.lockの作成

  • rails newするときに必要
touch Gemfile && touch Gemfile.lock
source 'https://rubygems.org'
gem 'rails', '~>5'

entrypoint.shを作成

  • 特定のserver.pidファイルが存在するときにサーバーが再起動しないようにするRails固有の問題を修正するエントリポイントスクリプトを提供します。このスクリプトは、コンテナが開始されるたびに実行されます。
  • touch entrypoint.sh
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

docker-compose.ymlの作成

touch docker-compose.yml
docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    ports:
      - '5432:5432'
    volumes:
      - postgresql-data:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  postgresql-data:
    driver: local

docker上でrailsのファイルを作成

docker-compose run web rails new . --force --no-deps --database=postgresql

# rails new . とすることで、カレントディレクトリ配下にrailsファイルが作成される  
--forceで事前に作成したファイルを上書き、今回はGemfile等

gem 'pg'のバージョン変更

gem 'pg', '~> 0.20.0'

イメージをbuild

docker-compose build

すると

ror_app_web:latestになります。

databese.ymlの書き換え

default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5
# host,username,passwordを追加しました。
hostをdbにすることでdocker-compose.ymlのdbを指定

本番環境や開発環境でdatabase関連のエラーが良く出てきます。
特に本番環境をAWSにするとproduciton:配下を書き換えないといけません。

データベースの作成

docker-compose run web rake db:create

サービスの起動

docker-compose up
# 終了はCtrl+C

ブラウザでlocalhost:3000にアクセスするとrailsの初期画面が出現したと思います。

docker-composeコマンドまとめ

ローカル環境 docker環境
rails s docker-compose up
rails db:migrate docker-compose run web rails db:migrate
bundle install docker-compose run web bundle install
rails g model モデル名 docker-compose run web rails g model モデル名

git push

railsの初期画面が出てきたらgit pushしておきましょう。

お疲れさまでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerのImageの保存先を変更する方法

動機

Surface Book 2 15''をWindows 10とUbuntu 18.04のデュアルブートにしたが、Surface Book 2 15''はSSD 256GBモデルのため、容量が足りない。そこで、仕方なく256GBのSDカードを購入。

このSDカードにDockerのイメージを保存できたら最高なんじゃないか??

という発想です。

方法

色んなサイトの方法を試したが、なかなかうまく行く方法が見つからなかったので、成功した方法の共有メモとして残しておきます。

/etc/init.d/docker stop
mv /var/lib/docker /[新しいDockerのイメージ保存先]
ln -s /[新しいDockerのイメージ保存先]/ /var/lib/docker
/etc/init.d/docker start

参考サイト:http://rikeiin.hatenablog.com/entry/2019/09/13/115611

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む