1. 介绍
我们从开发环境的安装,到创建项目,编写前后端代码开始了解一下django大概是什么样子的。
看一下django的文档其实就知道,django很强大,但是同时细节也真的非常多,同时相同的功能可能也有不同的实现。我们本着入门了解的态度,先看一下它大致是什么样子的。
2. 安装虚拟环境
python使用virtualenv来管理虚拟环境,它可以给当前的python程序一个隔离的依赖环境,在这个环境中安装依赖,使用依赖就都不依赖全局了,可以很好的解决多个项目依赖不同的情况。
使用命令pip install virtualenv
安装。
安装后,我们创建目录trydjango,然后cd到目录中运行 virtualenv -p python3 .
来在当前目录下面创建一个依赖目前系统中的python3版本的虚拟环境。具体使用哪个版本的python就看主机上具体的安装情况了。
命令执行后,会创建如下内容:
环境创建好后,使用命令source bin/activate
就可以进入虚拟环境中。这时所有的依赖和python版本就都是当前虚拟环境中的了,跟系统就隔离开了。
如图,前面出现了 (trydjango) 就说明已经进入了虚拟环境,如果需要离开环境,使用
deactivate
命令即可。
3. 安装Django
在虚拟环境中直接运行 pip install Django
即可(其他方式可以参考文档)
安装之后,需要重新运行一次source bin/activate
,才能使用django安装时安装的一些命令,比如django-admin。
4. 初始化项目
创建一个文件夹叫src,然后cd进入src,再执行命令django-admin startproject firstapp .
通过这个命令来初始化一个项目,初始化完成后,会出现manage.py文件和firstapp文件夹。
5. 运行服务
这时我们就可以通过执行命令python manage.py runserver
来启动django服务了
这里面红色的警告是因为没有初始化数据库造成的。
服务启动后,访问启动服务时给出的服务地址http://127.0.0.1:8000/,出现如下界面就说明服务启动成功了。
6. 初始化数据库
django需要连接数据库才能正常进行使用,我们通过如下命令来初始化数据库。
python manage.py migrate
7. 新建自己的app
app是什么我们后面再说,现在先放一下。
执行命令python manage.py startapp products
来创建一个名叫products的app,
命令执行后,会创建一个products的文件夹:
创建完app之后,要记得在settings.py里面注册我们的app,这个app才能被使用,我们修改里面对应的代码,增加products的app声明
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'products', # 我们新增的app,前面都是默认创建的app
]
7.1 创建模型
a. 修改模型文件,创建一个Product模型
修改models.py, 代码如下:
from django.db import models
# Create your models here.
class Product(models.Model):
title = models.TextField()
description = models.TextField()
price = models.TextField()
b. 创建移植脚本
每次修改模型文件之后,都要先运行python manage.py makemigrations
来生成移植脚本,这个脚本就是用来把我们对模型做出的改变,同步到数据库用的。
然后再运行python manage.py migration
命令来移植变化,这样数据库结构就会同步成我们新的模型的样子。
7.2 尝试修改模型
修改models.py, 增加一个summary字段,代码如下:
from django.db import models
# Create your models here.
class Product(models.Model):
title = models.TextField()
description = models.TextField()
price = models.TextField()
summary = models.TextField()
再次运行python manage.py makemigrations
, 会自动生成更新文件
然后我们再次运行一下
python manage.py migration
就可以把变化同步到数据库。
7.3 操作模型
Django默认提供了一些app给我们使用,其中admin就是一个。通过admin我们可以对模型、用户进行一些基本的管理,我们可以把我们的Product模型注册到admin中,然后通过admin来对它进行管理。
a. 在admin里面注册模型
修改admin.py,注册刚才创建的Product模型
from django.contrib import admin
from .models import Product
# Register your models here.
admin.site.register(Product)
b. 通过网页服务操作模型
前面我们已经启动了服务。我们访问http://127.0.0.1:8000/admin/ ,会进入一个登陆界面:
这时我们是没有用户的,我们需要到命令行去创建一个系统的用户出来。
执行命令
python manage.py createsuperuser
来创建超级用户(系统第一个用户),按照步骤创建即可:(警告是因为密码太短造成的)
然后我们回到登录界面,使用刚才创建的用户名密码登录界面,就可以看到我们新增的模型了:
通过界面就可以进行对模型的各项操作。
c. 通过命令行操作模型
除了通过界面来操作模型之外,我们也可以通过在命令行打开一个shell来进入python的执行环境,然后直接操作模型。
在命令行运行命令python manage.py shell
可以打开一个shell,我们在shell里面就可以引入模型,然后操作模型,如下:
基本就是引入Product之后,通过
Product.objects
上面的方法来进行各种操作。在几个新增操作后,我们也可以回到上面提到的网页服务来查看对象列表:
7.4 修改模型
模型定义好之后,有可能还需要进行修改,我们尝试把模型修改如下:
from django.db import models
# Create your models here.
class Product(models.Model):
title = models.CharField(max_length=64) # CharField必须填写max_length属性
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=10000) # DecimalField必须有这两个属性,否则报错
summary = models.TextField(blank=True, null=False) # blank控制界面上是否允许为空的检查,如果是True,就不检查空值, null控制如果数据是空时,应该传什么值给数据库
修改好后还是执行 python manage.py makemigrations
, python manage.py migrate
命令来使改动生效。
这时如果刷新列表界面或者使用命令行Product.objects.all()
查看数据可能会报错,那是因为我们修改了字段类型,原有的数据类型不对,导致出错。这时我们在命令行执行Product.objects.all().delete()
删除旧数据即可。
8. 界面
8.1 创建一个新的app
创建一个新的app并不是必要的操作,我们可以在上面的products里面继续创建界面,只是为了方便起见,我们再建一个app来做下面的操作。
使用命令创建pages app python manage.py startapp pages
。创建之后记得要在settings.py中注册新的app:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'products',
'pages',
]
8.2 编写界面文件
下面我们先修改一下网站的首页,修改首页需要如下几个步骤
a. 编写界面文件pages/views.py
from django.shortcuts import render
from django.http import HttpResponse # 引入HttpResponse来返回请求
# Create your views here.
def home_view(request, *args, **kwargs):
print(request.user) # request里面可以查看用户会话信息
return HttpResponse("<h1>this is the homepage</h1>") # 返回一段html
b. 配置路由信息
路由信息通过查看settings.py里面的ROOT_URLCONF配置得知使用的配置文件是 firstapp.urls
也就是这个文件:
我们在这个文件中配置我们新写的首页内容,内容如下:
from django.contrib import admin
from django.urls import path
from pages.views import home_view
urlpatterns = [
path('', home_view),
path('admin/', admin.site.urls),
]
在这个文件里,我们导入了新写的界面文件,然后注册了路由,这时访问服务即可看到效果:
同理,如果需要增加其他页面,一样操作即可。
8.3 编写templates而不是在python中直接返回字符串
在views.py中,可以通过render方法来渲染一个模板来返回请求
from django.shortcuts import render
from django.http import HttpResponse # 引入HttpResponse来返回请求
# Create your views here.
def home_view(request, *args, **kwargs):
print(request.user)
# return HttpResponse("<h1>this is the homepage</h1>") # 返回一段html
return render(request, 'home.html', {})
为了让home.html正确渲染,我们需要做两件事,一个是创建home.html,另外一个是把它注册到Django中。
a. 创建home.html
内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>home page</h1>
<p>当前登录的用户是: {{request.user}}</p>
</body>
</html>
我们创建templates目录,然后在其中新建home.html文件,在html文件中,我们获取了一下当前登录的用户。
b. 注册模板文件
模板文件在settings.py中进行注册,配置DIRS
进行注册模板目录,代码如下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 这里就是存放template的目录
'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',
],
},
},
]
现在我们刷新界面,就可以看到我们的模板正确被渲染了:
8.4 模板相关的一些用法
8.4.1 模板继承
对于一个有很多页面的应用来说,经常会遇到结构固定,但是内容各异的情况,这种时候通过模板继承的方式,我们就可以很好的来复用定制好的结构,在每一个详细的页面中,可以只关心需要替换掉的部分。
a. 我们在templates目录中新建base.html文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>base.html中的内容1</h1>
{% block content %}
<h1>默认内容</h1>
{% endblock %}
<h1>base.html中的内容2</h1>
{% block bottom %}
<h1>默认bottom</h1>
{% endblock %}
<h1>base.html中的内容3</h1>
{% block other %}
<h1>默认other信息</h1>
{% endblock %}
</body>
</html>
其中
{% block content %}
<h1>默认内容</h1>
{% endblock %}
类似结构部分是在模板中定义的可替换区域,block和endblock定义了一个内容的替换区域, content是区域的名称(用于多个block存在的情况下,指定继承模板时继承的是一块),block和endblock中间的内容就是没有替换内容的情况下,默认显示的内容。
我们修改home.html为如下内容:
{% extends 'base.html' %}
{% block content %}
<p>当前登录的用户是: {{request.user}}</p>
{% endblock content%}
<p>外部的信息</p>
{% block bottom %}
<p>底部块的信息</p>
{% endblock bottom %}
在这个文件中,我们通过 {% extends 'base.html' %}
指定了我们要继承的模板,然后下面两个block都通过名称指定了要覆盖的内容,中间的 <p>外部的信息</p>
是出现在继承了其他模板的模板中的独立代码,这一部分是不会被执行的,我们继承模板的时候,只能去覆盖block部分的内容。
最终界面如下显示:
8.4.2 导入其他模板内容
还有一种复用模板的情况是,我们把一些写好的模板整个导入到我们的模板中,这样可以把一些界面元素通过合理的拆分,变成单一的文件,自己管自己的业务,易于管理。
我们在templates目录下新建components目录,然后在components目录新建navbar.html
<nav>
<ul>
<li>首页</li>
<li>联系人</li>
<li>关于</li>
</ul>
</nav>
然后修改base.html,在其中引入navbar.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
{% include 'components/navbar.html' %}
<h1>base.html中的内容1</h1>
{% block content %}
<h1>默认内容</h1>
{% endblock %}
<h1>base.html中的内容2</h1>
{% block bottom %}
<h1>默认bottom</h1>
{% endblock %}
<h1>base.html中的内容3</h1>
{% block other %}
<h1>默认other信息</h1>
{% endblock %}
</body>
</html>
最终效果如下:
8.4.3 给模板传入上下文参数
views.py 中的 render方法中可以传入第三个参数,这个参数是一个dictionary,每一个dictionary的key都会变成模板中的一个变量供模板使用。
新建一个about.html,然后在views.py中渲染这个模板, 并且在urls.py中注册这个view,最终查看效果。
a. views.py中新增一个about_view
from django.shortcuts import render
from django.http import HttpResponse # 引入HttpResponse来返回请求
# Create your views here.
def home_view(request, *args, **kwargs):
print(request.user)
# return HttpResponse("<h1>this is the homepage</h1>") # 返回一段html
return render(request, 'home.html', {})
def about_view(request, *args, **kwargs):
context = {
'name': 'hushi',
'age' : 33
}
return render(request, 'about.html', context)
b. 在urls里面注册这个view
from django.contrib import admin
from django.urls import path
from pages.views import home_view, about_view
urlpatterns = [
path('admin/', admin.site.urls),
path('', home_view),
path('about/', about_view)
]
c. 编写about.html
{% extends 'base.html' %}
{% block content %}
<p>当前登录的用户是: {{request.user}}</p>
{% endblock content%}
{% block bottom %}
<p>my name is {{name}}, and I'm {{age}} years old</p>
{% endblock bottom %}
这里我们通过 {{ }}
来使用上下文中传入的参数,最终看到的效果是:
8.4.4 for循环
我们修改about.html,在其中加入for循环的代码:
{% extends 'base.html' %}
{% block content %}
<p>当前登录的用户是: {{request.user}}</p>
{% endblock content%}
{% block bottom %}
<p>my name is {{name}}, and I'm {{age}} years old</p>
<p>my friends are</p>
<ul>
{% for friend in friends %}
<li>{{forloop.counter}} - {{friend}}</li>
{% endfor %}
</ul>
{% endblock bottom %}
这一段代码就是for循环的主要语法:
{% for friend in friends %}
<li>{{forloop.counter}} - {{friend}}</li>
{% endfor %}
其中forloop.counter是循环的下标。
我们在views.py中传入参数查看一下效果:
def about_view(request, *args, **kwargs):
context = {
'name': 'hushi',
'age' : 33,
'friends' : [
'Jim',
'Alex',
'Joe'
]
}
return render(request, 'about.html', context)
8.4.5 条件
条件判断比较好理解,我们这里就给出基本的写法
{% if some_condition %}
do something
{% elif other_condition %}
do sth
{% else %}
blah blah blah
{% endif %}
8.4.6 模板中的tag与filter
a. tag:
在模板中,我们已经使用了if、include、extends、block等等tag,这些概念在模板中就叫做tag,django默认提供了很多tag,可以参考文档:https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#
b. filter:
filter是一些数据操作的方法,我们把数据输入进去,然后再输出一些数据,用法比较简单
{{ value| filter1 | filter2| ... }}
类似linux的管道运算符,把前面的值传给第一个filter,然后再把结果传给第二个filter...
具体可以参考文档:https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#built-in-filter-reference
8.5 从后端获取数据并展示
上面我们已经了解了如何给模板传递上下文参数,那么如何从后端获取数据并显示其实就非常简单了。具体的思路就是在views.py对应的视图方法中,通过models.py中定义的模型上的方法来获取数据库中的数据,然后通过上下文参数传递给模板
我们以Product的展示为例:
a. 在products的app目录中的views.py中新增展示Product详情的方法:
from django.shortcuts import render
from .models import Product
# Create your views here.
def product_detail_view(request, *args, **kwargs):
# context参数只能接受dictionary对象
return render(request, 'products/detail.html', {
'object': Product.objects.get(id='5')
})
(第三个参数把id=5的Product对象封装到了一个dictionary对象中,这是因为传给模板的这个参数只接受dictionary类型的对象)
b. 在urls.py里面注册这个新的view
from django.contrib import admin
from django.urls import path
from pages.views import home_view, about_view
from products.views import product_detail_view
urlpatterns = [
path('admin/', admin.site.urls),
path('', home_view),
path('about/', about_view),
path('product', product_detail_view)
]
c. 编写detail.html
在templates里面新建products目录,然后新建detail.html
{% extends '../base.html' %}
{% block content %}
<h1>{{ object.title }}</h1>
<p>price is {{ object.price }}</p>
{% endblock content %}
这里我们还是保持之前的模板继承写法。最终效果如下:
普通对象和dictionary对象的转换
上面我们为了把Product对象转换成dictionary对象,而封装了一层,这就导致我们需要在模板里面每次引用属性的时候都要增加一层object.
的开头,所以最好的还是能有一种方法能快速的把Product对象直接转换成dictionary对象。 还真有:
我们重写一下products/views.py 以及 templates/products/detail.html
from django.shortcuts import render
from .models import Product
# Create your views here.
def product_detail_view(request, *args, **kwargs):
# context参数只能接受dictionary对象
# return render(request, 'products/detail.html', {
# 'object': Product.objects.get(id='5')
# })
return render(request, 'products/detail.html', Product.objects.get(id='5').__dict__)
直接使用Product对象的__dict__
属性,这样模板里面就可以直接使用属性名称了:
{% extends '../base.html' %}
{% block content %}
<h1>{{ title }}</h1>
<p>price is {{ price }}</p>
{% endblock content %}
8.6 关于app
我们在前面创建过pages和products两个app,通过文件结构其实也很容易看出来,一个app其实就是一个可以被复用的完整的模块,这个模块完整的包含模型,界面,测试等等。
但是目前我们的app有一部分在内部,有一部分还在外部,其实更好的做法是做一定的解耦。
a. 界面的解耦
目前我们把模板文件都写到了外面的templates文件夹中,实际上我们可以把属于app的模板都写在app自己文件夹下面对应的templates目录里面,因为我们在settings.py里面我们做了
APP_DIRS: True
的配置:那么如果两个位置都存在相同名称的模板,哪个会生效呢?其实我们思考一下就应该知道,app是模块,模块是被组合使用的,而外面的templates(也就是我们在settings.py的TEMPLATES里面配置的DIRS)是整个project里面的代码,所以它的优先级会高,会覆盖app里面的模板。
我们故意把路径拼错来看一下报错信息,里面已经写清楚了Django寻找模板的路径和顺序:
b. 路由的解耦
界面解耦之后,其实就剩下urls.py里面有对app结构的耦合了,但是我们查看一下admin的路由配置就不难发现,实际上也是有解耦办法的
通过这种方式,app可以只暴露一个信息,通过这一个路由信息,来跟project组合起来,而不是需要暴露app里面的方法、路径等等具体的实现。
8.7 表单
我们在views.py里面新增一个表单的view,如果是get请求,就返回一个空的表单,如果是表单被post请求提交,那么我们就检查表单内容是否有效,如果有效就把表单保存到数据库,然后再返回一个新的空表单对象给界面。
a. 在products目录里面新建forms.py, 创建输入三个属性的一个form
from django.forms import ModelForm, Textarea
from .models import Product
class ProductForm(ModelForm):
class Meta:
# 指定我们的form是针对Product模型的
model = Product
# 指定
fields = [
'title',
'description',
'price'
]
# 覆盖默认的控件
widgets = {
'description': Textarea(attrs={'cols': 80, 'rows': 20}),
}
b. 在products/views.py里面增加product_create_view用来新建Product
from django.shortcuts import render
from .models import Product
from .forms import ProductForm
# Create your views here.
def product_detail_view(request, *args, **kwargs):
# context参数只能接受dictionary对象
# return render(request, 'products/detail.html', {
# 'object': Product.objects.get(id='5')
# })
return render(request, 'products/detail.html', Product.objects.get(id='5').__dict__)
def product_create_view(request, *args, **kwargs):
form = ProductForm(request.POST or None) # 使用request.POST对象或者None来初始化Form里面的内容, 如果需要编辑一个数据库对象,那么需要传入第二个参数 instance=要编辑的对象
if form.is_valid(): # 如果表单通过了校验
form.save() # 保存表单内容到数据库
form = ProductForm() #给界面重新初始化一个空的form对象,以重新填写下一个表单
context = {
'form' : form
}
return render(request, 'products/create.html', context)
c. 创建form的界面模板,在templates/products/里面创建create.html
{% extends '../base.html' %}
{% block content %}
<form method="post">
{% csrf_token %} {% comment %} 因为使用了post去提交表单,所以需要提供csrf_token {% endcomment %}
{{ form.as_p }} {% comment %} form.as_p 会把form里面的每一个字段用<p>标签封装起来 {% endcomment %}
<input type="submit" value="Save">
</form>
{% endblock content %}
d. 在urls里面增加product_create_view的路由:
from django.contrib import admin
from django.urls import path
from pages.views import home_view, about_view
from products.views import product_detail_view, product_create_view
urlpatterns = [
path('admin/', admin.site.urls),
path('', home_view),
path('about/', about_view),
path('product', product_detail_view),
path('create', product_create_view)
]
e. 访问地址,可以新建一个product对象
f.点击save后,我们到admin的后台去确认一下是否增加成功: