MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。 通俗的来讲就是,强制性的使应用程序的输入,处理和输出分开。
核心思想:解耦
优点:减低各个模块之间的耦合性,方便变更,更容易重构代码,最大程度的实现了代码的重用
Model: 即数据存取层。用于封装于应用程序的业务逻辑相关的数据,以及对数据的处理。说白了就是模型对象负责在数据库中存取数据
View: 即表现层。负责数据的显示和呈现。渲染的html页面给用户,或者返回数据给用户。
Controller: 即业务逻辑层。负责从用户端收集用户的输入,进行业务逻辑处理,包括向模型中发送数据,进行CRUD操作。
严格来说,Django的模式应该是MVT模式,本质上和MVC没什么区别,也是各组件之间为了保持松耦合关系,只是定义上有些许不同。
Model: 负责业务与数据库(ORM)的对象
View: 负责业务逻辑并适当调用Model和Template
Template: 负责把页面渲染展示给用户
注意: Django中还有一个url分发器,也叫作路由。主要用于将url请求发送给不同的View处理,View在进行相关的业务逻辑处理。
B/S—–browser—-server – 表示浏览器/服务器模式
C/S—-client—-server – 表示客户机/服务器结构
安装—————-pip install virtualenv
创建并进入d:\env
创建虚拟环境—-virtualenv –no-site-packages -p(xxx) + 环境名(testenv)
-p————–指定python版本
testenv——创建一个文件夹testenv来装虚拟环境
启动虚拟环境—-d:\env\testenv\Scripts>activate
退出虚拟环境—deactivate
启动虚拟环境并进入项目文件夹:
安装django—————–pip install django==1.11 (虚拟环境里面安装)
创建一个django项目—–django-admin startproject + 项目名
启动项目———————python manage.py runserver + ip:端口
将英文改成中文———–setting.py里面改成LANGUAGE_CODE = ‘zh-hans’
获取时间———————TIME_ZONE = ‘UTC’(比北京时间少8个小时)
TIME_ZONE = ‘Asia/Shanghai’(正常的北京时间)
创建app———————-python manage.py startapp + app名字
__init__.py——————-初始化配置pymysql连接的地方
setting.py——————–配置信息位置,database等
urls.py————————-url路由
wsgi—————————–网关
__init__.py——— —初始化
admin.py————-管理后台注册模型
apps.py—————setting.py里面注册app时需要用到,导入apps.py文件中的APPCONFIG下的name属性,一般不用这种方法,而是在项目的setting.py中直接加上app名字。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app',
'stu'
]
models.py————定义class模型的地方(继承models.Model)
tests.py—————-写测试脚本
views.py—————写处理业务逻辑的地方
import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'localhost',
'USER': 'root',
'PASSWORD': 'xxxxxx',
'PORT': 3306,
'NAME': 'hr' # 数据库名称
}
}
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=20) # 数据表中name的数据类型
sex = models.BooleanField() # 数据表中sex的数据类型
class Meta: # 定义元
db_table = 'stu' # 数据库中表名称
def add(request): # 保存数据
stu = Student()
stu.name = '李白'
stu.sex = 1
stu.save()
return HttpResponse('完成')
python manage.py makemigrations
python manage.py migrate
在浏览器中输入相应的url运行已经定义好的add函数,并观察数据库中相应表格数据的变化
理解ORM 对象关系映射–翻译机
from stu.models import Student
# 第二种注册方式 利用装饰器
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
def set_sex(self):
if self.sex:
return '男'
else:
return '女'
# 修改性别字段的描述
set_sex.short_description = '性别'
# 展示字段
list_display = ['id', 'name', set_sex]
# 过滤
list_filter = ['name']
# 搜索
search_fields = ['name']
# 分页
list_per_page = 2
# 1.注册的第一种方式
# admin.site.register(Student, StudentAdmin)
CharField:字符串
BooleanField:布尔类型
DateField:年月日,日期
auto_now:每次修改的时候赋值
AutoField:自动增长
DecimalField:
models.DecialField(max_digits=3, decimal_place=1) 最大99.9
max_digits:总位数
decimal_places:小数后多少位
TextField:存文本信息
IntegerField:整数
FloatField:浮点
FileField:文件上传字段
URLField:
EmailField:
ImageField:上传图片
python
i_image = models.ImageField(upload_to='upload', null=True)
# upload_to表示上传文件存入的位置:'/media/upload/'
# 需要自己创建media文件夹,并在setting.py中配置路径。在下文‘配置上传文件夹并设置为静态可访问文件夹’中有相应介绍
# 注意:ImageField需要依赖一个包:
# Pillow:pip install Pillow
# 注意:在使用此字段时。
# 如果相应html模板中有标签,其相应的form标签必须加入enctype="multipart/form-data"
通过 {模型.objects} 来实现数据的CRUD操作
获取所有
create():创建
def addStu(request):
if request.method =='GET':
return render(request,'addstu.html')
if request.method == 'POST':
Student.objects.create(
stu_birth=request.POST.get('birth'),
stu_sex=request.POST.get('sex'),
stu_name=request.POST.get('name'),
stu_yuwen=request.POST.get('yuwen'),
stu_shuxue=request.POST.get('shuxe'),
)
return render(request, 'addstu.html')
delete():删除
Student.objects.filter(id=nn).delete()
update():更新
Student.objects.filter(id=nn).update(stu_name='xx')
过滤条件
区别:
get:只返回一个满足条件的对象,没有满足条件的则直接报DoesNotExit的异常,查询到多个数据时报MultiObjectReturned异常
order_by(‘xxx’):按照xxx从小到大排序
last():返回最后一条数据
first():返回第一条数据
count():求和(返回个数)
exists():验证是否存在(返回布尔值)
__gt/__gte:大于/大于等于
__lt/__lte:小于/小于等于
__startswith:以xxx开始
__endswith:以xxx结束
__contains:包含xxx
__in:验证是否在xxx中
~:表示取反
F(): # 一般与__gt/__gte/__lt/__lte连用
Q(): # 一般与|和&连用
理解F(),Q()
from django.db.models import F,Q
# 传值:
"http://127.0.0.1:8000/user/refer/?name={{ user.u_name }}">查询相应权限
# 把user.u_name传递到http://127.0.0.1:8000/user/refer/
# 取值:
def refer(request):
name = request.GET.get('name')
url(r'^s/', include('stu.urls')) # one 项目下
url(r'^stu/(\d+)/',views.allstu) # two app下
"/s/stu/{{ g.id }}/"> # three 模板中
def stu(request,xxx): # four views中
xxx将获取g.id的值,后面直接调用xxx
url(r'^s/', include('stu.urls', namespace='s')) # one 项目下
url(r'^stu/(\d+)/',views.allstu,name='alls') # two app下
"{% url 's:alls' g.id %}"> # three 模板中
def ooo(request,xxx): # four views中
xxx将获取g.id的值,后面直接调用xxx
url(r'^s/', include('stu.urls', namespace='s')) # one 项目下
url(r'^stu/(\d+)/(\d+)/(\d+)/',views.allstu,name='alls') # two app下
"{% url 's:alls' 1 2 3 %}"> # three 模板中
def ooo(request,x,y,z):
x,y,z将分别或取1,2,3;x,y,z的位置很关键
优化
url(r'^s/', include('stu.urls', namespace='s')) # one 项目下
url(r'^stu/(?P\d+)/(?P\d+)/(?P\d+)/' ,views.allstu,name='alls') # two app下
"{% url 's:alls' 1 2 3 %}"> # three 模板中
def ooo(request,yy,xx,zz):
xx,yy,zz将分别取1,2,3;这里与xx,yy,zz的位置无关(正则表达式分组)
models.OneToOneField———-主键和外键是一对一的关系,关联表id与主表id一一对应
# 表 xxx
ooo = models.OneToOneField(yyy, on_delete=models.CASCADE, related_name='info')
主表找拓展表的信息: 主表对象.关联表的model_name——–object(xxx下的).ooo
拓展表找主表: 拓展信息对象.关联字段——————————object(yyy下的).info
models.ForeignKey————-主键和外键是一对多的关系,主表不同id可以对应关联表相同id,关联表相同的id不能对应主表同一个id
# 表 xxx
ooo = models.ForeignKey(yyy, null=True)
主表找拓展表的信息: 主表对象.关联表的model_name.all()——–object(xxx下的).ooo.all()
拓展表找主表: 拓展信息对象.关联字段————————————object(yyy下的).xxx_set.all()
models.ManyToManyField————主键和外键是多对多的关系,主表id、关联表id都可以相互任意搭配
# 表xxx
ooo= models.ManyToManyField(yyy)
(查表方法同一对多)
on_delete方法
STATIC_URL = '/static/' #在这之后加上下面语句
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static')
]
应用静态目录——-在static文件下创建图片文件夹images,后面模板中直接调用
{#第一种方法#}
"/static/images/1-1.PNG" alt="图片">
{#第二种方法#}
{# {% load static from staticfiles%} #}
{% load static %}
"{% static 'images/1-1.png' %}" alt="图片">
在项目下创建templates文件夹,并且在setting.py中加上路径
TEMPLATES = [
{
……
'DIRS': [os.path.join(BASE_DIR, 'templates')],
# 加上的语句
'APP_DIRS': True,
……
},
]
配置上传文件夹
在工程下创建上传文件夹media:
# sep----1
# 在工程setting.py中
# 配置上传文件路径
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
配置上传
# models.py中
class StudentInfo(models.Model):
i_addr = models.CharField(max_length=30)
s = models.OneToOneField(Student)
i_image = models.ImageField(upload_to='upload', null=True)
# upload_to='upload'表示在/media/upload/下存文件(upload提前创建好)
# 这样设置后,图片自动上传到/media/upload/下,数据库中保存图片地址
class Meta:
db_table = 'stuinfo'
# html的form表单中
头像:"file" name="img">
# views.py中
img = request.FILES.get('img')
StudentInfo.objects.create(i_addr=addr,s_id = stu_id,i_image=img)
结果展示
设置为静态url可访问文件夹
# sep----2
# 在工程urls.py中
from django.contrib.staticfiles.urls import static
from w9d5 import settings
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
应用
<img src="/media/{{ info.i_image }}" width="50" alt="头像">
{# #}
{% comment %}
...
{% endcomment %}
<body>
{% for user in users %}
商品:{{ user.u_name }}
<br>
{% empty %}
没有商品信息
{% endfor %}
body>
循环计数 forloop.counter
计数从0开始:{{ forloop.counter0 }}
计数从1开始:{{ forloop.counter }}
计数从最后开始,到1停:{{ forloop.revcounter }}
计数从最后开始,到0停:{{ forloop.revcounter0 }}
date:y-m-d h:m:s (年-月-日 时:分:秒)
年月日中:
y两位年,Y四位年
m数字月份,M中文或英文月份
d数字日期,D中文或英文星期几
时分秒中:
h十二时制,H二十四时制
m
s
lower/upper-------------大小写转换
add:xx------------------加上xx
{% widthratio 基数 分母 分子%}------给基数乘上 分子/分母 eg:{% widthratio stu.stu_yuwen 1 10 %}
divisibleby:xx----------------------判断能否被xx整除,返回布尔值
<body>
{% for stu in stus %}
# 第三个人的姓名变大
{% ifequal forloop.counter 3 %}
<h1>姓名:{{ stu.stu_name }}h1>
{% else %}
<h5>姓名:{{ stu.stu_name }}h5>
{% endifequal %}
性别:
{% if stu.stu_sex %}
男
{% else %}
女
{% endif %}
生日:{{ stu.stu_birth | date:'Y-m-d'}}
创建时间:{{ stu.stu_create_time }}
语文成绩:{{ stu.stu_yuwen | add:10 }} # 在原来的基础上加10
数学成绩:{{ stu.stu_shuxue | add:-10}} # 在原来的基础上减10
<br>
{% empty %}
没有学生信息
{% endfor %}
body>
{{block.super}}:
base.html(相同内容)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
{% block title %} {% endblock %}
title>
head>
<body>
{% block contain %} {% endblock %}
body>
html>
index.html(被调用的文件)
“`jinja2
{% extends ‘base.html’ %} {# 必须写到第一行 #}
{# extends可以修改原来内容,include不能修改已经定义的内容 #}
{% block title %}
我是标题
{% endblock %}
{% block contain %}
我是内容
{% for stu in stus %}
学生id:{{ stu.id }}
学生名称:{{ stu.stu_name }}
学生班级:{{ stu.g_id }}
{% endfor %}
{% endblock %}
“`
url(r'^s/', include('stu.urls', namespace='s')) # one 项目下
url(r'^redirect/(?P\d+)/' ,views.redirectStu, name='red'), # two app下
"{% url 's:red' g.id %}"> # three templates下
from django.core.urlresolvers import reverse
def redirectStu(request,g_id): # four views下
# 这里可以写业务处理
return HttpResponseRedirect(reverse('s:red', kwargs={'g_id':g_id}))
# 跳转页面的同时传递参数。注意:此处相应url中也必须有参数匹配的正则表达式
# (or)
# return HttpResponseRedirect('/g/grade/')
# 跳转页面,不传递参数
分别写出现404和500错误时显示的页面,名字分别为page_not_found,server_error,保存在templates文件夹下。然后在setting.py和项目urls.py文件中做如下修改
setting.py中修改
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True # 改为 DEBUG = False
ALLOWED_HOSTS = [] # 改为 ALLOWED_HOSTS = ['*']
项目urls.py中增加
from xxx.views import page_not_found, server_error
handler404 = page_not_found # django自动识别
handler500 = server_error
def regist(request):
if request.method == 'GET':
# 进入注册页面
return render(request, 'day6_regist.html')
if request.method == 'POST':
# 提交注册信息
name = request.POST.get('name')
password = request.POST.get('password')
# 对密码加密
password = make_password(password)
Users.objects.create(u_name=name,u_password=password)
# 跳转到登陆页面
return HttpResponseRedirect('/uauth/login/')
def login(request):
if request.method == 'GET':
# 进入登陆页面
return render(request, 'day6_login.html')
if request.method == 'POST':
# 提交登陆信息
name = request.POST.get('name')
password = request.POST.get('password')
# 验证用户是否存在
if Users.objects.filter(u_name=name).exists():
user = Users.objects.get(u_name=name)
# 验证密码是否正确
if check_password(password, user.u_password):
# 验证通过,为用户创建一个唯一的ticket
s = 'abcdefghijklmnopqrstuvwsyz1234567890'
ticket = ''
for i in range(15):
ticket += random.choice(s)
now_time = int(time.time())
ticket = 'TK' + ticket + str(now_time)
# response = HttpResponse('登陆成功,可以跳转到详情页面了"/stu/index"')
# 跳转到下一页面并绑定令牌到cookie中,max_age表示令牌存活时间
response = HttpResponseRedirect('/stu/index/')
response.set_cookie('ticket',ticket,max_age=30000)
# 同时把ticket存在服务端,为下次验证用户提供ticket
user.u_ticket = ticket
user.save()
return response
else:
# return HttpResponse('用户密码错误')
return render(request, 'day6_login.html', {'password':'用户密码错误'})
else:
# return HttpResponse('用户不存在')
return render(request, 'day6_login.html', {'name': '用户不存在'})
def logout(request):
# 注销登陆
if request.method == 'GET':
# 跳转到登陆页面
re= HttpResponseRedirect('/uauth/login/')
# 删除cookie中的ticket
re.delete_cookie('ticket')
# 只有response才有删除cookie功能
return re
在setting.py中‘MIDDLEWARE’下增加下面语句:
python
'utils.UserAuthMiddleware.AuthMiddleware',
UserAuthMiddleware.py文件类AuthMiddleware中:
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponseRedirect
from uauth.models import Users
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# 统一验证登陆
# return None or 不写return 表示通过
# 登陆和注册时不要验证ticket
if request.path == '/uauth/login/' or '/uauth/logout/':
return None
ticket = request.COOKIES.get('ticket')
if not ticket:
return HttpResponseRedirect('/uauth/login/')
users = Users.objects.filter(u_ticket=ticket)
if not users:
return HttpResponseRedirect('/uauth/login/')
request.user=users[0]
from django.contrib.auth.models import User
# django会把用户数据存到django已经创建好的auth_user表中
def djregist(request):
if request.method == 'GET':
return render(request, 'day6_regist.html')
if request.method == 'POST':
name = request.POST.get('name')
password = request.POST.get('password')
User.objects.create_user(username=name, password=password)
return HttpResponseRedirect('/uauth/dj_login/')
from django.contrib import auth
def djlogin(request):
if request.method == 'GET':
return render(request, 'day6_login.html')
if request.method == 'POST':
name = request.POST.get('name')
password = request.POST.get('password')
# 验证用户和密码,通过就返回user对象
user = auth.authenticate(username=name, password=password)
if user:
# 验证成功
auth.login(request, user)
return HttpResponseRedirect('/stu/index/')
pass
else:
return render(request, 'day6_login.html')
from django.contrib import auth
def djlogout(request):
if request.method == 'GET':
auth.logout(request)
return HttpResponseRedirect('/uauth/dj_login/')
from django.contrib.auth.decorators import login_required
urlpatterns = [
url(r'^index/', login_required(views.index)),
]
# setting.py中
# 没登陆的跳转地址
LOGIN_URL = '/uauth/dj_login/'
from django.core.paginator import Paginator
# 默认参数如下(查询相关手册)
class Paginator(object):
def __init__(self, object_list, per_page, orphans=0,allow_empty_first_page=True):
self.object_list = object_list
self._check_object_list_is_ordered()
self.per_page = int(per_page)
self.orphans = int(orphans)
self.allow_empty_first_page = allow_empty_first_page
在views.py中定义分页信息
def stuPage(request):
if request.method == 'GET':
page_id = int(request.GET.get('page_id', 1))
stus = Student.objects.all()
# 每页显示3个stus成员,不建议把数量写死,而是在setting.py中定义
pagina = Paginator(stus,3)
# 返回第几页的信息
page = pagina.page(page_id)
return render(request, 'index_page.html', {'page':page})
在html模板中提取页面信息
<body>
{% for stu in stus %}
姓名:{{ stu.s_name }}
电话:{{ stu.s_tel }}
地址:{{ stu.studentinfo.i_addr }}
头像:
<br>
{% endfor %}
<h4>一共{{ stus.paginator.num_pages }}页/共{{ stus.paginator.count }}条数据h4>
<h5>
{% for i in stus.paginator.page_range %}
<a href="/stu/stupage/?page_id={{ i }}">{{ i }}a>
{% endfor %}
h5>
{% if stus.has_previous %}
<a href="/stu/stupage/?page_id={{ stus.previous_page_number }}">上一页a>
{% endif %}
当前第{{ stus.number }}页
{% if stus.has_next %}
<a href="/stu/stupage/?page_id={{ stus.next_page_number }}">下一页a>
{% endif %}
body>
打开setting.py中的:’django.middleware.csrf.CsrfViewMiddleware’,中间件
然后再每个post请求表单中加入:{% csrf_token %}后就可以不被禁止
# 在models.py中
class Visit(models.Model):
v_url = models.CharField(max_length=255)
v_time = models.IntegerField()
class Meta:
db_table = 'visit_time'
# 在utils中创建中间件VisitTimesMiddleware.py
from django.utils.deprecation import MiddlewareMixin
from uauth.models import Visit
class VisitTimes(MiddlewareMixin):
def process_request(self, request):
# 统计访问的url以及次数
path = request.path
try:
visit = Visit.objects.get(v_url=path)
if visit:
visit.v_time += 1
visit.save()
except Exception as e:
print(e)
Visit.objects.create(v_url=path, v_time=1)
# 在setting.py文件MIDDLEWARE中加入下面语句
'utils.VisitTimesMiddleware.VisitTimes',
前后分离:前端vue,后端restframeork
# setting.py 中
# 创建日志的路径
LOG_PATH = os.path.join(BASE_DIR, 'log')
# 不存在地址则创建地址
if not os.path.isdir(LOG_PATH):
os.mkdir(LOG_PATH)
# setting.py中
LOGGING = {
# version只能为1
'version': 1,
# Flase表示打开loggers
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '%(levelno)s %(pathname)s %(funcName)s %(module)s %(asctime)s %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(created)s %(message)s'
}
},
'handlers': {
'stu_handlers': {
'level': 'DEBUG',
# 日志文件指定为5M,超过则重新备份,然后写入新的日志信息
'class': 'logging.handlers.RotatingFileHandler',
'maxBytes': 5 * 1024 * 1024,
# 文件地址
'filename': '%s/log.txt' % LOG_PATH,
'formatter': 'default',
},
'uauth_handlers': {
'level': 'DEBUG',
# 日志文件指定为5M,超过则重新备份,然后写入新的日志信息
'class': 'logging.handlers.RotatingFileHandler',
'maxBytes': 5 * 1024 * 1024,
# 文件地址
'filename': '%s/uauth.txt' % LOG_PATH,
'formatter': 'simple',
},
},
'loggers': {
'stu': {
'handlers': ['stu_handlers'],
'level': 'INFO',
},
'uauth': {
'handlers': ['uauth_handlers'],
'level': 'INFO',
},
},
'filters': {
},
}
# 在views.py中
import logging
logger = logging.getLogger('stu')
# -----------------------
def index(request):
infos = StudentInfo.objects.all()
logger.info('url:%s method:%s 获取学生信息成功' % (request.path, request.method))
return render(request, 'index.html', {'infos': infos})
# 在中间键中:
import logging
logger = logging.getLogger('uauth')
# ----------------------------
except Exception as e:
print(e)
logger.error(e)
什么是rest风格:
REST是所有Web应用都应该遵守的架构设计指导原则。Representational State Transfer,翻译是”表现层状态转化”
面向资源是REST最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。
REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。
GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT(PATCH)用来更新资源,DELETE用来删除资源。
http://xxx.com/api/
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
GET(SELECT):从服务器取出资源(一项或多项)
POST(CREATE):在服务器新建一个资源
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
DELETE(DELETE):从服务器删除资源
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
服务端向用户返回请求api的结果,在结果中包含了status codes 状态码的,可以通过状态码去判断请求api的状态是成功还是失败
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{
error: ‘错误信息’
}
pip install djangorestframework
pip install django-filter # Filtering support
在django的app中创建一个serializers.py设置序列化的字段
# serializers.py中
from rest_framework import serializers
from stu.models import Student
class StudentSerializer(serializers.ModelSerializer):
class Meta:
# 关联数据表
model = Student
# 确定需要序列化的字段,也就是返回数据中有的字段
fields = ['id', 's_name', 's_tel']
def to_representation(self, instance):
data = super().to_representation(instance) # instance会依次返回每个Student对象
try:
# 得到关联表中的字段,必须用try,否则如果存在一个没有定义i_addr的学生时,系统将报错
data['s_addr'] = instance.studentinfo.i_addr
except Exception as e:
data['s_addr'] = ''
return data
在django的app下views.py中创建一个序列化的类StudentEdit(名字自取),定义api对数据可用的请求 和数据内容筛选
# views.py 中
from rest_framework import mixins,viewsets
from stu.serializers import StudentSerializer
from stu.models import Student
class StudentEdit(mixins.ListModelMixin, # 表示可以在Postman类似的软件中查找所有数据
viewsets.GenericViewSet,
mixins.RetrieveModelMixin, # 表示可以在Postman类似的软件中查找单一数据
mixins.UpdateModelMixin, # 表示可以在Postman类似的软件中更新单一数据
mixins.DestroyModelMixin, # 表示可以在Postman类似的软件中删除单一数据
mixins.CreateModelMixin): # 表示可以在Postman类似的软件中创建单一数据
# 查询所有信息,固定写法
queryset = Student.objects.all()
# 序列化,固定写法
serializer_class = StudentSerializer
在app的urls.py文件中创建路由
# 在urls.py中
from stu import views
from rest_framework.routers import SimpleRouter
router = SimpleRouter() # 定义路由
router.register(r'student', views.StudentEdit) # 注册路径,不要写‘/’
urlpatterns = [
# 其他url
]
urlpatterns += router.urls # 把路由url“r'student'”加入urlpatterns
在utils文件夹下创建RenderResponse.py文件,里面定义api数据结构
from rest_framework.renderers import JSONRenderer
class CustomJsonRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
结构
{
'code':xxx,
'msg':请求成功,
'data':{返回数据}
}
"""
if renderer_context:
if isinstance(data, dict):
msg = data.pop('msg', '请求成功')
code = data.pop('code', 0)
else:
msg = '请求成功'
code = 0
response = renderer_context['response']
response.status_code = 200
res = {
'code': code,
'msg': msg,
'data': data
}
return super().render(res, accepted_media_type, renderer_context)
else:
return super().render(data, accepted_media_type, renderer_context)
配置结构
# 配置restful api返回结果
REST_FRAMEWORK = {
# 返回自定义结构
'DEFAULT_RENDERER_CLASSES': (
'utils.RenderResponse.CustomJsonRenderer', # 自定义结构这个类的路径
)
}
# 在setting.py配置api返回结果时 加上分页 如下:
REST_FRAMEWORK = {
# 分页
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': '2', # 每页数据条数
# 返回自定义结构
'DEFAULT_RENDERER_CLASSES': (
'utils.RenderResponse.CustomJsonRenderer', # 自定义结构这个类的路径
)
}
配置筛选
# 配置restful api返回结果时加上 筛选条件 如下:
REST_FRAMEWORK = {
# 分页
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': '2',
# 配置筛选
# 这里djangorestframework-3.8.2会报错,建议用djangorestframework-3.4.6
# 卸载:pip uninstall djangorestframework==3.8.2
# 安装:pip install djangorestframework==3.4.6
'DEFAULT_FILTER_BACKENDS':('rest_framework.filters.DjangoFilterBackend',
'rest_framework.filters.SearchFilter'
),
# 返回自定义结构
'DEFAULT_RENDERER_CLASSES': (
'utils.RenderResponse.CustomJsonRenderer',
)
}
在app下创建filters.py文件,里面定义筛选字段及方法:
from rest_framework import filters
import django_filters
from stu.models import Student
class StuFilter(filters.FilterSet):
name = django_filters.CharFilter('s_name', lookup_expr='icontains')
tel = django_filters.CharFilter('s_tel')
status = django_filters.CharFilter('s_status')
operate_time_min = django_filters.DateTimeFilter('s_operate_time', lookup_expr='gte')
operate_time_max = django_filters.DateTimeFilter('s_operate_time', lookup_expr='lt')
yuwen_min = django_filters.NumberFilter('s_yuwem', lookup_expr='gte')
yuwen_max = django_filters.NumberFilter('s_yuwem', lookup_expr='lt')
class Meta:
model=Student
# 高版本django必须写fields = [],里面的参数最好写上,也可以不写
# fields = ['s_name','s_tel','s_status','s_operate_time','s_yuwem']
fields = []
# 筛选时直接在api的url上命name、tel、status、operate_time_min等变量值,比较方法lookup_expr已经定义
在views.py下序列化的类StudentEdit中加入过滤类StuFilter:
from rest_framework import mixins,viewsets
from stu.serializers import StudentSerializer
from stu.models import Student
from stu.filters import StuFilter
class StudentEdit(mixins.ListModelMixin, # 表示可以在Postman类似的软件中查找所有数据
viewsets.GenericViewSet,
mixins.RetrieveModelMixin, # 表示可以在Postman类似的软件中查找数据
mixins.UpdateModelMixin, # 表示可以在Postman类似的软件中更新数据
mixins.DestroyModelMixin, # 表示可以在Postman类似的软件中删除数据
mixins.CreateModelMixin): # 表示可以在Postman类似的软件中创建数据
# 查询所有信息
queryset = Student.objects.all()
# 序列化
serializer_class = StudentSerializer
# 过滤
filter_class = StuFilter
实际应用:
筛选时间在2018-03-01到2018-05-01且分数在80-90的学生
http://127.0.0.1:8000/stu/student/?operate_time_max=2018-05-01&operate_time_min=2018-03-01&yuwen_max=90&yuwen_min=80
筛选语文成绩不及格的学生
http://127.0.0.1:8000/stu/student/?yuwen_max=60
筛选状态是NEXT_SCH的学生
http://127.0.0.1:8000/stu/student/?status=NEXT_SCH
在views.py下序列化的类StudentEdit中加入排序函数:
from rest_framework import mixins,viewsets
from stu.serializers import StudentSerializer
from stu.models import Student
from stu.filters import StuFilter
class StudentEdit(mixins.ListModelMixin, # 表示可以在Postman类似的软件中查找所有数据
viewsets.GenericViewSet,
mixins.RetrieveModelMixin, # 表示可以在Postman类似的软件中查找数据
mixins.UpdateModelMixin, # 表示可以在Postman类似的软件中更新数据
mixins.DestroyModelMixin, # 表示可以在Postman类似的软件中删除数据
mixins.CreateModelMixin): # 表示可以在Postman类似的软件中创建数据
# 查询所有信息
queryset = Student.objects.all()
# 序列化
serializer_class = StudentSerializer
# 过滤
filter_class = StuFilter
# 对要显示的学生(前提是没被删除)进行排序
def get_queryset(self):
query = self.queryset
return query.filter(s_delete=0).order_by('-id')
# 按照id倒序排序,filter(s_delete=0)表示学生还没有被删除,下面讲软删除
在django默认delete请求数据时会直接把数据库中的数据删除(),这样删除数据是及其危险的操作,为了安全,我们把删除的数据的s_delete字段设置为1,没有删除的s_delete字段设置为0;这样我们就需要重写mixins.DestroyModelMixin下destroy方法,具体如下:
from rest_framework import mixins,viewsets
from stu.serializers import StudentSerializer
from stu.models import Student
from stu.filters import StuFilter
class StudentEdit(mixins.ListModelMixin, # 表示可以在Postman类似的软件中查找所有数据
viewsets.GenericViewSet,
mixins.RetrieveModelMixin, # 表示可以在Postman类似的软件中查找数据
mixins.UpdateModelMixin, # 表示可以在Postman类似的软件中更新数据
mixins.DestroyModelMixin, # 表示可以在Postman类似的软件中删除数据
mixins.CreateModelMixin): # 表示可以在Postman类似的软件中创建数据
# 查询所有信息
queryset = Student.objects.all()
# 序列化
serializer_class = StudentSerializer
# 过滤
filter_class = StuFilter
# 对要显示的学生(前提是没被删除)进行排序
def get_queryset(self):
query = self.queryset
return query.filter(s_delete=0).order_by('-id')
# 按照id倒序排序,filter(s_delete=0)表示学生还没有被删除,下面讲软删除
# 重构delete请求方法,达到软删除目的
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.s_delete = 1
instance.save()
return Response({'msg': '删除成功','code':200})
这里我们要将s_status字段中的信息返回给前端,但不是”NONE”,”DROP_SCK”……而是对应的”正常”,”退学”……
# 在选择序列化字段时更改返回数据
# stu/serializers.py中
from rest_framework import serializers
from stu.models import Student
class StudentSerializer(serializers.ModelSerializer):
class Meta:
# 关联数据表
model = Student
# 确定需要序列化的字段,也就是返回数据中有的字段
fields = ['id', 's_name', 's_tel', 's_status', 's_operate_time', 's_yuwem']
def to_representation(self, instance):
data = super().to_representation(instance) # instance会依次返回每个Student对象
try:
# 得到关联表中的字段,必须用try,否则如果存在一个没有定义i_addr的学生时,系统将报错
data['s_addr'] = instance.studentinfo.i_addr
except Exception as e:
data['s_addr'] = ''
# 把列表STATUS转换成字典后利用键值对取值
data['s_status'] = dict(Student.STATUS)[data['s_status']]
return data
当在更新/添加数据时,出现输入数据与定义数据类型、长度、是否可以为空等冲突时,django会报英文错误,此处我们可以把英文替换为中文提示,返回给前端
在定义序列化字段的时定义好相应字段错误信息(重写CharField里面的默认错误信息default_error_messages为error_messages后作为参数传入CharField)
# stu/serializers.py
# 说明:这里序列化的字段只有['id', 's_name', 's_tel', 's_addr']
from rest_framework import serializers
from stu.models import Student
class StudentSerializer(serializers.ModelSerializer):
# 定义s_name和s_tel错误返回信息
s_name = serializers.CharField(error_messages = {
'invalid': '数据类型错误',
'blank': '用户名不能为空',
'max_length': '用户名不能超过10个字符',
'min_length': '用户名太短'
}, max_length=10)
s_tel = serializers.CharField(error_messages={
'invalid': '数据类型错误',
'blank': '用户名不能为空',
'max_length': '用户名太长',
'min_length': '用户名太短'
})
class Meta:
# 关联数据表
model = Student
# 确定需要序列化的字段
fields = ['id', 's_name', 's_tel']
def to_representation(self, instance): # instance会依次返回每个Student对象
data = super().to_representation(instance)
try:
# 得到关联表中的字段,必须用try,否则如果存在一个没有定义i_addr的学生时,系统将报错
data['s_addr'] = instance.studentinfo.i_addr
except Exception as e:
data['s_addr'] = ''
return data
基础页面
<html lang="en">
<head>
<meta charset="UTF-8">
<title>展示所有学生信息title>
<script type="text/javascript" src="/static/js/jquery.min.js">script>
head>
<body>
{% csrf_token %}
{# {% csrf_token %}会自动创建下面input标签 #}
{# <input type="hidden" name="csrfmiddlewaretoken"\
value="dRopPSt0IKs5OkaMHn83H3R05dsx2wGHKypCFTnsdvdtIQI85SnUDHMCaweReACZ"> #}
{# 目的是为了得到 \
"dRopPSt0IKs5OkaMHn83H3R05dsx2wGHKypCFTnsdvdtIQI85SnUDHMCaweReACZ" 给后端进行提交验证,\
否则将禁止访问 #}
<div id="div_stu">div>
<div id="div_update_stu">div>
<input id="showstu" type="button" value="获取学生信息">
<input id="addstu" type="button" value="添加学生信息" onclick="add_stu()">
body>
html>
利用get方法获取数据:get
<script type="text/javascript" src="/static/js/jquery.min.js">script>
<script type='text/javascript'>
$(function () {
$('#showstu').click(function () {
$.get('/stu/student/', function (msg) {
datas = msg.data;
s = 'ID 姓名 地址 操作 ';
for(var i=0; i'' + datas[i].id + ' \
' + datas[i].s_name + ' \
' + datas[i].s_tel + ' \
')">\
编辑|\
')">删除\
'
}
s += '
';
$('#div_stu').html(s)
}, 'json')
})
});
script>
利用ajax删除数据:delete
<script type="text/javascript" src="/static/js/jquery.min.js">script>
<script type='text/javascript'>
function del_stu(i) {
csrf = $('input[name="csrfmiddlewaretoken"]').val();
// 取{% csrf_token %}的val()值,name为固定写法
$.ajax({
url:'/stu/student/' + i + '/',
type:'delete',
headers:{'X-CSRFToken':csrf},
//把上面val()放在头部传递给后端,否则会 被'django.middleware.csrf.CsrfViewMiddleware',禁止
dataType:'json',
success:function () {
alert('删除成功')
},
error:function () {
alert('删除失败')
}
})
}
script>
利用ajax更新数据:patch
<script type="text/javascript" src="/static/js/jquery.min.js">script>
<script type='text/javascript'>
function update_stu(i) {
s = '姓名\
电话:\
')">';
$('#div_update_stu').html(s);
}
function update(i) {
csrf = $('input[name="csrfmiddlewaretoken"]').val();
s_name = $('#s_name').val();
s_tel = $('#s_tel').val();
$.ajax({
url:'/stu/student/'+ i +'/',
type:'patch',
data:{'s_name':s_name, 's_tel':s_tel},
dataType:'json',
headers:{'X-CSRFToken':csrf},
success:function (msg) {
alert('更新成功')
},
error:function (msg) {
alert('修改失败')
}
})
}
script>
利用ajax添加数据:post
<script type="text/javascript" src="/static/js/jquery.min.js">script>
<script type='text/javascript'>
function add_stu(){
s = '姓名\
电话:\
';
$('#div_update_stu').html(s);
}
function add() {
s_name = $('#name').val();
s_tel = $('#tel').val();
csrf = $('input[name="csrfmiddlewaretoken"]').val();
$.ajax({
url:'/stu/student/',
type:'post',
data:{'s_name':s_name, 's_tel':s_tel},
dataType:'json',
headers:{'X-CSRFToken':csrf},
success:function (msg) {
alert('添加成功');
console.log(msg)
},
error:function (msg) {
alert('添加失败')
}
})
}
script>
数据显示时最好利用DOM操作创建标签,不用字符串形式,这样看起来比较清晰,比如下面方法
<script type="text/javascript" src="/static/js/jquery.min.js">script>
<script type='text/javascript'>
$(function () {
$('#getstu').on('click', function () {
$.ajax({
url: '/stu/student/',
type:'get',
dataType:'json',
success: function (obj) {
var myarray = obj.data;
var ones = $('').append($('').text('id')).
append($(' ').text('姓名')).
append($(' ').text('电话')).
append($(' ').text('地址'));
$('#stus').append(ones);
for (var i = 0; i < myarray.length; i += 1){
var stus = $('');
var one = $('').text(myarray[i].id);
var two = $(' ').text(myarray[i].s_name);
var three = $(' ').text(myarray[i].s_tel);
var four = $(' ').text(myarray[i].s_addr);
stus.append(one).append(two).append(three).append(four);
$('#stus').append(stus);
}
}
})
})
})
script>
项目部署(ubuntu为例)
项目文件配置
修改setting.py文件
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False # 关闭debug模式,否则会在网页上显示错误信息
ALLOWED_HOSTS = ['*'] # '*' 表示任何ip/域名都可以访问
# 在STATIC_UTL下一行或上一行加上下面语句,为浏览器获取静态文件时定位文件地址
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
修改项目urls.py文件
from axfweb import settings
from django.views.static import serve
from django.contrib.staticfiles.urls import static
# 在urlpatterns中加上下面三句话,分别定位静态文件/媒体文件/首页地址
urlpatterns = [
………
url(r'^static/(?P.*)$' , serve, {"document_root": settings.STATIC_ROOT}),
url(r'^media/(?P.*)$' , serve, {"document_root": settings.MEDIA_ROOT}),
url(r'^$', views.home)
]
服务器环境配置
更新安装源
# 进入ubuntu系统
sudo apt update
# 或者
sudo apt-get update
安装python及使用的库
apt intall python3
apt install python3-pip
pip3 install django==1.11
pip3 install pymysql
pip3 install Pillow
安装数据库及配置
# 安装数据库MySQL,mysql默认端口为3306,确保没被占用
apt install mysql-server mysql-client
# 配置MySQL
cd /etc/mysql/mysql.conf.d
vim mysqld.cnf
# 打开后注释掉bind_address ,为了数据库可以远程访问
# 重启数据库并进入数据库
use mysql
# 为root用户配置密码,此处我的密码为123456,根据自己需要设置
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
#
flush privileges;
## 补充
netstat -lntp 查看进程,可以看到端口号
## 注意
# 必须保证项目代码中连接的数据库信息与服务器mysql信息一致
安装nginx服务
# 安装nginx
sudo apt-get install nginx
# 查看nginx的状态
systemctl status nginx # 查vi看nginx的状态
systemctl start/stop/enable/disable nginx # 启动/关闭/设置开机启动/禁止开机启动
#
service nginx status/stop/restart/start
安装python的uwsgi库
pip install uwsgi
项目部署
准备数据,上传代码
利用navicat把本地数据传输到远程数据库
利用xshell的xftp把本地代码上传到服务器
此处我的代码上传到/home/cfx/app/下
方法1(不推荐)
cd /home/cfx/app/axfweb/axfweb # 此处我的项目文件夹为axfweb,工程目录也为axfweb
python3 manage.py runserver 0.0.0.0:8000 # 0.0.0.0表示任何ip都可以访问
# 项目启动后就可以用服务器域名或ip访问
方法2(推荐)
# 利用nginx+uwsgi部署
# 配置
# 在app目录下创建conf和log文件夹,分别用来存放配置文件和日志
# 在conf文件夹下创建nginx.conf来配置nginx,写入下面代码
server{
listen 80; # 监听80端口,外网80端口访问此服务
server_name 127.0.0.1 localhost; # 127.0.0.1可以更换成自己的域名
access_log /home/cfx/app/log/access.log;# 日志
error_log /home/cfx/app/log/error.log; # 日志(目录为自己设置的)
location / { # 所有地址访问地址
include uwsgi_params;
uwsgi_pass 127.0.0.1:8890; # 服务启动在本地8890端口,外网用80端口访问
}
location /static/ { # 静态文件访问地址
alias /home/cfx/app/axfweb/static/; # 静态文件地址
expires 30d;
}
location /favicon.ico { # 禁用商标加载失败时日志写入,因为本项目没有商标
log_not_found off;
access_log off;
}
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
# 在conf文件夹下创建uwsgi.ini来配置uwsgi,写入下面代码
[uwsgi] # 必须写
master = true
processes = 4 # 启动4个进程
pythonpath = /home/cfx/app/axfweb # 配置项目路径
module = axfweb.wsgi # 定义wsgi在项目下的位置
socket = 127.0.0.1:8890 # 服务启动在本地端口8890,必须和nginx中配置的一样
logto = /home/cfx/app/log/uwsgi.log # uwsgi日志存放路径
## 启动方法:
cd /home/cfx/app/conf
uwsgi --ini uwsgi.ini #执行后,服务器就启动起来了
## 注意:
# 部署方法2存在权限问题,如果项目文件为root权限才能操作,那么要求nignx要有root权限,否则在执行中nginx需要写文件,执行文件时将受到阻止:最好把项目文件归属于普通用户组