03 Django入门part2
一. 静态文件配置
1. 什么是静态文件
# 静态文件(static): js, css, img, 第三方前端框架, ...
网站写好的js文件
网站写好的css文件
网站用到的图片文件
第三方前端框架
...
提示: 只要已经写好功能的文件, 可以拿来就直接使用的文件就属于静态文件
# 补充: django默认是不会自动帮你创建static文件夹 需要你自己手动创建
一般情况下我们在static文件夹内还会做进一步的划分处理
static/
├── js
├── css
├── img
├── bootstrap-3.3.7-dist
├── ....
2. 静态文件配置
**强调: **在浏览器中输入url能够看到对应的资源, 是因为后端提前开设了该资源的接口, 如果访问不到资源 说明后端没有开设该资源的接口
提示: django项目默认不会帮你创建静态文件, 需要手动创建. 约定熟成文件名为 static
路径: 到settings.py配置文件中找到STATIC_URL在其下面加上以下内容
'''
静态文件查找步骤:
1. 先找到STATIC_URL中拿到 /static/ 令牌
2. 再到STATICFILES_DIRS中拼接访问静态文件资源的路径. os.path.join(BASE_DIR,'static')
3. 拿到路径再到我们创建的static静态文件中查找. 一层层查找, 第一个static文件夹中没有, 则到第二个种找, 都没有才会报错.
'''
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
]
补充: 当你在写django项目的时候 可能会出现后端代码修改了但是前端页面没有变化的情况
1.你在同一个端口开了好几个django项目
一直在跑的其实是第一个django项目: 因为默认设置的django端口是8000, 端口已经被第一个占用, 后面无论开启多个个django项目端口都不能够分配, 也就无法启动.
2. 浏览器缓存的问题
打开浏览器 -> settings -> network -> disable cache 勾选上
3. 静态文件动态解析
'''
在templates管理的html文件中, 在head引用外部资源之前以如下书写方式:
{% load static %} # load static 类似于于导入python模块
<script src="{% static '静态文件中的文件路径' %}"></script>
'''
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
二. request对象方法初识
1. 知识储备: form表单
form表单功能: 在该form标签内部书写的获取用户的数据都会被form标签提交到后端.
属性: action 控制数据提交到后端的路径(给哪个服务短提交数据)
1. 什么都不写. 默认就是朝当前页面所在的url提交数据
2. 写全路径: https//:www.baidu.com 朝百度服务端提交
3. 只写路径后缀action='/index/'. 自动识别出当前服务端的ip和port拼接到前面. host:port/index/
属性: method 通过method指定提交请求的方式
1. get: form表单默认提交数据的方式 是get请求 数据是直接放在url后面的. 以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456.
2. post: 把提交的数据放在HTTP包的请求体中(注意: 这里指定post, flask服务器才可以通过request.files,request.form获取文件,表单数据)
提示: GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码.
属性: enctype="multipart/form-data" 指定数据提交的编码格式
1. 默认是urlencoded 只能够提交普通的文本数据, 不能发送文件.
2. form-data 就可以支持提交文件数据
2. 注意: 在使用form表单开启post功能之前必须要先到settings.py配置文件中找到MIDDLEWARE注释一行代码
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
3. request用法介绍
参数功能介绍:
'''
提示: get请求携带的数据是有大小限制的, 而post请求则没有限制.
chrome(谷歌)的url长度限制不能超过8182个字符
'''
request.method 返回大写字符串格式的当前请求方式
request.GET 返回URL中问好后面携带的参数. QueryDict对象: <QueryDict: {'username': ['egon'], 'hobbies': ['play', 'study']}>
request.POST 返回POST请求提交过来的普通键值对(不包含文件)
request.GET.get() 返回对象列表中最后一个的值 -> str
request.POST.get()
request.GET.getlist() 返回对象列表中所有的值 -> list
request.POST.getlist()
实例:
from django.shortcuts import HttpResponse, render, redirect
# Create your views here.
def login(request):
"""request对象方法初识"""
print(request.method, type(request.method)) # GET <class 'str'>
print(request.GET, type(request.GET))
'''
<QueryDict: {'username': ['egon'], 'password': ['123']}>
<class 'django.http.request.QueryDict'>
'''
print(request.GET.get('username')) # egon
print(request.GET.get('password')) # 123
print(request.GET.getlist('username')) # ['egon']
return render(request, 'login.html')
def register(request):
print(request.method, type(request.method)) # POST <class 'str'>
print(request.POST, type(request.POST))
'''
<QueryDict: {'username': ['egon'], 'password1': ['123'], 'password2': ['123'], 'hobbies': ['吃', '喝']}>
<class 'django.http.request.QueryDict'>
'''
print(request.POST.get("username")) # egon
print(request.POST.get('hobbies')) # 喝
print(request.POST.getlist("hobbies")) # ['吃', '喝']
return render(request, 'register.html')
三. pycharm链接数据库(MySQL)
可能出现的错误: Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' prope
四. django链接数据库(MySQL)
第一步配置数据库: 到配置文件settings.py中找到DATABASES配置
'''
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'IP地址',
'PORT': 3306,
'USER': '登录用户',
'PASSWORD': '用户密码',
'NAME': '库名',
'CHARSET': 'utf8',
}
}
'''
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db10',
'USER': 'root',
'password': '1234',
'HOST': '127.0.0.1',
'PORT': '3306',
'CHARSET': 'utf8',
}
}
第二步代码声明: 使用pymysql替换django自带的数据库模块MySQLdb.
'''
没有配置第二步则会抛出异常: django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'.
Did you install mysqlclient or MySQL-python?
django默认用的是mysqldb模块链接MySQL
但是该模块的兼容性不好 需要手动改为用pymysql链接
你需要告诉django不要用默认的mysqldb还是用pymysql
'''
# 在项目名下的__init__.py或者任意应用名下书写以下代码:
import pymysql
pymysql.install_as_MySQLdb()
五. Django ORM
1. ORM简介
ORM 全称 object relational mapping 对象关系映射
功能: 通过ORM实现对操作对象的操作模式来操作数据库中的数据
实现: 通过models中的类来对应数据库中的一个表,一个对象对应一个数据行,一个属性对应数据库中的一个字段
缺陷: 封装程度太高, 有时候sql语句的效率偏低 需要你自己写SQL语句
2. ORM基本使用
from django.db import models
# Create your models here.
class User(models.Model):
"""
CharField 必须要指定max_length参数不指定会直接报错
verbose_name该参数是所有字段都有的 就是用来对字段的解释
"""
# id int primary key auto_increment
id = models.AutoField(primary_key=True, verbose_name='主键id')
# username varchar(32)
username = models.CharField(max_length=32, verbose_name='用户名')
# age int
age = models.IntegerField(verbose_name='年龄')
# password varchar(255)
password = models.CharField(max_length=255, verbose_name='密码')
class Author(models.Model):
"""
由于一张表中必须要有一个主键字段 并且一般情况下都叫id字段
所以orm当你不定义主键字段的时候 orm会自动帮你创建一个名为id主键字段
也就意味着 后续我们在创建模型表的时候如果主键字段名没有额外的叫法 那么主键字段可以省略不写
"""
# username varchar(32)
username = models.CharField(max_length=32)
# password varchar(255)
password = models.CharField(max_length=255)
3. 数据库迁移命令
# 前提: 在执行命令之前要切换到项目文件夹下
python3.6 manage.py makemigrations # 生成数据库同步脚本(将操作记录, 记录到migrations中)
python3.6 manage.py migrate # 数据迁移(将操作真正的同步到数据库中)
4. 字段的增删改查
# 增
1. 在执行了生成数据库迁移记录命令之后在终端命令行中输入默认值
2. 字段设置可以为空(默认不为空)
info = models.CharField(max_length=32,verbose_name='个人简介',null=True)
3. 直接给字段指定默认值
info = models.CharField(max_length=255, verbose_name='个人简介', default='喜欢吃生蚝!')
# 删
直接注释对应的字段然后执行数据库迁移的两条命令即可
# password = models.CharField(max_length=255, verbose_name='密码')
注意(危险): 删除的字段对应的数据也将会删除
# 改
直接修改代码然后执行数据库迁移的两条命令即可
# password = models.CharField(max_length=255, verbose_name='密码')
password = models.IntegerField(verbose_name='密码')
# 查: 略
5. 数据的增删改查
5.1 方法介绍
# 增:
方式一: 使用models自动创建
user_obj = models.User.objects.create(**kwargs)
方式二: 手动创建
user_obj = models.User(**kwargs)
user_obj.save()
# 查:
user_queryset = models.User.objects.filter(**kwargs)
user_queryset是一个QuerySet对象. 它是一个列表套数据的对象的格式: <QuerySet [<User: User object>, <User: User object>, ...]>
# 查询单个数据的2种方式
方式一: 索引取值. 不能使用负数.
user_obj = user_queryset[0]
方式二: 推荐使用.first(). 但是其内部使用的也是通过索引取值
user_obj = user_queryset.first()
# 查询所有数据的2种方式
方式一:
data_queryset = models.User.objects.filter()
print(data_queryset) # <QuerySet:[<User: object1>, <User: object2>, ...]>
方式二: 更加见名知意
data_queryset = models.User.objects.all()
# 改
方式一:
'''
批量操作对象
只会更新指定的. 将filter查询出来的列表中所有的对象全部更新
批量更新操作只修改被修改的字段
'''
models.User.objects.filter(id=edit_id).update(**kwargs)
方式二:
'''
单独操作对象
无论有或者没有就是执行更新. 因此当字段特别多的时候效率会非常的低,
它会从头到尾将数据的所有字段全部更新一边 无论该字段是否被修改
'''
edit_obj = models.User.objects.filter(id=edit_id).first()
edit_obj.username = username
edit_obj.password = password
edit_obj.save()
# 删
'''
真正的删除功能应该需要二次确认, 我们这里先不做.
平常的删除数据内部其实并不是真正的删除 我们会给数据添加一个标识字段用来表示当前数据是否被删除了,如果数据被删了仅仅只是讲字段修改一个状态, 标志状态有is_delete, is_alive, is_status. 这里我们用is_delete示例:
username password is_delete
jason 123 0
egon 123 1
'''
不做2次确认的删除: 直接删除
models.User.objects.filter(id=delete_id).delete()
5.2 利用数据的查询和增加实现登录注册功能实例
# 数据的查询: 登录功能例子
from app01 import models
username = request.POST.get('username')
res = models.User.objects.filter(username=username)
'''
res=[数据对象1, 数据对象2] 列表取值不能使用负数
补充: filter括号内可以带多个参数. 内部默认用and链接
'''
res = models.User.objects.filter(username=username).first() # 获取数据对象方式一: 推荐. 内部使用的是下面索引取值的的方式
user_obj = res[0] # 获取数据对象方式一: 推荐. 内部使用的是下面索引取值的的方式
user_obj = res.first() # 获取数据对象方式二: 与上面等同
print(user_obj.id) # 支持对象操作获取对应对象的id
print(user_obj.username)
print(user_obj.password)
# 数据的增加: 注册功能的例子
1. 准备注册页面
2. 获取用户输入
# 第一种: 利用models自动帮你保存
username = request.POST.get('username')
password = request.POST.get('password')
res = models.User.objects.create(username=username, password=password)
# res 返回值就是当前被创建的对象本身
# 第二种: 自己手动保存数据
user_obj = models.User(username=username, password=password)
user_obj.save()
5.3 利用数据的查改删实现对数据的编辑和删除功能实例
步骤流程:
1. 在模型类中创建数据
2. 准备数据展示HTMl页面.
2.1 使用模型类语法查询数据库中的数据
2.2 将查询到的数据传递到HTML页面中, 使用模板语法进行渲染
提示: 都默认get提交数据
2.3 在HTML页面准备编辑按钮:
数据定位: 使用/edit_data/?user_id={{ user.id }}. 提交到后端, 可以通过获取user_id进行数据的定位.
2.4 准备删除按钮
3. 后端处理用户请求
3.1 通过GET提交方式拿到用户的user_id, 再定位到数据对象, 将数据对象传入HTML页面返回一个编辑页面
3.2 用户编辑表单, 通过POST请求, 判断如果是POST请求, 那么用户就是编辑页面了.
3.3 再对数据对象进行修改/删除
3.4 修改完毕以后重定向返回用户数据展示页面
代码部分:
- models.py
from django.db import models
# Create your models here.
class User(models.Model):
# id = models.AutoField(primary_key=True, verbose_name='主键')
username = models.CharField(max_length=66, verbose_name='用户名')
password = models.CharField(max_length=255, verbose_name='用户密码')
age = models.IntegerField(verbose_name='年龄')
sex = models.CharField(max_length=1, default='男', verbose_name='性别')
phone = models.BigIntegerField(verbose_name='电话号码')
- view.py
from django.shortcuts import render, redirect
from app01 import models
"""
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^user_table/', views.user_table),
url(r'^edit_data/', views.edit_data),
url(r'^del_data/', views.del_data),
]
"""
# Create your views here.
def user_table(request):
"""查看功能"""
# 查询出用户表里面所有的数据
# user_list_obj = models.User.objects.filter() # 方式一
user_queryset = models.User.objects.all() # 方式二: 更加见名知意
# print(user_list_obj)
return render(request, 'user_table.html', locals())
def edit_data(request):
"""编辑功能"""
edit_id = request.GET.get('user_id')
edit_obj = models.User.objects.filter(id=edit_id).first()
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
age = request.POST.get('age')
sex = request.POST.get('sex')
phone = request.POST.get('phone')
models.User.objects.filter(id=edit_id)
# 去数据库中修改对应的数据内容
# 修改数据方式一:
'''
当字段特别多的时候效率会非常的低,
它会从头到尾将数据的所有字段全部更新一边 无论该字段是否被修改
'''
edit_obj.username = username
edit_obj.username = password
edit_obj.username = age
edit_obj.username = sex
edit_obj.phone = phone
edit_obj.save()
# 修改数据方式二:
'''
将filter查询出来的列表中所有的对象全部更新
批量更新操作只修改被修改的字段
'''
models.User.objects.filter(id=edit_id).update(username=username, password=password, age=age, sex=sex,
phone=phone)
return redirect('/user_table/')
return render(request, 'edit_data.html', locals())
def del_data(request):
"""删除功能"""
del_id = request.GET.get('user_id')
'''
真正的删除功能应该需要二次确认, 我们这里先不做.
平常的删除数据内部其实并不是真正的删除 我们会给数据添加一个标识字段用来表示当前数据是否被删除了,如果数据被删了仅仅只是讲字段修改一个状态
username password is_delete
jason 123 0
egon 123 1
'''
models.User.objects.filter(id=del_id).delete()
return redirect('/user_table/')
总结
# 静态文件
# 什么是静态文件?
已经写好功能的文件, 可以拿来就直接使用的文件
例如: js, css, img, 前端第三方框架 ...
# django静态文件配置如何配置
提示: django项目默认不会帮你创建静态文件, 需要手动创建. 约定熟成文件名为 static
到settings.py中找到STATIC_URL. 并在其下面添加上
STATICFILES_DIRS = [
os.path.join(BASE_DIR, '创建的静态文件'),
os.path.join(BASE_DIR, '创建的静态文件2'),
]
# 如何解决接口前缀不断变化,html页面上路径的引用需要反复修改的问题
在templates管理的html文件中, 在head引用外部资源之前以如下书写方式:
{% load static %}
<script src="{% static '静态文件中的文件路径' %}"></script>
# from表单如果指定post请求, 需要注释一行代码:
settings.py -> MIDDLEWARE
# 'django.middleware.csrf.CsrfViewMiddleware',
# request对象的方法
request.method 返回大写字符串格式的当前请求方式
request.GET 返回URL中问好后面携带的参数. QueryDict对象: <QueryDict: {'username': ['egon'], 'hobbies': ['play', 'study']}>
request.POST 返回POST请求提交过来的普通键值对(不包含文件)
request.GET.get() 返回对象列表中最后一个的值 -> str
request.POST.get()
request.GET.getlist() 返回对象列表中所有的值 -> list
request.POST.getlist()
# django连接数据库
# django自带的数据库: db.sqlite3
# 配置更换
1. 配置数据库: 找到settings.py文件中的DATABASES输入如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'IP地址',
'PORT': 3306,
'USER': '登录用户',
'PASSWORD': '用户密码',
'NAME': '库名',
'CHARSET': 'utf8',
}
}
2. 代码声明: 使用pymysql替换django自带的数据库模块MySQLdb.
前提: 在项目文件夹下或者任意的应用文件夹下__init__.py中书写代码声明
import pymysql
pymysql.install_as_MySQLdb()
# django orm
# 什么是django orm
ORM 全称 object relational mapping 对象关系映射
功能: 通过ORM实现对操作对象的操作模式来操作数据库中的数据
实现: 通过models中的类来对应数据库中的一个表,一个对象对应一个数据行,一个属性对应数据库中的一个字段
缺陷: 封装程度太高, 有时候会出现创建sql语句的效率问题.
# 使用django orm: 进入models.py中书写模型类
'''
1. 不指定默创建id主键
2. ChildField必须指定max_length
3. verbose_name 所有字段都有 对字段的描述
'''
# id int primary key auto_increment
models.AutoField(primary_key=True, verbose_name='主键')
# username varchar(32)
models.CharField(max_length=32, verbose_name='用户名')
# password varchar(255)
models.charField(max_length=255, verbose_name='密码')
# age int
models.IntegerField(verbose_name='年龄')
# 数据库迁移命令
前提: 执行终端命令行当前路径一定要在项目文件夹下, 且配置了python的环境变量
生成数据库迁移记录, 记录到migrations文件夹中: python3 manage.py makemigrations
将数据迁移提交: python3 manage.py migrate
# orm字段的增删改查
增:
1. 在执行了生成数据库迁移记录命令之后在终端命令行中输入默认值
2. 直接为字段指定可以为空
username = models.CharField(max_length=32, null=True)
3. 为字段指定默认值
username = models.CharField(max_length=32, default='我是默认值')
删:
只要将需要删除的字段注释以后执行数据库迁移命令即可
注意(危险): 删除的字段对应的数据也将会删除
改:
直接在原来的字段的基础之上进行修改即可
# 数据的增查
增:
方式一: 使用models自动创建
user_obj = models.User.objects.create(**kwargs)
user_obj是一个QuerySet对象. 当前创建的对象本身
方式二: 手动创建
user_obj = models.User(**kwargs)
user_obj.save()
查:
user_obj_list = models.User.objects.filter(**kwargs)
user_obj_list是一个QuerySet对象. 它是一个列表套数据的对象: <QuerySet [<User: User object>, <User: User object>, <User: User object>]>
方式一: 索引取值. 不能使用负数.
user_obj = user_obj_list[0]
方式二: 推荐使用.first(). 但是其内部使用的也是通过索引取值
user_obj = user_obj_list.first()
六. django orm中如何创建表关系
1. 表关系分析
表与表之间的关系: 一对多 多对多 一对一 没有关系
判断表关系的方法: 换位思考
用4张表举例: 图书表 出版社表 作者表 作者详情表
图书和出版社是一对多的关系 外键字段建在多的那一方
图书和作者是多对多的关系 需要创建第三张表来专门存储
作者与作者详情表是一对一
提示: 创建表关系 先将基表创建出来 然后再添加外键字段. 这里建表可以没有先后顺序, 不同于mysql中建立外键的创建表以及插入记录的先后顺序.
2. 建立表
book | |||
---|---|---|---|
id | title | price | publish_id |
1 | python从入门到入土 | 123.12 | 1 |
2 | 生蚝的吃法大全 | 666.66 | 1 |
3 | 说不是渣男本质其实就是 | 444.44 | 2 |
author | ||
---|---|---|
id | name | age |
1 | jason | 84 |
2 | egon | 73 |
book2author | ||
---|---|---|
id | book_id | author_id |
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 2 |
4 | 3 | 3 |
publish | ||
---|---|---|
id | name | add |
1 | 北方出版社 | 北京 |
2 | 南方出版社 | 南京 |
author2detail | ||
---|---|---|
id | phone | info |
1 | 80080088 | 我喜欢跑步, 所以我是跑王 |
2 | 88888888 | 我喜欢吃生蚝和装逼 |
3. 建立外键表关系基本语法
# django orm中建立表关系
一对一:
author_detail = models.OneToOneField(to='AuthorDetail')
一对多:
publish = models.ForeignKey(to='Publish')
多对多:
authors = models.ManyToManyField(to='Book')
拓展: 还可以有另一种书写方式, 不过这种方式必须放在被关联的类后. 这里必须放在Publish定义之后
publish = models.ForeignKey(to=Publish)
# 特点和注意事项:
1. 先建立基表最后再建立外键关系, 没有sql中建立外键必须先建立被关联表, 加入记录先插入被关联表这么一说, 直接建立就行.
2. django 1.x版本无序指定级联更新级联删除, 默认会帮你指定.
3. 一对多, 一对一无需在需要关联的字段后面加_id, 默认会帮你加. 例如: publish -> publish_id
4. 一对多的表关系外键字段建立在多的一方
5. 多对多的表关系无需类似于sql语句需要建立中间表, 会默认帮你创建虚拟的中间表
6. 一对一, 多对多的表关系外键字段建立在查询频率较高的地方.
4. 在models.py中创建以上模型类
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=255, verbose_name='书名')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格') # 总共八位 小数点后面占两位
# 一. 建立一对多关系: 图书和出版社是一对多 并且书是多的一方 所以外键字段放在书表里面
"""
如果字段对应的是ForeignKey 那么会orm会自动在字段的后面加_id
如果你自作聪明的加了_id那么orm还是会在后面继续加_id
后面在定义ForeignKey的时候就不要自己加_id
"""
publish = models.ForeignKey(to='Publish') # 默认就是与出版社表的主键字段做外键关联
# 二. 建立多对多关系: 图书和作者是多对多的关系 外键字段建在任意一方均可 但是推荐你建在查询频率较高的一方
"""
authors是一个虚拟字段 主要是用来告诉orm 书籍表和作者表是多对多关系
让orm自动帮你创建第三张关系表
"""
authors = models.ManyToManyField(to='Author')
class Publish(models.Model):
name = models.CharField(max_length=255, verbose_name='出版社名称')
addr = models.CharField(max_length=255, verbose_name='出版社地址')
class Author(models.Model):
name = models.CharField(max_length=255, verbose_name='作者姓名')
age = models.IntegerField(verbose_name='作者年龄')
# 三. 建立一对一关系: 作者与作者详情是一对一的关系 外键字段建在任意一方都可以 但是推荐你建在查询频率较高的表中
"""
OneToOneField也会自动给字段加_id后缀
所以你也不要自作聪明的自己加_id
"""
author_detail = models.OneToOneField(to='AuthorDetail')
class AuthorDetail(models.Model):
phone = models.BigIntegerField(verbose_name='作者电话号码')
addr = models.CharField(max_length=255, verbose_name='作者家庭住址')