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项目,都有它自己的环境
基于pip安装
pip install django==2.1
在交互的环境中,验证的django
>>> import django
>>> django.__version__
django-admin startproject mysite
项目结构说明:
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 进行部署 了解更多细节。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操作。同时支持模型之间的外键关联操作。
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 = ['*'] # 白名单
第一步:创建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 # 自带的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),
]
第一步:配置数据库
采用默认的数据库 sqlite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
第二步: 创建模型类
from django.db import models
# Create your models here.
class User(models.Model):
# 默认情况下 存在 id主键
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
进入django shell中
python manage.py shell
>>> from login.models import User
>>> User.objects.all()
【注意】objects是什么?
- objects是Model的元类中创建Manage类实例, 也是QuerySet查询结果类实例
- objects提供了对模型的查询相关方法 【非常重要】
查询数据
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() 同步数据(从数据库的表中更新当前实例的属性)
参考文档: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: [email protected]
>> 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
# @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')
admin.site.register(User, UserAdmin)
修改模型类:
主要添加Field字段参数的blank和verbose_name
class User(models.Model):
# 默认情况下 存在 id主键
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
第一步:在xxx app的__init__.py
文件中,添加如下代码:
# 自模块下的AppConfig的子类
default_app_config = 'testingapp.apps.TestingappConfig'
第二步:在TestingappConfig
中添加verbose_name属性
class TestingappConfig(AppConfig):
name = 'testingapp'
verbose_name = '自动化测试'
一个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'
}
}
django-admin或manager.py中提供了 inspectdb命令,可以将已存在的表生成模型类,命令如下:
python manager.py inspectdb --database db1 > testingapp/models.py
连接db1数据库,将库中所有表全部生成模型类,并写入到testingapp应用下的models.py脚本中。
由于,模型类操作时,默认选择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() # 指定using为db1数据库的查询集实例
class Meta:
managed = False
db_table = 'tb_person'
verbose_name_plural = verbose_name = '人员信息'
对于模型类拥有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 = '人员信息'
from .views import index, update_user
urlpatterns = [
path('', index),
path('/' , update_user)
]
def update_user(request, user_id):
return HttpResponse('修改用户ID:%s' % user_id)
首先在项目的根目录下,创建一个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',
],
},
},
]
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新用户title>
head>
<body>
<h3> {{ user.name }} 用户信息修改h3>
body>
html>
在templates目录下创建
def update_user(request, user_id):
# 通过模型将user_id对应的数据表的数据加载到
user = User.objects.get(pk=user_id)
# 将数据渲染到模板中,并返回HttpResponse对象
# render(request, template_name, context= {})
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('/' , update_user),
path('list/', view_users )
]
【Http404】抛出404异常
def update_user(request, user_id):
# 通过模型将user_id对应的数据表的数据加载到
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
raise Http404('未找到 {}'.format(user_id))
# 将数据渲染到模板中,并返回HttpResponse对象
# render(request, template_name, context= {})
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
第一步: 在路由中声明url路径时,可以指定name属性(path())
path('/' , update_user, name='update'),
path('list/', view_users, name='ulist')
第二步:在template模板文件中
查看所有用户
【重要】{% url %} 指令是反向查找路径,好处,无论url路径如何修改,都可以反向获取完整的请求路径。另外格式可以带有namespace:
查看所有用户
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('/' , update_user, name='update'),
path('user_list/', view_users, name='ulist')
]
<!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
中间件。
def update_user(request: HttpRequest, user_id):
user = get_object_or_404(User, pk=user_id)
# 判断当前请求的方法
if request.method == 'GET':
# 通过模型将user_id对应的数据表的数据加载到
# 将数据渲染到模板中,并返回HttpResponse对象
# render(request, template_name, context= {})
resp = render(request, 'update.html', {'user': user})
return resp
# 处理POST请求
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
是请求体的原始数据。
如果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>[email protected]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()),
]
如果需要从模型加载数据并显示其列表,则可以使用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()),
]
View类是TemplateView、ListView、CreateView等类的父类,主要实现了针对不同请求方法的转发功能。在View类中,提供与请求方法对应的请求处理函数(同普通view视图函数一样)。
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)
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')
# pk非自增的情况下,获取最大的pk并加1
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')
# 如果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):
# 获取请求body中的数据
data = request.body.decode('utf-8')
print('--body--', data)
# 解析body的数据(application/x-www-form-urlencoded)
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': '成功'
})
from django.test import TestCase
from .models import User
# Create your tests here.
class TestUser(TestCase):
def test_save(self):
User.objects.create(name='disen', pwd='123')
users = User.objects.all()
self.assertGreater(users.count(), 0, '末查找数据')
单元测试类中提供了丰富的断言方法, 统一格式:
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
python manage.py test <应用名>
test_01_save()
,test_02_add()
等。test_
开头。【练习】
from django.test import TestCase
from .models import User
# Create your tests here.
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') # 模型类抛出 DoesNotExist
self.assertIsNotNone(user, 'disen用户不存在')
user.pwd = '456'
user.save()
user = User.objects.get(pk=user.pk)
self.assertEqual(user.pwd, '456', '更新失败')
通过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'])
# self.assertQuerysetEqual(resp.context['users'], [], msg='结果中包含数据')
def test_update(self):
u1 = User.objects.create(name='disen', pwd='123', phone='110') # pk=1
u2 = User.objects.create(name='jack', pwd='123', phone='120') # pk=2
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)
在 app应用模块目录下, 创建static目录,存放js,css和图片等相关的静态资料。
在项目的目录下创建static目录, 要求在settings.py文件配置:
STATIC_URL = '/static/'
STATICFIELS_DIRS = [
os.path.join(BASE_DIR, 'static')
]
在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>
...
@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')
# fields = ('name', 'phone', 'email', 'pwd')
fieldsets = [
('必填信息', {'fields': ('name', 'pwd')}),
('其它信息', {'fields': ('email', 'phone'),
'classes': ('collapse',)
})
]
list_per_page = 10 # 分页效果
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, # models.SET_NULL
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
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'price', 'pay_status', 'user')
ordering = ['price']
list_filter = ['user']
class OrderInline(admin.TabularInline):
model = Order
extra = 1
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
...
inlines = [OrderInline]
主要是一端模型类上,编辑时可以同时添加关联子类模型的数据。
@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属性。
如果一个实例支持搜索时,需要满足objects.filter()方法的规则,如:
@admin.register(TbPersonRole)
class PersonRoleAdmin(admin.ModelAdmin):
list_display = ['person', 'role']
fields = ['person', 'role']
# 可搜索的字段:person, person_id, pr_id, role, role_id
# ?支持外键关联关系的内部属性(字段)的搜索
# 搜索方法使用是 objects.filter()方法,条件的写法,应该满足filter过滤规则
search_fields = ['person__name', 'role__name']
参考一个电商类的app页面,分析页面的数据,设计表或模型类。
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 信息已添加')
我的订单
页面所有WSGI请求信息的封装类,包含请求报文的数据和客户端相关信息。
一个HTTP请求的数据包含
参数的类型可以不提供。urls.py
...
path('items///' , 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文件类实例对象。
request.session是客户端将数据存储在后端的一种方式,默认存储在数据库的django_session表中,客户端的Cookie中存在一个sessionid,存放的数据即为django_session表的session_key。请求处理时,根据cookie中的session_id,确认是哪一个客户端,从而获取客户端存储在后端的数据(session中)。
在view视图函数中,用法如下:
request.session['login_user'] = 'disen666'
通用属性: verbose_name, null, blank, primary_key, unique, default, db_column, name
共有三个相关的字段类:DateField/TimeField/DateTimeField
重要的属性:
auto_now 每次保存时,都会更新时间
auto_now_add 只有第一次保存时,保存时间
如果使用图片字段 ImageField,则需要安装pillow库
另外,可以指定upload_to,width_field, height_field三个字段
# Create your models here.
class LoginUser(models.Model):
name = models.CharField(max_length=20, unique=True)
pwd = models.CharField(max_length=100)
phone = models.CharField(max_length=11)
# upload_to 相对于MEDIA_ROOT配置的目录
# ImageField需要安装 pillow库
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 '
'.format(self.head.url)
head_img.short_description='用户头像'
# 声明Meta元信息
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
# 配置文件或图片的媒体存储路径和URL访问路径
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)
# Create your models here.
class LoginUser(models.Model):
name = models.CharField(max_length=20, unique=True)
# ...
def get_filename(self, filename):
# print(type(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
的目录名称,也可以自定义的函数.
from django.utils.html import format_html
class XXX(models.Model):
# ...
img = models.ImageField(...)
def img_html(self):
return format_html('
', 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', ...)
常见约束:
- default
- primary_key
- unique
- null
- blank
- max_length
- verbose_name
- db_column
通用属性: on_delete 级联操作
关系字段放在多端,
如果没有指定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()
关系字段可以放在任何的一端上, 如 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
# 在OneToOne()方法中指定了related_name为 ac
user = LoginUser.objects.get(pk=1)
user.ac.money
- 创建或插入新的数据: 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()
示例模型类:
from django.db import models
# Create your models here.
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=''
# 向Order对象中关联用户对象
o1.user = user # 对象关系
o1.user_id = user.id # 表的外键值关系
goods_list = Goods.objects.all()[:3] # 获取前三个商品
# 将商品添加到订单中 (借助订单详情表)
o1.save()
o1.price = 0
# 第一种方式: 创建OrderDetail实例
for g in goods_list:
OrderDetail.objects.create(order=o1, goods=g, num=1)
o1.price += g.price * 1
# 第二种方式: 通过Order反向引用属性方式
for g in goods_list:
od = OrderDetail(goods=g,num=1)
o1.orderdetail_set.add(od) # 关联集合方式
o1.price += g.price * 1
o1.save() # 警告od应该先保存
关系模型的更新操作
# 直接更新
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()
User.objects.filter(属性名=属性值)
User.objects.filter(属性名__方法名=条件值)
__方法名
常见的有:
contains 包含,类似于SQL的like
startswith 以xxx开头
endswith 以 xxx结尾的
isnull 数据表的数据是
isnotnull 数据表的数据是非
lt 小于
gt 大于
lte 小于等于
gte 大于等于
# 时间条件之后,还可以添加 lt,gt,lte, gte的条件
# Order.objects.filter(pay_time__day__gte=24)
year 年
month 月
day 日
hour 小时
minute 分钟
second 秒
在查询时,可以指定哪些数据不要的条件,即满足条件的数据除外。
# 查询 除24号之外所有订单
Order.objects.exclude(pay_time__day=24)
django.db.models.Q 提供多个条件的查询,支持
and
与、or
或、not
非逻辑关系的查询,但以&
,|
,~
等符号依次表示与、或、非等逻辑关系。
Q表达式同filter()或exclude()等方法一样,都是通过模型类的属性=值
方式。
# 查询 除24号之外所有订单
Order.objects.filter(Q(pay_time__day__lt=24) | Q(pay_time__day__gt=24))
Order.objects.filter(~Q(pay_time__day=24))
# 查询5月的24日之前的,或者西安市且金额大于1000所有订单
Order.objects.filter(
Q(Q(pay_time__day__lt=24) & Q(pay_time__month=5)) |
Q(Q(address__startswith='西安') & Q(price__gt=1000))
)
练习:
# 查询5月的24日之前的未支付的订单
# Order.objects.filter(pay_time__lt='2021-05-24', pay_state=0)
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))
# 查询用户名为disen的订单且订单金额不少于1000
# 可以使用关联的属性
Order.objects.filter(user__name='disen', price__gte=1000)
django.db.models.F 可以在更新时提取属性的值
# 所有disen购买的订单一律1折
Order.objects.filter(user__name='disen', pay_state=0).update(price=F('price')*0.1)
# 5月20日这一天下订单(未支付的)的价格 优惠 520
Order.objects.filter(create_time__month=5,create_time__day=20, pay_state=0, price__gte=520).update(price=F('price')-520)
# 将所有用户的手机号前面添加029-
# 手机号目前是的长度为11位
# [问题] F 提取的内容支持数值计算,针对字符串的不是特别突出。
LoginUser.objects.all().update(phone='029-'+F('phone'))
模型的save()用于数据的存储(永久性写入数据库中),在模型类中可以重写save()方法,将重要的字段信息预先处理,再进行永久化存储。
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):
# make_password() 生成密文,PCK
# check_password() 验证密文的
if len(self.pwd) < 30:
# 明文口令,加密
# hasher的参数值可以参考django.conf.global_settings的PASSWORD_HASHERS
# hasher默认为default, PBKDF2PasswordHasher
self.pwd = make_password(self.pwd, hasher=BCryptSHA256PasswordHasher())
print('self.pwd', self.pwd)
return super(LoginUser, self).save()
# Create your views here.
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() # QuerySet
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)
每一个模型类,都存在一个objects隐性对象, 它的类型是models.Manager
objects是模型类的元类(ModelBase)添加的.
objects是用于数据的查询及更新的, 默认查询的数据是全部,返回是QuerySet.
当默认查询结果集中不包含特定状态(条件)的数据时,需要重新定义,达到简化查询条件的目的。如,用户注销后(未物理删除,只是修改了状态),每次查询数据时,应该提供一个用户是否注销的条件。
# 声明models.Manager的子类
class LoginUserManager(models.Manager):
# 获取查询结果集,重写BaseManager类的方法
def get_queryset(self):
return super().get_queryset().filter(is_del=False)
在LoginUser模型类中显式声明objects
# 添加新的属性,表示是否注销
is_del = models.BooleanField(default=False)
# 声明objects
objects = LoginUserManager() # 自定义
object_set = models.Manager() # 默认的
注销disen123用户:
LoginUser.object_set.filter(name='disen123').update(is_del=True)
1.模型类的QuerySet.raw()
2.模型类的QuerySet.extra()
3.django.db.connection数据库连接对象
qs = LoginUser.object_set.raw('select * from tb_login where id>=5')
print(list(qs))
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%'])
djang.db提供了一个属性connection,表示当前项目与数据库的连接
可以通过connection获取连接的cursor游标对象,然后再通过cursor对象进行sql语句执行。同pymysql(mysqlclient)的cursor。
用法:
from djang.db import connection
with connection.cursor() as c:
# pymysql的cursor.execute(sql, args=None)
# 注意: django的connection执行的sql语句中只能包含%s, 不支持%(name)s
sql = 'select id,name,pwd,head from tb_login where name=%s'
c.execute(sql, params=['disen123'])
ret = c.fetchone() # fetchall() # 返回一个是tuple或list[tuple, ]
django与数据库交互时,每一个模型的执行都会提交一次事务。
django中提供了一个手动提交或开启事务的方法, 需要使用django.db.transaction对象。
另外,在settings.py文件的配置数据库的OPTIONS选项中,可以配置事务的隔离级别。
transaction提供了保存还原点(savepoint), 开启事务(begin), 提交事务(commit), 回滚事务(rollback), 回滚到指到的还原点(rollback to )。
用法1:
from djang.db import transaction, connection
with transaction.get_connection(using='db1').cursor() as c:
# sqlite3 integer类型的主键默认自增
c.execute('create table tb_login(id integer primary key,name,pwd)') # 执行DDL语句时,会自动提交事务
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: 保存还原点
#.. sql的cud操作
sid1 = transaction.savepoint(using='db1')
#... sql的cud操作
#...当出现异常时可以回滚到sid1现场
transaction.savepoint_commit(sid1, using='db1')
transaction.savepoint_rollback(sid1, using='db1')
transaction.commit(using='db1')
在settings.py的数据库配置中,使用OPTIONS
配置:
DATABASES = {
'default': {
'ENGINE': '',
'NAME': '',
'OPTIONS': {
'isolation_level': 'repeatable_read'
}
}
}
除了默认的repeatable_read
可重复读之外,还有
- read_uncommitted 读未提交
- read_committed 读已提交
- serializable 串行化
模型类操作时,可以通过objects的select_for_update()进行查询数据。
用法:
# 行级锁 数据库必须要支持才行
# 必须在事务中执行,可以借助transaction.atomic()
# 关闭事务的自动提交
transaction.set_autocommit(False, using=None)
with transaction.atomic():
u = Userinfo.objects.select_for_update().get(pk=1)
u.pwd='789'
u.save()
可以在view视图处理函数(web接口的处理函数)上使用transaction.atomic装饰器,使得view函数内部的模型操作处理事务内(开始事务),如果出现异常,则会自动回滚事务。虽然事务内操作数据比较安全,但也会影响Web接口响应的速度。
用法:
# main.views
from django.db import transaction
from .models import Userinfo
@transaction.atomic(savepoint=True)
def update_user(request, pk):
if request.method == 'POST':
return HttpResponse('不支持POST方法的请求
')
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('修改成功
')
return HttpResponse('修改失败, 用户名不能为空
')
测试接口: http://127.0.0.1:8000/user/2/?name=disen&pwd=123
可以在父模板中,声明结构的区域(块), 在子模板中可以重写内容.
用法:
{% block name %}
{% endblock %}
文档的整体结构从extends指定的父模板继承下来
用法:
{% extends "模板文件位置" %}
可以将其模板内容插入到当前的位置
用法:
{% include "模板文件位置" %}
用法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 %}
格式1:
{% if 属性 %}
{% endif %}
格式2:
{% if 属性 >|<|==|<= | >= 值 %}
{% endif %}
格式3:
{% if 属性|关系表达式 %}
内容1
{% else %}
内容2
{% endif %}
格式4:
{% if 属性|关系表达式 %}
内容1
{% elif 属性|关系表达式 %}
{% else %}
内容2
{% endif %}
格式:
{% url "namespace:name" 路径参数 %}
[说明]
namespace : 主路由包含子路由时指定的,
如 path('order/', include('order.urls', namespace="order"))
name: 一般在子路由的path()声明路径的名称,如
path('del_cart//', views.remove_cart, name="delcart")
有效的url指令:
{% url "order:delcart" 3 %}
生成的url路径: /order/del_cart/3/
使用{{ 变量名 }} 获取变量的值
变量标签中使用
.
语法, 包含:
- 对象的属性,如
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>
在主项目包下的__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 = ''
return format_html(html, value, type
View类,是一个视图父类, 主要实现了as_view()和dispatch()两个函数。
参考文档: https://www.django-rest-framework.org/
django-rest-framework是基于Django实现RESTful接口规则, 依据restful四个设计原则,实现请求与响应数据的序列化与处理。
python3.7
django 2.1
参考安装 说明: https://pypi.org/project/djangorestframework/3.11.0/
pip install djangorestframework==3.11.0
在settings.py的INSTALLED_APPS列表中添加rest_framework
INSTALLED_APPS = [
'...'
'rest_framework',
]
在主路由中引入api-auth/
urlpatterns = [
path('', index),
path('admin/', admin.site.urls),
...
re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
在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__'
# 模型类视图集(提供 get, post, put, delete等方法)
class GoodsViewset(viewsets.ModelViewSet):
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
# 创建默认的路由对象
router = routers.DefaultRouter()
router.register('goods', GoodsViewset)
将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')),
]
可以在settings中配置全局API接口的权限验证,也可以在单个viewset或APIView中配置局部的。
在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 # add, change, delete, view
- DjangoModelPermissionsOrAnonReadOnly
- DjangoObjectPermissions
可以在viewset类中指定验证类,如下:
class GoodsViewset(viewsets.ModelViewSet):
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
permission_classes = [
#permission.VipUserHasMoney,
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
- 开发人员 2人
- 开发时间(7天)
- 项目需求文档(原型截图,每一个截图下提供功能描述(多个), 功能描述时,最好设计一下数据关系-数据以json格式进行描述。)
- 项目开发进度表(合理化、理性化)
每一组组长发邮件,邮件的规范如下:
标题: 组名+日期+项目名+日报
收件人: [email protected], 两位就业老师QQ邮箱, 张思聪副总监
内容:
两位总监,
您好!
预计完成任务:
实际完成任务(可以是百分比):总体完成 90%, 其中最慢的是xxx模块,原因是个人电脑 或 沟通中产生分歧或技术点不明确。
问题的:截图
附件: 任务分配详情表 (明日的计划任务+今日任务的完成实际情况)
上报时间:晚上12点之前。
所有的项目代码(正常运行)托管到gitee平台上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7f0jbIJ-1638535206529)(D:\docs\XA2101\week10\images\image-20210531094704193.png)]
进入到组织的设置
页面,左侧点击或选择成员管理
,以下是成员管理的页面,点击【添加成员】可以添加成员。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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仓库的相关命令,以及上传本地仓库的方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dRc2bHgY-1638535206539)(D:\docs\XA2101\week10\images\image-20210531100746883.png)]
组长:
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 [email protected]:组织名/仓库名.git
- 【第一次上传】git push -u origin master
组员: git-bash
- 确认在gitee平台是否可见组织及仓库
- git config --global user.name ""
- git config --global user.email ""
- 找到一个存放项目代码的目录(仓库的名称不存在于当前目录)
- git clone [email protected]:组织名/仓库名.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
.
参考:https://docs.djangoproject.com/zh-hans/2.1/topics/auth/customizing/
django-admin startproject meituanms
cd meituanms
django-admin startapp login
在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)
在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
# Register your models here.
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)
声明AUTH_USER_MODEL
变量,如下所示:
# 配置授权用户
AUTH_USER_MODEL = 'login.User'
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser <回车>
User: admin
Email: [email protected]
Password: admin123
Password(Again): admin123
参考:https://docs.djangoproject.com/zh-hans/2.1/topics/auth/default/
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')
参考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) # .add(p1, p2, p3...)
清除disen用户的所有权限
disen.user_permissions.clear()
删除单个权限:
p1 = Permission.objects.get(codename='add_order')
disen.user_permissions.remove(p1)
除了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视图函数中验证是否具有权限:
参考:https://pypi.org/project/django-tinymce/2.8.0/
pip安装
pip install django-tinymce==2.8.0
参考: https://pypi.org/project/django-mdeditor
pip安装
pip install django-mdeditor==0.1.18
ettings.py
声明AUTH_USER_MODEL
变量,如下所示:
# 配置授权用户
AUTH_USER_MODEL = 'login.User'
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser <回车>
User: admin
Email: [email protected]
Password: admin123
Password(Again): admin123
参考:https://docs.djangoproject.com/zh-hans/2.1/topics/auth/default/
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')
参考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) # .add(p1, p2, p3...)
清除disen用户的所有权限
disen.user_permissions.clear()
删除单个权限:
p1 = Permission.objects.get(codename='add_order')
disen.user_permissions.remove(p1)
除了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视图函数中验证是否具有权限:
参考:https://pypi.org/project/django-tinymce/2.8.0/
pip安装
pip install django-tinymce==2.8.0
参考: https://pypi.org/project/django-mdeditor
pip安装
pip install django-mdeditor==0.1.18