Django框架
Django 框架是Python Web较早或历史较长,并且稳定、功能齐全的框架。
目前主要用于开发后台管理、服务运维站点平台、云计算运维平台及信息化管理系统等。
django框架适合初级开发人员,快速掌握和使用。对于高级开发人员来说,主要优化其不足的功能。
官方文档:https://docs.djangoproject.com/zh-hans/2.1/
django的重要的版本: 1.x(1.9/1.11), 2.x(2.1, 2.2, 2.3), 3.x(3.0, 3.1, 3.2)
一、Django 入门
【建议】每一个django项目,都有它自己的环境
1.1 安装
基于pip安装
pip install django==2.1
在交互的环境中,验证的django
>>> import django
>>> django.__version__
1.2 django-admin命令
1.2.1 创建项目命令
django-admin startproject mysite
项目结构说明:
- 最外层的: 根目录只是你项目的容器, Django 不关心它的名字,你可以将它重命名为任何你喜欢的名字。
manage.py : 一个让你用各种方式管理 Django 项目的命令行工具。你可以阅读 django-admin and manage.py 获取所有 manage.py 的细节。- 里面一层的
mysite/ 包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 mysite.urls ). mysite/__init__.py :一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。如果你是 Python 初学者,阅读官方文档中的 更多关于包的知识。mysite/settings.py :Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django settings 了解细节。mysite/urls.py :Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL调度器 文档来获取更多关于 URL 的内容。mysite/wsgi.py :作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。
1.2.2 创建应用
django项目的结构:由多个app组成的, 一个app即为一个模块。
django中自带了 admin站点管理、session会话管理、log日志等app应用。
django-admin startapp <应用名称>
当项目创建成功之后,项目中包含了一个 manage.py 脚本,可以执行此脚本也可以执行django-admin 的命令。
python manage.py startapp <应用名>
应用目录结构:
- migrations 包含所有生成当前应用的数据模型迁移文件脚本
- apps.py 应用的配置信息
- admin.py 配置站点下的应用信息 【一般】
- models.py 应用的所有模型类声明所在的脚本 【重点】
- tests.py 应用测试的脚本
- views.py 应用的所有Web接口 【重点】
【重点】views和models的关系
- Django采用 MVC设计思想,设计出自己的MTV
- 在Views中,角色是Controller控制器, 通过Models将数据加载出来,再通过T(emplate)模板将数据渲染出来,最后将渲染后的HTML封装成Response响应对象返回给Django框架
- models 数据模型, 采用了ORM框架,实现数据的CURD操作。同时支持模型之间的外键关联操作。
1.2.3 运行服务
python manage.py runserver
默认启动了 127.0.0.1:8000 Web服务
可以在runserver后面指定 端口:
python manage.py runserver 9000
可以指定ip的绑定的host:
python manage.py runserver 0:9000
0: 表示为0.0.0.0: ,绑定当前host主机的实际IP地址。
在浏览器访问:
- http://localhost:9000
- http://127.0.0.1:9000
- http://10.36.174.32:9000
【注意】修改主工程目录下的settings.py文件
ALLOWED_HOSTS = ['*'] # 白名单
1.2.4 创建Web接口
第一步:创建view处理函数
from django.http import HttpResponse
def index(request):
return HttpResponse('hi, django')
【说明】view处理函数的参数必须包含request,它是 WSGIRequest类的实例,也是HttpRequest的子类。可以从request对象中获取客户端请求的路径以及请求头和body数据。
第二步: 配置路由
方式1: 直接在主路由中配置(主工程包/urls.py)
from login.views import index
from django.urls import path
from django.contrib import admin
urlpatterns = [
path('', index),
path('admin/', admin.site.urls),
]
方式2:创建应用的子路由urls.py(复制主路由的urls.py)
from .views import index
from django.urls import path
urlpatterns = [
path('', index),
]
使用子路由时,必须要做2件事件:
- 将应用模块 添加到 settings.py中的INSTALLED_APPS 列表中 [实际上不需要]
- 将子路由添加到主路由中
修改主urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('user/', include('login.urls')),
path('admin/', admin.site.urls),
]
1.2.5 创建模型类
第一步:配置数据库
采用默认的数据库 sqlite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
第二步: 创建模型类
from django.db import models
class User(models.Model):
user_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=20, unique=True)
pwd = models.CharField(max_length=100)
第三步: 配置INSTALLED_APPS
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
'login',
]
login 是应用名
第四步: 生成数据表(模型类)的迁移文件
python manage.py makemigrations
【注意】确认模型类所在的app是否已添加到settings.py的INSTALLED_APPS列表中。
第五步: 执行数据模型类的迁移文件
python manage.py migrate
【注意】第一次迁移时,会自动迁移django自带app下的迁移文件.
【重要】如果修改了模型类, 必须要makemigrations 和migrate
1.2.6 shell调试模型
进入django shell中
python manage.py shell
>>> from login.models import User
>>> User.objects.all()
【注意】objects是什么?
- objects是Model的元类中创建Manage类实例, 也是QuerySet查询结果类实例
- objects提供了对模型的查询相关方法 【非常重要】
1.2.7 模型类操作
查询数据
User.objects.all() 查询所有的数据,返回可迭代的对象(QuerySet实例),元素是模型类的实例对象。
QuerySet实例方法
- all()
- first()
- last()
- get(属性名=属性值) # pk主键列名属性
- delete() # 模型类的实例对象
- filter(属性名=属性值)
- count() 统计记录个数
创建模型类实例
u1 = User(name="disen", pwd="123", phone="17791692095")
u2 = User()
u2.name="jack"
u2.pwd="123"
u2.phone="18999971112"
模型类实例方法
- save() 保存数据
- delete() 删除数据
- refresh_from_db() 同步数据(从数据库的表中更新当前实例的属性)
1.2.8 admin站点管理
参考文档:https://docs.djangoproject.com/zh-hans/2.1/ref/contrib/admin/
django本身提供了admin站点管理应用, 在每一个app中提供了admin.py脚本,可以将当前应用的models.py中模型类,添加到admin站点中,以方便管理员管理模型对应的数据。
第一步: 创建后台管理员账号(超级管理员)
python manage.py createsuperuser
>> username: admin
>> email: 610039018@qq.com
>> password: admin123
>> password (again): admin123
>> Bypass password validation and create user anyway? [y/N]: :y
启动服务之后,可以访问/admin 进入站点后台管理页面。
可以尝试创建后台管理人员账号, 将active 和staff status 勾选上。并添加login应用的管理User模型的权限。
第二步:将login应用中的模型类添加到admin.py中
from django.contrib import admin
from .models import User
class UserAdmin(admin.ModelAdmin):
list_display = ('user_id', 'name', 'phone', 'email')
list_display_links = ('name', )
list_filter = ('name', 'phone')
list_editable = ('phone', 'email')
search_fields = ('name', 'phone', 'email')
admin.site.register(User, UserAdmin)
修改模型类:
主要添加Field字段参数的blank和verbose_name
class User(models.Model):
user_id = models.IntegerField(primary_key=True, blank=True,verbose_name='用户ID')
name = models.CharField(max_length=20, unique=True,verbose_name='用户名')
pwd = models.CharField(max_length=100, verbose_name='口令')
phone = models.CharField(max_length=11, null=True, verbose_name='手机', blank=True)
email = models.CharField(max_length=50, null=True, verbose_name='邮箱', blank=True)
def __str__(self):
return f'{self.user_id } <{self.name}>'
class Meta:
verbose_name = 'Vip会员'
verbose_name_plural = verbose_name
【站点中显示app应用的中文名称】
第一步:在xxx app的__init__.py 文件中,添加如下代码:
default_app_config = 'testingapp.apps.TestingappConfig'
第二步:在TestingappConfig 中添加verbose_name属性
class TestingappConfig(AppConfig):
name = 'testingapp'
verbose_name = '自动化测试'
1.2.9 配置多数据库
一个django项目,可以存在多个数据库连接,在settings.py的DATABASES字典对象中配置,如下所示:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'db1': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '180.76.121.47',
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'rootxaqf2101',
'NAME': 'testdb'
}
}
1.2.10 反向生成模型
django-admin或manager.py中提供了 inspectdb命令,可以将已存在的表生成模型类,命令如下:
python manager.py inspectdb --database db1 > testingapp/models.py
连接db1数据库,将库中所有表全部生成模型类,并写入到testingapp应用下的models.py脚本中。
1.2.11 重写Manager
由于,模型类操作时,默认选择default数据库,因此需要重新指定模型类的objects实例的类型,需要先声明一个models.Manager类的子类,并重写get_queryset()方法,如下:
class DB1Manager(models.Manager):
def get_queryset(self):
return self._queryset_class(model=self.model, using='db1', hints=self._hints)
之后,在模型类中,显式地增加objects成员,如下:
class TbPerson(models.Model):
person_id = models.IntegerField(primary_key=True, verbose_name='ID')
phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号')
objects = DB1Manager()
class Meta:
managed = False
db_table = 'tb_person'
verbose_name_plural = verbose_name = '人员信息'
1.2.12 重写模型类之save
对于模型类拥有objects成员,只能用于查询和修改,如果是新增,则需要重写save方法,保证数据写入到正确的数据库中。
class TbPerson(models.Model):
person_id = models.IntegerField(primary_key=True, verbose_name='ID')
phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号')
objects = DB1Manager()
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
return super(TbPerson, self).save(using='db1')
class Meta:
managed = False
db_table = 'tb_person'
verbose_name_plural = verbose_name = '人员信息'
1.3 views视图与模板
1.3.1 请求路径参数
from .views import index, update_user
urlpatterns = [
path('', index),
path('<user_id>/', update_user)
]
def update_user(request, user_id):
return HttpResponse('修改用户ID:%s' % user_id)
1.3.2 模板配置
首先在项目的根目录下,创建一个templates目录,并在settings.py的TEMPLATES配置DIRS模板路径,如下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
],
},
},
]
1.3.3 创建模板的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新用户</title>
</head>
<body>
<h3> {{ user.name }} 用户信息修改</h3>
</body>
</html>
1.3.4 在views渲染模板
在templates目录下创建
def update_user(request, user_id):
user = User.objects.get(pk=user_id)
resp = render(request, 'update.html', {'user': user})
return resp
【练习】查看所有用户信息,显示到模板上
def view_users(request):
users = User.objects.all()
return render(request, 'user_list.html', {'users': users})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<h3>查询用户结果</h3>
<ul>
{% for u in users %}
<li> {{ u.id }} - {{ u.name }} - {{ u.phone }} </li>
{% endfor %}
</ul>
</body>
</html>
from django.urls import path
from .views import index,update_user, view_users
urlpatterns = [
path('', index),
path('<int:user_id>/', update_user),
path('list/', view_users )
]
【Http404】抛出404异常
def update_user(request, user_id):
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
raise Http404('未找到 {}'.format(user_id))
resp = render(request, 'update.html', {'user': user})
return resp
from django.shortcuts import render, get_object_or_404
def update_user(request, user_id):
user = get_object_or_404(User, pk=user_id)
resp = render(request, 'update.html', {'user': user})
return resp
1.3.5 模板的url指令
第一步: 在路由中声明url路径时,可以指定name属性(path())
path('<int:user_id>/', update_user, name='update'),
path('list/', view_users, name='ulist')
第二步:在template模板文件中
<a href="{% url 'ulist' %}">查看所有用户</a>
【重要】{% url %} 指令是反向查找路径,好处,无论url路径如何修改,都可以反向获取完整的请求路径。另外格式可以带有namespace:
<a href="{% url 'user:ulist' %}">查看所有用户</a>
user: 代表是include()方法中指定的namespace属性值。
主路由中添加子路由时,可以同时指定的namespace
urlpatterns = [
path('user/', include('login.urls', namespace='user')),
path('admin/', admin.site.urls),
]
但是在子路由urls.py文件中,必须指定app_name 应用名称。
app_name = 'user'
urlpatterns = [
path('', index),
path('<int:user_id>/', update_user, name='update'),
path('user_list/', view_users, name='ulist')
]
1.4 form表单提交
1.4.1 修改模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新用户</title>
</head>
<body>
<h3> {{ user.name }} 用户信息修改</h3>
<a href="{% url 'user:ulist' %}">查看所有用户</a>
<form method="post">
{% csrf_token %}
<label>用户名</label> <input name="name" value="{{ user.name }}"><br>
<label>口令</label> <input name="pwd" type="password" value="{{ user.pwd }}"><br>
<label>手机号</label> <input name="phone" value="{{ user.phone }}"><br>
<label>邮箱 </label> <input name="email" value="{{ user.email }}"><br>
<button>提交</button>
</form>
</body>
</html>
【注意】csrf_token 指令,防止跨域伪造请求,当渲染模板时,django会生成一个token默认保存到session中,同时在html中生成一个隐藏域。一般post提交form表单时,必须声明此指令。如果不想使用csrf_token ,可用在settings.py中去掉django.middleware.csrf.CsrfViewMiddleware 中间件。
1.4.2 修改view视图函数
def update_user(request: HttpRequest, user_id):
user = get_object_or_404(User, pk=user_id)
if request.method == 'GET':
resp = render(request, 'update.html', {'user': user})
return resp
user.name = request.POST.get('name')
user.pwd = request.POST.get('pwd')
user.phone = request.POST.get('phone')
user.email = request.POST.get('email')
user.save()
return HttpResponseRedirect(reverse('user:ulist'))
【注意】request是HttpRequest类实例, 包含了请求头和请求体相关的数据以及客户端的环境信息。GET 是查询参数,POST 是表单参数, FILES 是上传的文件, META 是客户端环境信息,COOKIES 是客户存储的cookies信息。body 是请求体的原始数据。
1.5 CBV的view视图
1.5.1 TemplateView应用
如果html模板中不需要后端的数据来渲染时,可以直接使用此类进行资源url的GET请求的处理视图。
path('about/', TemplateView.as_view(template_name='about.html')),
TemplateView在渲染模板时,自动将request传入,因此在模板文件中,可以直接使用request变量,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>关于我们</title>
</head>
<body>
<h3>disen@千锋教育西安校区</h3>
<h4>610039018@qq.com</h4>
<p>{{ request.method }}</p>
</body>
</html>
当然,也可以实现TemplateView的子类,子类的写法:
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
urlpatterns = [
path('about/', AboutView.as_view()),
]
1.5.2 ListView应用
如果需要从模型加载数据并显示其列表,则可以使用ListView类。
使用步骤如下:
-
创建ListView的子类 from django.shortcuts import render
from django.views.generic import ListView
from .models import TbPerson
class PersonListView(ListView):
model = TbPerson
-
创建模板文件 根据当前ListView子类所在的app模板及model模型类,创建相应的目录及模板文件,如当前app为testingapp ,因此在templates目录下创建其子目录,并在其子目录中,创建tbperson_list.html(tbperson是TbPerson模型类名,_list是后辍名)。 -
根根ListView中渲染模板的上下文变量,将数据渲染到的HTML中 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>所有人员信息</title>
</head>
<body>
<h1>人员信息</h1>
<table>
<thead>
<th>ID</th>
<th>Name</th>
<th>Phone</th>
</thead>
<tbody>
{% for person in object_list %}
<tr>
<td>{{ person.person_id }}</td>
<td>{{ person.name }}</td>
<td>{{ person.phone }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
object_list是ListView渲染模板时的可用上下文变量, 表示为model模型类的queryset查询结果集。 -
添加urls路径 urlpatterns = [
path('persons/', PersonListView.as_view()),
]
1.5.3 View应用
View类是TemplateView、ListView、CreateView等类的父类,主要实现了针对不同请求方法的转发功能。在View类中,提供与请求方法对应的请求处理函数(同普通view视图函数一样)。
1.5.3.1 dispatch分发
View类的业务处理最核心的方法是dispatch(), 源码如下:
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
1.5.3.2 自定义View
from django.views import View
from django.shortcuts import render
class RoleView(View):
def get(self, request):
return render(request, 'xx.html', locals())
def post(self, request):
pass
def put(self, request):
pass
def delete(self, request):
pass
详细的代码如下:
class RoleView(View):
def get(self, request):
roles = TbRole.objects.all()
return render(request, 'role/list.html', locals())
def post(self, request):
name = request.POST.get('name')
code = request.POST.get('code')
max_role_id = TbRole.objects.aggregate(max_id=models.Max('role_id')).get('max_id')
role = TbRole.objects.create(name=name, code=code, role_id=max_role_id+1)
role.save()
return JsonResponse({
'code': 0,
'msg': '新增成功'
})
def delete(self, request):
role_id = request.GET.get('role_id')
try:
role = TbRole.objects.get(pk=role_id)
role.delete()
return JsonResponse({
'code': 0,
'msg': '删除成功!'
})
except:
return JsonResponse({
'code': 1,
'msg': '删除失败!'
})
def put(self, request:WSGIRequest):
data = request.body.decode('utf-8')
print('--body--', data)
forms = {}
for kv in data.split('&'):
k,v = kv.split('=')
forms[k] = unquote(v)
role_id = forms.get('role_id')
name = forms.get('name')
code = forms.get('code')
role = TbRole.objects.get(pk=role_id)
role.name = name
role.code = code
role.save()
return JsonResponse({
'code': 0,
'msg': '成功'
})
1.6 django的单元测试
1.6.1 声明单元测试类
from django.test import TestCase
from .models import User
class TestUser(TestCase):
def test_save(self):
User.objects.create(name='disen', pwd='123')
users = User.objects.all()
self.assertGreater(users.count(), 0, '末查找数据')
1.6.2 单元测试的断言
单元测试类中提供了丰富的断言方法, 统一格式:
self.assertXXXX(..., msg='断言失败的消息')
断言失败之后, 则会抛出AssertionError异常
常用的断言方法:
assertGreater(a, b, msg) 断言a > b
assertIs(a, b, msg) 断言a是b
assertEqual(a, b, msg) 断言a==b
assertLess(a, b, msg) 断言a<b
assertContains(a, b,msg) 断言a是b集合的元素
assertQuerysetEqual(qs, values, msg=)
assertIsNotNone(a, msg)
assertIsNone(a, msg)
1.6.3 运行单元测试
python manage.py test <应用名>
1.6.4 注意事项
- 多个单元测试的方法,默认是按方法名称的ASCII值排列。一般在方法名的上添加序号。如
test_01_save() ,test_02_add() 等。 - django的单元测试的每个方法都有自己的测试数据库,相互之间数据没有关系。
- 单元测试的方法名,一般以
test_ 开头。
【练习】
from django.test import TestCase
from .models import User
class TestUser(TestCase):
def test_save(self):
print('---save--')
u = User.objects.create(name='disen', pwd='123')
users = User.objects.all()
self.assertGreater(users.count(), 0, '末查找数据')
def test_update(self):
print('--update--')
User.objects.create(name='disen', pwd='123')
user = User.objects.get(name='disen')
self.assertIsNotNone(user, 'disen用户不存在')
user.pwd = '456'
user.save()
user = User.objects.get(pk=user.pk)
self.assertEqual(user.pwd, '456', '更新失败')
1.6.5 测试view视图
通过django.test.Client类,实现对view视图的请求
from urllib.parse import urlencode
from django.test import TestCase
from .models import User
class TestUserView(TestCase):
def test_list(self):
User.objects.create(name='disen', pwd='123', phone='110')
User.objects.create(name='jack', pwd='123', phone='120')
resp = self.client.get('/user/user_list/')
self.assertEqual(resp.status_code, 200, '请求失败')
print(resp.context['users'])
def test_update(self):
u1 = User.objects.create(name='disen', pwd='123', phone='110')
u2 = User.objects.create(name='jack', pwd='123', phone='120')
u1.name = 'disen888'
u1.phone='17791692095'
data = urlencode({
'name': u1.name,
'phone': u1.phone,
'pwd': '123'
})
resp = self.client.post('/user/1/', data=data,
content_type='application/x-www-form-urlencoded')
print(resp.status_code, resp.content, resp.url)
self.assertEqual(resp.status_code, 302, '未获取重定向的地址')
resp2 = self.client.get(resp.url)
print(resp2.content)
1.7 静态资源文件
1.7.1 应用中的静态资源
在 app应用模块目录下, 创建static目录,存放js,css和图片等相关的静态资料。
1.7.2 项目中的静态资源
在项目的目录下创建static目录, 要求在settings.py文件配置:
STATIC_URL = '/static/'
STATICFIELS_DIRS = [
os.path.join(BASE_DIR, 'static')
]
1.7.3 在模板中使用
在static/css/login.css 文件,内容如下:
label {
display: inline-block;
width: 100px;
text-align: right;
height: 50px;
line-height: 50px;
background-color: yellowgreen;
}
body{
background-image: url('/static/images/bg2.jpeg');
background-repeat: no-repeat;
}
在模板文件中(update.html), 先执行{% load static %} 指令加载静态资源,然后再使用{% static 'css/login.css' %} 获取静态资源的完整路径。
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新用户</title>
<link rel="stylesheet" href="{% static 'css/login.css' %}">
</head>
...
1.8 定制admin的表单
1.8.1 User模型admin表单
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = ('user_id', 'name', 'phone', 'email')
list_display_links = ('name', )
list_filter = ('name', 'phone')
list_editable = ('phone', 'email')
search_fields = ('name', 'phone', 'email')
fieldsets = [
('必填信息', {'fields': ('name', 'pwd')}),
('其它信息', {'fields': ('email', 'phone'),
'classes': ('collapse',)
})
]
list_per_page = 10
1.8.2 声明关系模型
class Order(models.Model):
pay_status_list = [
(0, '未支付'),
(1, '已支付'),
(2, '已完成')
]
title = models.CharField(max_length=50, verbose_name='订单标题')
price = models.FloatField(verbose_name='订单金额', default=0)
pay_status = models.IntegerField(verbose_name='订单状态',
choices=pay_status_list, default=0)
user = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='orders', verbose_name='用户')
def __str__(self):
return self.title
class Meta:
verbose_name = verbose_name_plural = '用户订单'
on_delete 声明级联删除时的操作,可以设置models.CASCAD 、models.SET_NULL 和models.SET_DEFAULT 。
【重要】ForeignKey()中的related_name说明
- related_name 即为User动态添加一个属性,方便查找关系的Order数据
- 即 User.orders 属性,集合,表示某一个用户的所有订单信息
- 如果related_name不指定时, 默认添加的属性为 order_set
1.8.3 注册模型
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'price', 'pay_status', 'user')
ordering = ['price']
list_filter = ['user']
1.8.4 inlines显示
class OrderInline(admin.TabularInline):
model = Order
extra = 1
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
...
inlines = [OrderInline]
主要是一端模型类上,编辑时可以同时添加关联子类模型的数据。
1.8.5 自定义字段函数
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'price', 'pay_status', 'user_name')
ordering = ['price']
list_filter = ['user']
def user_name(self, instance):
return instance.user.name
user_name.admin_order_field = 'user_id'
user_name.short_description = '用户名'
【注意】自定义属性方法时,必须提供两个参数,除self之外,instance表示当前admin装饰模型实例对象。
【重点】short_description类似于Field字段的verbose_name属性。
1.8.6 外键关联实体属性搜索
如果一个实例支持搜索时,需要满足objects.filter()方法的规则,如:
@admin.register(TbPersonRole)
class PersonRoleAdmin(admin.ModelAdmin):
list_display = ['person', 'role']
fields = ['person', 'role']
search_fields = ['person__name', 'role__name']
1.9 综合项目任务
参考一个电商类的app页面,分析页面的数据,设计表或模型类。
1.9.1 实现用户登录功能
request.session[‘user_id’] = u.user_id 将数据存储在session中
session的使用示例
def index(request: HttpRequest):
print(type(request))
if request.session.get('user_id', None):
if request.GET.get('logout', '0') == '1':
del request.session['user_id']
return HttpResponse('用户退出登录')
return HttpResponse('用户已登录')
request.session['user_id'] = '123'
return HttpResponse('Session 信息已添加')
1.9.2 实现用户注册功能
1.9.3 设计商品或产品模型类
1.9.4 设计订单类
1.9.5 实现产品列表显示页面
1.9.6 实现用户购买商品或产品功能
1.9.7 设计我的订单 页面
1.10 WSGIRequest类
所有WSGI请求信息的封装类,包含请求报文的数据和客户端相关信息。
1.10.1 请求数据
一个HTTP请求的数据包含
-
路径参数
- 路由中需要
<int:参数名> 参数的类型可以不提供。 - 参数名必须存在 路由对应的view视图处理函数的形参中,形参必然是request,后台依次是路径参数
urls.py
...
path('items/<page>/<item_type>/', views.all_items)
views.py
def all_items(request, page, item_type):
pass
-
查询参数 从request的GET属性读取,GET是QueryDict类型,即为字典类型。 查询单个参数: request.GET.get(‘参数名’) 查询数组的参数: request.GET.getlist(‘参数名’) -
表单参数 从request.POST读取的数据,也是QueryDict类型。 如果请求方法非post时,需要从request.body读取数据。 【注意】请求头的Content-Type可以是application/x-www-form-urlencoded 或multipart/form-data 。 -
json数据 json数据是在请求体中的,即是从request.body属性中读取的。 request.body是bytes字节数据类型,需要自己解码和反序列化为dict或list。 【注意】请求头的Content-Type必须是application/json -
上传的文件 request.FILES 存放客户端上传的文件信息,它也是一个QueryDict字典类型,前端文件字段的名称是QueryDict字典的key, 对应的value是一个UploadedFile文件类实例对象。
1.10.2 请求头
- content_type 请求数据类型
- method 请求方法
- path 请求路径
- META[‘HTTP_AUTHORIZATION’] 获取请求头的身份信息
- META[‘HTTP_REFERER’] 当前请求的来源(上一个页面的地址)
- META[‘HTTP_USER_AGENT’] 请求客户端应用名称
1.10.3 客户端数据
- META[‘HTTP_HOST’] 当前服务主机的地址
- META[‘REMOTE_ADDR’] 当前客户端的IP地址
- COOKIES 客户端存放的所有Cookie, 是QueryDict字典类型
1.10.4 session数据
request.session是客户端将数据存储在后端的一种方式,默认存储在数据库的django_session表中,客户端的Cookie中存在一个sessionid,存放的数据即为django_session表的session_key。请求处理时,根据cookie中的session_id,确认是哪一个客户端,从而获取客户端存储在后端的数据(session中)。
在view视图函数中,用法如下:
request.session['login_user'] = 'disen666'
二、模型类进阶
2.1 Field类型
通用属性: verbose_name, null, blank, primary_key, unique, default, db_column, name
2.1.1 CharField
2.1.2 IntegerField
2.1.3 TextField
2.1.4 日期时间字段
共有三个相关的字段类:DateField/TimeField/DateTimeField
重要的属性:
auto_now 每次保存时,都会更新时间
auto_now_add 只有第一次保存时,保存时间
2.1.5 文件与图片字段
如果使用图片字段 ImageField,则需要安装pillow库
另外,可以指定upload_to,width_field, height_field三个字段
class LoginUser(models.Model):
name = models.CharField(max_length=20, unique=True)
pwd = models.CharField(max_length=100)
phone = models.CharField(max_length=11)
head = models.ImageField(upload_to='images', blank=True, null=True, verbose_name='头像')
last_login_time = models.DateTimeField(auto_now=True,verbose_name='最后登录时间')
regist_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
def __str__(self):
return self.name
def head_img(self):
return '<img width=50 height=50 src="{}" >'.format(self.head.url)
head_img.short_description='用户头像'
class Meta:
db_table = 'tb_login'
verbose_name = verbose_name_plural = '会员登录信息'
ordering = ['-last_login_time', '-regist_time']
【注意】如果想存储上传图片的宽和高,则添加两个字段,并通过width_field和height_field来指定两个字段
class User(models.Model):
...
hegiht = models.IntField(default=0)
width = models.InteField(default=0)
head = models.ImageField(upload_to='images',
width_field='width',
height_field= 'height')
另外, 在settings.py配置MEDIA_URL 和MEDIA_ROOT
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'medias')
需要在主路由文件中,通过django.conf.urls.static.static 函数将MEDIA资源添加进来:
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from storems import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('login.urls'))
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2.1.6 自定义上传图片的文件名
class LoginUser(models.Model):
name = models.CharField(max_length=20, unique=True)
def get_filename(self, filename):
ext_name = filename.split('.')[-1]
newfilename = self.name
return 'images/{}.{}'.format(newfilename, ext_name)
head = models.ImageField(upload_to=get_filename, blank=True, null=True, verbose_name='头像')
upload_to 可以是images 的目录名称,也可以自定义的函数.
2.1.7 admin站点显示html字段
from django.utils.html import format_html
class XXX(models.Model):
img = models.ImageField(...)
def img_html(self):
return format_html('<img src="{}">', self.img.url)
img_html.short_description = '图片'
在admin.py脚本中:
from django.contrib import admin
form .models import XXX
@admin.register(XXX)
class XXXAdmin(admin.ModelAdmin):
list_diplay = ('img_html', ...)
2.1.8 FloatField
2.2 字段约束
常见约束:
- default
- primary_key
- unique
- null
- blank
- max_length
- verbose_name
- db_column
2.3 关系模型
通用属性: on_delete 级联操作
2.3.1 ForeignKey外键关系
关系字段放在多端,
如果没有指定related_name属性, 在一端获取多端数据时, 多端模型类名小写+_set
如:
class Goods(models.Model):
title = models.CharField(max_length=50, db_index=True)
summary = models.CharField(max_length=100, blank=True,null=True,verbose_name='简介')
content = models.TextField(verbose_name='产品说明', blank=True, null=True)
cover = models.ImageField(upload_to='goods')
price = models.FloatField(verbose_name='单价')
brand_name = models.CharField(max_length=50, verbose_name='品牌')
def __str__(self):
return self.title
class Meta:
db_table = 'tb_goods'
verbose_name = verbose_name_plural = '商品信息'
class GoodsImages(models.Model):
goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品名称')
img = models.ImageField(verbose_name='图片', upload_to='goods')
ord_num = models.IntegerField(default=0, verbose_name='序号')
label = models.CharField(max_length=20, verbose_name='标签')
def __str__(self):
return self.label
class Meta:
db_table = 'tb_goods_images'
verbose_name_plural = verbose_name = '商品图库'
g1 = Goods.objects.first()
g1.goodsimages_set.all()
使用related_name, 指定为images
g1.images.all()
2.3.2 OneToOne 一对一关系
关系字段可以放在任何的一端上, 如 LoginUser - > Account( user )
对于LoginUser实例 默认可以通过 account 属性来访问它的 账户信息. 当然如果Account类声明user属性的OneToOne()方法中指定了related_name时,通过它的名称来访问,如 related_name = ‘ac’, user.ac : Account
如:
user = LoginUser.objects.get(pk=1)
user.account.money
user = LoginUser.objects.get(pk=1)
user.ac.money
2.3.3 ManyToMany多对多关系
2.4 CURD操作
2.4.1 常用的CURD操作
- 创建或插入新的数据: Create
- User.objects.create(属性=值,...)
- User(属性=值,...).save()
- u = User()
u.属性=值
u.save()
- 获取数据(模型实例对象):READ
- 单个数据或对象的获取
- User.objects.get(pk=主键值)
- User.objects.first()
- User.objects.last()
- 获取多个数据或对象
- User.objects.all()
- User.objects.filter()
- User.objects.values('字段名1', '字段名2', ...)
返回 QuerySet< [{'字段名': 字段值}, {..}] >
- 更新数据: Update
- u = User.objects.get(pk=主键值)
u.属性=值
u.save()
- u.refresh_from_db() 从数据库中读取最新的数据
- 删除数据
- 单个对象删除
- u.delete()
- User.objects.first.delete()
- User.objects.get(pk=主键值).delete()
- 删除多个对象
- User.objects.all().delete() 清空表
- User.objects.filter(属性=属性值).delete()
2.4.2 关系模型的CURD
示例模型类:
from django.db import models
from goods.models import Goods
from login.models import LoginUser
class Order(models.Model):
title = models.CharField(max_length=50)
price = models.FloatField(default=0, verbose_name='应付金额')
address = models.CharField(max_length=100, verbose_name='收货地址')
phone = models.CharField(max_length=11)
pay_state = models.IntegerField(verbose_name='支付状态',
choices=((0, '未支付'), (1, '已支付')))
pay_time = models.DateTimeField(verbose_name='支付时间', null=True)
create_time = models.DateTimeField(verbose_name='下单时间', auto_now_add=True)
user = models.ForeignKey(LoginUser, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
class Meta:
db_table = 'tb_order'
verbose_name_plural = verbose_name = '会员订单信息'
ordering = ('-pay_time', '-price')
class OrderDetail(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name='订单')
goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品')
num = models.IntegerField(verbose_name='数量', default=1)
def __str__(self):
return '{} - {}'.format(self.goods.title, self.num)
class Meta:
db_table = 'tb_order_detail'
verbose_name_plural = verbose_name = '订单详情信息'
user = User.objects.get(pk=1)
o1 = Order()
o1.title = ''
o1.address=''
o1.phone=''
o1.user = user
o1.user_id = user.id
goods_list = Goods.objects.all()[:3]
o1.save()
o1.price = 0
for g in goods_list:
OrderDetail.objects.create(order=o1, goods=g, num=1)
o1.price += g.price * 1
for g in goods_list:
od = OrderDetail(goods=g,num=1)
o1.orderdetail_set.add(od)
o1.price += g.price * 1
o1.save()
关系模型的更新操作
o1.orderdetail_set.filter(goods_id=1).update(num=5)
od = o1.orderdetail_set.filter(goods_id=1).first()
od.num -= 1
od.save()
关系模型的删除操作
o1.orderdetail_set.filter(goods_id=1).delete()
o1.delete()
2.4.3 QuerySet查询
2.4.3.1 filter 查询条件
User.objects.filter(属性名=属性值)
User.objects.filter(属性名__方法名=条件值)
__方法名 常见的有:
contains 包含,类似于SQL的like
startswith 以xxx开头
endswith 以 xxx结尾的
isnull 数据表的数据是 <null>
isnotnull 数据表的数据是非 <null>
lt 小于
gt 大于
lte 小于等于
gte 大于等于
# 时间条件之后,还可以添加 lt,gt,lte, gte的条件
# Order.objects.filter(pay_time__day__gte=24)
year 年
month 月
day 日
hour 小时
minute 分钟
second 秒
2.4.3.2 exclude排除条件
在查询时,可以指定哪些数据不要的条件,即满足条件的数据除外。
Order.objects.exclude(pay_time__day=24)
2.4.3.3 Q表达式
django.db.models.Q 提供多个条件的查询,支持 and 与、or 或、not 非逻辑关系的查询,但以& , | , ~ 等符号依次表示与、或、非等逻辑关系。
Q表达式同filter()或exclude()等方法一样,都是通过模型类的属性=值 方式。
Order.objects.filter(Q(pay_time__day__lt=24) | Q(pay_time__day__gt=24))
Order.objects.filter(~Q(pay_time__day=24))
Order.objects.filter(
Q(Q(pay_time__day__lt=24) & Q(pay_time__month=5)) |
Q(Q(address__startswith='西安') & Q(price__gt=1000))
)
练习:
Order.objects.filter(Q(pay_time__lt='2021-05-24') & Q(pay_state=0))
Order.objects.filter(Q(pay_time__lt='2021-05-24', pay_state=0))
Order.objects.filter(user__name='disen', price__gte=1000)
2.4.3.4 F表过式
django.db.models.F 可以在更新时提取属性的值
Order.objects.filter(user__name='disen', pay_state=0).update(price=F('price')*0.1)
Order.objects.filter(create_time__month=5,create_time__day=20, pay_state=0, price__gte=520).update(price=F('price')-520)
LoginUser.objects.all().update(phone='029-'+F('phone'))
2.4.4 重写模型的save方法
模型的save()用于数据的存储(永久性写入数据库中),在模型类中可以重写save()方法,将重要的字段信息预先处理,再进行永久化存储。
2.4.4.1 make_password的使用
from djang.contrib.auth.hashers import make_password, BCryptSHA256PasswordHasher
class LoginUser(models.Model):
...
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if len(self.pwd) < 30:
self.pwd = make_password(self.pwd, hasher=BCryptSHA256PasswordHasher())
print('self.pwd', self.pwd)
return super(LoginUser, self).save()
2.4.4.2 check_password的使用
class LoginView(View):
def get(self, request):
return render(request, 'user/login.html')
def post(self, request):
...
login_user = LoginUser.objects.filter(name=name).first()
if login_user and check_password(pwd, login_user.pwd):
request.session['login_user'] = {'id': login_user.id,
'name': login_user.name,
'head': login_user.head.url if login_user.head else ''
}
return redirect('/')
context['errors'] = '用户名或口令错误'
return render(request, 'user/login.html', context)
2.4.5 重定义objects
每一个模型类,都存在一个objects隐性对象, 它的类型是models.Manager
objects是模型类的元类(ModelBase)添加的.
objects是用于数据的查询及更新的, 默认查询的数据是全部,返回是QuerySet.
当默认查询结果集中不包含特定状态(条件)的数据时,需要重新定义,达到简化查询条件的目的。如,用户注销后(未物理删除,只是修改了状态),每次查询数据时,应该提供一个用户是否注销的条件。
class LoginUserManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_del=False)
在LoginUser模型类中显式声明objects
is_del = models.BooleanField(default=False)
objects = LoginUserManager()
object_set = models.Manager()
注销disen123用户:
LoginUser.object_set.filter(name='disen123').update(is_del=True)
2.4.6 原生SQL查询
1.模型类的QuerySet.raw()
2.模型类的QuerySet.extra()
3.django.db.connection数据库连接对象
2.4.6.1 raw方式
qs = LoginUser.object_set.raw('select * from tb_login where id>=5')
print(list(qs))
2.4.6.2 extra方式
select和select_params
qs = LoginUser.object_set.extra(
select={'orders':'select count(1) from tb_order t2 where t2.user_id=tb_login.id'})
where和params条件与参数:
LoginUser.object_set.extra(
where=['name=%s', 'phone like %s'],
params=['disen123', '177%'])
2.4.6.4 db.connection
djang.db提供了一个属性connection,表示当前项目与数据库的连接
可以通过connection获取连接的cursor游标对象,然后再通过cursor对象进行sql语句执行。同pymysql(mysqlclient)的cursor。
用法:
from djang.db import connection
with connection.cursor() as c:
sql = 'select id,name,pwd,head from tb_login where name=%s'
c.execute(sql, params=['disen123'])
ret = c.fetchone()
2.5 事务
django与数据库交互时,每一个模型的执行都会提交一次事务。
django中提供了一个手动提交或开启事务的方法, 需要使用django.db.transaction对象。
另外,在settings.py文件的配置数据库的OPTIONS选项中,可以配置事务的隔离级别。
2.5.1 transaction事务对象
transaction提供了保存还原点(savepoint), 开启事务(begin), 提交事务(commit), 回滚事务(rollback), 回滚到指到的还原点(rollback to )。
用法1:
from djang.db import transaction, connection
with transaction.get_connection(using='db1').cursor() as c:
c.execute('create table tb_login(id integer primary key,name,pwd)')
c.execute('insert into tb_login(name,pwd) values(%s, %s)', params=['disen', '123'])
c.execute('delete from tb_login where name=%s', params=['disen'])
c.execute('insert into tb_login(name, pwd) values(%s,%s)', params=['lucy', '123'])
【注意】在上下文环境中,执行多条语句时,事务不会自动提交。只有退出上下文环境时,才会提交事务。
用法2:
cursor = transaction.get_connection(using='db1').cursor()
transaction.set_autocommit(False, using='db1')
cursor.execute(sql, params=None)
...
transaction.commit(using='db1')
用法3: 保存还原点
sid1 = transaction.savepoint(using='db1')
transaction.savepoint_commit(sid1, using='db1')
transaction.savepoint_rollback(sid1, using='db1')
transaction.commit(using='db1')
2.5.2 事务的隔离级别
在settings.py的数据库配置中,使用OPTIONS 配置:
DATABASES = {
'default': {
'ENGINE': '',
'NAME': '',
'OPTIONS': {
'isolation_level': 'repeatable_read'
}
}
}
除了默认的repeatable_read 可重复读之外,还有
- read_uncommitted 读未提交
- read_committed 读已提交
- serializable 串行化
2.5.3 行级锁
模型类操作时,可以通过objects的select_for_update()进行查询数据。
用法:
transaction.set_autocommit(False, using=None)
with transaction.atomic():
u = Userinfo.objects.select_for_update().get(pk=1)
u.pwd='789'
u.save()
2.5.4 view视图函数上的事务
可以在view视图处理函数(web接口的处理函数)上使用transaction.atomic装饰器,使得view函数内部的模型操作处理事务内(开始事务),如果出现异常,则会自动回滚事务。虽然事务内操作数据比较安全,但也会影响Web接口响应的速度。
用法:
from django.db import transaction
from .models import Userinfo
@transaction.atomic(savepoint=True)
def update_user(request, pk):
if request.method == 'POST':
return HttpResponse('<h3 style="color:red">不支持POST方法的请求</h3>')
u = Userinfo.objects.select_for_update(nowait=False).get(pk=pk)
name = request.GET.get('name', '')
pwd = request.GET.get('pwd', '')
if name and pwd:
u.username = name
u.pwd = pwd
u.save()
return HttpResponse('<h3>修改成功</h3>')
return HttpResponse('<h3 color="yellow">修改失败, 用户名不能为空</h3>')
测试接口: http://127.0.0.1:8000/user/2/?name=disen&pwd=123
三. Django模板进阶
3.1 布局指令
3.1.1 block块
可以在父模板中,声明结构的区域(块), 在子模板中可以重写内容.
用法:
{% block name %}
{% endblock %}
3.1.2 extends继承
文档的整体结构从extends指定的父模板继承下来
用法:
{% extends "模板文件位置" %}
3.1.3 include包含
可以将其模板内容插入到当前的位置
用法:
{% include "模板文件位置" %}
3.2 模板标签
3.2.1 for指令
用法1:
{% for v in objs %}
{{ forloop.counter }} {{ v.name }}
{% endfor %}
【注意】forloop是for循环对象,包含counter记数器(从1开始)。还包括first 和last
用法2:
{% for v in objs %}
{{ forloop.counter }} {{ v.name }}
{% empty %}
当前不存在数据
{% endfor %}
3.2.2 if指令
格式1:
{% if 属性 %}
{% endif %}
格式2:
{% if 属性 >|<|==|<= | >= 值 %}
{% endif %}
格式3:
{% if 属性|关系表达式 %}
内容1
{% else %}
内容2
{% endif %}
格式4:
{% if 属性|关系表达式 %}
内容1
{% elif 属性|关系表达式 %}
{% else %}
内容2
{% endif %}
3.2.3 url指令
格式:
{% url "namespace:name" 路径参数 %}
[说明]
namespace : 主路由包含子路由时指定的,
如 path('order/', include('order.urls', namespace="order"))
name: 一般在子路由的path()声明路径的名称,如
path('del_cart/<pk>/', views.remove_cart, name="delcart")
有效的url指令:
{% url "order:delcart" 3 %}
生成的url路径: /order/del_cart/3/
3.2.4 变量标签
使用{{ 变量名 }} 获取变量的值
变量标签中使用. 语法, 包含:
- 对象的属性,如
g.title - 对象的方法,此方法必须有返回值
- 可迭代对象的索引,如果超过索引范围,则不显示
- dict对象的key
示例:
def show_cart(request):
test_items = ['A', 'B', 'C']
title = 'hi,test,django variable tag'
test_dict = {'name': 'disen', 'age': 20}
return render(request, 'carts.html', locals())
<div>
{{ test_items.3 }}
{{ test_items.pop }}
{{ test_dict.name }}
{{ title.title }}
{{ title.10 }}
</div>
3.2.5 其它指令与标签
- {% widthratio 变量 分母 分子 %} 乘法
- {% load static %} 加载静态资源
- {% autoescape on %} {{ html }} {% endautoescape %} 显示源内容
- {% autoescape off %} {{ html }} {% endautoescape %} 显示转义之后内容
3.3 标签过滤器
3.3.1 内置过滤器
3.3.2 自定义过滤器
在主项目包下的__init__.py 中定义:
from django.template.defaultfilters import register
@register.filter(is_safe=True)
def short(value, arg=2):
return value[:arg]
@register.filter(is_safe=True)
def input(value, type='text'):
html = '<input value="{}" type="{}" size=2>'
return format_html(html, value, type
四、CBV视图设计
4.1 View类
View类,是一个视图父类, 主要实现了as_view()和dispatch()两个函数。
4.2 TemplateView类
4.3 自定义View类
五、Django中间件
六、Django的缓存配置
七、Django的Session配置
八、Django的信号
九、Logging日志
十、DjangoRESTFramework
参考文档: https://www.django-rest-framework.org/
django-rest-framework是基于Django实现RESTful接口规则, 依据restful四个设计原则,实现请求与响应数据的序列化与处理。
10.1 安装环境
python3.7
django 2.1
参考安装 说明: https://pypi.org/project/djangorestframework/3.11.0/
pip install djangorestframework==3.11.0
10.2 入门示例
10.2.1 配置settings
在settings.py的INSTALLED_APPS列表中添加rest_framework
INSTALLED_APPS = [
'...'
'rest_framework',
]
10.2.2 配置主路由
在主路由中引入api-auth/
urlpatterns = [
path('', index),
path('admin/', admin.site.urls),
...
re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
10.2.3 创建api包
在api包下创建views.py脚本,内容如下:
from goods.models import Goods
from rest_framework import serializers, viewsets, routers
class GoodsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Goods
fields = '__all__'
class GoodsViewset(viewsets.ModelViewSet):
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
router = routers.DefaultRouter()
router.register('goods', GoodsViewset)
10.2.4 添加api路由
将api包下的router路由配置到主路由中
urlpatterns = [
path('', index),
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
10.3 perssions权限验证
可以在settings中配置全局API接口的权限验证,也可以在单个viewset或APIView中配置局部的。
10.3.1 全局验证
在settings.py可以如下配置DEFAULT_PERMISSION_CLASSES :
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
DjangoModelPermissionsOrAnonReadOnly 验证模型类操作的权限,所有的模型对应的viewset接口必须具有它的相应操作权限,否则只能查看。
除此之外,rest_framework自带了很多其它权限:
- AllowAny
- IsAuthenticated
- IsAdminUser
- IsAuthenticatedOrReadOnly
- DjangoModelPermissions
- DjangoModelPermissionsOrAnonReadOnly
- DjangoObjectPermissions
10.3.2 局部验证
可以在viewset类中指定验证类,如下:
class GoodsViewset(viewsets.ModelViewSet):
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
permission_classes = [
permissions.DjangoModelPermissionsOrAnonReadOnly
]
也可以自定义一个验证类,如VipUserHasMoney类, 只需要重写has_permssion方法即可:
class VipUserHasMoney(BasePermission):
def has_permission(self, request, view):
if 'login_user' in request.session:
print('---->session--user')
from login.models import LoginUser
request.user = LoginUser.object_set.get(pk=request.session['login_user']['id'])
return request.user.ac.money > 0
return False
10.4 APIView类
10.5 authentication授权
十一、项目周
11.1 项目管理
11.1.1 项目进度规则
- 开发人员 2人
- 开发时间(7天)
- 项目需求文档(原型截图,每一个截图下提供功能描述(多个), 功能描述时,最好设计一下数据关系-数据以json格式进行描述。)
- 项目开发进度表(合理化、理性化)
11.1.2 日报
每一组组长发邮件,邮件的规范如下:
标题: 组名+日期+项目名+日报
收件人: 610039018@qq.com, 两位就业老师QQ邮箱, 张思聪副总监
内容:
两位总监,
? 您好!
? 预计完成任务:
? 实际完成任务(可以是百分比):总体完成 90%, 其中最慢的是xxx模块,原因是个人电脑 或 沟通中产生分歧或技术点不明确。
? 问题的:截图
附件: 任务分配详情表 (明日的计划任务+今日任务的完成实际情况)
上报时间:晚上12点之前。
11.1.3 gitee仓库
所有的项目代码(正常运行)托管到gitee平台上。
11.1.3.1 创建组织
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7f0jbIJ-1638535206529)(D:\docs\XA2101\week10\images\image-20210531094704193.png)]
11.1.3.2 添加开发者成员
进入到组织的设置 页面,左侧点击或选择成员管理 ,以下是成员管理的页面,点击【添加成员】可以添加成员。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUNRj49Y-1638535206532)(D:\docs\XA2101\week10\images\image-20210531095143237.png)]
添加成员的页面,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oNbr1hrt-1638535206532)(D:\docs\XA2101\week10\images\image-20210531095622813.png)]
添加完用户之后,再点击【添加】按钮,在弹出的确认页面中,点击【提交】,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfKq4YHi-1638535206533)(D:\docs\XA2101\week10\images\image-20210531095725153.png)]
11.1.3.3 创建组织的仓库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EevlBzcW-1638535206534)(D:\docs\XA2101\week10\images\image-20210531095820355.png)]
点击【新建仓库】,在打开的页面中,根据实际情况填写信息,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RRNOhOQV-1638535206535)(D:\docs\XA2101\week10\images\image-20210531100034455.png)]
仓库创建成功之后,跳转到以下页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o18OBGyS-1638535206535)(D:\docs\XA2101\week10\images\image-20210531100256385.png)]
提供了本地创建git仓库的相关命令,以及上传本地仓库的方式。
11.1.3.4 配置仓库的开发人员
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xbgdp16b-1638535206536)(D:\docs\XA2101\week10\images\image-20210531100433542.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhYahXU1-1638535206537)(D:\docs\XA2101\week10\images\image-20210531100501010.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1GbkeIHH-1638535206538)(D:\docs\XA2101\week10\images\image-20210531100531877.png)]
11.1.3.5 配置gitee用户的公钥
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dRc2bHgY-1638535206539)(D:\docs\XA2101\week10\images\image-20210531100746883.png)]
11.1.3.6 综合练习
组长:
gitee:
- 创建组织
- 组织中添加开发人员
- 组织中新建仓库
- 配置仓库的开发人员(组织中的开发成员)
本地(git-bash)
- 选择一个项目
- 进入项目目录
- git init 初始化项目, 在当前目录下创建一个.git目录(本地仓库)
- 【可选】如果本地仓库出现错误时,可以尝试将.git删除,重新再来
- vi .gitignore 文件
.idea
__pycache__
- git add .
- git status 当前的git cache中是否包含 .gitignore中的文件,如果不包含则正常,如果包含了,则重新再来。[rm -rf .git] [git init]
- git commit -m "初始提交"
- git config -l 查看是否包含user.name和user.email,对比gitee的config配置
- gic config --global user.name "用户名"
- git config --global user.email "邮箱"
- git remote add origin git@gitee.com:组织名/仓库名.git
- 【第一次上传】git push -u origin master
组员: git-bash
- 确认在gitee平台是否可见组织及仓库
- git config --global user.name ""
- git config --global user.email ""
- 找到一个存放项目代码的目录(仓库的名称不存在于当前目录)
- git clone git@gitee.com:组织名/仓库名.git
- cd 仓库目录
- git config -l
- 修改或新增一个文本文件,写入测试内容
- git add .
- git commit -m "修改或新增xxx文件"
- git push
- 查看gitee平台,仓库的内容是否发生了变化 ,如果内容上传成功,则OK。
【注意】除了第一次上传(first commit)之外,每一次上传(push)之前必须先更新(pull)。如果本地仓库的代码强制替换远程仓库(gitee),则执行git push --force -u origin master .
11.2 改造后台User
参考:https://docs.djangoproject.com/zh-hans/2.1/topics/auth/customizing/
11.2.1 创建项目和应用
django-admin startproject meituanms
cd meituanms
django-admin startapp login
11.2.2 创建User模型
在login应用模块的models.py中,添加如下代码:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
"""
AbstractUser 本身包含的属性:
- username, password, first_name,last_name,email
- is_superuser 是否为超级用户
- groups 用户所在的组
- user_permissions 用户的权限
- is_active 是否激活
- is_staff 是否为职员,默认是,用户需要登录
- date_joined 加入时间
"""
phone = models.CharField(max_length=11, verbose_name='手机号', blank=True, null=True, unique=True)
head = models.CharField(max_length=50, verbose_name='用户头像', blank=True, null=True)
sex = models.CharField(max_length=2, default='男')
city = models.CharField(max_length=10, blank=True, null=True)
11.2.3 配置admin
在login应用的admin.py脚本中,添加如下代码:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext, gettext_lazy as _
from .models import User
class MyUserAdmin(UserAdmin):
list_display = ('username', 'is_active', 'is_staff', 'is_superuser', 'phone', 'sex', 'city')
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
('VIP会员', {'fields': ('phone', 'sex', 'city', 'head')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
admin.site.register(User, MyUserAdmin)
11.2.4 配置settings.py
声明AUTH_USER_MODEL 变量,如下所示:
AUTH_USER_MODEL = 'login.User'
11.2.5 迁移与创建超级用户
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser <回车>
User: admin
Email: 610039018@qq.com
Password: admin123
Password(Again): admin123
11.3 Django权限
参考:https://docs.djangoproject.com/zh-hans/2.1/topics/auth/default/
11.3.1 权限描述
django的权限涉及的表:
-
django_content_type表,保存app应用模块的信息(id, app_label, model) 每一条记录,表示一个实际的models.Model子类名称(小写) 如果通过sql语句创建的表,则手动向django_content_type添加记录,以便添加权限和验证权限。 -
auth_permission表,用于声明每一个models模型类的四个权限,通过content_id来关联哪一个模型。 字段: id, name(文本描述), content_id(模型类的id), codename(实际上使用的) codename 格式: add|change|delete|view_model名称(同django_content_type表的model字段相同的) -
auth_user_user_permissions(默认的)或login_user_user_permissions(重写了AUTH_USER_MODEL), 授权用户的权限。 字段: id, user_id (用户ID), permission_id (权限ID)
查看用户的相关权限的sql语句:
select u.username, p.codename
from auth_user u
join auth_user_user_permissions auup
on u.id = auup.user_id
join auth_permission p
on auup.permission_id = p.id;
通过用户模型查看权限(非超级管理员 is_superuser=False)
from django.contrib.auth.models import *
disen = User.objects.get(username='disen')
disen.user_permissions.all()
如, 查询当前用户是否具有add_goods 权限
disen.user_permissions.filter(codename='add_goods')
11.3.2 添加与删除权限
参考django.contrib.auth.models.User类的相关方法。
添加权限之前,先查询权限(根据codename):
p1=Permission.objects.get(codename='change_goods')
重新设置权限:
disen.user_permissions.set([p1])
将goods和order订单的所有权限,添加给disen用户:
from django.db.models import Q
ps = Permission.objects.filter(Q(codename__endswith='goods') | Q(codename__endswith='order'))
disen.user_permissions.set(ps)
清除disen用户的所有权限
disen.user_permissions.clear()
删除单个权限:
p1 = Permission.objects.get(codename='add_order')
disen.user_permissions.remove(p1)
11.3.3 验证权限
除了user_permissions.filter(codename=)查询之外,还可以通过has_perm(‘app_label.codename’)方式验证.
验证单个权限:
disen.has_perm('goods.add_goods') # 返回 True或False
验证多个权限:
disen.has_perms(['goods.add_goods', 'order.add_order'])
在view视图函数中验证是否具有权限:
- 在view处理函数内部进行验证 [正常]
- 使用django.contrib.auth.decorates.permission_required(‘order.add_order’)
11.4 富文本的集成
11.4.1 tinymce
参考:https://pypi.org/project/django-tinymce/2.8.0/
pip安装
pip install django-tinymce==2.8.0
11.4.2 mdeditor
参考: https://pypi.org/project/django-mdeditor
pip安装
pip install django-mdeditor==0.1.18
11.5 Form表单类
ettings.py
声明AUTH_USER_MODEL 变量,如下所示:
AUTH_USER_MODEL = 'login.User'
11.2.5 迁移与创建超级用户
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser <回车>
User: admin
Email: 610039018@qq.com
Password: admin123
Password(Again): admin123
11.3 Django权限
参考:https://docs.djangoproject.com/zh-hans/2.1/topics/auth/default/
11.3.1 权限描述
django的权限涉及的表:
-
django_content_type表,保存app应用模块的信息(id, app_label, model) 每一条记录,表示一个实际的models.Model子类名称(小写) 如果通过sql语句创建的表,则手动向django_content_type添加记录,以便添加权限和验证权限。 -
auth_permission表,用于声明每一个models模型类的四个权限,通过content_id来关联哪一个模型。 字段: id, name(文本描述), content_id(模型类的id), codename(实际上使用的) codename 格式: add|change|delete|view_model名称(同django_content_type表的model字段相同的) -
auth_user_user_permissions(默认的)或login_user_user_permissions(重写了AUTH_USER_MODEL), 授权用户的权限。 字段: id, user_id (用户ID), permission_id (权限ID)
查看用户的相关权限的sql语句:
select u.username, p.codename
from auth_user u
join auth_user_user_permissions auup
on u.id = auup.user_id
join auth_permission p
on auup.permission_id = p.id;
通过用户模型查看权限(非超级管理员 is_superuser=False)
from django.contrib.auth.models import *
disen = User.objects.get(username='disen')
disen.user_permissions.all()
如, 查询当前用户是否具有add_goods 权限
disen.user_permissions.filter(codename='add_goods')
11.3.2 添加与删除权限
参考django.contrib.auth.models.User类的相关方法。
添加权限之前,先查询权限(根据codename):
p1=Permission.objects.get(codename='change_goods')
重新设置权限:
disen.user_permissions.set([p1])
将goods和order订单的所有权限,添加给disen用户:
from django.db.models import Q
ps = Permission.objects.filter(Q(codename__endswith='goods') | Q(codename__endswith='order'))
disen.user_permissions.set(ps)
清除disen用户的所有权限
disen.user_permissions.clear()
删除单个权限:
p1 = Permission.objects.get(codename='add_order')
disen.user_permissions.remove(p1)
11.3.3 验证权限
除了user_permissions.filter(codename=)查询之外,还可以通过has_perm(‘app_label.codename’)方式验证.
验证单个权限:
disen.has_perm('goods.add_goods') # 返回 True或False
验证多个权限:
disen.has_perms(['goods.add_goods', 'order.add_order'])
在view视图函数中验证是否具有权限:
- 在view处理函数内部进行验证 [正常]
- 使用django.contrib.auth.decorates.permission_required(‘order.add_order’)
11.4 富文本的集成
11.4.1 tinymce
参考:https://pypi.org/project/django-tinymce/2.8.0/
pip安装
pip install django-tinymce==2.8.0
11.4.2 mdeditor
参考: https://pypi.org/project/django-mdeditor
pip安装
pip install django-mdeditor==0.1.18
11.5 Form表单类
|