見出し画像

なぜPythonを使うと爆速でアプリ開発できるのか

効率的でスマートなアプリ開発はプロジェクトの成功率を大幅に向上させます。PythonとDjangoでアプリ開発をすることで効率的なアプリ開発を実現できます。

そこで前半はPythonとDjangoの組み合わせがいい理由を、後半は実際に投票アプリを作成する方法を記載していきます。
(本サイトはアフィリエイト広告を利用しています)


PythonとDjangoの組み合わせは相性がいい

Pythonはシンプルな構文と高い可読性を持ち、Djangoは効率的なWebアプリケーション開発を支援するフレームワークです。
開発者はコードを迅速に書くことができ、煩雑な構文や冗長な宣言を避けることができます。

開発したいロジックに集中できる

Djangoはデータベースアクセス、フォーム処理、ユーザー認証などの共通の開発タスクを自動化し、開発者がこれらの基盤作業に時間を費やすことなく、開発したいアプリケーションロジックに集中できる環境を提供しています。

強力なセキュリティがついている

さらに、Djangoの強力なセキュリティ機能は、アプリケーションの安全性を確保するのに役立ちます。Djangoはクロスサイトスクリプティング(XSS)、SQLインジェクション、CSRF(クロスサイトリクエストフォージェリ)などの一般的な脆弱性からアプリケーションを保護するためのツールを提供しています。

ここからは実際に投票アプリを開発してDjangoの爆速を体験してみましょう!

Dockerで環境構築

以下のようにDockerを使って環境構築をします。
django-tutorialの中にweb-1とdb-1というコンテナを作成し、web-1コンテナの中でアプリを投票アプリを作成していきます。

コンテナとフォルダのイメージ

Docker準備

デスクトップに「Django-Tutorial」フォルダを作成します。

requirement.txt に以下のように必要なライブラリを記載します。

requirements.txt
---------------------------------------------------------------------------

asgiref==3.7.2
Django==4.2.4
psycopg2==2.9.7
psycopg2-binary==2.9.7
sqlparse==0.4.4
typing_extensions==4.7.1

Dockerfileにコンテナへの設定を記載します。

Dockerfile
---------------------------------------------------------------------------

FROM python:3.11
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/

docker-compose.yml に作成するコンテナを記載します。

docker-compose.yml
---------------------------------------------------------------------------

version: "3"

services:
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: "password123"
  web:
    build: .
    command: python3 poll_project/manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

ターミナルで以下のコマンドを実行してpoll_projectフォルダを作成します。

Django-Tutorial % docker-compose run web django-admin startproject poll_project

以下のようなフォルダが作成されます、

poll_project/
    manage.py
    poll_project/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

poll_project/poll_project/settings.pyのDB設定を変更

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

ついでにタイムゾーンと言語を変更

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'
Docker Desktop画面


投票機能を作成する(準備)

docker exec -it django-tutorial-web-1 bash

poll_projectフォルダへ移動して投票機能となるアプリ機能を作成します。

root@2a64f6359306:/code# cd poll_project/
root@2a64f6359306:/code/poll_project# python3 manage.py startapp polls

以下のようなアプリフォルダが作成されます。

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

pollsが機能するか確認する

アプリ機能が正常に機能するか確認します。
polls/views.py を開いて、以下のように記載してください。

poll_project/polls/views.py
---------------------------------------------------------------------------

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. これはpollsのViewが表示されています。")

polls/urls.py ファイルを作成し、viewへ遷移するように記載します。

poll_project/polls/urls.py
---------------------------------------------------------------------------

from django.urls import path

from . import views

urlpatterns = [
    path("", views.index, name="index")
]

poll_project/urls.py ファイルに投票アプリへ接続したときにpolls/urls.pyへ遷移するように記載します。

poll_project/poll_project/urls.py
---------------------------------------------------------------------------

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

urlpatterns = [
    path("polls/", include("polls.urls")),
    path("admin/", admin.site.urls),
]

http://localhost:8000/polls/ をブラウザで表示すると画像のようになっていれば正しく表示されています。

ブラウザ画面

投票機能を作成する(作成)

ここからは投票機能を作成していきます。

投票アプリ概要

  • ユーザが投票したり結果を表示したりできる公開用サイト

  • 投票項目の追加、変更、削除を行うための管理 (admin) サイト

pollsの機能を追加する

pollsの機能を使えるようにするため、poll_project/settings.pyにpollsを追加します。

poll_project/poll_project/settings.py
---------------------------------------------------------------------------

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

データベースを設定

コンテナで作成しているpostgresqlにDjangoでしようするテーブルの設定を反映していきます。

ターミナルでDjango-Tutorialフォルダにいることを確認し、以下のコマンドを実行します。

Django-Tutorial % docker exec -it django-tutorial-web-1 bash

poll_projectフォルダに移動して以下のコマンドを実行することでDjangoでしようするテーブルが反映されます。



root@2a64f6359306:/code# cd poll_project/

root@2a64f6359306:/code/poll_project# python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

Djangoで設定している、あるいは設定したコードをデータベースに反映することをマイグレーションといいます。

モデルの作成

ここからはデータベースと連携するファイルであるmodels.pyの編集をします。

pollsのmodels.pyに以下のように記載します。

poll_project/polls/models.py
---------------------------------------------------------------------------

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

データのつながりは以下のようになっています。

データのつながりのイメージ

idはDjangoが自動で付与してくれます。もちろん、自身でquestion_idのように設定することもできます。

ChoiceのquestionがintになっているのはQuestionのidが外部キーとして入ってくるからです。

モデルをデータベースに反映

models.pyで設定したモデルをもとにマイグレーションファイルを作成します。

root@2a64f6359306:/code/poll_project# python manage.py makemigrations polls

Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Question
    - Create model Choice

マイグレーションファイルが作成されたのを確認し、以下のコマンドでマイグレーションの内容をデータベースに反映します。

root@2a64f6359306:/code/poll_project# python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK

投票の管理機能を追加

ここではDjangoの管理機能で投票を管理する方法を記載します。

管理者ユーザーの作成

Djangoにはデフォルトでユーザーを管理する機能があります。
以下のコマンドでユーザーを作成します。

root@2a64f6359306:/code/poll_project# python manage.py createsuperuser
ユーザー名 (leave blank to use 'root'): adminuser    
メールアドレス: 
Password: password
Password (again): password
このパスワードは一般的すぎます。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

今回はチュートリアルということなので簡単なユーザー名とパスワードを使用しましたが、本番環境にもデプロイする可能性がある場合は推測しにくいものをしようしてください。

次はブラウザで http://127.0.0.1:8000/admin/ にアクセスします。
以下のような admin のログイ ン画面が表示されます。

ログイン画面

ログインするとDjangoの管理画面が表示されます。

Djangoの管理画面

管理画面でデータを編集できるようにする

管理画面でモデルで定義されているデータを登録できるようにadmin.pyを以下のように記載します。

poll_project/polls/admin.py
---------------------------------------------------------------------------

from django.contrib import admin

from .models import Question, Choice

admin.site.register(Question)
admin.site.register(Choice)

もう一度 http://127.0.0.1:8000/admin/ にアクセスします。
すると、管理画面に作成したモデルが表示されました。

データ編集ができるようになった画面

モデルの日本語化

しかし、モデルをそのまま表示しているだけになっているので、日本語で表示されるように修正します。

アプリ機能のタイトルが「POLLS」になっているので、「投票調査」に変更します。

poll_project/polls/apps.py
---------------------------------------------------------------------------

from django.apps import AppConfig


class PollsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'polls'
    verbose_name = '投票調査'

モデル名が表示されている部分を日本語表示にしていきます。

poll_project/polls/models.py
---------------------------------------------------------------------------

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200, verbose_name='質問')
    pub_date = models.DateTimeField( verbose_name='発表日')

    def __str__(self):
        return self.question_text

    class Meta:
        verbose_name = '質問'
        verbose_name_plural = '質問'


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE,verbose_name='質問')
    choice_text = models.CharField(max_length=200, verbose_name='選択理由')
    votes = models.IntegerField(default=0, verbose_name='投票数')

    def __str__(self):
        return self.choice_text

    class Meta:
        verbose_name = '選択理由'
        verbose_name_plural = '選択理由'

http://127.0.0.1:8000/admin/ にアクセスすると日本語化されていることがわかります。

モデルの日本語化

データを追加

投票調査の質問にある「+追加」をクリックし、質問を追加します。

質問を追加する画面

質問を追加すると質問一覧に質問が表示されます。

質問一覧画面

同じように選択理由も追加してみます。
管理画面で選択理由の右側にある「+追加」をクリックして選択理由を追加します。

管理画面

先ほど追加した質問を選択してそれぞれの入力欄を記入します。

選択理由画面

保存をクリックすると選択理由が保存されます。

選択理由画面

投票機能を作成

ここからはユーザーが操作する画面を作成していきます。

アクセスフロー全体図

アクセスは以下のように処理されます。
ブラウザからurls.pyを経由してviewsにアクセスを振り分けます。
viewsはmodelsにアクセスして必要なデータをDBから持ってきてもらいます。
データを取得するとtemplatesにデータを埋め込んでブラウザに表示します。
この処理の方法をMVTモデルといいます。

アクセスフロー

プロジェクトのルーティングを作成

ブラウザから最初にアクセスされたリクエストを振り分ける部分を作成します。

poll_project/poll_project/urls.py
---------------------------------------------------------------------------

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

urlpatterns = [
    path("polls/", include("polls.urls")),
    path("admin/", admin.site.urls),
]

アプリのルーティングを作成

プロジェクトから受け取ったリクエストをどの機能(view)に送るかを記載します。

poll_project/polls/urls.py
---------------------------------------------------------------------------

from django.urls import path

from . import views

app_name = "polls"
urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path("<int:pk>/", views.DetailView.as_view(), name="detail"),
    path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

アプリの処理を作成

一覧表示処理(IndexView)、詳細表示処理(DetailView)、結果表示処理(ResultsView)、投票処理(vote)を記載します。

poll_project/polls/views.py
---------------------------------------------------------------------------

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from django.utils import timezone

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = "polls/index.html"
    context_object_name = "latest_question_list"

    def get_queryset(self):

    # Return the last five published questions (not including those set to be published in the future).

        return Question.objects\
                .filter(pub_date__lte=timezone.now())\
                .order_by("-pub_date")[:5]

class DetailView(generic.DetailView):
    model = Question
    template_name = "polls/detail.html"


class ResultsView(generic.DetailView):
    model = Question
    template_name = "polls/results.html"

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST["choice"])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(
            request,
            "polls/detail.html",
            {
                "question": question,
                "error_message": "You didn't select a choice.",
            },
        )
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/results.html", {"question": question})

アプリのモデルを作成

DBに設定するデータ型や管理画面で表示する文言などを記載します。

poll_project/polls/models.py
---------------------------------------------------------------------------

import datetime
from django.db import models
from django.utils import timezone

class Question(models.Model):
    question_text = models.CharField(max_length=200, verbose_name='質問')
    pub_date = models.DateTimeField( verbose_name='発表日')

    def __str__(self):
        return self.question_text


    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

    class Meta:
        verbose_name = '質問'
        verbose_name_plural = '質問'

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE,verbose_name='質問')
    choice_text = models.CharField(max_length=200, verbose_name='選択理由')
    votes = models.IntegerField(default=0, verbose_name='投票数')

    def __str__(self):
        return self.choice_text

    class Meta:
        verbose_name = '選択理由'
        verbose_name_plural = '選択理由'

アプリのテンプレートを作成

ブラウザに表示する画面を作成します。

  • 一覧画面

poll_project/polls/templates/polls/index.html
---------------------------------------------------------------------------

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>投票</title>
    <!-- Bootstrap CSS link -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f8f9fa;
        }

        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: white;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }

        .mt-4 {
            margin-top: 1.5rem;
        }

        .list-group-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            border: none;
        }

        .list-group-item a {
            flex: 1;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1 class="mt-4">投票一覧</h1>
        <hr>

        {% if latest_question_list %}
        <ul class="list-group">
            {% for question in latest_question_list %}
            <li class="list-group-item">
                <a class="btn btn-primary" href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a>

            </li>
            {% endfor %}
        </ul>
        {% else %}
        <p>No polls are available.</p>
        {% endif %}
    </div>

    <!-- Bootstrap JS scripts (optional) -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</body>

</html>
  • 詳細画面

poll_project/polls/templates/polls/detail.html
---------------------------------------------------------------------------

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>質問詳細</title>
    <!-- Bootstrap CSS link -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f8f9fa;
        }

        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: white;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }

        .mt-4 {
            margin-top: 1.5rem;
        }

        .form-check-label {
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1 class="mt-4">{{ question.question_text }}</h1>
        <hr>

        <form action="{% url 'polls:vote' question.id %}" method="post">
            {% csrf_token %}
            <fieldset>
                <legend>
                    <h2>{{ question.question_text }}</h2>
                </legend>
                {% if error_message %}
                <p class="text-danger"><strong>{{ error_message }}</strong></p>
                {% endif %}
                {% for choice in question.choice_set.all %}
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="choice" id="choice{{ forloop.counter }}"
                        value="{{ choice.id }}">
                    <label class="form-check-label" for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label>
                </div>
                {% endfor %}
            </fieldset>
            <button type="submit" class="btn btn-primary mt-3">投票する</button>
        </form>

        <a href="{% url 'polls:index'%}" class="btn btn-secondary mt-3">投票一覧に戻る</a>
    </div>

    <!-- Bootstrap JS scripts (optional) -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</body>

</html>
  • 投票結果画面

poll_project/polls/templates/polls/results.html
---------------------------------------------------------------------------

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>結果</title>
    <!-- Bootstrap CSS link -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f8f9fa;
        }

        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: white;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }

        .mt-4 {
            margin-top: 1.5rem;
        }

        .list-group-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            border: none;
        }

        .btn-secondary {
            margin-top: 0.5rem;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1 class="mt-4">{{ question.question_text }}</h1>
        <hr>

        <ul class="list-group">
            {% for choice in question.choice_set.all %}
            <li class="list-group-item">
                {{ choice.choice_text }} -- {{ choice.votes }} 票
            </li>
            {% endfor %}
        </ul>

        <a href="{% url 'polls:detail' question.id %}" class="btn btn-secondary mt-3">もう一度投票する</a>
        <a href="{% url 'polls:index'%}" class="btn btn-secondary mt-3">投票一覧に戻る</a>
    </div>

    <!-- Bootstrap JS scripts (optional) -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</body>

</html>

もっと学びたい方へ

DjangoはPythonのWebフレームワークで1番使用されていて、中・大規模の開発に向いているフレームワークです。
Djangoを使って設計から開発、デプロイまで経験することでよりエンジニアとしてステップアップできます。

本書は、大人気フレームワーク「Django」によるWebアプリ開発手法を解説した書籍です。
具体的には、DjangoによるWebアプリの設計・作成の基本、動的機能の作成(フォーム作成)の基本、
認証処理の基本、データベースとの連携の基本(日記機能)、クラウドとの連携の基本(デプロイも含む)、
セキュリティの基本など一通り学ぶことができます。
フルスタックエンジニア必携の1冊です。

動かして学ぶ! Python Django開発入門
動かして学ぶ! Python Django開発入門

Django・Pythonでのバックエンドはもちろん、フロントエンド、インフラを理解しておくことでエンジニアの価値が向上します。
詳細はこちら

未経験からのITエンジニア

IT技術の進化速度が速くなっていっている今、IT技術にアンテナを張っておくことはとても重要です。エンジニアになろうとしている方やそうではない方もITスキルを磨くことで問題解決能力や論理的思考を養うことができます。複雑な課題に対処し、効果的な解決策を見つける能力はどの職種でも重宝されます。

そこで、エンジニア未経験の時にあったらよかったなと思っていた講座をマガジンにまとめました!アプリの基礎がわかるTodoアプリの開発からGit&GitHubでのチーム開発までエンジニアに必要な知識が詰まっています!
エンジニアリングの第一歩として使ってみてください!


「売上げアップに効果的」なキャッチコピーやテキストを作成してくれる国内最大級の「AIコピーライティングツール」
【Catchy】


会議・ミーティングの内容をリアルタイムで文字に起こすAI自動文字起こしサービス
 【Notta】


ブログの記事制作にかかる時間を1/10で制作できる高品質SEO記事生成AIツール
【Value AI Writer byGMO】



参考文献


サポートよろしくお願いいたします! いただいたサポートの一部ははクリエイターとしての活動費に使わせていただきます! ※ サポートの一部は子供たちの教育などの団体に寄付する予定です。