django项目学习

socket编程

scocket 协议位于服务端和客户端之间,b/s架构之间的通信步骤如下图所示。我们web开发做的就是socket服务端的时,通过客户端发来的数据,返回给客户端想要的数据。
django项目学习_第1张图片

简单使用

import socket

sk = socket.socket()        #创建socket对象
sk.bind(('127.0.0.1', 8000))    #绑定ip端口

sk.listen()     #监听

while True:
    conn,addr = sk.accept()     # while true接受多个连接
    data = conn.recv(2048)      # 接收数据
    print(data)
    conn.send(b'ok')            # 返回数据
    # conn.send(b'HTTP/1.1 200 OK\r\n\r\n

ok

') #这样不管我们访问什么样的路径,都会返回一样的值,所以需要对不同的路径返回不同的值
conn.close() # 断开连接

返回Ok的时候我们在浏览器测试访问:
django项目学习_第2张图片
可以看见服务端返回的数据无效。是因为返回的数据没有遵守http协议,所以我们在返回数据的时候必须要遵守http规范。

http协议

http请求格式:
django项目学习_第3张图片
下面是我们浏览器访问127.0.0.1:8000时打印出来的data的请求数据:

GET / HTTP/1.1\r\n
Host: 127.0.0.1:8000\r\n
Connection: keep-alive
\r\nsec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"\r\n
sec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: "Windows"\r\n
Upgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nSec-Fetch-Site: none\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Sec-Fetch-Dest: document\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
\r\n
'	//这里应该是请求数据的地方,但是由于是get请求,没有数据,所以为空,post的庆祝这里就有

http响应格式:
django项目学习_第4张图片
所以我们前面至恢复一个ok是不行的,没有遵循响应的格式。
响应头部的内容可以不加。我们可以回复这样一个东西。
第一个\r\n是状态行的,第二个是响应正文前面的,我们的正文内容是ok

conn.send(b'HTTP/1.1 200 OK\r\n\r\n

ok

'
)

django项目学习_第5张图片

但是此时无论我们后面跟的路径是什么,返回的数据都是一样的,所以我们需要根据不同的路径返回不同的内容。
此时的服务端可以看见我们访问的路径:
在这里插入图片描述

那我们根据这个路径就可以返回不同的内容了:

根据不同的路径返回不同的内容

import  socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8000))

sk.listen()

/// 简单版本
while True:
    conn,addr = sk.accept()
    data = conn.recv(2048).decode('utf-8') #将byte类型转化为utf-8
    # print(data.split()[1])
    url = data.split()[1]		#这样可以打印出访问的路径,没有根路径则显示 / 
    print(url)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')    #这样不管我们访问什么样的路径,都会返回一样的值,所以需要对不同的路径返回不同的值

    if url == '/rihan':
        conn.send(b'/rihan/')
    elif url == '/oumei':
        conn.send(b'/oumei/')
    else:
        conn.send(b'404 not found')
    conn.close()

django项目学习_第6张图片

django项目学习_第7张图片

//升级函数版
def rihan(url):
    return '欢迎访问日韩板块 {}'.format(url)

def oumei(url):
    return '欢迎访问欧美模块 {}'.format(url)

while True:
    conn,addr = sk.accept()
    data = conn.recv(2048).decode('utf-8') #将byte类型转化为utf-8
    # print(data.split()[1])
    url = data.split()[1]
    print(url)
    conn.send(b'HTTP/1.1 200 OK\r\ncontent-type: text/html; charset=utf-8\r\n\r\n')    
		# 注意这里加上了content-type: text/html; charset=utf-8\r\n,不然会乱码
    if url == '/rihan':
        ret = rihan(url)
    elif url == '/oumei':
        ret = oumei(url)
    else:
        ret = '404 not found'

    conn.send(ret.encode('utf-8'))
    conn.close()

django项目学习_第8张图片
当要添加的路径过多时,一直去加函数和elif是不合适的,所以多返回数据这一块还得优化:

def rihan(url):
    return '欢迎访问日韩板块 {}'.format(url)

def oumei(url):
    return '欢迎访问欧美模块 {}'.format(url)

def guochan(url):
    return '欢迎访问国产模块 {}'.format(url)

list1 = [
    ('/rihan',rihan),
    ('/oumei',oumei),
    ('/guochan',guochan)
]
while True:
    conn,addr = sk.accept()
    data = conn.recv(2048).decode('utf-8') #将byte类型转化为utf-8
    # print(data.split()[1])
    url = data.split()[1]
    print(url)
    conn.send(b'HTTP/1.1 200 OK\r\ncontent-type: text/html; charset=utf-8\r\n\r\n')    #这样不管我们访问什么样的路径,都会返回一样的值,所以需要对不同的路径返回不同的值

    func = None
    for i in list1:
        if url == i[0]:
            func = i[1]
            break
    if func:
        ret = func(url)		/用这种方式进行自定匹配路径
    else:
        ret = '404 not found'

    conn.send(ret.encode('utf-8'))
    conn.close()

我们就只需要修改函数和list就行了,while true 里面的内容不用动
返回html页面

def home(url):
    with open('home.html','r',encoding='utf-8') as f:
        ret = f.read()
        return ret

home.html内容:<h1>欢迎回家!!!</h1>

django项目学习_第9张图片
返回动态页面

import time
### 返回动态页面  从数据库中拿出需要的数据,不是写死的,可以更换数据内容
def timer(url):
    now = time.time()
    with open('timer.html','r',encoding='utf-8') as f:
        ret = f.read()
        return ret.replace('@@time@@',str(now))		/动态生成时间,用now()函数替换timer.html中的@@time@@

list1 = [
    ('/rihan',rihan),
    ('/oumei',oumei),
    ('/guochan',guochan),
    ('/home',home),
    ('/time',timer),
]

time.html内容:<h1>现在的时间是:@@time@@</h1>

django项目学习_第10张图片
django项目学习_第11张图片
可以看见每次刷新时间都是会变的。是动态的,而不是议程不变的。

bootstrap 和jQuery前端工具包

web框架的原理

https://www.cnblogs.com/maple-shaw/p/8862330.html

服务器程序和应用程序

对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。

应用程序则负责具体的逻辑处理,根据不同的路径怎样返回内容。
为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等
。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

常用的WSGI服务器有uWSGI、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

wsgi

web框架的四个功能

web框架的4个功能
1.收发消息 wsgi实现
2.根据不同的路径返回不同的内容
3.返回动态的页面,(从数据库取数据)
4.模板渲染,(字符串替换)

常用框架:

  • Django(实现234)
  • Flask(2)
  • tornado(2)
    1功能统一都由wsgi实现

Django下载及使用/创建

在Pycharm中安装或者使用pip安装
django项目学习_第12张图片
创建Django
D:>django-admin startproject aom
在D盘下就会出现一个aom目录

pycharm需要专业版的才可已创建django项目

aom/
├── manage.py  # 管理文件
├── templates  # 存放模板文件(html文件)
└── aom  # 项目目录
    ├── __init__.py
    ├── settings.py  # 配置
    ├── urls.py  # 路由 --> URL和函数的对应关系
    └── wsgi.py  # runserver命令就使用wsgiref模块做简单的web server
启动项目    python manage.py runserver 127.0.0.1:8000	默认启动127.0.0.1:8000
更改端口    python manage.py runserver 80
更改ip    python manage.py runserver 0.0.0.0:80

Django提供admin后台,便于统一管理用户、权限和权限组,超级用户初始化方法
初始化命令行:

python manage.py createsuperuser

登录功能的实现

这里我们使用bootstrap的简单的登录页面
django项目学习_第13张图片
这是这个页面的html页面
django项目学习_第14张图片
注意form表单这里

  • 需要有action(表单提交地址) 默认为当前地址,可以不写;
  • 需要有method提交方法默认为get,设置为post则提交表单时为post请求
  • input标签需要添加name属性,有的标签还需要有value值
  • 提交时需要有一个button按钮或者type = submit的input标签
  • novalidate不去校验我们要提交的内容的合法性,不如输入邮箱时没有@报错
    django项目学习_第15张图片
    django项目学习_第16张图片
    这样用户才能去提交请求。

然后我们
django项目学习_第17张图片
刷新地址栏,使用的get请求。

django项目学习_第18张图片
刷新这个登录框则使用的是form表单的post请求。
所以我们要区分这两种情况,当刷新页面是get,输入账号密码时是post:
django项目学习_第19张图片
django项目学习_第20张图片
这些信息其实都封装到了request中:
django项目学习_第21张图片
django项目学习_第22张图片
django项目学习_第23张图片
代码如下:
django项目学习_第24张图片
访问输入正确用户密码:
django项目学习_第25张图片
输入错误的信息:
django项目学习_第26张图片
就又返回到了登录页面.

我们不推荐在form表单使用GET的方法,因为用户的账户和密码就会显示在url地址上。
django项目学习_第27张图片

request总结:
request.method 请求方法
request.POST form表单POST请求提交的数据
request.GRT URL上的窗体参数(查询参数)

但是我们登录成功后通常会跳转,就有了 redirect模块
from django.shortcuts import HttpResponse,render,redirect
django项目学习_第28张图片
django项目学习_第29张图片
django项目学习_第30张图片
django项目学习_第31张图片
跳转到我们的index.html所在的路径 /index/;
django项目学习_第32张图片
就会直接跳转到我们的index页面,内容和上面的路径都发生变化,如果直接render返回而不用跳转的话,就只有内容改变,上面的路径不会变。
django项目学习_第33张图片
路径就和内容不符了。

创建一个app

创建app是为了更好的去划分一个django项目,有利于开发的管理和规范。

python manage.py startapp app01

django项目学习_第34张图片

admin是管理后台
apps是我们的app相关的
models是和数据库相关的orm
tests是测试相关
views里面写我们的一些函数

我们如何在settings.py文件中 中注册我们的app:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'app01',    #注册app,直接写名称
    'app01.apps.App01Config',       #推荐写法,通过路径注册
]

我们现在就可以吧url.py文件中之前写的那些函数放到views中了,放过去之后我们的usrl文件这样写。
django项目学习_第35张图片

orm

上面我们判断用户和密码是写死的,这样不友好,我们可以吧他放到数据库中去取,来让多个账户使用,连接到数据库近行校验。但是我们不直接取写数据库,而是使用一种新的方式orm操作关系型数据库。

ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。很早就有人提出,关系也可以用对象表达,这样的话,就能使用面向对象编程,来操作关系型数据库。
django项目学习_第36张图片

简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。

ORM 把数据库映射成对象。

  • 数据库的表(table) --> 类(class)
  • 记录(record,行数据)–> 对象(object)
  • 字段(field)–> 对象的属性(attribute)

settings中已经定义了数据库

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

默认是sqlite3数据库,我们暂时先使用这个,后面用到mysql再说。

如何去modles.py中定义数据表:

class User(models.Model):       #创建user表
    username = models.CharField(max_length=32)  #username字段,相当于varchar32
    password = models.CharField(max_length=32)

在Database插件中查看,在plugins中安装 Database-nevagita插件。
我们可以看到目前库中还没有这张表
django项目学习_第37张图片

Termianl执行:

python manage.py makemigrations	//检测models文件有无变更,制作成变更文件,当我们注释掉上面的User类在执行的话就会删除这张表

Migrations for 'app01':
  app01\migrations\0001_initial.py
    - Create model User

如果无变化就会:
django项目学习_第38张图片

django项目学习_第39张图片
就创建了这个迁移文件。这一部分如果出错就删除这个迁移文件,在重新执行上面的命令。

然后在执行

D:\aom>python manage.py migrate		/将变更的记录同步到数据库
Operations to perform:
  Apply all migrations: admin, app01, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying app01.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

在这里插入图片描述
创建很多表,我们的表已项目名_表名命名。邮件编辑表添加行。
切记要这样添加,不能通过直接点击空白添加。
django项目学习_第40张图片
我们怎样使用哪,有固定的写法:
django项目学习_第41张图片

(<QuerySet [<User: User object>, <User: User object>]>, <class 'django.db.models.query.QuerySet'>)
(<User: User object>, u'alex', u'123', <type 'unicode'>)
(<User: User object>, u'peiqi', u'123', <type 'unicode'>)
//获取一条数据,就返回一个对象
    ret = models.User.objects.get(username='alex',password='123')   
    print(ret,ret.username,ret.password)
    # (< User: User object >, u'alex', u'123')
    # ret = models.User.objects.get(username='alex',password='1234')
    # ret = models.User.objects.get(password='123')   #输入错误或者,返回多条数据是不行的
   //过滤满足条件的数据
    ret = models.User.objects.filter(password='123')
    # print(ret,ret.username,ret.password)
    print(ret,type(ret))

那我们的login这个函数就可以这样操作了。

def login(request): #优化方法
    if request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')

        if models.User.objects.filter(username=user,password=pwd):
            return render(request, 'index.html')
    return render(request, 'login.html')

总结:
到目前我们在settings中设置了哪些内容:

MIDDLEWARE = [          # 中间件
    # 'django.middleware.csrf.CsrfViewMiddleware',  

STATIC_URL = '/static/' # 静态文件的别名

# 指定静态文件目录
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static'),
    os.path.join(BASE_DIR,'static1'),
    os.path.join(BASE_DIR,'static2'),

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],	模板地址
INSTALLED_APPS = [
    'django.contrib.admin',
    # 'app01',    #注册app,直接写名称
    'app01.apps.App01Config', 		app地址

orm操作有哪些:

    ret = models.User.objects.all()         #是一个对象列表
    # print(ret,type(ret)) #(, ]>, )
    for i in ret:
        print(i,i.username,i.password,type(i.username))

    ret = models.User.objects.get(username='alex',password='123')   #获取一条数据,就返回一个对象
    # (< User: User object >, u'alex', u'123')
    ret = models.User.objects.filter(password='123')   #过滤满足条件的数据,返回对象列表

使用mysql数据库

首先更改settings里面的配置,

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'login',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': 'root'
    }
}

由于django默认使用MySQLDB连接数据库,但是mysqldb只支持python2,所以我们需要在settings文件或者同级的init文件中加上下面两行代码:

import pymysql
pymysql.install_as_MySQLdb()

然后再重新执行表创建的语句:

python manage.py makemigrations
python manage.py migrate	

我们用pycharm的插件去连接的时候发现报错:
The server time zone value is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the ‘serverTimezone’ configuration property) to use a more specifc time zone
是mysql 默认使用美国时区,设置为东八区即可:

set time_zone = '+8:00';
set global time_zone = '+8:00';			#只要这一步即可
SET global character_set_server = utf8 ;
ALTER database bookmanager DEFAULT CHARSET utf8;
select schema_name,default_character_set_name from information_schema.schemata;	查看数据库的字符集
flush privileges;

django项目学习_第42张图片

我们新建一个用户,这是测试用这个用户去登录就可以访问了。

总结

  1. Django相关命令
下载安装:pip install  django==1.11.28
创建Django项目:django-admin startproject 项目名
启动项目(切换到项目根目录): python manager.py runserver
创建app:python manager.py startapp app名
数据库迁移命令:
	python manager.py makemigrations
	python manager.py migrate
  1. settings配置
BASE_DIR 项目的根目录
INSTALLED_APPS	注册的APP
MIDDLEWARE	中间件,注释掉csrf中间件,才可以随意提交POST请求
TEMPLATES	模板
	DIRS:[os.path.join(BASE_DIR,'templates')]
DATABASE	数据库
STATIC_URL='/static/'	静态文件的别名
STATICFILES_DIRS=[os.path.join(BASE_DIR,'static')]
  1. django 使用MySQL流程
1.创建一个mysql库
2.配置settings
3.使用pymysql模块连接mysql数据库
	import pymysql
	pymysql.install_as_MySQLdb
4.在app下的modles.py写:
	from django.db import models

	# Create your models here.
	class Publisher(models.Model):
    	name = models.CharField(max_length=32)
5.执行数据库迁移命令
  1. urls.py路径和函数对应关系
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publisher_list/', views.publisher_list),
    url(r'^publisher_add/', views.publisher_add),
    url(r'^publisher_del/', views.publisher_del),
    url(r'^publisher_edit/', views.publisher_edit),
] 
  1. 函数
from django.shortcuts import render,redirect,HttpResponse
def xxx(request):
    request.method	//请求方式
    request.GET		//url上携带的参数 ?k1=v1&k2=v2	{} request.GET['k1']不建议
    request.GET.get('k1')
    request.POST	//POST请求提交数据,input框的name属性
    
    return:
    	HttpResponse('字符串')	返回字符串	
    	render(request,'模板文件名',{})		返回html页面
    	redirect('地址')	重定向	
  1. form表单
1 form表单的属性	action地址 method=‘POST’
2.input标签name属性,有些需要value值
3 需要一个butto按钮或者type='submit'input
  1. ORM 对象关系映射
数据库的表(table) --> 类(class)
记录(record,行数据)–> 对象(object)
字段(field)–> 对象的属性(attribute)

from app01 import models
查:
models.Publisher.objects.all().order_by('id')	//查询所有数据
models.Publisher.objects.get(name='xx',pk=1)	//查询一个数据,对象,没有或者多个报错
models.Publisher.objects.filter(name='xx',pk=1) //查询多个数据 QuerySet 对象列表

增:
models.Publisher.objects.create(name='xx')	//Publisher对象
删:
models.Publisher.objects.get(pk=1).delete()	//对象删除
models.Publisher.objects.filter(pk=1).delete()	//多个对象删除
修改:
pub_obj = models.Publisher.objects.get(pk=1)
pub_obj.name = 'xxx'
pub_obj.save()		//提交
  1. 模板语法
return render(request,'模板文件名',{'k1':'xxx','all_list':all+list})	//返回一个html页面

{{ k1 }}
{% for i in all_list %}
	{{ forloop.counter }}
	{{ i.name }}
	{{ i.pk }}
{% endfor %}

图书管理系统

展示出版社

新建项目bookmanager
更改settings文件:
csrf
加templates路径
更改使用mysql数据库。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bookmanager',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': '123456'
    }
}

django项目学习_第43张图片
我们配置访问:

// urls.py
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publisher_list/', views.publisher_list),
]

//views.py
def publisher_list(request):
    # 逻辑,1,从数据库获取出版社信息 2, 返回一个页面
    ans_publishers = models.Publisher.objects.all()
    # for i in ans_publishers:
    #     print(i)
    #     print(i.id)
    #     print(i.name)
    return render(request,'publisher_list.html')

//publisher_list.html
<body>
<table border="1 ">
    <thead>
        <tr>
            <th>序号</th>
            <th>id</th>
            <th>出版社名</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>1</td>
            <td>商务印书馆</td>
        </tr>
    </tbody>
</table>
</body>

测试访问:
django项目学习_第44张图片
现在的内容是我们手动加进去的。
下面我们用模板去渲染。类似于jinja2,Django有自己的语法,{{xxx}} 代表xxx这个变量,而jinja中用的是@@

def publisher_list(request):
    # 逻辑,1,从数据库获取出版社信息 2, 返回一个页面
    all_publishers = models.Publisher.objects.all()	#也可以通过pymysql连接数据库去返回
    return render(request,'publisher_list.html',{'publishers':all_publishers})		
	# 增加了{'publishers':all_publishers}给publisher_list.html传的参数,publishers对应变量,all_publishers是变量的值

//publisher_list.html
<table border="1 ">
    <thead>
        <tr>
            <th>序号</th>
            <th>id</th>
            <th>出版社名</th>
        </tr>
    </thead>
    <tbody>
        {% for i in publishers %}
            <tr>
<!--                拿到循环的次数-->
                <td>{{ forloop.counter}}</td>
<!--                拿到id-->
                <td>{{ i.id }}</td>
<!--                拿到出版社的值-->
                <td>{{ i.name }}</td>
            </tr>
        {% endfor %}		#for循环需要以这个结束
    </tbody>
</table>

django项目学习_第45张图片

新增出版社

我们应该新增加一个路径,返回给界面一个表单让我们去post提交。把对应的数据提交到数据库,再返回一个界面。

//urls
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publisher_list/', views.publisher_list),
    url(r'^publisher_add/', views.publisher_add),
]
//views
def publisher_add(request):
    # get请求返回一个表单,页面包含from表单。
    return render(request,'publisher_add.html')
//html
<form action="" method = "post">
    出版社名称:<input type="text" name="pub_name">
    <button>提交</button>
</form>

django项目学习_第46张图片
django项目学习_第47张图片
打开页面是GET请求。
django项目学习_第48张图片
提交内容是POST请求。

def publisher_add(request):
    #POST请求
    if request.method == 'POST':
        #1,获取用户提交数据
        pub_name = request.POST.get('pub_name')
        print(pub_name)
        #2,将数据新增到数据库
        ret = models.Publisher.objects.create(name=pub_name)
        print(ret,type(ret))
        #3,返回一个重定向到出版社信息展示
        return redirect('/publisher_list/')
    # get请求返回一个表单,页面包含from表单。
    return render(request,'publisher_add.html')

我们在次新增出版社内容后
django项目学习_第49张图片
就重定向到了list界面。

新增补充

在界面展示这里,我们可以按照id排序:

order_by 排序
def publisher_list(request):
    # 逻辑,1,从数据库获取出版社信息 2, 返回一个页面
    all_publishers = models.Publisher.objects.all().order_by('id')
    all_publishers = models.Publisher.objects.all().order_by('-id')-则为倒叙排列

django项目学习_第50张图片
但是当前我们可以提价重复的出版社,不会报错,为了控制,我们应该加上相应的逻辑。

控制重复值
def publisher_add(request):
    #POST请求
    if request.method == 'POST':
        #1,获取用户提交数据
        pub_name = request.POST.get('pub_name')
        print(pub_name)
        if models.Publisher.objects.filter(name=pub_name):
            return render(request,'publisher_add.html',{'error':'出版社名已存在'})
        #2,将数据新增到数据库
        ret = models.Publisher.objects.create(name=pub_name)
        print(ret,type(ret))
        #3,返回一个重定向到出版社信息展示
        return redirect('/publisher_list/')
    # get请求返回一个表单,页面包含from表单。
    return render(request,'publisher_add.html')

//html页面中添加span标签渲染error
    出版社名称:<input type="text" name="pub_name"><span>{{ error }}</span>

django项目学习_第51张图片
当前没有error的值,则替换为字符串。
当我们再次提交新华出版社的时候:
django项目学习_第52张图片
就有提示了。

list界面增加跳转按钮

当前我们需要输入地址才可以切换到add页面,我们可以再list页面添加一个按钮跳转到add页面。

<body>
<!--{{ all_publishers }}-->

<a href="/publisher_add/">新增出版社</a>		在这个位置添加a 链接标签

<table border="1 ">
    <thead>

django项目学习_第53张图片
点击就可以跳转到add页面。

控制空值
//urls.py
def publisher_add(request):
    #POST请求
    if request.method == 'POST':
        #1,获取用户提交数据
        pub_name = request.POST.get('pub_name')
        print(pub_name)
            #输入为空
        if not pub_name:		新增这两行代码
            return render(request, 'publisher_add.html', {'error': '出版社名不能为空'})

django项目学习_第54张图片

删除出版社

从数据库删除出版社,修改 list.html

            <th>出版社名</th>
            <th>操作</th>	新增这一行
            
                <td>{{ i.name }}</td>
                <td> <a href="">删除</a> </td>	新增这一行

django项目学习_第55张图片
我们要做的就是点击删除的时候转到相应的逻辑。
为了点击每一行的删除处理不同的数据。

                <td>{{ i.name }}</td>
                <td> <a href="/publisher_del/?id={{ i.id }}">删除</a> </td>		改为这样既可

django项目学习_第56张图片
django项目学习_第57张图片
由于/publisher_del/?id={{ i.id }} 是一种get的方式,所以拿到这个id我们也用get的方法。
我们把id换成pk,/publisher_del/?pk={{ i.id }}

//html
                <td> <a href="/publisher_del/?pk={{ i.id }}">删除</a> </td>
//urls
    url(r'^publisher_del/', views.publisher_del),
//views.py
def publisher_del(request):
    #获取要删除的数据库id
    pk = request.GET.get('pk')		直接从?后面截取pk的值
    print(pk)
    #根据id查询到一个对象到数据库删除
    models.Publisher.objects.get(pk=pk).delete()    #第一个pk是主键的意思,数据库中主键是id,故既可写id,也可写pk,第二个是上面的变量
    # 返回重定向到展示出版社页面
    return redirect('/publisher_list/')

django项目学习_第58张图片
原来id为2的新华出版社已经删除了。

补充
我们在list.html文件里写的
				<td>{{ i.id }}</td>	
                <td>{{ i.pk }}</td>
                <td> <a href="/publisher_del/?pk={{ i.pk }}">删除</a> </td>

id有可能不是主键,所以这里我们建议写pk。

当我们手动输入url地址去删除数据时可能不存在数据而报错。
django项目学习_第59张图片
我们就要去过滤:

def publisher_del(request):
    #获取要删除的数据库id
    pk = request.GET.get('pk')
    print(pk)
    #根据id到数据库删除
    # models.Publisher.objects.get(pk=pk).delete()    #查询到一个对象,删除该对象
    models.Publisher.objects.filter(pk=pk).delete()    #查询到一个对象列表,删除该对象列表
    # 返回重定向到展示出版社页面
    return redirect('/publisher_list/')

把get改为filter即可,区别在于:
get对 对象进行删除,不存在则报错
filter对列表删除,不存在不会报错

编辑出版社

//list.html
                <td>{{ i.name }}</td>
                <td>
                    <a href="/publisher_del/?pk={{ i.id }}">删除</a>
                    <a href="">编辑</a>		和删除放到一起
                </td>

django项目学习_第60张图片

//urls
    url(r'^publisher_edit/', views.publisher_edit),

//views
def publisher_edit(request):

    pk = request.GET.get('pk')
    pub_obj = models.Publisher.objects.get(pk=pk)
    # get 返回一个页面 页面包含form表单 input中含有原始数据
    return render(request,'publisher_edit.html',{'pub_obj':pub_obj})
//edit.html
<form action="" method = "post">
    #出版社名称:{{ error }}	#先把要编辑的数据放到input框
    出版社名称:<input type="text" name="pub_name" value="{{ pub_obj.name }}"><span>{{ error }}</span>	#先把要编辑的数据放到input框
    <button>提交</button>

django项目学习_第61张图片
接下来就是用户在这里修改数据然后再提交.

//edit.html
    出版社名称:<input type="text" name="pub_name" value="{{ pub_obj.name }}"><span>{{ error }}</span>
    <button>提交</button>
//views
def publisher_edit(request):

    pk = request.GET.get('pk')
    pub_obj = models.Publisher.objects.get(pk=pk)
    # get 返回一个页面 页面包含form表单 input中含有原始数据
    if request.method == 'GET':		点击编辑时为get请求
        return render(request,'publisher_edit.html',{'pub_obj':pub_obj})

    else:
        # post  修改数据库数据
        # 获取用户提交的数据
        pub_name = request.POST.get('pub_name')		对应html页面中input标签的pub_name属性
        # 修改数据库中的数据
        pub_obj.name = pub_name     #只是在内存中修改了
        pub_obj.save()              #将修改的操作提交到数据库
        # 返回重定向到展示出版社页面
        return redirect('/publisher_list/')


django项目学习_第62张图片
修改为:
django项目学习_第63张图片
django项目学习_第64张图片

返回了修改过后的页面。

添加bootstrap样式

//settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

django项目学习_第65张图片
创建这个目录。
到下面这个地址,右键检查,
https://v3.bootcss.com/examples/dashboard/
只复制body中的内容

保留这两个js
django项目学习_第66张图片
现在访问就可以看到初步的效果。
在添加头部:

    <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/dashboard.css">
</head>

效果如下
django项目学习_第67张图片
django项目学习_第68张图片

外键连接书本和出版社

一个出版社有很多书,要把他们连接起来,就得用到外键,满足一对多,多对一的关系。

# Create your models here.
class Publisher(models.Model):
    name = models.CharField(max_length=32)
    
class Book_list(models.Model):
    name = models.CharField(max_length=32)
    # 外键指向Publisher类,也可以写'Publisher',不过要写到Publisher类的上面,
    publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE,default=1)
    """
       on_delete:   2.0版本后必选
         models.CASCADE     默认级联删除,删除Publisher表的一行数据,Book表对应的内容也会删除。
         models.PROTECT     保护模式,当Publisher表中数据还关联着其他表的时候,就进制删除Publisher表
         models.SET(v)       删除后设置为某个值
         models.SET_DEFAULT()   删除后设置为默认值,需要加上 default=
         models.SET_NULL()      删除后设置为NULL  
    """
D:\bookmanager>python manage.py makemigrations
D:\bookmanager>python manage.py migrate

django项目学习_第69张图片
含有外键的表就创建好了。django会自动的给外键的字段添加一个_id结尾。
这样 book.publisher代表外键出版社的对象,book.publisher_id则代表对应的出版社id

//urls.py
    url(r'^book_list/', views.book_list),
//views.py
# 展示书籍
def book_list(request):
    #查询所有书籍
    all_books = models.Book_list.objects.all()
    #返回页面
    # for book in all_books:
    #     print(book)
    #     print(book.pk)
    #     print(book.name)
    #     print(book.publisher_id)
    #     print(book.publisher)   #拿到关联的publisher的对象
    #     print(book.publisher.pk,book.publisher.name)
    # return HttpResponse('books')
    return render(request, 'book_list.html', {'all_books': all_books})
//html
<table border="1">
    <thead>
        <tr>
            <th>序号</th>
            <th>id</th>
            <th>书名</th>
            <th>出版社</th>
        </tr>
    </thead>
    <tbody>
        {% for book in all_books %}
            <tr>
                <td>{{ forloop.counter }}</td>
                <td>{{ book.pk }}</td>
                <td>{{ book.name }}</td>
                <td>{{ book.publisher.name }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>

简单的界面:
django项目学习_第70张图片

新增书籍

//urls.py
    url(r'^book_add/', views.book_add),
   
//views.py
def book_add(request):
    error = ''
    if request.method == 'POST':
        #POST 获取用户提交的数据
        book_name = request.POST.get('book_name')
        pub_id = request.POST.get('pub_id')
        #判断为空
        if not book_name:
            error = 'bookname not null'
        #判断重复
        elif models.Book.objects.filter(name=book_name):
            error = 'bookname already exists'
        # 插入数据至数据库
        else:
            models.Book.objects.create(name=book_name, publisher_id=pub_id)
            return redirect('/book_list/')

    #查询所有的出版社
    all_publishers = models.Publisher.objects.all()
    #GET请求返回一个包含form表单的页面
    return render(request, 'book_add.html', {'all_publishers': all_publishers, 'error': error})
//book_add.html
<body>
<form action="" method="post">
    <p>
        书名: <input type="text" name="book_name"> <span> {{ error }} </span>
    </p>
    <p>
        出版社:
        <select name="pub_id" id="">
        {% for publisher in all_publishers %}
            <option value="{{ publisher.pk }}">{{ publisher.name }}</option>	value为name="pub_id"赋值为publisher.name对应的pk
        {% endfor %}
        </select>
    </p>

    <button>提交</button>
</form>
</body>

django项目学习_第71张图片
django项目学习_第72张图片

删除书籍

//urls
    url(r'^book_del/', views.book_del),
//views
def book_del(request):
    #获取用户要删除的id
    pk = request.GET.get('id')
    #获取要删除的数据,执行删除
    models.Book.objects.filter(pk=pk).delete()
    #返回一个出版社
    return redirect('/book_list/')
//book_list.html
<table border="1">
    <thead>
        <tr>
            <th>序号</th>
            <th>id</th>
            <th>书名</th>
            <th>出版社</th>
            <th>操作</th>		新增部分
        </tr>
    </thead>
    <tbody>
        {% for book in all_books %}
            <tr>
                <td>{{ forloop.counter }}</td>
                <td>{{ book.pk }}</td>
                <td>{{ book.name }}</td>
                <td>{{ book.publisher.name }}</td>
                <td>
                    <a href="/book_del/?id={{ book.pk }}">删除</a>		新增部分
                </td>

django项目学习_第73张图片

编辑书籍

GET请求

//urls
    url(r'^book_edit/', views.book_edit),
//views
def book_edit(request):
    #get请求
    #查询要编辑对象的id
    pk = request.GET.get('id')
    #根据id查到编辑对象
    book_obj = models.Book.objects.get(pk=pk)
    print(book_obj.name)
    #返回页面
    all_publishers = models.Publisher.objects.all()
    return render(request, 'book_edit.html',{'book_obj': book_obj, 'all_publishers':all_publishers})
//list.html
                    <a href="/book_del/?id={{ book.pk }}">删除</a>
                    <a href="/book_edit/?id={{ book.pk }}">编辑</a>		新增

//edit.html
<body>
<form action="" method = "post">
    <p>
        书名:<input type="text" name="book_name" value="{{ book_obj.name }}"><span>{{ error }}</span>
    </p>
    <p>
        出版社:
        <select name="pub_id" id="">
        {% for publisher in all_publishers %}
            <option value="{{ publisher.pk }}">{{ publisher.name }}</option>
        {% endfor %}
        </select>
    </p>
    <button>提交</button>

</form>

django项目学习_第74张图片
django项目学习_第75张图片
但是书名和出本社不对称,这是因为没有给一个selected默认值,去匹配书名。

<form action="" method = "post">
    <p>
        书名:<input type="text" name="book_name" value="{{ book_obj.name }}"><span>{{ error }}</span>
    </p>
    <p>
        出版社:
        <select name="pub_id" id="">
        {% for publisher in all_publishers %}
            {% if book_obj.publisher == publisher %}	判断publisher对象是否一致
                <option selected value="{{ publisher.pk }}">{{ publisher.name }}</option>		selected选中,给个默认值,
            {% else %}
                <option value="{{ publisher.pk }}">{{ publisher.name }}</option>	其他的值不选中
            {% endif %}

        {% endfor %}
        </select>
    </p>
    <button>提交</button>

django项目学习_第76张图片
django项目学习_第77张图片
这样选中书籍就会带出正确的出版社。

加上POST请求:

def book_edit(request):
    #查询要编辑对象的id
    pk = request.GET.get('id')
    #根据id查到编辑对象
    book_obj = models.Book.objects.get(pk=pk)

    # post请求
    if request.method == 'POST':
        # 获取用户新提交的数据
        book_name = request.POST.get('book_name')
        pub_id = request.POST.get('pub_id')

        # 编辑的对象做对应的修改
        book_obj.name = book_name
        book_obj.publisher_id = pub_id
        book_obj.publisher = models.Publisher.objects.get(pk=pub_id)	
        book_obj.save()

	    #方式二
        models.Book.objects.filter(pk=pk).update(name=book_name,publisher_id=pub_id)	#这两种方法都可以
        # 重定向
        return redirect('/book_list/')

    #get请求
    #返回页面
    all_publishers = models.Publisher.objects.all()
    return render(request, 'book_edit.html',{'book_obj': book_obj, 'all_publishers':all_publishers})

django项目学习_第78张图片

当前如果我们要改的字段很多的话,就不好用了:

        # 编辑的对象做对应的修改
        #方式一 
        # book_obj.name = book_name
        # book_obj.publisher_id = pub_id
        # # book_obj.publisher = models.Publisher.objects.get(pk=pub_id)
        # book_obj.save()
        
        #方式二
        models.Book.objects.filter(pk=pk).update(name=book_name,publisher_id=pub_id)	更简洁

django项目学习_第79张图片
也是可以的

回顾

django项目学习_第80张图片
django项目学习_第81张图片
django项目学习_第82张图片
django项目学习_第83张图片
django项目学习_第84张图片

书籍添加样式

前面我们已经给出版社的展示添加了一个样式,现在我们给书籍也添加相同的样式用于展示。

//booklist.html
            <li><a href="/publisher_list/">出版社列表</a></li>
            <li class="active"><a href="/book_list/">书籍列表</a></li>		class=active的作用是选择哪个,哪个就处于激活状态,加个背景

//publisher_list
            <li class="active"><a href="/publisher_list/">出版社列表</a></li>
            <li><a href="/book_list/">书籍列表</a></li>

django项目学习_第85张图片
django项目学习_第86张图片
点击出版社列表就跳转到了这里。

现在给新增也添加上样式:

//publisher_list.html
          <h2 class="sub-header">出版社列表</h2>
          <a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a>		新增这一行

//publisher_add.html
<body>

    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Project name</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav navbar-right">
            <li><a href="#">Dashboard</a></li>
            <li><a href="#">Settings</a></li>
            <li><a href="#">Profile</a></li>
            <li><a href="#">Help</a></li>
          </ul>
          <form class="navbar-form navbar-right">
            <input type="text" class="form-control" placeholder="Search...">
          </form>
        </div>
      </div>
    </nav>

    <div class="container-fluid">
      <div class="row">
        <div class="col-sm-3 col-md-2 sidebar">
          <ul class="nav nav-sidebar">
            <li><a href="/publisher_list/">出版社列表</a></li>
            <li class="active"><a href="/book_list/">书籍列表</a></li>
            <li><a href="#">Analytics</a></li>
            <li><a href="#">Export</a></li>
          </ul>
        </div>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
            <div class="panel panel-primary">
                <div class="panel-heading">新增出版社</div>
                <div class="panel-body">
                    <form class="form-horizontal" action="" method = "post">
                        <div class="form-group">
                            <label for="inputEmail3" class="col-sm-2 control-label">出版社名称</label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control" name="pub_name" id="inputEmail3" placeholder="请输入出版社名称">
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-2 col-sm-10">
                                <button type="submit" class="btn btn-default">提交</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@bootcss/[email protected]/dist/js/bootstrap.min.js"></script>

</body>

django项目学习_第87张图片
当前我们直接点击提交页面不会变,因为views里面的逻辑如果输入为空则会报错,并重新返回add页面。

所以:

                        <div class="form-group {% if error %}has-error{% endif %}">		//当传进来的error存在时则添加has-error样式
                            <label for="inputEmail3" class="col-sm-2 control-label">出版社名称</label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control" name="pub_name" id="inputEmail3" placeholder="请输入出版社名称">
                                <span class="help-block">{{ error }}</span>	//新增一个span标签打印error的内容help-block为提示信息类型
                            </div>
                        </div>

django项目学习_第88张图片
django项目学习_第89张图片

         <div class="panel-heading">编辑出版社</div>	/改这里
                <div class="panel-body">
                    <form class="form-horizontal" action="" method = "post">
                        <div class="form-group {% if error %}has-error{% endif %}">
                            <label for="inputEmail3" class="col-sm-2 control-label">出版社名称</label>
                            <div class="col-sm-8">
                                <input type="text" class="form-control" name="pub_name" value="{{ pub_obj.name }}" id="inputEmail3" placeholder="请输入出版社名称">		/这里添加value="{{ pub_obj.name }}即可
                                <span class="help-block">{{ error }}</span>

django项目学习_第90张图片

编辑书籍样式

为book_add添加样式
django项目学习_第91张图片

多对多关系创建

创建作者表。一个作者可以写多个书,多个做个也可以写一本书,这样我们在记录作者表的时候就会有重复的数据,所以:

多对多关系的时候需要创建第三张表,这个表有两个外键分别关联作者表和书籍表。类似下面:
django项目学习_第92张图片
auther_book就可以反映多对多的关系。
但是这个表示不需要创建的,只需要:

class Author(models.Model):
    name = models.CharField(max_length=32)
    books = models.ManyToManyField('Book')      //表示和Book表呈现多对多的关系,也可以写在Book表中指向author表

django项目学习_第93张图片
就会自动的创建这两张表。

django项目学习_第94张图片
这样就代表老王 和aom 写了阿宾正传,大头一个人写了578三本书。

展示作者

url(r'^author_list/', views.author_list),

def author_list(request):
    # 查询所有的作者
    all_authors = models.Author.objects.all()
    for author in all_authors:
        print(author)
        print(author.id)
        print(author.name)
        print(author.books, type(author.books))     #是一个关系管理对象,类似于外键对象,可用户获取信息
        print(author.books.all())		#所关联的所有对象,是一个对象列表,可以循环
        print('*' * 40)
    
    return render(request,'author_list.html',{'all_authors' : all_authors})
Author object
2
aom
app01.Book.None <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager..ManyRelatedManager'>
<QuerySet [<Book: Book object>]>
****************************************
Author object
3
大头
app01.Book.None <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager..ManyRelatedManager'>
<QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
****************************************
<body>
<table border="1">
    <thead>
    <tr>
        <th>序号</th>
        <th>ID</th>
        <th>姓名</th>
        <th>代表作</th>
    </tr>
    </thead>
    <tbody>
    {% for author in all_authors %}
        <tr>
            <td>{{ forloop.counter }}</td>
            <td>{{ author.pk }}</td>
            <td>{{ author.name }}</td>
            <td>
                {% for book in author.books.all %}		这里不用写all() 也会执行
                    《 {{ book.name }}{% endfor %}
            </td>
        </tr>
    {% endfor %}
    </tbody>
</table>
</body>

django项目学习_第95张图片

添加作者

//urls.py
    url(r'^author_add/', views.author_add),

//views.py
def author_add(request):
    #POST获取用户提交数据,
    if request.method == 'POST':
        author_name = request.POST.get('author_name')
        book_ids = request.POST.getlist('book_ids')     #获取多个数据用getlist,get只取得最后一个值
        print(request.POST)     #
        #插入作者数据库
        author_obj = models.Author.objects.create(name=author_name)
        #作者和书籍绑定多对多关系
        author_obj.books.set(book_ids)
        # 然后返回到作者展示页
        return redirect('/author_list/')
    # get
    # 查询所有书籍
    all_books = models.Book.objects.all()

    # 返回一个页面,包含form表单,让用户输入

    return render(request,'author_add.html',{'all_books': all_books})

//html
<form action="" method="POST">
    <p>
        作者姓名:<input type="text" name="author_name">
    </p>
    <p>
        代表作:
        <select name="book_ids" id="" multiple>		#多选
            {% for book in all_books %}
            <option value="{{ book.pk }}">{{ book.name }}</option>
            {% endfor %}
        </select>
    </p>
    <button>提交</button>
</form>

django项目学习_第96张图片
django项目学习_第97张图片
django项目学习_第98张图片

删除作者

    url(r'^author_del/', views.author_del),

def author_del(request):
    # 获取要删除的对象
    pk = request.GET.get('id')
    # 根据id查到对象进行删除,删除了作者和对应的书籍关系
    models.Author.objects.filter(pk=pk).delete()
    # 返回作者展示页面
    return redirect('/author_list/')

<table border="1">
    <thead>
    <tr>
        <th>序号</th>
        <th>ID</th>
        <th>姓名</th>
        <th>代表作</th>
        <th>操作</th>		新增
    </tr>
    </thead>
    <tbody>
    {% for author in all_authors %}
        <tr>
            <td>{{ forloop.counter }}</td>
            <td>{{ author.pk }}</td>
            <td>{{ author.name }}</td>
            <td>
                {% for book in author.books.all %}{{ book.name }}{% endfor %}
            </td>
            <td>
                <a href="/author_del/?id={{ author.pk }}">删除</a>
                <a href="/author_edit/?id={{ author.pk }}">编辑</a>			//新增,带出作者的id
            </td>
        </tr>
    {% endfor %}
    </tbody>
</table>

编辑作者

    url(r'^author_edit/', views.author_edit),

//views.py
def author_edit(request):
    # 获取编辑对象的id
    pk = request.GET.get('id')
    # 根据id获取到作者对象
    author_obj = models.Author.objects.get(pk=pk)

    if request.method == 'POST':
        # POST
        #获取用户提交的数据
        author_name = request.POST.get('author_name')
        book_ids = request.POST.getlist('book_ids')
        #修改数据
        author_obj.name = author_name
        author_obj.save()
        #修改和书籍的对应关系,重新设置
        author_obj.books.set(book_ids)
        #重定向
        return redirect('/author_list/')
    # get
    # 获取所有的书籍
    all_books = models.Book.objects.all()
    # 返回一个页面包含作者的姓名,包含代表作
    return render(request, 'author_edit.html', {'author_obj': author_obj,'all_books': all_books})

//html
<form action="" method="POST">
    <p>
        作者姓名:<input type="text" name="author_name" value="{{ author_obj.name }}">
    </p>
    <p>
        代表作:
        <select name="book_ids" id="" multiple>
            {% for book in all_books %}
                {% if book in author_obj.books.all %}
                    <option selected value="{{ book.pk }}">{{ book.name }}</option>
                {% else %}
                    <option value="{{ book.pk }}">{{ book.name }}</option>
                {% endif %}
            {% endfor %}
        </select>
    </p>
    <button>提交</button>
</form>

django项目学习_第99张图片
django项目学习_第100张图片
就编辑成功了。

总结

// ORM操作
from app01 import models

# 查询
pub_obj = models.Publisher.objects.all() 	/查询所有的数据,Queryset   对象列表
models.Publisher.objects.get(name='xx',pk='1')	/获取唯一的一个对象
models.Publisher.objects.filter(name='xx')	/获取多个对象,对象列表
pubobj.pk	pub_obj.name

book_obj.pub		/ 外键,即所关联的Publisher对象
book_obj.pub_id		/ 外键的id,这是django的快捷方式,也可以book_obj.pub.id

author_obj.books	/ 多对多字段,是一个关系管理对象
author_obj.books.all()		/关联的所有对象,对象列表


# 新增
models.Publisher.objects.create(name='xx')

models.Books.objects.create(name='xx',pub='出版社对象')
models.Books.objects.create(name='xx',pub_id='出版社对象id')

author_obj = models.Author.objects.craete(name='xx')
author_obj.books.set(book_ids)

# 删除
models.Publisher.object.get(pk=1).delete()		//通过对象删除
models.Publisher.object.filter(pk=1).delete()	//批量删除

# 修改
pub_obj.name = 'xx'		//内存中修改
pub_obj.save()		//提交到数据库

models.Publisher.object.filter(pk=1).update(name='xx')

# 模板
render(request, 'book_add.html', {'all_publishers': all_publishers, 'error': error})

<div class="form-group {% if error %}has-error{% endif %}">		/error存在时则有has-error错误提示
<span class="help-block">{{ error }}</span>		//error的使用

{{ for publisher in all_publishers }}
	{{ forloop.counter }}
	{{ publisher.name }}
	{% if 条件1 %}
	(% elif 条件2 %)
	{% else %}
	{% endif %}
{{ endfor }}

其他细节和内容

MVC和MTV

MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),具有耦合性低、重用性高、生命周期成本低等优点。
django项目学习_第101张图片
Django框架的设计模式借鉴了MVC框架的思想,也是分成三部分,来降低各个部分之间的耦合性。

Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和View(视图),也就是MTV框架。

  • Model(模型):负责业务对象与数据库的对象(ORM)
  • Template(模版):负责如何把页面展示给用户
  • View(视图):负责业务逻辑,并在适当的时候调用Model和Template

此外,Django还有一个urls分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template
django项目学习_第102张图片

MVC:
M:model 模型,操作数据库
V:view	 视图 展示数据
C:controller 控制器 流程 业务逻辑

MTV
M:model ORM
T:template 模板
V:view 视图,业务逻辑

模板

Django模板中只需要记两种特殊符号:
{{ }}和 {% %}
{{ }}表示变量,在模板渲染的时候替换成值,{% %}表示逻辑相关的操作。

简单使用

新建一个项目进行模板的学习。

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^template_test/', views.template_test),
]

def template_test(request):
    num = 1
    string = 'i love you'
    name_list= ['老曹','老李','老王']
    dic = {'name':'alex','age':'18'}
    tup = ('天','地','人')
    name_set = {'天','地','人'}

    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
        def __str__(self):
            return "".format(self.name,self.age)
        def talk(self,str):
            return '你在无中生友'
    alex = Person('alex',23 )
    return  render(request,
                   'template_test.html',
                    {
                       'num':num,
                       'string':string,
                       'name_list':name_list,
                       'dic':dic,
                       'tup':tup,
                       'name_set':name_set,
                       'alex':alex,
                    }
                   )

<body>
{{ num }}
<br>
{{ string }}
<br>
{{ name_list }} {{ name_list.0 }}		/0是索引,跟python中的[0]方式不停,而且不能用负数
<br>
{{ dic }}   {{ dic.name }}  {{ dic.keys }}		/. 带出属性和方法
<br>
{{ tup }}
<br>
{{ name_set }}
<br>
{{ alex }}
<br>
{{ alex.name }} : {{ alex.talk }}
</body>

django项目学习_第103张图片

我们可以看到使用了,而且.操作只能调用不带参数的方法
.索引	.属性	.key	.方法   //方法后不加()

优先级为:
.key > .属性  > .方法 > .索引

过滤器Filters

过滤器,用来修改变量的显示结果。最多有一个参数。
语法: {{ value|filter_name:参数 }}
’:'左右没有空格没有空格没有空格

  • default
    return  render(request,
                   'template_test.html',
                    {
                       'num':num,
                       'string':string,
                       'name_list':name_list,
                       'dic':dic,
                       'tup':tup,
                       'name_set':name_set,
                       'alex':alex,
                       'kong':False,		空字符串或者空列表,空字典,false
                    }
                   )

//html

{{ baobei}}		//无这个变量
<br>
{{ baobei | default:'曹仔'}}		// |左后可以加空格,但是:左右不可以
<br>
{{ kong|default:'暂无数据' }}

django项目学习_第104张图片
default就可以进行替换

  • filter、add
    filesizeformat:将值格式化为一个 “人类可读的” 文件尺寸 (例如 ‘13 KB’, ‘4.1 MB’, ‘102 bytes’, 等等
    add:类似于python的 + ,可对数字,字符串列表进行相加
                       'size':1,
                       'size1':1024,

//html
{{ size|filesizeformat }}
<br>
{{ size1|filesizeformat }}
<br>
{{ 4|add:2  }}
<br>
{{ '4'|add:'-2' }}
<br>
{{ '4'|add:'xxx2' }}
<br>
{{ name_list|add:name_list }}

django项目学习_第105张图片

  • lower、upper、title
{{ alex.name|upper }}			
{{ alex.name|upper|lower }}
{{ alex.name|title}}		首字母大写

django项目学习_第106张图片

  • length
{{ alex.name|length }}		4
<br>
{{ name_list|length }}		3
<br>
{{ name_list|add:name_list|length }}		6
  • slice切片
{{ name_list|slice:'::' }}		所有值
<br>
{{ name_list|slice:'0:2'}}0,1个值
<br>
{{ name_list|slice:'::2' }}		步长为2,所以取第1,3个值
<br>
{{ name_list|slice:'::-2' }}	步长-2,逆序取第1,3
<br>
{{ name_list|slice:'::3' }}		步长3,去第一个
<br>
{{ name_list|slice:'::-1' }}	
<br>
{{ name_list|slice:'-1:-3:-1' }}-1-3,方向为-1

在这里插入图片描述

  • first、last、join
{{ name_list|first }}
<br>
{{ name_list|last }}
<br>
{{ name_list|join:'//' }}
<br>

django项目学习_第107张图片

  • truncatechars、truncatewords 如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“…”)
                        'long_str':'这样就是从右到左 提取元素 那么大家 一定明白了 如果是步长是正数就是从左到右提取元素'

{{ long_str|truncatechars:'9'}}			...也占三个位置
<br>
{{ long_str|truncatewords:'4'}}		按照单词去取
<br>

django项目学习_第108张图片

  • date配置
    now = datetime.datetime.now()

//html
{{ now|date:"Y-m-d H:i:s"}}

在这里插入图片描述
快捷设置:settings.py

USE_L10N = False		//设置为False
DATETIME_FORMAT = 'Y-m-h H:i:s'

这样就不用用上面的date过滤器了

  • safe
    Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
<script>

for (var i = 0; i < 5; i++) {
alert('11111')
}

</script>

比如这段代码如果渲染到了我们的页面中就会不断的产生提示的效果。
django项目学习_第109张图片
为了安全Django会对他们进行转义,加上两个"" :

                        'a':'路费',

django项目学习_第110张图片
在这里插入图片描述
我们未来正常渲染,就需要safe过滤器

{{ a }}
<br>
{{ a|safe }}


#from django.utils.safestring import mark_safe

django项目学习_第111张图片

自定义过滤器

  1. 在app下创建一个名为templatetag的python包 固定,名不能变
  2. 创建一个文件mytags.py,名字随意
  3. 文件中注册tag library并添加函数+装饰器:
from django import template

register = template.Library()		register固定名称,注册过滤器对象

@register.filter
def add_arg(value,arg):
    #功能
    return "{}_{}".format(value,arg)


//html
{% load mytags %}		//导入包
{{ alex.name|add_arg:'dsb' }}		//使用自定义过滤器

专业版重启项目,社区版需要在settings文件中:

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',
            ],
        'libraries':{
                'my_tags': 'app01.templatetags.tags',		加入这两行
            }
        },
    },
]

使用:

{% load my_tags %}
{{ alex.name|add_arg:'dsb' }}

django项目学习_第112张图片
就可以使用了。

逻辑操作

  • for
<ul>
    {% for name in name_list %}
        <li>{{ forloop.last }}-{{ name }}</li>		//判断是否是最后一个循环
    {% endfor %}
</ul>

django项目学习_第113张图片
其他可用参数:

Variable Description
forloop.counter 当前循环的索引值(从1开始)
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(到1结束)
forloop.revcounter0 当前循环的倒序索引值(到0结束)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环
  • empty
//views
'kong':'',	kong变量为空字符串

//html
{% for i in kong %}
    {{ i }}
{% empty %}
    空空如也		//当i没有内容的时候返回这个值
{% endfor %}

django项目学习_第114张图片

  • if else
    注意!:
    if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,过滤器。
    不支持用 算数运算,不支持连续判断
{% if alex.age > 30 %}
    old man!
{% elif alex.age < 30 %}
    young man!
{% elif alex.age|add:1 == 30 %}
    old boy!
{% endif %}

{% if 10 > 5 > 1 %}		和python不同,和js语法相同,10>5True,在拿 True1进行判断
    正确
{% else %}
    错误
{% endif %}

django项目学习_第115张图片

  • with定义中间变量
<!--{% with a=name_list.0 %}-->
{% with name_list.0  as a %}		//两种方式都可以
    {{ a }}
    {{ a }}
    {{ a }}
{% endwith %}

django项目学习_第116张图片

  • csrf_token
    这个标签用于跨站请求伪造保护。在页面的form表单里面写上{% csrf_token %}
    浏览器 访问 服务器 A
    A 返回一个表单 ,但是提交地址写的服务器 B
    B无法判断表单是否符合自己的规范,不符合则拒绝表单的提交。加上csrf_token就OK 了
//form,html
<form action="" method="POST">
    <input type="text" name="k1">
    <button>提交</button>
</form>

django项目学习_第117张图片
浏览器访问提交的时候服务器会拒绝这个POST请求,因为它无法判断这个form表单是自己的还是别的服务器的表单。

<form action="" method="POST">
    {% csrf_token %}		加上这一行标签
    <input type="text" name="k1">		
    <button>提交</button>
</form>

django项目学习_第118张图片
就可以看到转换成了隐藏的一个csrf标签。这样服务器就认可了。

1.跨站请求伪造攻击简介
跨站请求伪造(Cross-site request forgery)简称为CSRF,这是一种对网站的恶意利用。CSRF实际上就是攻击者通过各种方法伪装成目标用户的身份,欺骗服务器,进行一些非法操作,但是这在服务器看来却是合法的操作。

django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。
全局:中间件 django.middleware.csrf.CsrfViewMiddleware;
局部:@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

在渲染模板时,django会把 {% csrf_token %} 替换成一个元素。在提交表单的时候,会把这个token给提交上去。
django默认启动 'django.middleware.csrf.CsrfViewMiddleware’中间件, 这个中间件就是来验证csrf_token的。如果没有加csrf_token,就会出错。

补充:
加减操作我们可以通过add过滤器完成,但是乘除可以通过自定过滤器完成,也可以:
1

<span>{% widthratio 100 2 1 %}</span>		100/2*1		50
<span>{% widthratio 100 1 2 %}</span>		100/1*2		200

2

//mytags.py	新增过滤器

from django.utils.safestring import mark_safe

@register.filter()
def show_a(name,url):
    return mark_safe('{}'.format(url,name))

//html
{{ '百度'|show_a:'www.baidu.com' }}

mark-safe的作用就相当于safe过滤器{{ ‘百度’|show_a:‘www.baidu.com’|safe }},这里的safe就可以省略了

母版和继承

我们通常会在母板中定义页面专用的CSS块和JS块,方便子页面替换。
当前我们的publisher_list和book_list 分开使用了两个相同html样式,没有使用母版,author_list 还未配置html样式。
现在,我们以publisher_list为母版,应用到其他两个页面中:

//publisher_list.html
        {% block main %}

          <h2 class="sub-header">出版社列表</h2>
          <a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a>
          <div class="table-responsive">
            <table class="table table-bordered table-hover">
				...
            </table>
          </div>
            
        {% endblock %}

将中间的内筒部分放到{% block main %}块中,相当于
django项目学习_第119张图片
吧出版社列表这一部分放到了block块中,其他为母版。

改book_list:

{% extends 'publisher_list.html' %}			//这相当于引用了模板的所有内容,包括block中的内容

{% block main %}			//所需需要把内容放到自定义的块中
      <h2 class="sub-header">书籍列表</h2>
      <div class="table-responsive">
        <table class="table table-bordered table-hover">
				...
        </table>
      </div>
{% endblock %}

author_list

{% extends 'publisher_list.html' %}
<h1>asdasdasdasdasdadasdasd</h1>		//这部分内容不在block中,所以不显示	
{% block main %}
    <h2>作者列表</h2>
    <table class="table table-responsive table-hover">	//稍微改了下table头部
		...
	</table>
{% endblock %}

django项目学习_第120张图片

但是现在虽然内容变了,但是左侧选择框没变,我们需要调整。

//publisher_list
        <div class="col-sm-3 col-md-2 sidebar">
          <ul class="nav nav-sidebar">
            <li class="{% block pub_active %}active{% endblock %}"><a href="/publisher_list/">出版社列表</a></li>	/吧active也变成block块
            <li class="{% block book_active %} {% endblock %}"><a href="/book_list/">书籍列表</a></li>
            <li class="{% block author_active %} {% endblock %}"><a href="/author_list/">作者列表</a></li>
          </ul>
        </div>

//book_list
{% block book_active %}
active		//加上自己的active
{% endblock %}

{% block pub_active %}
		//去掉pub_active的active
{% endblock %}

//author_list
{% block author_active %}
active
{% endblock %}

{% block pub_active %}

{% endblock %}

django项目学习_第121张图片
django项目学习_第122张图片
现在点击就随意切换了。

总结:母版和继承
django项目学习_第123张图片
多写一个css和js的block块,方便子页面有自己独立的样式。
django项目学习_第124张图片
django项目学习_第125张图片
django项目学习_第126张图片

组件

我们已经把所欲的list页面写好了,但是add等页面还是原来的样子
django项目学习_第127张图片

我们吧add页面只保留上面的导航栏,就可以用到组件,只保留一小部分的页面。

bapublisher_list页面的导航栏部分的代码单独放到template文件夹下,叫nav.html
django项目学习_第128张图片然后在:django项目学习_第129张图片
include表示包含。效果相同django项目学习_第130张图片

改publisher_add文件,删除左侧内容,include导航栏:
django项目学习_第131张图片

django项目学习_第132张图片

静态文件

//settings中的静态文件是这样定义的

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

//在html页面中使用
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/dashboard.css">
</head>

当我们的STATIC_URL = ‘/static/’ 换一个名字后,我们所有的html里面的内容都得手动换,所以有简单的方式
STATIC_URL = ‘/xx/’

django项目学习_第133张图片
可见我们的样式就没有了。
django项目学习_第134张图片
我们只需要这样引入static文件就可以了。也可以
django项目学习_第135张图片
自动获取我们settongs中的配置。

simaple_tag的定义

和自定义filter类似,只不过接收更灵活的参数。他是标签用法和过滤器的用法略有不同,不能写在{% if/for %}中

//tags.py
\@register.simple_tag
def str_join(*args,**kwargs):
    return "{}_{}".format('_'.join(args),'*'.join(kwargs.values()))

//template_test.html
{% load my_tags %}
{% str_join 'a' 'b' 'c' k1='d' k2='e' k3='f' %}filter{{}} 不同

django项目学习_第136张图片

inclusion_tag

多用于返回html代码片段,simple_tag也可以,不过很麻烦。

新增页码功能
//tag.py
from django import template
from django.utils.safestring import mark_safe
register = template.Library()

@register.simple_tag
def pagination(num):

    li_list = ['
  • {}
  • '
    .format(i) for i in range(1,num+1)] return mark_safe("""""".format(''.join(li_list))) //publisher_list {% block main %} <h2 class="sub-header">出版社列表</h2> <a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a> <div class="table-responsive"> </div> {% load tags %} {% pagination 3 %} 加上这两行 {% endblock %}

    django项目学习_第137张图片
    上面定义了3,则只有3个数字。不过这种方式是很麻烦的。

    //tags.py
    @register.inclusion_tag('page.html')		使用内置标签,将num参数传给'page.html'
    def pagination(num):
        return {'num': range(1,num+1)}
    
    //page.html
    <nav aria-label="Page navigation">
      <ul class="pagination">
        <li>
          <a href="#" aria-label="Previous">
            <span aria-hidden="true">&laquo;</span>
          </a>
        </li>
    
        {% for li in num %}
          <li><a href="#">{{ li }}</a></li>		duinum参数进行循环
        {% ednfor %}
          
        <li>
          <a href="#" aria-label="Next">
            <span aria-hidden="true">&raquo;</span>
          </a>
        </li>
      </ul>
    </nav>
    
    //publisher_list
            {% block main %}
    
                <h2 class="sub-header">出版社列表</h2>
                <a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a>
                <div class="table-responsive">
                </div>
                {% load tags %}
                {% pagination 3 %}		调用函数传参数
    
            {% endblock %}
    
    //book_list
    {% block main %}
        <h2 class="sub-header">书籍列表</h2>
        <div class="table-responsive">
    		。。
        </div>
        {% load tags %}
        {% pagination 10 %}		10{% endblock %}
    
    //author_list
    {% block main %}
        <h2>作者列表</h2>
        <table class="table table-responsive table-hover">
    		...
    	</table>
        {% load tags %}
        {% pagination 2 %}			2{% endblock %}
    

    django项目学习_第138张图片
    django项目学习_第139张图片

    视图

    Django的View(视图)
    一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。

    响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。

    无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你当前项目目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,大家约定成俗将视图放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。

    FBV和CBV

    我们之前使用的方式都是FBV

    FBV function base view
    CBV class base view
    具体选择看情况而定
    

    对比:

    # FBV版添加班级
    def add_class(request):
        if request.method == "POST":
            class_name = request.POST.get("class_name")
            models.Classes.objects.create(name=class_name)
            return redirect("/class_list/")
        return render(request, "add_class.html")
    # urls.py
        url(r'^author_edit/', views.author_edit),			//views.方法名
    
    
    # CBV版添加班级
    from django.views import View
    
    class AddClass(View):
        def get(self, request):
            return render(request, "add_class.html")
    
        def post(self, request):
            class_name = request.POST.get("class_name")
            models.Classes.objects.create(name=class_name)
            return redirect("/class_list/")
    # urls.py
    url(r'^add_class/$', views.AddClass.as_view()),			//views.类名.as_view()
    

    CBV的方式更清晰规范,具体功能二者相同。

    我们测试更改新增出版社这个功能:

    //views.py
    # 新增出版社CBV
    from django.views import View
    
    class PublisherAdd(View):
    
        def get(self,request):
            print('get')
            return render(request, 'publisher_add.html')
    
        def post(self,request):
            print('post')
            pub_name = request.POST.get('pub_name')
            print(pub_name)
    
            if not pub_name:
                return render(request, 'publisher_add.html', {'error': '出版社名不能为空'})
    
            if models.Publisher.objects.filter(name=pub_name):
                return render(request,'publisher_add.html',{'error':'出版社名已存在'})
    
            ret = models.Publisher.objects.create(name=pub_name)
    
            return redirect('/publisher_list/')
    //urls.py
        # url(r'^publisher_add/', views.publisher_add),
        url(r'^publisher_add/', views.PublisherAdd.as_view()),
    

    django项目学习_第140张图片
    测试访问。
    django项目学习_第141张图片
    可见会自动识别我们使用的方法,post请求会直接执行类中的post函数。

    as_view()方法的流程

    views.PublisherAdd.as_view())
    看源码帮助我们了解这个过程

    1. 首先运行项目的时候加载urls.py文件,执行 as_views()方法
      @classonlymethod
      def as_view(cls, **initkwargs):
    2. as_view()是一个类方法,内部定义并返回了返回了一个view函数:return view
    3. 当请求到来的时候,执行view函数:
      • 首先实例化一个PublisherAdd类对象赋给self:self = cls(**initkwargs)
      • 把wsgi的request请求赋给这个对象:self.request = request
      • 执行self.dispatch方法 :return self.dispatch(request, *args, **kwargs)
      • dispatch方法对用户的请求方法进行了判断
      • 允许则通过反射获取对应的方法:handler = getattr(self, request.method.lower(),不允许则返回 handler = self.http_method_not_allowed
          def dispatch(self, request, *args, **kwargs):
              # Try to dispatch to the right method; if a method doesn't exist,
              # defer to the error handler. Also defer to the error handler if the
              # request method isn't on the approved list.
              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		不是则返回405
              return handler(request, *args, **kwargs)
      

    这里要注意http_method_names是view()方法定义好的八种方法,但是当我们自己在类中定义的时候会优先取我们自己定义的,比如:

    class PublisherAdd(View):
        http_method_names = ['get', ]
    

    我们只定义允许get请求。
    django项目学习_第142张图片

    django项目学习_第143张图片
    那我们点击这个提交的post请求就会被拒绝

    CBV加装饰器

    添加一个统计时间的装饰器。

    import time
    def timer(func):
        def inner(*args,**kwargs):
            start = time.time()
            ret = func(*args,**kwargs)
            print('执行时间是{}'.format(time.time()-start))
            return ret
        return inner
    
    • 第一种方式,对个别方法添加装饰器
      method_decorator不会吧self这个对象参数也穿进去,而直接使用@timer就会。
    from django.utils.decorators import method_decorator
    class PublisherAdd(View):
        # http_method_names = ['get', ]
    
        @method_decorator(timer)
        def get(self,request):
            print('get')
            return render(request, 'publisher_add.html')
    
    • 第二种方式,对所有方法加装饰器
    from django.utils.decorators import method_decorator
    
    class PublisherAdd(View):
        # http_method_names = ['get', ]
    
        @method_decorator(timer)		
        def dispatch(self, request, *args, **kwargs):
            ret =super().dispatch(request, *args, **kwargs) #执行View中的dispatch方法
            return ret
    
        # @method_decorator(timer)
        def get(self,request):
    

    这样对dispatch方法加装饰器就可以。

    • 第三种方式,对类加装饰器
    @method_decorator(timer,name='post')
    @method_decorator(timer,name='get')
    class PublisherAdd(View):
    

    django项目学习_第144张图片

    • 第四种方式
    @method_decorator(timer,name='dispatch')
    class PublisherAdd(View):
    

    类似于第二种方式

    而FBV的方式就直接@timer加到上面就行了

    补充:装饰器

    from functools import wraps
    import time
    
    def timer(func):
        def inner(*args,**kwargs):
            start = time.time()
            ret = func(*args,**kwargs)
            print('执行时间是{}'.format(time.time()-start))
            return ret
        return inner
    
    @timer
    def func():
        """
        funcx相关信息
        :return:
        """
        time.sleep(0.5)
        print('func')
    
    func()
    print(func.__name__)
    print(func.__doc__)
    

    django项目学习_第145张图片
    可以看出实际上加装饰器后变成了返回的inner函数

    def timer(func):
        @wraps(func)			加上wraps这个装饰器
        def inner(*args,**kwargs):
            start = time.time()
            ret = func(*args,**kwargs)
            print('执行时间是{}'.format(time.time()-start))
            return ret
        return inner
    

    django项目学习_第146张图片
    则打印出了func的,推荐使用这种方式。

    request对象和response对象

    当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
    Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用wsgi封装 request 参数承接这个对象请求成request对象。

    request

    请求相关的属性

    用法 含义
    request.method 请求方法 GET,POST
    request.GET URL上携带的参数?v1=k1&v2=k2 类似于字典
    request.POST post请求提交的数据,编码方式是URLencode,才有数据,但是body中肯定有
    request.path_info 返回用户访问url,不包括域名ip,端口,以及GET参数
    request.body 请求体,byte类型 request.POST的数据就是从body里面提取到的,GET方法没有请求体,
    scheme 表示请求方案的字符串(通常为http或https)
    encodeing 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 ‘utf-8’)。 这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。
    COOKIES 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。
    FILES 一个类似于字典的对象,包含所有的上传文件信息。 FILES 中的每个键为 中的name,值则为对应的数据。注意,FILES 只有在请求的方法为POST 且提交的 带有enctype=“multipart/form-data” 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。
    META 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器
    user 一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
    session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。完整的细节参见会话的文档。

    在这里插入图片描述

    请求相关的方法:

    Request.get_host() 返回请求的原始主机,例如:"127.0.0.1:8000"注意:当主机位于多个代理后面时,get_host() 方法将会失败
    get_full_path() 返回 path,如果可以将加上查询字符串。例如:"/music/bands/the_beatles/?print=true"
    is_secure() 如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。
    is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串’XMLHttpRequest’。大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。
    response

    from django.shortcuts import render,redirect,HttpResponse

    HttpResponse(‘字符串’) 返回字符串对象
    render(request,‘publisher_list.html’,{‘publishers’:all_publishers}) 返回一个html页面
    redirect(’/publisher_list/’) 重定向

    render是如何实现的:

    def render(request, template_name, context=None, content_type=None, status=None, using=None):
        """
        Returns a HttpResponse whose content is filled with the result of calling
        django.template.loader.render_to_string() with the passed arguments.
        """
        content = loader.render_to_string(template_name, context, request, using=using)
        return HttpResponse(content, content_type, status)
    

    实际上市Django帮我们做了一个渲染,然后在返回一个 HttpResponse,我们发现也可以直接在HTML页面中使用request.去进行渲染

    rediert是如何实现的:

    django项目学习_第147张图片
    我们看到是添加了一个Locaton的请求头以及一个301/302的状态码,我们再去访问publisher_list这个地址。
    我们可以换成:

            # return redirect('/publisher_list/')
            
            ret = HttpResponse('', status=301)		手动重定向,status必须设置为301 302
            ret['Location'] = '/publisher_list/'
            return ret
    

    一些属性HttpResponse.content:响应内容
    HttpResponse.charset:响应内容的编码
    HttpResponse.status_code:响应的状态码

    JsonResponse对象
    import json
    def get_json(request):
        # return HttpResponse({'k1':'v1'})		//只返回k1
        return HttpResponse(json.dumps({'k1':'v1'}))
    

    django项目学习_第148张图片

    我们就可以用Django自带的功能实现

    from django.http import JsonResponse
    def get_json(request):
        data = {'k1':'v1'}
        return JsonResponse(data)
        data = [1,2,3]
        return JsonResponse(data,safe=False)
    

    返回非字典类型内容的时候需要加上safe=False参数,不然会报错
    django项目学习_第149张图片
    返回一个json对象

    上传文件
        url(r'^upload/', views.Upload.as_view()),
    
    class Upload(View):
    
        def get(self,request):
        
            return render(request,'upload.html')
            
    <form action="" method="post" enctype="multipart/form-data">	更换编码方式,才可以通过request.Files看见内容
        {% csrf_token %}
    
        <input type="file" name = "f1">
        <button>上传</button>
    </form>
    

    django项目学习_第150张图片
    在写我们的post方法。

        def get(self,request):
            return render(request,'upload.html')
    
        def post(self,request):
            print(request.POST)	<QueryDict: {'csrfmiddlewaretoken': ['06AeCUIEEBreRGCfz0b3eLtDhXAFQ02ph5Zo0WVonPjlp55PYSOapE1RMiVZvP0E']}>
            print(request.FILES)	<MultiValueDict: {'f1': [<InMemoryUploadedFile: MeetNowUninstall.log (application/octet-stream)>]}>
     				//可以看见request.FILES这个字典对象文件InMemoryUploadedFile       
            file = request.FILES.get('f1')
            print(file,type(file),file.name)
            //MeetNowUninstall.log <class 'django.core.files.uploadedfile.InMemoryUploadedFile'> MeetNowUninstall.log
            file1 = request.FILES['f1']		这两种方式都可以
            print(file1,type(file1),file1.name)
            return HttpResponse('ok')
    
            with open(file.name,'wb') as f:
                for i in file:
                    f.write(i)
                    
            return HttpResponse('ok')
    

    django项目学习_第151张图片

    django项目学习_第152张图片
    文件就上传进去了。图片,视频也是一样,都是通过二进制上传进去 的。

    路由

    Django的路由系统:URL配置(URLconf),也就是urls.py这个文件。就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。
    我们就是以这种方式告诉Django,遇到哪个URL的时候,要对应执行哪个函数。

    正则表达式

    • 基本格式
    
    urlpatterns = [
         url(正则表达式, views视图,参数,别名),
    ]```
    url用法只是1.1的用法
    
    
    ```python
        #路由
        url(r'^blog/',views.blog),
        url(r'^blog/2022/',views.blogs),
    
    views.py
    def blog(request):
        return HttpResponse('it is blog')
    def blogs(request):
        return HttpResponse('it is blogs')
    

    django项目学习_第153张图片
    但是我们访问的时候还是匹配到了blog方法,因为都是以blog开头,所以按照顺序从上往下就先执行了blog方法,而不是blogs,
    所以我们要加上 $
    django项目学习_第154张图片
    现在是静态路由,动态路由就需要正则表达式了:

        url(r'^blog/\d{4}/\d{2}/$',views.blogs),
    

    django项目学习_第155张图片
    django项目学习_第156张图片

        url(r'^blog/[0-9]{4}/[a-z]{2}/$',views.blogs),
    

    django项目学习_第157张图片
    注意

    • urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
    • 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
    • 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
    • 每个正则表达式前面的’r’ 是可选的但是建议加上。

    补充
    django项目学习_第158张图片
    我们url里面写的是/$结尾的,但是我们浏览器访问的时候不加/也会给我们自动重定向到带/的路径,这是Django帮我们做的一件事,可以再setting中控制。
    Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加’/’。

    • APPEND_SLASH=True # 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项

    分组和命名分组

    分组

        url(r'^blog/([0-9]{4})/[a-z]{2}/$',views.blogs),		加了个小括号
    
    def blogs(request,year):		// 将括号括起来的内容传参给blogs函数
        print(year,type(year))		// 2222 <class 'str'>,得到这个参数
        return HttpResponse('it is blogs')
    

    上面的示例使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。
    在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。
    在Python的正则表达式中,分组命名正则表达式组的语法是(?Ppattern),其中name是组的名称,pattern是要匹配的模式。

    每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式

    命名分组
    下面是以上URLconf 使用命名组的重写:

        url(r'^blog/(?P[0-9]{4})/(?P[a-z]{2})/$',views.blogs),
    
    def blogs(request,month,year):
        print(year,month)
        return HttpResponse('it is blogs')
    

    虽然year和month的顺序不一致,但是还是通过关键字匹配到了值

    下面我们改一下publisher_edit的url路径:

        url(r'^publisher_edit/(\d+)/', views.publisher_edit),
    
    # 编辑出版社
    def publisher_edit(request,*args,**kwargs): 	也可以这样直接获取所有的未知参数和关键字参数,分组是位置参数,命名分组是关键字参数。
    def publisher_edit(request,pk):     #添加参数,正则获取到pk
        # print(request)
        # pk = request.GET.get('pk')	#就不需要之前的方式获取了
    
    <!--                            <a class="btn btn-info btn-sm" href="/publisher_edit/?pk={{ i.id }}">编辑</a>-->
                                <a class="btn btn-info btn-sm" href="/publisher_edit/{{ i.id }}">编辑</a>		替换上面的方式
    

    django项目学习_第159张图片
    就可以通过位置参数匹配到了。

    默认参数

        url(r'^pages/$',views.page),
        url(r'^pages/page(?P[0-9]+)/$',views.page),
    
    def page(request,num='1'):		num默认等于1
        return  HttpResponse(num)
    

    在上面的例子中,两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。
    如果第一个模式匹配上了,page()函数将使用其默认参数num=“1”,如果第二个模式匹配,page()将使用正则表达式捕获到的num值。
    django项目学习_第160张图片
    django项目学习_第161张图片
    传递额外的参数给视图函数(了解)

        url(r'^blog/(?P[0-9]{4})/(?P[a-z]{2})/$',views.blogs,{'year':'1998'})
    

    后面的这个year是一个关键字参数,会替代前面year的值

    include其他的URLconfs

    为了便于不同的app开发功能,就需要这种方式:

    在app01下创建一个urls目录,

    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        url(r'^blog/(?P[0-9]{4})/(?P[a-z]{2})/$',views.blogs,{'year':'1998'}),
    
        url(r'^pages/$',views.page),
        url(r'^pages/page(?P[0-9]+)/$',views.page),
    ]
    

    把项目中的urls.py中就可以这样写

    from django.conf.urls import url,include		#导入include
    from django.contrib import admin
    from app01 import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'app01/',include('app01.urls'))		#include app01中的urls.py
        url(r'app02/',include('app02.urls'))		#include app02中的urls.py
    

    这样我们必须访问到 /app01/pages/ 才可以执行views.page这个方法,app01匹配一部分,app01.urls匹配一部分。
    django项目学习_第162张图片

    我们也可以不写app01
        url(r'',include('app01.urls'))
    

    django项目学习_第163张图片
    这样也能访问了,建议加上

    命名URL和URL反向解析

    静态方式
    //urls
        url(r'',include('app01.urls'))
    
    //app01/urls
    urlpatterns = [
        url(r'^blog/$',views.blogs,name='blog'),
        url(r'^blog/(?P[0-9]{4})/(?P[0-9]{2})/$', views.blogs, {'year': '1998'}),
    
        url(r'^pages/$',views.page),
        url(r'^pages/page(?P[0-9]+)/$',views.page),
    ]
    

    无论url里面怎么写,我们都可以通过固定的方式访问到这个地址,类似于起别名。就可以用到反向解析URL、反向URL 匹配。
    django项目学习_第164张图片
    现在我们需要通过http://127.0.0.1:8000/app01/blog/才能访问到这个地址.
    我们创建一个index1.html里面使用a标签指向上面的地址:

    <a href="/app01/blog/">博客</a>
    

    django项目学习_第165张图片
    我们需要写上完整的路径才可以访问到。

    urlpatterns = [
        url(r'^blog/$',views.blog,name='blog'),		给blog的地址起一个别名
    
    //html
    <a href="{% url blog %}">博客</a>		使用这种方法使用别名
    

    django项目学习_第166张图片
    这样的话不论地址改成了什么,我们都可以用别名访问到。

    那我们如果想在py文件函数中通过别名拿到这个地址,应该这样做:

    views.py
    from django.shortcuts import render,HttpResponse,reverse
    from django.urls import NoReverseMatch, reverse		这两种方式都行 
    def blog(request):
        url = reverse('blog')
        print(url,type(url))
        return HttpResponse('it is blog')
    

    django项目学习_第167张图片
    就访问到了。

    分组
        url(r'^blog/([0-9]{4})/([0-9]{2})/$', views.blogs, name='blogs'),	起名为blogs
    
    <a href="{% url 'blogs' '2022' '02' %}">blogs</a>	这种方式去传参
    

    django项目学习_第168张图片
    py文件:

    def blogs(request,month,year):
        print(year,month)
        url = reverse('blogs',args=('2018','11'))
        print(url,type(url))		/app01/blog/2018/11/ <class 'str'>
        return HttpResponse('it is blogs')
    
    命名分组

    命名分组和分组用法相同不过多了一种传参的方式:

        url(r'^blog/(?P[0-9]{4})/(?P[0-9]{2})/$', views.blogs, name='blogs'),
    
    <a href="{% url 'blogs' '2022' '02' %}">blogs</a>
    <a href="{% url 'blogs' month='11' year='2011' %}">blogs</a>	#这种不用注意顺序,通过关键字匹配
    

    上面两种方式都可以了

    py文件中使用:
        url = reverse('blogs',kwargs={'year':'2111','month':'07'})	用kwargs,不用args了
    
    总结

    django项目学习_第169张图片
    django项目学习_第170张图片

    namespace

    //urls
        url(r'app01/',include('app01.urls')),
        url(r'app02/',include('app02.urls'))
    
    //app01/urls
        url(r'^home/$',views.home)
    //app02/urls
        url(r'^home/$',views.home)
    

    django项目学习_第171张图片
    django项目学习_第172张图片

    app01和app02都有共同的name home

    index.html
    
    {% url 'home' %}
    

    django项目学习_第173张图片
    但是我们访问app01/index的时候却访问到了app02,就要用到namespace了

    urls.py
        url(r'app01/',include('app01.urls',namespace='app01')),
        url(r'app02/',include('app02.urls',namespace='app02')),
    

    各自有各自的命名空间。
    不过这是我们在使用反向解析的时候就需要加上namespace的名字才好用:

    views.py
    def blog(request):
        url = reverse('app01:blog')
        print(url,type(url))
        return HttpResponse('it is blog')
    
    index.html
    
    <a href="{% url 'app01:blog' %}">博客</a>
    <a href="{% url 'app01:blogs' '2022' '02' %}">blogs</a>
    <a href="{% url 'app01:blogs' month='11' year='2011' %}">blogs</a>
    {% url 'app01:home' %}
    

    都得加上namespace的名字

    删除功能的合并

    通过一个url地址,一个函数,去实现出版社,书籍以及组着的删除。

    //urls.py
        url(r'^(publisher|book|autor)_del/(\d+)/', views.delete, name='del'),
        url(r'^publisher_list/', views.publisher_list,name='publisher'),	起和(publisher|book|autor)里面相同的名字
        url(r'^book_list/', views.book_list,name='book'),
        url(r'^author_list/', views.author_list,name='author'),
        
    //views.py
    def delete(request,name,pk):
        print(name,pk)
        return HttpResponse('ok')
    
    author/book/publisher_list.html
                    <a class="btn btn-danger btn-sm" href="{% url 'del' 'publisher' i.pk %}">删除</a>
                    <a class="btn btn-danger btn-sm" href="{% url 'del' 'book' book.pk %}">删除</a>
                    <a href="{% url 'del' 'author' author.pk %}">删除</a>
    

    urls.py中用两个分组去匹配功能和对应的id。然后把三个html文件汇总对应的删除按钮的地址链接到del上面,传入book和id参数。
    django项目学习_第174张图片
    点击删除测试正确。
    django项目学习_第175张图片
    并且可以获取到对应的值。我们就可以通过获取的对象去执行删除。

    
    
        # dic = {
        #     'publisher' : models.Publisher,
        #     'book' : models.Book,
        #     'author' : models.Author,
        # }
        cls = getattr(models,name.capitalize())		通过getattr反射获得类名,capitalize()首字母大写
        print(cls)
        cls.objects.filter(pk=pk).delete()
        # 返回重定向
        # return HttpResponse('ok')
        return redirect(reverse(name))			#直接通过reverse获取名字即可
    

    django项目学习_第176张图片
    还可以这样用:

        url(r'^(\w+)_del/(\d+)/', views.delete, name='del'),
        
    //views.py
        if not cls:
            return HttpResponse('请检测表名')
        ret = cls.objects.filter(pk=pk)
        if ret:
            ret.delete()
        else:
            return HttpResponse('要删除的表不存在')
    
        return redirect(reverse(name))
        return redirect(name)
    

    补充一点redirect函数可以自动reverseurl的name。

    模型 ORM

    下面在深入的介绍介绍ORM。

    • 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

    • ORM的优势
      ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。
      ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。
      让软件开发人员专注于业务逻辑的处理,提高了开发效率。

    • ORM的劣势
      ORM的缺点是会在一定程度上牺牲程序的执行效率。
      ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了。
      ORM用多了SQL语句就不会写了,关系数据库相关技能退化…

    新见一个项目进行学习aoout_orm,创建相应的库

    常见字段类型

    class Person(models.Model):
        pid = models.AutoField(primary_key=True)    #自己创建的主键,默认为id
        name = models.CharField(max_length=32)      #varchar32
        age = models.IntegerField()     #一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
        birth = models.DateTimeField(auto_now_add=True)     #auto_now_add:新创建对象时自动添加当前日期时间。auto_now:每次修改时修改为当前日期时间。
    

    django项目学习_第177张图片
    插入一条数据

    D:\Django\about_orm>python manage.py shell
    Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> from app01 import models
    >>> ret = models.Person.objects.create(name='mom',age=50)
    >>> print(ret)
    Person object
    >>> ret
    <Person: Person object>
    >>> ret.age=51
    >>> ret.save()
    >>> ret.age
    51
    

    python manage.py shell可直接加载shell环境。
    django项目学习_第178张图片
    在这里插入的数据是django自动帮我们创建的birth时间。
    注意:auto_now和auto_now_add和default参数是互斥的,不能同时设置。

    自定义字段

    class MyCharField(models.Field):        #所有的类型都继承于Field类
        """
        自定义的char类型的字段类
        """
    
        def __init__(self, max_length, *args, **kwargs):
            self.max_length = max_length
            super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
    
        def db_type(self, connection):
            """
            限定生成数据库表的字段类型为char,长度为max_length指定的值
            """
            return 'char(%s)' % self.max_length
    
    
    class Person(models.Model):
        pid = models.AutoField(primary_key=True)    #自己创建的主键,默认为id
        name = models.CharField(max_length=32)      #varchar32
        age = models.IntegerField()     #一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
        birth = models.DateTimeField(auto_now_add=True)     #auto_now_add:新创建对象时自动添加当前日期时间。
        phone = MyCharField(max_length=11)
    

    django项目学习_第179张图片
    执行makemigrations的时候就会提示为已有的数据添加新字段的默认值,或者在models中添加一个default值,给所有数据添加一个默认值。
    django项目学习_第180张图片

    字段参数

    使用django的admin

    1. 创建超级用户
      django项目学习_第181张图片
      访问
      django项目学习_第182张图片
    2. 注册models
      django项目学习_第183张图片
      在app01下的admin.py注册models。
      django项目学习_第184张图片
      django项目学习_第185张图片

    django项目学习_第186张图片

    class Person(models.Model):
        pid = models.AutoField(primary_key=True)   
        name = models.CharField(max_length=32)  
        age = models.IntegerField()
        birth = models.DateTimeField(auto_now_add=True)  
        phone = MyCharField(max_length=11)
        def __str__(self):
            return '{}-{}'.format(self.name,self.age)		//添加这两行,上面才显示 mom-51
    
    参数 作用
    null 数据库中字段是否可以为空
    db_column 数据库中字段的列名
    default 数据库中字段的默认值
    primary_key 数据库中字段是否为主键
    db_index 数据库中字段是否可以建立索引
    unique 数据库中字段是否可以建立唯一索引
    unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
    unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
    unique_for_year 数据库中字段【年】部分是否可以建立唯一索引
    verbose_name Admin中显示的字段名称
    blank Admin中是否允许用户输入为空
    editable Admin中是否可以编辑
    help_text Admin中该字段的提示信息
    choices Admin中显示选择框的内容(性别),用不变动的数据放在内存中从而避免跨表操作,如:gf = models.IntegerField(choices=[(0, ‘何穗’),(1, ‘大表姐’),],default=1)
    error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date如:{‘null’: “不能为空.”, ‘invalid’: ‘格式错误’}
    validators 自定义错误验证(列表类型),从而定制想要的验证规则

    每次添加字段参数后都需要python manage.py makemigrations以及python manage.py migrate 才能生效

    表的参数

    class Person(models.Model):
        pid = models.AutoField(primary_key=True)    #自己创建的主键,默认为id
        name = models.CharField(max_length=32,null=True,blank=True)      #varchar32
    
        def __str__(self):
            return '{}-{}'.format(self.name,self.age)
    
    	//定义一个类嵌套在person类中
        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 app01_Person
            db_table = "table_name"
            # admin中显示的表名称
            verbose_name = '个人信息'
            # verbose_name加s
            verbose_name_plural = '所有用户信息'
            # 联合索引 
            index_together = [
                ("name", "age"),  # 应为两个存在的字段
            ]
            # 联合唯一索引
            unique_together = (("name", "age"),)  # 应为两个存在的字段,添加了唯一约束
    

    django项目学习_第187张图片
    verbose_name_plural对应这里。

    django项目学习_第188张图片
    verbose_name对应这里。此时库里的表名已经变成了person。

    必知必会13条

    ORM操作

    在django console中的方式执行查询,退出后就不保存了,
    django项目学习_第189张图片
    我们可以:

    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
    import django
    django.setup()		这四行就可以让我们去执行了
    
    from app01 import  models
    
    #all查询所有数据, QuerySet 对象列表[对象,对象]
    ret = models.Person.objects.all()
    print(ret)	///显示 [-23>, -51>, -5>]>,列表类型调用了__repr_
    
    #get 获取唯一一个对象,没有或者多个报错
    ret = models.Person.objects.get(pk=1)
    print(ret)	///显示 aom-23		直接print调用了我们定义的__str__
    
    #filter 获取满足条件的数据 对象列表
    ret = models.Person.objects.filter(age=18)
    print(ret)	///[-18>, -18>]> 
    #order by
    ret = models.Person.objects.all().order_by('age')
    # ret = models.Person.objects.all().order_by('-age')  #降序
    # ret = models.Person.objects.all().order_by('-age').reberse()  #对已经排序的列进行反转
    # ret = models.Person.objects.all().order_by('-age','pid')    #多字段排序
    print(ret)
    
    #value,一个字典列表,拿到对象的指定属性,不指定则拿到所有属性
    ret = models.Person.objects.all().values('pid','name')
    # ret = models.Person.objects.all().values_list('pid','name') #编程元组列表了,不显示字段名了
    for i in ret:
        print(i)
    
    # distinct 去重,.all()可以省略,默认拿的也是all
    ret = models.Person.objects.all().values('age').distinct()
    #count 计数
    ret = models.Person.objects.all().count()
    #first 首个对象
    ret = models.Person.objects.all().first()
    #last 最后一个对象
    ret = models.Person.objects.all().last()
    #exists() 判断是否有结果
    ret = models.Person.objects.filter(pk=10).exists()
    #exclude 排除某个结果
    ret = models.Person.objects.exclude(pk=1)
    print(ret)
    

    django项目学习_第190张图片

    总结

    django项目学习_第191张图片

    单表的双下划线

    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
    import django
    django.setup()
    from app01 import  models
    
    ret = models.Person.objects.filter(age=18)
    print(ret)
    ret = models.Person.objects.filter(age__lt=18)
    print(ret)
    ret = models.Person.objects.filter(age__gte=18)
    print(ret)
    ret = models.Person.objects.filter(pid__in=[1,2,3,4])
    print(ret)
    ret = models.Person.objects.filter(age__range=[40,60])
    print(ret)
    ret = models.Person.objects.filter(name__contains='aom')        #like
    # ret = models.Person.objects.filter(name__icontains='aom')       #like i忽略大小写
    print(ret)
    ret = models.Person.objects.filter(name__startswith='aom')      #以什么开头
    # ret = models.Person.objects.filter(name__istartswith='aom')   #i忽略大小写
    # ret = models.Person.objects.filter(name__endswith='aom')
    # ret = models.Person.objects.filter(name__iendswith='aom')
    print(ret)
    ret = models.Person.objects.filter(name__isnull=True)      #以什么开头
    print(ret)
    
    #对于datetime类型的数据还可以
    ret = models.Person.objects.filter(birth__year='2019')      #以什么开头
    print(ret)
    

    django项目学习_第192张图片

    外键的操作

    对前面的外键操作进行补充。依然使用出版社和书本做演示

    class Publisher(models.Model):
        name = models.CharField(max_length=32,verbose_name='出版社名称')
        def __str__(self):
            return ''.format(self.pk,self.name)
    class Book(models.Model):
        name = models.CharField(max_length=32,verbose_name='书名')
        pub = models.ForeignKey('Publisher',on_delete=models.CASCADE)
        # pub = models.ForeignKey('Publisher',on_delete=models.CASCADE,related_name='books')
        def __str__(self):
            return ''.format(self.pk,self.name)
    

    django项目学习_第193张图片

    django项目学习_第194张图片

    #基于对象的查询
    ##	正向查询,从书籍拿到对应的外键出版社
    book_object = models.Book.objects.get(pk=1)
    print(book_object)
    print(book_object.pub)  #关联的出版社对象
    print(book_object.pub_id)   #关联的出版社对象的id
    ##	反向查询,从一个出版社获取多个书籍
    pub_obj = models.Publisher.objects.get(pk=3)
    print(pub_obj)
    print(pub_obj.book_set,type(pub_obj.book_set))         #定义好的类名,是个关系管理对象: 类名小写_set
    print(pub_obj.book_set.all())     #拿到对应的所有书籍
    
    # 对象管理
    pub_obj = models.Publisher.objects.get(pk=3)
    # print(pub_obj)
    ## set,add,create外键不可以用id,只可以用对象的方式
    pub_obj.books.set(models.Book.objects.filter(id_in=[1,2,3]))
    pub_obj.books.add(*models.Book.objects.all())
    ## remove clear 移除外键的关系,必须设置外键 null=True属性才可以使用这两个方法
    pub_obj.books.clear()
    

    在这里插入图片描述
    这里的book_set是已经定义好的一个类,我们可以通过related_name=‘books’ 去设置它的名称,就没有类名小写_set的写法了

        pub = models.ForeignKey('Publisher',on_delete=models.CASCADE,related_name='books')
        pub = models.ForeignKey('Publisher',on_delete=models.CASCADE,related_name='books',related_query_name='book')
    

    指定related_name='books’不指定related_query_name时使用related_name的值
    指定related_name='books’并指定related_query_name时使用related_query_name的值

    多对多的操作

    多对多的操作用ManyToManyField,django会自动创建第三张多对多的表

    class Author(models.Model):
        name = models.CharField(max_length=32,verbose_name='姓名')
        books = models.ManyToManyField('Book')
    

    在这里插入图片描述
    django项目学习_第195张图片

    author_obj = models.Author.objects.get(pk=2)
    print(author_obj.books)     #关系管理对象
    print(author_obj.books.all())   #关系管理对象的集合
    
    book_obj = models.Book.objects.get(pk=2)
    print(book_obj)
    print(book_obj.author_set.all())        #用法和外键相同都是author_set类型的
    
    ret = models.Book.objects.filter(author__name='aom')
    print(ret)
    ret = models.Author.objects.filter(books__name='爱她')
    print(ret)
    

    django项目学习_第196张图片

    这里也可以用ralate_name ,用法相同上面。

    # 关系管理对象的方法
    ## all查询所有对象
    ## set设置多对多关系 [2,3]
    ## add添加多对多关系
    author_obj = models.Author.objects.get(pk=2)
    author_obj.books.set([2,3]) #修改对应的书籍
    # author_obj.books.set(models.Book.objects.filter(id__in=[2,3])) #修改对应的书籍
    author_obj.books.add(4)
    author_obj.books.add(*models.Book.objects.filter(id__in=[2,3])) #这里不能用李彪,所以用*吧列表拆分
    
    ## remove 删除关系
    author_obj.books.remove(4)		删除pk=2的作者对象对应的id为4的书籍
    author_obj.books.remove(*models.Book.objects.filter(id__in=[2,3]))
    
    ## clear清空多对多关系
    author_obj.books.clear()		,外键字段必须为null=True才可清除
    

    django项目学习_第197张图片

    ## create 通过作者创建新书籍,并与这个书籍建立关系
    author_obj.books.create(name='桃园外传',pub_id=1)
    

    通过书籍创建作者也是同样的方式
    django项目学习_第198张图片
    django项目学习_第199张图片
    通过出版社拿到author信息。

    ret = models.Author.objects.filter(books__pub__name='出版社1').distinct()	去重
    print(ret)
    

    通过books__pub__name找到出版社对象为出版社1的所有作者。

    聚合和分组查询

    aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。

    # 聚合aggregate max和min为聚合值的指定名称,不指定则为price_max
    # ret = models.Book.objects.filter(id__gt=2).aggregate(max=Max('price'),min=Min('price'))
    # print(ret)
    
    # 分组 group by 配合聚合使用
    ## 例子1 统计每一本书的作者数
    ret = models.Book.objects.all()
    print(ret)
    ret = models.Book.objects.annotate(Count('authors'))
    print(ret)
    ret = models.Book.objects.annotate(Count('authors')).values()   #添加了额外信息进结果
    for i in ret:
        print(i)
    

    在这里插入图片描述
    django项目学习_第200张图片
    聚合信息放入了authors_count字段。

    ##例子2 统计每个出版社最便宜的书
    ###方法1
    # ret = models.Publisher.objects.annotate(Min('books__price')).values()
    # for i in ret:
    #     print(i)
    
    ##方法2,按照pub_id,pub_name进行分组
    ret = models.Book.objects.values('pub_id','pub__name').annotate(Min('price'))
    for i in ret:
        print(i)
    

    在这里插入图片描述

    ## 统计不止一个作者的书籍
    ret = models.Book.objects.annotate(count=Count('authors')).values()
    for i in ret:
        print(i)
    ret = models.Book.objects.annotate(count=Count('authors')).filter(count__gt=1)
    print(ret)
    
    ## 对书籍的作者数进行排序
    ret = models.Book.objects.annotate(count=Count('authors')).order_by('-count')
    print(ret)
    
    ## 查询各个作者出的书的总价格
    ret = models.Author.objects.annotate(sum=Sum('books__price')).values()
    for i in ret:
        print(i)
    
    ret = models.Book.objects.values('authors','authors__name').annotate(sum=Sum('price'))
    for i in ret:
        print(i)
    

    django项目学习_第201张图片

    F查询和Q查询

    在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
    Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
    filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
    django项目学习_第202张图片
    Book表新增两列。

    import os
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
    
    import django
    from django.db.models import F,Q
    django.setup()
    
    from app01 import models
    
    #找出sale大于库存的
    ret = models.Book.objects.filter(sale__gt=F('kucun'))   #where 'sale' > 'kucun'
    print(ret)
    # 吧id<3的书的sale的量乘2假13
    ret = models.Book.objects.filter(id__lte=3).update(sale=F('sale') * 2 + 13)
    

    django项目学习_第203张图片

    #找到id小于三或者id大于5 的书籍
    ret = models.Book.objects.filter(Q(id__lt=2)|Q(id__gt=3))
    print(ret)
    

    在这里插入图片描述

    |是或,&是与,~是非。还可以套娃Q(Q(id__lt=2)|Q(id__gt=3))
    ~Q(name__startswith=‘香’)

    事务

    什么是事务? 原子性 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

    import os
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
    
    import django
    from django.db.models import F,Q
    django.setup()
    from app01 import models
    from django.db import transaction
    
    with transaction.atomic():
        models.Book.objects.update(kucun=F('kucun')-10)
        int('sss')		#在这里会报错
        models.Book.objects.update(sale=F('sale')+10)
    

    django项目学习_第204张图片
    虽然 models.Book.objects.update(kucun=F(‘kucun’)-10)已经执行了,但是由于事务的一致性,中途出错了,前面的部分也不会生效
    注释掉后九成宫了:
    django项目学习_第205张图片
    一般为了代码的正常运行,我们可以吧事务加到try里面。不可以它try放到事务里面

    cookies操作

    保存在浏览器的一组键值对
    特性:
    1. 由服务器让浏览器进行设置的
    2. cookies信息保存在浏览器本地,浏览器有权不保存
    3. 浏览器再次访问的时候自动携带对应的cookies信息

    登录后保存登录状态

    给图书管理系统加上登录功能。当前我们不登录也可以进行增删改查的工作,我们想要在登录后才能就进行操作。

        url(r'^login/', views.login),
    
    def login(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
    
            if user == 'alex' and pwd == '123':
                return redirect('publisher')
            else:
                error = '用户名或密码错误'
        return render(request,'login.html',locals())
    
    <form action="" method="post">
        {% csrf_token %}
        <p>
            用户名:<input type="text" name="user">
        </p>
        <p>
            密码:<input type="passowrd" name="pwd">
        </p>
        <p>{{ error }}</p>
        <button>登录</button>
    </form>
    

    django项目学习_第206张图片
    我们可以做判断用户是否登录,登录才可以进行操作。

    当前我们的每个请求之间都是独立的,没有任何的关系,我们可以用cookies保存上一次登录的状态信息。

    Django中如何操作cookie

    def login(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
    
            if user == 'alex' and pwd == '123':
                #登录成功后保存登录状态到cookie中
                ret = redirect('publisher')
                ret.set_cookie('is_login','1')		//这一步
                return ret
            else:
                error = '用户名或密码错误'
        return render(request,'login.html',locals())
    

    django项目学习_第207张图片
    django项目学习_第208张图片
    可见登录的时候服务器给浏览器了一个cookie,浏览器第二次访问的时候就携带上了这个cookie。 print(request.COOKIES)打印cookies信息

    def publisher_list(request):
        all_publishers = models.Publisher.objects.all().order_by('id')
        print(request.COOKIES)
        is_login = request.COOKIES.get('is_login')
        if is_login != '1':
            #没有登录
            return redirect('/login/')
        return render(request,'publisher_list.html',{'publishers':all_publishers})
    

    django项目学习_第209张图片
    没有登陆就直接重定向到了登录页面。那么对于publisher我们就实现了这个功能。。我们可以做成装饰器,给每个页面加上这个功能。

    def login_required(func):
        @wraps(func)
        def inner(request,*args,**kwargs):
            print(request.COOKIES)
            is_login = request.COOKIES.get('is_login')
            if is_login != '1':
                # 没有登录
                return redirect('/login/')
            ret = func(request,*args,**kwargs)
            return ret
        return inner()
    
    @login_required		#加到想加的页面即可
    def publisher_list(request):
    

    登录后跳转到上次登录的界面

    def login_required(func):
        @wraps(func)
        def inner(request,*args,**kwargs):
            print(request.COOKIES)
            is_login = request.COOKIES.get('is_login')
            if is_login != '1':
                # 没有登录
                # return redirect('/login/')
                return redirect('/login/?url={}'.format(request.path_info)) #装饰器中定义好url,在login后获取
            ret = func(request,*args,**kwargs)
            return ret
        return inner
    
    def login(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
    
            if user == 'alex' and pwd == '123':
                url = request.GET.get('url')		#获取上次的url
                if url:
                    return_url = url
                else:
                    return_url = reverse('publisher')		#没有则返回publisher_list界面
                #登录成功后保存登录状态到cookie中
                # ret = redirect('publisher')
                ret = redirect(return_url)
                ret.set_cookie('is_login','1')
                return ret
            else:
                error = '用户名或密码错误'
        return render(request,'login.html',locals())
    

    cookies操作

    设置加密cookies

    def login_required(func):
        @wraps(func)
        def inner(request,*args,**kwargs):
            print(request.COOKIES)
            # is_login = request.COOKIES.get('is_login')
            is_login = request.get_signed_cookie('is_login',salt='aom',default='')	#加密的cookie必须这样获取
            print(is_login)
    。。。。
        return inner
    
    def login(request):
        if request.method == 'POST':
    。。。
                ret.set_signed_cookie('is_login','1',salt='aom')	#使用加密cookie
                return ret
            else:
                error = '用户名或密码错误'
        return render(request,'login.html',locals())
    

    获取Cookie,获取cookie在请求头中:

    request.COOKIES[‘key’]
    request.get_signed_cookie(‘key’,default=RAISE_ERROR, salt=’’, max_age=None)

    get_signed_cookie方法的参数:

    • default: 默认值
    • salt: 加密盐
    • max_age: 后台控制过期时间

    设置Cookie,设置cookie在响应头中
    rep = HttpResponse(…)
    rep = render(request, …)
    rep.set_cookie(key,value,…)
    rep.set_signed_cookie(key,value,salt=‘加密盐’,…)

    参数:

    • key, 键
    • value=’’, 值
    • max_age=None, 超时时间
    • expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.)
    • path=’/’, Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
    • domain=None, Cookie生效的域名
    • secure=False, https传输
    • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖

    设置path参数后,我们登录及book_list页面后在调到publisher_list页面就会要求重新登录,因为cookies没有生效
    设置httponly参数后我们在浏览器的console中九五发用document.cookie获取了

    在这里插入图片描述
    清除cookie

    def logout(request):
        #清除cookies,清除某个键值对
        ret=redirect('/login/')
        ret.delete_cookie('is_login')
        #重定向到登录页面
        return ret
    

    在这里插入图片描述
    具体实现就是把is_login的值设置为空,然后超市时间设置为0,然后就立马失效了。

    你可能感兴趣的:(Python学习,python,django)