通过一个待办事项小程序来学习Django

·  阅读 326

前言

上一篇小教程([Django简易小教程Django简易小教程 (juejin.cn) 通过一个hello world小程序熟悉了django开发最基本的环境搭建部分,那么有了这些基础之后我们这次来写一个稍微复杂一点点的待办事项app,通过这个示例来进一步熟悉django的用法。

本人电脑环境为win10系统,文本编辑器是vs code,浏览器是微软edge。

本文默认你有python3基本语法知识,以及少量html语法常识。

最终效果展示

image.png

上图就是最终写完后的效果,一个简单的待办事项软件,最上面是标题,接下来是一个可以输入事项内容的输入框(表单),最后将你所有未完成的事项显示出来,并且附带了一个删除按钮,点下即可删除这个事项,既表示此任务已经完成了。

安装django

使用带有管理员权限的命令行工具cmd或者powershell,运行下面的命令

pip install django==2.2
复制代码

创建你的django项目

继续在命令行上执行以下命令,首先切换工作目录至桌面(这里假定把项目放在桌面,也可以放在你喜欢的任何位置),然后创建项目,本次项目名字为todoapp

cd C:\Users\Derek\Desktop
django-admin startproject todoapp
复制代码

注意桌面的路径,你需要把上面的derek替换成你的windows用户名。

测试服务器是否能正常工作

需要在manage.py文件的同一层级启动服务,由于刚才我们的工作目录处于桌面,所以现在需要切换到项目文件夹todoapp内部,运行下面命令。

 cd todoapp
 python.exe .\manage.py runserver
复制代码

如果可以看到和下面类似的界面说明成功了。

image.png

创建应用

在可以暂时关闭浏览器窗口,并按照终端输出窗口中的指示使用 Ctrl+C 停止服务器。 一个Django项目可以包含多个应用模块,就像一个超市里面分为水果区域,饮料区域,零食区域一样。所以现在需要为我们的项目创建第一个应用,起名为main,主应用。

python .\manage.py startapp main
复制代码

目录组成

到目前为止这次开发需要的目录结构已经都有了,这时可以使用vs code打开我们的项目文件夹,即位于桌面的todoapp文件夹。

具体的目录结构如下图,大家可以和我对照下。

image.png

在settings.py中加入我们新添加的应用main

找到主项目文件夹todoapp下的settings.py文件,在INSTALLED_APPS区域添加应用main,

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'main',
]
复制代码

此举目的是让django知道我们有一个新建的应用叫main。

配置URL解析

我们知道网页应用程序的使用方式就是用户在浏览器输入URL也就是网址,然后访问到对应的页面展示出来,这个过程就是URL解析或者叫URL路由,即一个URL对应一个功能页面,此时我们整个项目的入口就是项目文件夹todoapp中的urls.py文件,可以观察到有个叫urlpatterns的变量,这个变量中存储的就是URL和功能的对应关系,我们现在需要将URL的解析控制权从主项目入口移交到我们的名为main的应用。具体做法如下:

第一步,在项目文件夹todoapp下的urls.py文件中,写入以下代码

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



urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('main.urls')),
]
复制代码

第二步,在应用main文件夹下,也创建一个urls.py的文件,添入以下代码

from django.urls import path
from . import views

urlpatterns = [
    path('',views.index, name='index'),
]
复制代码

解释下,上面第一步中,除了默认的导入外,我们又多导入了一个include函数,然后在默认的admin下方又添加了一行新的URL匹配规则

path('',include('main.urls')), 第一个参数是一个空的单引号,表示的URL为 http://127.0.0.1/ ,由于主机地址后面不包含任何关键字,所以引号中为空字符串,第二个参数是include('main.urls'),表示意思是当用户输入127.0.0.1/这个地址后,将解析控制权交给main下面的urls文件,然后接下来就到了main下的urls文件,这里也有一个urlpatterns变量,结构是一样的,一行path对应一条url解析规则,我们添加了path('',views.index, name='index'),第一个参数还是空的,第二个参数是 views.index,表示将URL 127.0.0.1/ 对应至views文件中的名为index的功能函数,第三个参数表示给这条URL解析规则起一个名字叫‘index’。

创建第一个功能函数index

由于刚才最后的URL匹配规则最终落到了views下的index这个功能函数上面,但是此时我们的index函数还不存在,所以现在需要创建一个功能函数index。

打开views文件,路径为main/views.py,用下面的代码覆盖掉原有的代码。

from django.shortcuts import render


def index(request):
    return render(request,'index.html')
复制代码

我们首先导入了render这个函数,它的作用就是把一个html页面文档返回给用户的浏览器,这里面的html文档可以是静态的,也可以是包含了变量的动态页面,这个动态页面一般叫模板。然后创建一个函数叫index,这个request参数表示用户的请求对象,是固定的,每次都要写,最后就是把 index.html 这个文档返回给最终用户并显示在浏览器中。

创建第一个html模板

上面的index函数将index.html这个模板返回给了用户,所以现在需要编写这个html模板的内容。首先在main文件夹下面再建一个文件夹叫做templates, 在其中新建一个文件index.html, 填写以下内容。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page</title>
</head>
<body>
    <h1>待办事项</h1>
</body>
</html>
复制代码

基本上就是一个很简单的html文件,包括一些基础的标签,如果不理解可以自行到网上搜搜教程,还是比较简单的。

所以最终的实际页面上应该只显示“待办事项”这四个字。

现在我们来测试下,在命令行下敲下启动服务器的命令。

python manage.py runserver
复制代码

image.png

能看到这四个大字就表示成功了。

开发流程总结

写到这里我们基本上可以看出开发web应用的大体步骤,即第一步配置url路由,第二步编写视图函数,第三部编写html模板文件。

涉及的几个文件:urls.py -- views.py -- xxx.html

如此循环往复便可以写出一个简单的web应用程序。

建立数据库模型

目前我们的程序还没有连接数据库,django中编写web应用的时候不需要写原始的SQL语句,而是通过ORM(对象关系映射)来调用数据库,简单来说就是将关系型数据库如sqllite中的一个表映射为python中的一个class类,这样编程程序时非常简单方便。

找到main下的models.py更新下面代码。

from django.db import models

class TodoModel(models.Model):
    name = models.CharField(max_length = 200)

    def __str__(self):
        return self.name
复制代码

首先我们从django中导入models,然后定义一个名为TodoModel的class继承models.Model类,TodoModel类表示数据库的一张表,而下面的每一个属性代表数据库表中的字段,比如我们的第一个字段为name,类型是字符串表示为CharField,并且限定最大长度200. 最后的__str__部分的效果是当你输出某个对象时候,会友好的显示成这个对象的名字,而不是object。

迁移数据库

上面只是定义了模型,还没有真正的在数据库中建立表,执行下面的命令迁移数据库。

python .\manage.py makemigrations
python .\manage.py migrate
复制代码

登录admin后台

django为管理员准备了一个现成的后台页面,可以添加数据到数据库中。下面我们创建一个管理员账户:

python manager.py createsuperuser
复制代码

接下来会提示你输入用户名和密码以及邮箱地址,记住,稍后会使用到。

打开main应用下的admin.py文件,更新如下代码:

from django.contrib import admin
from .models import TodoModel

admin.site.register(TodoModel)
复制代码

上面的代码表示将我们之前定义的数据库模型注册到了后台界面当中。

由于先前的项目总文件夹todoapp下的urls.py中存在这样的代码 path('admin/', admin.site.urls),所以我们可以打开路径为admin的后台管理界面,完整的地址为 http://127.0.0.1:8000/admin

image.png

在此界面下输入刚才我们创建的用户名密码即可登录.

image.png

如图所示,我们的todo模型已经出现在屏幕上,我们接下来点击add按钮,添加两个待办事项,分别为“学习django”和“学习HTML”。

将数据库中的待办事项显示在网页上

我们现在需要更新views.py文件,之前我们只是让index函数直接返回了index.html模板文件,而本次我们先从数据库中查询到目前所有的待办事项,然后把数据传给html模板并展示出来.

更新views.py中的index函数:

from django.shortcuts import render,redirect
from .models import TodoModel

def index(request):
    list = TodoModel.objects.all()
    return render(request,'index.html',{'list':list})
复制代码

TodoModel.objects.all() 为固定用法,表示查询数据库中的所有数据,并赋值给变量list,除了之前的返回html文件外还增加了一项功能,就是把list里面的数据全部注入到前端的index模板里。

下面,更新index.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page</title>
</head>
<body>
    <div>
        <div>
            <a href="{% url 'index' %}">Todo List</a>
        </div>
        <div>
            {% for item in list %}
                <div>
                    <span>{{ item }}</span>
                </div>
            {% endfor %}
        </div>
    </div>
</body>
</html>
复制代码

需要关注的是一个新的语法:{% for item in list %}{% endfor %},表示在html模板中使用循环,因为我们不知道到底一共有多少个待办事项存储在数据库中,{{ item }} 这个表示显示变量的值。

{% url 'index' %} 表示一个名字为index的url,对应的是我们之前在urls.py中写的 path('',views.index, name='index')

再次启动server,网页应该显示为下面的样子。

image.png

增加表单让用户可以自己添加待办事项

现在我们的程序只能以管理员身份在后台添加数据到数据库中,但是我们需要最终用户也可以在前端页面上输入待办事项的内容,这需要使用到表单,表单就是网页上常见的输入框,方便用户自己提交数据到网站后台。

首先继续更新index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page</title>
</head>
<body>
    <div>
        <div>
            <a href="{% url 'index' %}">Todo List</a>
        </div>
        <div>
            <form action="{% url 'index' %}" method="post">
                {% csrf_token %}
                <input  type="text" name="name">
                <input  type="submit" value="Add">
            </form>
            {% for item in list %}
                <div>
                    <span>{{ item }}</span>
                </div>
            {% endfor %}
        </div>
    </div>
</body>
</html>
复制代码

我们这次增加了html中的form表单,如果对这块语法不熟悉的小伙伴可以上网搜索下html表单。

action="{% url 'index' %}" 的意思是本表单提交后,数据被送往index这个路由,也就是首页。

method="post" 表示使用http中的post方法,由于需要提交数据,所以使用post而不是get。

{% csrf_token %} 增加了安全方面的东西,属于django中强制规定的。

image.png

如上图所示,现在页面上多了一个输入框。接下来还需要更新views.py让程序可以处理前端提交过来的数据。

from django.shortcuts import render,redirect
from .models import TodoModel

def index(request):
    if request.method == 'POST':
        new = TodoModel(name=request.POST['name'])
        new.save()
        return redirect('/')
    

    list = TodoModel.objects.all()
    return render(request,'index.html',{'list':list})
复制代码

这次增加了一个if判断,如果post请求,那么我们就在数据库中新增一条数据,注意,这里新增数据就等于实例化了TodoModel类的一个对象,将刚才的表单中的name属性的value赋值给类属性name,然后save到数据库中,最后需要将页面重定向回主页index。

如果还是正常的GET请求的话,那么还是如之前一样的显示index主页,并注入list数据。

这时我们便可以尝试在页面中输入待办事项的内容,然后点击add按钮提交至数据库中。

增加删除按钮

完成新增条目后,我们继续制作一个delete删除按钮,以便用户可以删除不想要的待办事项条目,首先更新index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page</title>
</head>
<body>
    <div>
        <div>
            <a href="{% url 'index' %}">Todo List</a>
        </div>
        <div>
            <form action="{% url 'index' %}" method="post">
                {% csrf_token %}
                <input  type="text" name="name">
                <input  type="submit" value="Add">
            </form>
            {% for item in list %}
                <div>
                    <span>{{ item }}</span> <span><a href="{% url 'delete' item.id %}">Delete</a></span>
                </div>
            {% endfor %}
        </div>
    </div>
</body>
</html>
复制代码

这次增加了一个a标签,也就是超链接,指向了一个delete路由(目前还没有),以及传入了一个item.id的主键,这是django自动为我们添加的id属性,表示数据库的主键,是一个从1开始累加的整数。

我们现在来增加delete路由:

from django.urls import path
from . import views

urlpatterns = [
    path('',views.index, name='index'),
    path('delete/<str:pk>/',views.delete,name='delete'),
]
复制代码

我们增加了一个新的url路由叫做delete,这里用到了动态url参数,参数名是pk,类型是str字符串,实际这样做的效果等于是 http://127.0.0.1/delete/1, http://127.0.0.1/delete/2, http://127.0.0.1/delete/3, 以此类推。

然后增加一个新的delete视图函数:

views.py

from django.shortcuts import render,redirect
from .models import TodoModel

def index(request):
    if request.method == 'POST':
        new = TodoModel(name=request.POST['name'])
        new.save()
        return redirect('/')
    

    list = TodoModel.objects.all()
    return render(request,'index.html',{'list':list})



def delete(request,pk):
    target = TodoModel.objects.get(id=pk)
    target.delete()
    return redirect('/')
复制代码

image.png

添加样式

现在我们的网页页面还仍然非常单调,非常难看,这时CSS就登场了,CSS全称是层叠样式表,说白了就是修饰网页样式的,首先我们需要在mian文件夹下新建一个叫static的文件夹,然后在下面新建一个style.css的文件,把下面的代码粘贴进去:

body {
    background-color:cornflowerblue;
}

.container {
    text-align: center;
    background-color: blanchedalmond;
    border: darkmagenta;
    border-style: solid;
    border-radius: 5%;
    width: 600px;
    height: 800px;
    margin: auto;
}

.home-button {
    text-decoration: none;
    font-size: 50px;
}

.name-input {
    width: 60%;
    height: 25px;
    margin-top: 20px;
    margin-bottom: 20px;
}

.submit {
    background-color: darkmagenta;
    font-size: 20px;
    width: 70px;
}

.todo-box {
    border-style: solid;
    border-width: thin;
    border-color: darkmagenta;
    background-color: blanchedalmond;
    margin-left: 30px;
    margin-right: 30px;
    margin-top: 10px;
    margin-bottom: 10px;
}

.delete-button {
    text-decoration: none;

}


.box-right {
    float: right;
    margin-right: 10px;
}
复制代码

然后更新我们的index.html, 在标签中增加一些class:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page</title>
    {% load static %}
    <link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
</head>
<body>
    <div class="container">
        <div>
            <a class="home-button" href="{% url 'index' %}">Todo List</a>
        </div>
        <div>
            <form action="{% url 'index' %}" method="post">
                {% csrf_token %}
                <input class="name-input" type="text" name="name">
                <input class="submit" type="submit" value="Add">
            </form>
            {% for item in list %}
                <div class="todo-box">
                    <span>{{ item }}</span>  <span class="box-right"><a class="delete-button" href="{% url 'delete' item.id %}">Delete</a></span> 
                </div>
            {% endfor %}
        </div>
    </div>
</body>
</html>
复制代码

大功告成,现在关闭浏览器重新打开应该可以看到变化。

image.png

代码对照

由于步骤比较多,这里我把项目的源代码放到了gitee中方便大家对照,谢谢。

https://gitee.com/derekll/todoapp
复制代码
分类:
后端
标签: