为了学习WEB应用,我尝试使用Django开发备忘录注册应用程序

◇关于这篇文章

这是我第一次投稿(其实本来想在平成时代就发表的,结果因为常见的日程延误而拖到了令和时代….)。
虽然我有一些编程经验,但对于WEB应用相关的知识几乎一无所知,所以为了拓宽知识和技能,我尝试创建了这个应用。
首先,我希望能够通过这个应用逐渐加深对于WEB应用的知识。

◇帖子发布顺序(2019/06/29追加)

本次是通过第一篇文章创建了基础的WEB应用程序,并通过接下来的文章进行了功能的添加和改进。如有需要,请参考其他文章。

    1. 【本文】为了学习WEB应用,我用Django制作了一个备忘录登记的应用程序

 

    1. 基于Django制作的备忘录登记WEB应用的功能提升第一部分(添加标题、标签、正文搜索功能)

 

    基于Django制作的备忘录登记WEB应用的功能提升第二部分(显示标签列表,添加按标签分类的文章等)

◇开发环境

    • OS : Ubuntu 18.04.2 LTS(Windows Subsystem for Linux)

 

    • 言語 : Python 3.6.7

 

    • Webアプリフレームワーク : Django (2.2)

 

    DB : SQLite3

◇前期准备

    • Python : 勉強始めて1年程度

 

    • HTML/CSS/JavaScript : 勉強始めて2~3か月程度( 主にpaizaラーニング、ドットインストールを使って学習 )

 

    Django : Djangoドキュメントのはじめての Django アプリ作成をさらっと試した程度

◇实施内容

◆初始设定关系

根据创建第一个 Django 应用程序的步骤,我们进行了 Django 的安装过程。虽然省略了一些细节,但在以下命令中,我们将创建项目和备忘录应用。

$ django-admin startproject mysite
$ python manage.py startapp memorandum

下一步,需要修改自动生成文件中的settings.py。具体来说,

INSTALLED_APPS内へのmemorandum.apps.MemorandumConfigの追加

LANGUAGE_CODEをjaに修正

TIME_ZONEをAsia/Tokyoに修正

我正在做…


------

# Application definition

INSTALLED_APPS = [
    'memorandum.apps.MemorandumConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

------

# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

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

USE_I18N = True

USE_L10N = True

USE_TZ = True

------

将memorandum.apps.MemorandumConfig添加到INSTALLED_APPS中

在这里,我们将位于mysite/memorandum/apps.py中的MemorandumConfig类注册到项目中。
正如代码中所示,name = ‘memorandum’,因此使用这个名称(memorandum)进行注册应该没有问题(实际上,还有一些解释网站也是这样做的)。但是,这次我们按照参考文档的要求编写了类名。


from django.apps import AppConfig


class MemorandumConfig(AppConfig):
    name = 'memorandum'

将时区TIME_ZONE修正为Asia/Tokyo。

关于这个问题,在最开始的时候我并没有进行修正,但在应用程序完成后的验证过程中发现了一个数据添加日期时间不一致的问题,因此进行了调查并进行了附加修正。
通过进行这次修正,日期时间将以日本时间显示。
※如果设置 USE_TZ = True,则内部保存的数据将以UTC格式保存。

USE_TZ = True
TIME_ZONE = 'Asia/Tokyo'

只需要一种选项:用中文本地化地改写以下句子:
在这个设置中的组合中,

    • 内部で保存されるデータはUTC

 

    表示される日時データは日本時刻に自動変換される

这将变成这种形式。

参考链接:如何在Django中进行时区转换。

◆文件结构

下面是在运行tree命令时在终端上的输出结果(已删除__pycache__目录下的文件等)。关于我明确添加或修改的部分,我在<-后注明了评论。

$ tree mysite/
mysite/
├── db.sqlite3
├── manage.py
├── memorandum
│   ├── __init__.py
│   ├── __pycache__
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   ├── models.py                    <-データベース内容定義等
│   ├── static
│   │   └── memorandum
│   │       └── css
│   │           └── memorandum.css   <-スタイルシート用ファイル
│   ├── templates
│   │   └── memorandum
│   │       ├── base-layout.html     <-レイアウト用ファイル
│   │       ├── create-tag.html      <-新規タグ作成ページ
│   │       ├── create.html          <-新規記事作成ページ
│   │       ├── delete.html          <-記事削除ページ
│   │       ├── detail.html          <-記事詳細表示ページ
│   │       ├── index.html           <-トップページ(記事一覧表示)
│   │       └── update.html          <-記事内容修正ページ
│   ├── tests.py
│   ├── urls.py                      <-memorandum内のルーティング定義等
│   └── views.py                     <-各関数のメイン処理定義等
└── mysite
    ├── __init__.py
    ├── __pycache__
    ├── settings.py                  <-言語設定、タイムゾーン設定等の定義(前述のとおり)
    ├── urls.py                      <-mysiteプロジェクト全体のルーティング定義等
    └── wsgi.py

◆数据库模型定义(models.py)

根据模型的定义,我分别创建了Article类和Tag类。
我将Tag类分离出来是因为我希望每篇文章可以注册多个标签。
在Tag类中,我只保存标签名(name)。
而在主数据库中的Article类中,

    • タイトル(title)

 

    • タグ名(tag)

 

    • 本文(body)

 

    • 作成日(published_date)

 

    更新日(modified_date)

通过定义,对于标签名(tag),使用ManyToManyField来引用Tag类。
通过添加ManyToManyField,可以实现多对多关联。

请参考以下URL:Django2.0中使用ManyToMany(多对多)关系。

另外,对创建日期(published_date)应用了auto_now_add=True选项,对更新日期(modified_date)应用了auto_now=True选项。

auto_now_add=True:データが新規作成される際に、その時の日付が自動で設定されます。

auto_now=True:データが新規作成または__更新__される際に、その時の日付が自動で設定されます。

如果您勾选了这些选项,新建文章页面等将自动设置发布日期(published_date)和修改日期(modified_date),无需添加表单。

参考网址:模型字段参考

from django.db import models

# Create your models here.
class Tag(models.Model):
    name = models.CharField(max_length=128, unique=True)

    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=255)
    tag = models.ManyToManyField(Tag)
    body = models.TextField()
    published_date = models.DateField(auto_now_add=True)
    modified_date = models.DateField(auto_now=True)
    
    #これ入れないと、リスト一覧でタイトル名が表示されない
    def __str__(self):
        return self.title

修正完models.py之後,執行以下命令,將修改過的模型定義反映到專案中。

$ python manage.py makemigrations
$ python manage.py migrate 

◆定义URL路由(urls.py)

首先,在mysite目录下的urls.py文件中设置整体的URL路由。

"""mysite URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path

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

通过在 urlpatterns 中添加 path(‘memorandum/’, include(‘memorandum.urls’)),当访问 WEB 应用的 memorandum/ 页面时,将应用 memorandum 下的 urls.py 规则。

下一步,我们将设置备忘录/URL路由。

from django.urls import path

from . import views

app_name = "memorandum"
urlpatterns = [
    # path("", views.index, name="index"),
    path("", views.ArticleListView.as_view(), name="index"),
    path("create/", views.ArticleCreateView.as_view(), name="create"),
    path("<int:pk>/detail/", views.ArticleDetailView.as_view(), name="detail"),
    path("<int:pk>/update/", views.ArticleUpdateView.as_view(), name="update"),
    path("<int:pk>/delete/", views.ArticleDeleteView.as_view(), name="delete"),
    path("tag/create/", views.TagCreateView.as_view(), name="create_tag"),
]

写了这个条目后,例如,如果有人访问“memorandum/create”,那么在views.py文件中的ArticleCreateView.as_view()函数将被调用。通过给它起一个名字为“create”,你可以在HTML中的href等地方使用这个名字来创建链接。

◆视图函数的定义(views.py)

在views.py中,我们将描述每个页面访问时要显示哪些数据。
顺便提一下,这次我们使用了Django支持的类视图功能。
※这个类视图功能是预定义在Django中的通用视图类,比如ListView和CreateView等,通过使用这些类,可以提高实现效率。

参考链接:Django中类视图的入门和用法示例

from django.shortcuts import render,redirect
from django.urls import reverse_lazy
from django.http import HttpResponse
from django.views import generic

from .models import Tag, Article 

class ArticleListView(generic.ListView):
    model = Article

    #参照するhtmlファイルを指定
    template_name = "memorandum/index.html"

class ArticleDetailView(generic.DetailView):
    model = Article
    #参照するhtmlファイルを指定
    template_name = "memorandum/detail.html"

class ArticleCreateView(generic.edit.CreateView):
    model = Article
    fields = ["title","tag","body"]
    #参照するhtmlファイルを指定
    template_name = "memorandum/create.html"
    success_url = reverse_lazy('memorandum:index')  #POSTが正しく行われた際に飛ばすURL

class ArticleUpdateView(generic.edit.UpdateView):
    model = Article
    fields = ["title","tag","body"]
    #参照するhtmlファイルを指定
    template_name = "memorandum/update.html"
    success_url = reverse_lazy('memorandum:index')  #POSTが正しく行われた際に飛ばすURL

class ArticleDeleteView(generic.edit.DeleteView):
    model = Article
    #参照するhtmlファイルを指定
    template_name = "memorandum/delete.html"
    success_url = reverse_lazy('memorandum:index')  #POSTが正しく行われた際に飛ばすURL

class TagCreateView(generic.edit.CreateView):
    model = Tag
    fields = ["name"]
    #参照するhtmlファイルを指定
    template_name = "memorandum/create-tag.html"
    success_url = reverse_lazy('memorandum:index')  #POSTが正しく行われた際に飛ばすURL

每个类的第一个model选择用于Article或Tag的模型定义,并且最少只需这样就能进行列表显示,但根据需要,需要将要修改的部分从默认设置更改为自定义的形式。

template_nameはページアクセス時に返すhtmlファイルの__ローカル側__のパスになります(デフォルトではクラス名_list.htmlのような名前のhtmlファイルを探してしまうようなので、基本的に明示的に設定してあげた方がいいかと思います)。

success_urlはUpdateViewやCreateViewの際に使用されるもので、保存実行時に正常に終了した場合に遷移させるページのURLになるようです。

reverse_lazyは、’memorandum:index’などで定義したルーティング情報をURLに変換する関数のようです(完全に理解できてない・・)。
参考サイトを読んだ限りでは、success_urlで設定する場合、reverse関数ではなく、reverse_lazy関数を使う必要があるとのことだったので、それに倣っています。

参考网址:[Django] success_url 和 get_success_url 以及 reverse 和 reverse_lazy 的使用区别。

◆创建模板(HTML文件)

最后,将开始创建HTML文件和CSS文件。为了避免文章过度冗长,我们只会简要概括地展示其中几个HTML页面。

创建base-layout.html

关于页面标题和标志画面,我们使用通用模板进行描述。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
  <head>
    <link rel="stylesheet" href="{% static 'memorandum/css/memorandum.css' %}">
    <title>{% block title %}Memo Site{% endblock title %}</title>
    {% block head %}{% endblock head %}
  </head>

  <body>
    <header>
      <div class="top-title">
        <a class="home-link-text" href="{% url 'memorandum:index' %}">備忘録登録サイト</a>
      </div>
      {% block header %}
      {% endblock header %}
    </header>
    <div class="main-content">
      {% block content %}
      {% endblock content %}
    </div>
  </body>
</html>

创建index.html(文章列表显示)的首页。

由于object_list中包含了定义的模型数据,所以需要使用for循环逐个取出其中的项目,并显示数据。
对于每个元素,您可以使用在models.py中定义的变量名(例如title)来指定{{ item.title }}的形式,以显示该数据。

{% extends 'memorandum/base-layout.html' %}

{% block content %}
  <div class="local-nav">
    <ul class="local-nav-list">
      <li class="local-nav-item"><a class="local-nav-link-text item-normal" href="{% url 'memorandum:create' %}">新規記事</a></li>
      <li class="local-nav-item"><a class="local-nav-link-text item-normal" href="{% url 'memorandum:create_tag' %}">新規TAG</a></li>
    </ul>
  </div>
  <ul class="article-list">
    {% for item in object_list %}
      <li class="article-list-item">
        <a class="link-text" href="{% url 'memorandum:detail' item.pk %}">{{ item.title }}</a>
        <div class="article-date">作成日: {{ item.published_date|date:"Y年m月d日(D)" }}<br>更新日: {{ item.modified_date|date:"Y年m月d日(D)" }}</div>
      </li>
    {% endfor %}
  </ul>
{% endblock content %}
index.png

创建详细显示页面detail.html

基本上,我們使用類似於 index.html 的{{ object.title }}的形式來指定要顯示的數據。
這次由於我們只需要顯示一個文章的詳細信息,所以不需要使用for循環。
同樣地,通過添加linebreaks,如{{ object.body | linebreaks }},我們可以將表單中輸入的換行符反映在HTML顯示中。

参考网址:Django: 内置标签和过滤器列表

{% extends 'memorandum/base-layout.html' %}

{% block content %}
  <div class="local-nav">
    <ul class="local-nav-list">
      <li class="local-nav-item"><a class="local-nav-link-text item-normal" href="{% url 'memorandum:update' object.pk %}">編集</a></li>
      <li class="local-nav-item"><a class="local-nav-link-text item-caution" href="{% url 'memorandum:delete' object.pk %}">削除</a></li>
    </ul>
  </div>
  <div class="main-article">
    <h1 class="article-title">{{ object.title }}</h3>
    <ul class="article-tag-group">
      タグ:
      {% for tag in object.tag.all %}
        <li>
          <div class="article-tag">{{ tag }}</div>
        </li>
      {% endfor %}
    </ul>
    <div class="article-date">
      <span class="">作成日: {{ object.published_date|date:"Y年m月d日(D)" }}</span>
      <span class="">更新日: {{ object.modified_date|date:"Y年m月d日(D)" }}</span>
    </div>
    <p class="article-body">{{ object.body | linebreaks }}</p>
  </div>
{% endblock content %}
detail.png

创建页面create.html用于新建文章。

在create.html中,我们将使用表单来添加数据。
您可以使用{{ form.title }}这样的形式来显示标题的输入表单。
此外,如果添加label_tag,例如{{ form.title.label_tag }},那么该元素的名称将会显示出来。

{% extends 'memorandum/base-layout.html' %}

{% block content %}
  <div class="main-article">
    <form method="post">{% csrf_token %}
        <div class="input-form">
          <div class="input-form-position partition-bottom-line">
            <span class="input-label-text">{{ form.title.label_tag }}</span>
            <div class="input-form-title">{{ form.title }}</div>
          </div>
          <div class="input-form-tag-group partition-bottom-line">
            <div class="input-form-position">
              <span class="input-label-text">{{ form.tag.label_tag }}</span>
              <div class="input-form-tag">{{ form.tag }}</div>
            </div>
            <a class="update-inner-link-text item-normal" href="{% url 'memorandum:create_tag' %}">Add New TAG</a>
          </div>
          <div class="input-form-position partition-bottom-line">
            <span class="input-label-text">{{ form.body.label_tag }}</span>
            <div class="input-form-body">{{ form.body }}</div>
          </div>
        </div>
        <!-- {{ form.as_p }} -->
        <input class="button-common" type="submit" value="Save">
    </form>
  </div>
{% endblock content %}
create.png

这次暂不涉及更新和删除的问题。
通过目前的工作,我们成功地创建了具备备忘录新建、列表展示、详细展示、文章修改和删除功能的WEB应用程序。
※关于标签,目前只能创建,如果要删除标签,需要通过管理员网站进行操作,希望我们能在未来改进。

◇除此之外,还参考了其他文章。

    • Djangoの 汎用クラスビューをまとめて、実装について言及する

 

    [Django]NoReverseMatch ‘hoge’ is not a valid view function or pattern name.とエラーが出た時の確認事項

◇未来想要增加的功能

タイトル検索、タグ検索機能の実装(現状、タグが役に立っていない・・)。
-> こちらに追加記事を記載しました。(2019/06/02追記)

タグの管理メニュー(現状追加しかできない・・)。

新規タグ追加画面からタグを追加した場合にtopページに飛んでしまうため、その点の修正。
-> こちらに追加記事を記載しました。(2019/06/29追記)

フォームの改善(forms.pyの作成)。

CSSフレームワーク(Bootstrapなど)の導入(今回のアプリレベルでcssファイルが雑多になってきたため・・)
⇒フレームワークどーせ入れるならSass(SCSS or SASS)も試してみたい。

◇最后

    • 今回、初めての投稿となったわけですが、記事作成に想定よりも時間がかかるというのを改めて痛感しました(結局、GW最終日の夜中まで掛かった)。。

 

    • 私よりわかりやすい記事を書いている方々の凄さを実感するとともに、そんな記事を載せてくれる人達のありがたみがを再認識できるGWとなりました。

 

    Djangoについては、Qiita内でも多くの記事が既に出ていますので、そちらの方が参考になるかとは思いますが、少しでも参考になる方がいれば幸いです。