django

1、开发环境的搭建

1.安装 pyenv    https://github.com/pyenv/pyenv-installercurl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bashvim ~/.bashrc  export PATH="~/.pyenv/bin:$PATH"eval "$(pyenv init -)"eval "$(pyenv virtualenv-init -)"将其写入最后   source ~/.bashrc echo $PATH   #如果出现这个 /home/qulinx/.pyenv/shims  表示 pyenv 安装成功  pyenv updatecd ~ ls -al .pyenv pyenv install --list pyenv versions  #查看已经安装的Python版本   下载软件包  Python-2.7.9.tar.xz  Python-3.6.4.tar.xzmkdir -p  ~/.pyenv/cache
把它放到  ~/.pyenv/cache pyenv install 3.6.4 -v #-v查看安装过程
pyenv rehash
pyenv global 3.6.4

1.1 pyenv

两个产品 同时使用Python3.6 但是 a产品 使用的是 django 或者flask 1.0 b产品使用的是 djang 或者flask 2.0 为了让这两个产品同时在一个服务器上 运行 需要 pyenv + virtualenv 结合来使用

pip install virtualenv
sudo pip install --upgrade virtualenv 使用virtualenv  1.创建项目目录  sudo mkdir -p ~/myproject/blog  2.切换到项目目录  cd   ~/myproject/blog 3.创建虚拟环境   pyenv virtualenv python版本号  虚拟环境名字   4.pyenv activate 虚拟环境名字 (env36) qulinx@qulinx-virtual-machine:  #表示当前位于env36环境下  在这里可以安装 django 指定的版本  pip install django==2.0.2
5.pyenv deactivate env36   切出虚拟环境  psm pip 默认从官网上下载软件  为了提高下载速度   psm 能够让pip从国内源下载 pip install psm psm ls 列出所有国内源  psm show 查看当前用的哪一个  psm use douban  应用豆瓣 源  

1.2 virtualenv 问题

创建虚拟环境 首先需要创建 项目目录 如果说 不同的人创建项目目录 不在一个地方 不太好管理

需求 :所有的虚拟环境 统一在一个目录下

virtualenvwrapper 让管理虚拟环境 更加简单化

安装 virtualenvwrapper  pip install virtualenvwrappervim ~/.bashrc
将你的用户名替换
if [ -f /home/你的用户名/.pyenv/versions/3.6.4/bin/virtualenvwrapper.sh ]; thenexport WORKON_HOME=$HOME/.virtualenvsexport VIRTUALENVWRAPPER_PYTHON=/home/你的用户名/.pyenv/versions/3.6.4/bin/pythonexport VIRTUALENVWRAPPER_VIRTUALENV=/home/你的用户名/.pyenv/versions/3.6.4/bin/virtualenvsource /home/你的用户名/.pyenv/versions/3.6.4/bin/virtualenvwrapper.sh
fi===================替换好的==========================================================
if [ -f /home/json/.pyenv/versions/3.6.4/bin/virtualenvwrapper.sh ]; thenexport WORKON_HOME=$HOME/.virtualenvsexport VIRTUALENVWRAPPER_PYTHON=/home/json/.pyenv/versions/3.6.4/bin/pythonexport VIRTUALENVWRAPPER_VIRTUALENV=/home/json/.pyenv/versions/3.6.4/bin/virtualenvsource /home/json/.pyenv/versions/3.6.4/bin/virtualenvwrapper.sh
fi
======================================================================================source ~/.bashrc   1.创建虚拟环境
mkvirtualenv  虚拟环境的名字  mkvirtualenv my_env
deactivate  退出虚拟环境
lsvirtualenv  列出所有的虚拟环境
rmvirtualenv 虚拟环境名字  #删除虚拟环境
workon 虚拟环境名字  #切换到指定的虚拟环境
workon 后面什么都不加  列出可以使用的 虚拟环境
workon 虚拟环境名字  然后  cdvirtualenv  #切换到虚拟环境所在的目录  创建虚拟环境的时候指定 Python版本mkvirtualenv --python=python路径  虚拟环境的名字     

1.3 windows virtualenvwrapper安装

pip install virtualenvwrapper-win  mkvirtualenv --python=C:\Python\Python36\python.exe 虚拟环境的名字   创建虚拟环境并制定Python版本
deactivate 退出虚拟环境
workon 虚拟环境名字 #切换虚拟环境
lsvirtualenv  #列出所有的虚拟环境
rmvirtualenv  虚拟环境名字  #删除制定的虚拟环境
cdvirtualenv 切换到虚拟环境所在的目录 C:\Users\neyo>workon my_env
(my_env) C:\Users\neyo>cdvirtualenv
(my_env) C:\1806env\my_env>

1.3.1 创建一个目录用来存放所有的虚拟环境

到这个目录下面就能知道 有哪些虚拟环境  我的电脑 属性  环境变量 系统变量
添加  WORKON_HOMEC:\1806env  #你指定的目录  

1.3.2 准备工作

python 3.6 版本
安装 virtualwrapper
安装 django==2.0
pycharm professional 专业版
安装 mysql5.7
安装 pymysql

2、django 介绍

2003 发布第一个版本 2005年正式版本 Simon、andrian 目的是为了 节省开发者的时间

django版本 Python版本
1.8 2.7 3.2 3.3 3.4 3.5
1.9 1.10 2.7 3.4 3.5
1.11 2.7 3.4 3.5 3.6
2.0 3.4 3.5 3.6
2.1 3.5 3.6 3.7

2.1 web服务器 应用服务器 应用框架

  • web服务器 处理http请求 响应 静态文件 将动态请求传给应用服务器 apache nginx lighttpd 、IIS (微软)
  • 应用服务器 用来处理业务逻辑 Python php j2ee .net web服务器不直接响应 动态请求
  • web应用框架 用Python php java javascript 封装一些常用的web功能
    • django
    • flask
    • struct2 spring3
    • thinkphp laravel zendframwork ci YII2

2.2 mvc设计模式

  • m model 数据模型 一个数据表 就是 一个model
  • v view 视图
  • c controller

2.3 MTV设计模式

  • m model 模型 一个数据表 就是一个model
  • t template 模板
  • v view 视图

2.4 官网

https://docs.djangoproject.com/en/2.1/ 中文文档 +官网

http://python.jobbole.com/87814/ Python + Django 如何支撑了 7 亿月活用户的 Instagram?

https://www.v2ex.com/

https://segmentfault.com/

2.5 url

组成部分 url 又叫统一资源定位符 uniform resource locator

https://www.baidu.com:443/tag/小说/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=Python培训#3https  访问协议  http  https  ftp
www.baidu.com 主机名/域名/ip地址
443  表示端口号
tag/小说  表示查找的路径  表示访问子目录
/?s 表示查询的字符串
#3 锚点  后台不用管 前台用来页面定位用

url 中所有的字符都是 ASCII 码 如果非 ASCII 比如中文 浏览器会先进行编码 然后 传输

3、创建 django 项目

3.1 用命令行的方式

windows cmd  cd 表示查看当前位于哪个目录下
可以创建一个目录 单独用来存放所有的项目
1.先进入虚拟环境  workon 虚拟环境名称
2.django-admin startproject 项目名称
3.进入项目所在的目录
4.启动项目  python manage.py runserver  在浏览器 访问  127.0.0.1:8000
ctrl+c  结束   
  1. 用pycharm
file -newproject -django  选择项目安装目录 existing interpreter  (这里要选择你创建的虚拟环境的目录 )有上角 绿色按钮 启动   红色 按钮停止  编辑配置 里边  选择 single instance only  表示每次 只运行 一个实例 避免 以后多个实例 不知道哪一个真正生效  关闭项目   file->close project  ->然后  出来所有项目的列表 ->点击x即可   

让别人访问你的项目

1.windows 下面    python manage.py runserver 0.0.0.0:8000 然后 在settings.py下面   谁要访问你   你的ip地址ALLOWED_HOSTS = ['10.11.53.58']
2.pycharm editing configuration  host 改为  0.0.0.0 然后制定端口号  然后 在settings.py下面   谁要访问你   你的ip地址ALLOWED_HOSTS = ['10.11.53.58']记得重启  上面会出现一个问题    本地没法访问
只需要在  settings.py下面   谁要访问你   你的ip地址ALLOWED_HOSTS = ['10.11.53.58','127.0.0.1']
重启即可

3.2 django 目录架构

  • manage.py 项目交互都是 基于这个文件 python manage.py 子命令

    • python manage.py startproject
    • python manage.py help
    • 除非有特殊需求 一般这个文件 不编辑
  • settings.py 本项目的设置项 和项目有关的配置都放在 这里边
  • urls.py 配置我们的url 路由的 比如 我们访问 http://127.0.0.1:8000/news 目标是访问新闻页面 在urls中 让你通过 news 找到 新闻页面
  • wsgi.py 专门用来部署 一般不需要修改

3.3 django 项目规范

  • 最开始 希望 不同的模块单独分开 需要创建 一个 Python package

  • django 根据功能进行了分层 比如 豆瓣中的 读书 电影 音乐 模块 所有跟模块相关的views 统一写在 该app下面的views.py中

  • python 提供了一个非常方便的命令

    python manage.py startapp app名称

    创建成功以后 里边自动创建了多个文件 比如 views.py admins.py models.py

    我们需要访问 http://127.0.0.1:9000/movie/

    1.服务器接收 浏览器的请求
    2.先到 settings.py里边查看 ROOT_URLCONF = 'first.urls' 也就是  所有的url请求 都到 first 下面urls.py 下设置   找 urlspatterns 变量
    3.到 first 下面urls.py 下
    from movie import views  #意思是 引入movie 下面的 views.py path('movie',views.movie),4.到 movie 下面  编辑  views.py from django.http import HttpResponsedef movie(request):return HttpResponse("电影首页")

3.4 视图函数

127.0.0.1:9000 movie() 就是它的视图函数

from django.http import HttpResponse
def movie(request): #第一个参数必须是 request 这个参数绝对不能少  return HttpResponse("电影首页") #返回值必须是 django.http.response.HttpResponseBase 的一个子类对象  

3.5 django debug模式

项目下面的 settings.py 第26行  DEBUG = True  默认开启  开启以后好处: 1.每次ctrl+s 以后会自动重启项目 不需要手动重启  2.代码冲出现bug 在浏览器和控制台会提示错误信息 3.在生产环境下面 我们要求关闭掉 debug  4.DEBUG = False  当debug为False  注意   F大写   下面的ip地址 必须设置 ALLOWED_HOSTS = ['127.0.0.1','10.11.53.58']

4、url

4.1 url 传递参数

vim book应用下 views.py

from django.shortcuts import render #这个render 用来渲染模板 后期会学到
from django.http import HttpResponsedef book(request):return HttpResponse("图书首页 多读书多看报少吃零食多睡觉")
def book_detail(request,book_id): #通过参数将book_id传递过去text = "您要请求的图书id为:%s" % book_idreturn HttpResponse(text)def author_detail(request):author_id = request.GET['id']  #用来接收 用户在url中传递过来的参数 text = '作者的id是:%s' % author_id #http://127.0.0.1:9000/book/author/?id=5return HttpResponse(text)
def book_detail(request,book_id,category_id): #传递两个参数的时候  text = "您要请求的图书id为:%s 图书类目id为: %s" % (book_id,category_id) 括号隔开  return HttpResponse(text)

vim 项目下 urls.py

from django.urls import path
from django.http import HttpResponse
from book import views
def index(request):return HttpResponse('首页')
urlpatterns = [path('',index),path('book/',views.book),path('book/detail/<book_id>/',views.book_detail),path('book/detail/<book_id>/<category_id>',views.book_detail)#使用<> 传递参数  记住  <里边的变量> 要跟应用views下面的 参数一样  跟上面的 book_id 一样
]

4.2 指定参数的类型

def publisher_detail(request,publisher_id):text = '出版社的id是:%s' % publisher_idreturn HttpResponse(text)
from django.urls import converters (鼠标放上去 ctrl+点击)
path('book/publisher/<path:publisher_id>',views.publisher_detail)
path('book/publisher/<int:publisher_id>',views.publisher_detail)
path('book/publisher/<uuid:publisher_id>',views.publisher_detail)
path('book/publisher/<slug:publisher_id>',views.publisher_detail)
int 只能是一个或者多个整型数字
path 所有字符都能满足  dc9c10b6-b153-4a25-a102-44cc0e1eb5ea  import uuid  print(uuid.uuid4())
str 除了  /之外的都可以
slug '[-a-zA-Z0-9_]+'当 <> 里边 不写 int  str uuid  默认是 匹配str

4.3 urls模块化

如果项目越来越大 模块越来越多 我们需要在项目主目录下 urls 添加多行 path()

每个app 自己的url 自己管理

首选 需要在 应用 中创建一个新文件 urls.py

vim book/urls.py

from django.urls import path
from . import views
urlpatterns = [path('',views.book),path('detail/<book_id>/',views.book_detail)
]

vim book/views.py

from django.shortcuts import render
from django.http import  HttpResponse
def book(request):return HttpResponse("图书首页")# Create your views here.

vim 主目录下面的urls.py

from django.urls import path,include
urlpatterns = [path('book/',include('book.urls')), #这里边 book后面加了/   app 下面的urls.py 注意不要多加/  urls是拼接起来的
]

4.5 urls 命名 及 命名空间

为什么要给url命名

因为url 是经常变化的 如果在代码中写死 可能会经常该代码  给 url 起个名字 以后使用的时候  对名字进行反转 获取到修改后的url  这时候就不需要写死代码   示例代码 : def index(request):username = request.GET.get('username')if username:return HttpResponse("后台首页")else:login_url = reverse('front:login')print(login_url)# return redirect('/signup/') 这是写死的情况#最好是个变量return redirect(login_url)

4.5.1 app 命名空间

在app 下面的 urls.py中 命名

多个app之间 可能产生相同的url  为了避免 reverse('url名字')产生混乱 可以使用命名空间来区分  定义命名空间只需要在 app 下 urls.py 下面 app_name = '新名字'
revser() 就变成了   reverse('新名字:url名字');示例代码:  cms 下 url
app_name = 'cms'
urlpatterns = [path('',views.index,name='index'),path('signup/',views.login,name='login')  name这里是给url起名字
]

4.5.2 实例命名空间

在主目录下面的urls.py下 命名

一个app 可以拥有多个实例  多个url 可以映射同一个 app 好比 公司有一套后台管理系统    a组 只能通过http://127.0.0.1:9000/cms1   b组只能通过 http://127.0.0.1:9000/cms2 主目录的 urls.py
from django.urls import path,includeurlpatterns = [path('',include('front.urls')),path('cms1/',include('cms.urls',namespace='cms1')),path('cms2/',include('cms.urls',namespace='cms2')), #多个url 指向一个 app
]views.py#当你用cms1访问的时候 用cms1的命名空间#cms2访问  用cms2的命名空间#获取当前的命名空间current_namespace = request.resolver_match.namespaceprint(current_namespace)urls = reverse("%s:login" % current_namespace)return redirect(urls)

4.5.3 include函数

include(moudule,namespace=None)
* moudule 子url的模块  比如 cms.urls
* namespace 给每个实例七个名字  如果说 你在主目录下面的path 里边写了 namespace 实例命名空间 但是 没有写 应用命名空间 app_name
会报错 path('book/',include('book.urls',namespace='book')),
path('book/',include('book.urls','book')),

4.5.4 re_path

re_path 跟 path 作用一样 不同的是 re_path 写url的时候 可以使用正则表达式 功能更强大

写正则表达式的时候 推荐使用原声字符串
如果re_path 正则表达式定义变量 用 ()包裹起来 看成一个分组

参数有名字 前面需要加 ?P ?P<参数名字 >后面跟正则表达式规则

urlpatterns = [#r'' 表示原生字符串 python正则表达式中  加上rre_path(r'^$',views.article),#http://127.0.0.1/article/list/year#在python中参数有名字 那么需要使用?P<参数名字>re_path(r'^list/(?P<year>2\d{3})/$',views.article_list),re_path(r'^list/(?P<month>\d{2})/$',views.article_list_month),
]

**如果不是特殊要求 (不得不使用正则表达式来解决 ) 能用path 就是用path re_path 容易把代码弄复杂 **

4.5.5 reverse 补充

如果在reverse url的时候 需要添加参数(鼠标放到reverse上 CTRL+点击)  可以添加 kwargs 参数  到reverse 中  示例代码
def index(request):username = request.GET.get('username')if username:return HttpResponse("首页")else:#login_url = reverse('login')#打开首页 自动跳转到 http://127.0.0.1/detail/10/20#detail_url = reverse('detail',kwargs={"book_id":10,"page":20})#想要打开首页自动跳转到 http://127.0.0.1/login/?next=#return redirect(detail_url)#login_url = reverse('login',kwargs={"?next=":"haha"})login_url = reverse('login')+"?next=/"return redirect(login_url)def login(request):return HttpResponse("登录页")
# Create your views here.def book_detail(request,book_id,page):text = "您的图书id是:%s" % book_idreturn HttpResponse(text)如果需要添加 查询字符串的参数  比如 打开首页 http://127.0.0.1:9000 自动跳转到 http://127.0.0.1:9000/?next=/这个时候  kwargs 参数不生效   只能  login_url = reverse('login')+"?next=/"  这种形式

4.6 url 默认参数

打开糗事百科  默认查看第一页内容   用户可以选择其它页面
在使用 path 或者 re_path的时候  通过传递参数 打开首页 http://127.0.0.1:9000 显示默认内容  from django.urls import path
from . import viewsurlpatterns = [#path('admin/', admin.site.urls),path('',views.benshan),path('page/<int:page>/',views.benshan)
]from django.http import HttpResponse
dongbeiF4 = ['尼古拉斯赵四','刘能','宋小宝','小沈阳'
]
# def index(request):
#     return HttpResponse(dongbeiF4[0])def benshan(request,page=0):return HttpResponse(dongbeiF4[page])

5、模板

上几节课 我们 直接返回的是文本 生产环境 页面需要样式 为了让 页面漂亮 市场上 好多不错 模板系统

其中就有 DTL jinja2 django 默认自带 DTL

**DTL django template language ** 模板引擎

DTL 和 html的区别 :

DTL 是由特殊语法的html 会解析参数 让页面更加动态化   编译完成以后 生成一个普通的html文件  然后发给客户端

render_to_string 先找到 模板 (页面) 将模板编译以后将内容渲染成python字符串格式 然后通过 HttpResponse 类 包装成 一个 HttpResponse 对象 返回给 前台

from django.shortcuts import render
from django.http import HttpResponse
from django.template.loader import render_to_string# Create your views here.
def index(request):html = render_to_string(‘index.html’)return HttpResponse(html )#以上可能有点繁琐   from django.shortcuts import render
def index(request):return render(request,'index.html')

5.1 模板查找路径配置

settings.py 中有一个  TEMPLATES 配置
BASE_DIR 你的项目目录
'DIRS': [os.path.join(BASE_DIR, 'templates')]  项目目录下templates文件夹
也就是说  render render_to_string 会来这里 查找模板 #'DIRS': [r"C:\templates"]  可以自定义主 templates 文件夹位置  'APP_DIRS': True, 要记得 在 settings.py中
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','book'  #加入你创建的应用  要不然找不到这个模板
]
APP_DIRS 为true  先到 app 应用下面的 templates 查找  没有 到 主目录下面  Flase 直到 主目录下面  templates文件下查找 页面   1.先到应用自己的目录下面的 templates 2.如果自己目录没有 到其它应用 templates 下  3.其它应用也没有  到主目录下面templates 下   4,全部没有  抛异常 templatedoesnotexists 异常
如果 为 false  不从应用下面找  直接从主目录下  templates下查找
顺序:
render(request,‘home.html’)
先到  DIRS 有就返回 没有  到自己的应用 templates下面   别忘了应用加入到  INSTALLED_APPS  如果有 返回
如果没有 到 其它已经安装的应用下面  templates文件夹下  都没有 抛异常 templatedoesnotexists 异常

5.2 IDE

  • pycharm
  • sublime
  • atom
  • hbuilder

5.3 django 模板常用的变量

1.在模板中使用变量 需要将其放入  {{变量 }}# haha = {#     'username':'兰陵笑笑生',#     'age':18# }{{username }}
2.访问对象的属性   对象.属性名来访问 class Person(object):def __init__(self,username):self.username = usernamequlinx = Person("秋林兄")# haha ={#     'person':qulinx# }{{person.username}}3.访问字典对应的value值   字典.key   不能通过[]访问   contexts = {#     'person':{#         'username':'qulinx666',#         'keys':'abc'   尽量不要使用 keys 作为key ##     }# }{{person.username}}4.访问 元组 或者列表   contexts = {'person':['红楼梦','三国演义','西游记','水浒传',]}{{person.0}}   #红楼梦

5.4 django 模板常用的标签

所有的标签都是在{% 中  %}
if 判断条件  == != < <= > >= in not in is is not
if有关闭标签 {% endif %} 因为模板不像python 有缩进  必须要有关闭
elif
else举例子  context = {'age':18}return render(request,'index.html',context=context){% if age < 18   %}<p>您还没成年,不能大宝剑</p>{% elif age == 18 %}<p>您满18周岁了 可以进来了</p>{% else %}<p>虽然你成年了但是大宝剑不是想有就有的</p>
{% endif %}{% if '雷鸣兄' in Tokyo %}<p>雷鸣兄正在工作</p>{% else %}<p>雷鸣兄正在待命</p>
{% endif %}

for in

类似于python 中的 for…in…

  • 元组
  • 字典
  • 列表
  • 字符串等
{% for book in books %}  正序遍历 <li>{{ book }}</li>
{% endfor %}{% for book in books reversed%}  倒序遍历  <li>{{ book }}</li>{% endfor %}{% for book in books %}<tr><td>{{ forloop.counter0 }}</td><td>{{ book.name }}</td><td>{{ book.author }}</td><td>{{ book.price }}</td></tr>
{% endfor %}forloop.counter 当前循环的下标 以 1开始
forloop.counter0 当前循环的下标 以 0开始
forloop.revcounterforloop.revcounter0
forloop.first 是否第一次遍历  第一行
forloop.last 是否是最后一次遍历 最后一行
forloop.parentloop 多个循环嵌套的情况下  这个表示上一个for  <tbody>{% for book in books %}{% if forloop.first %}<tr style="background: pink">{% elif forloop.last %}<tr style="background: green">{% else %}<tr>{% endif %}<td>{{ forloop.counter }}</td><td>{{ book.name }}</td><td>{{ book.author }}</td><td>{{ book.price }}</td></tr>{% endfor %}</tbody>遍历的对象没有任何元素的情况下   用{%empty%} 去匹配 显示 默认内容 比如 <ul>{% for content in contents %}<li>{{ content }}</li>{% empty %}<li>暂时没有任何人评论赶紧抢沙发</li>{% endfor %}</ul>遍历字典 ‘items’ ‘keys’ ‘values’ 'person':{'username':'zelinx','sanwei':'18 80 100','height':'181cm',}<ul>{% for key in person.keys %}<li>{{ key }}</li>{% endfor %}</ul><ul>{% for key in person.values %}<li>{{ key }}</li>{% endfor %}</ul>{% for key,value in person.items %}<p>{{ key }}</p><p>{{ value }}</p>{% endfor %}

5.5 autoescape 标签

自动转义 DTL 自动开启了转义 < &lt 比如
<a href='http://www.baidu.com'>百度大叔</a>
&lt;a href='http://www.baidu.com'&gt;百度大叔&lt;/a&gt;
你要是不清楚自己相干什么的情况下 最好是 开启 自动转义 防止XSS漏洞攻击
如果变量完全信任  那么可以 关闭自动 转移  {% autoescape off %}变量{% endautoescape %}{% autoescape on %}{{ info }}{% endautoescape %}<a href="http://www.qfedu.com">qianfeng</a>

5.6 url 标签

url 标签  因为  我们跳转的url 不能写死 最好是 通过反转的形式  跟 reverse 差不多具体写法  <li><a href="{% url 'book' %}">读书</a></li><li><a href="{% url 'city' %}">同城</a></li><li><a href="{% url 'movie' %}">电影</a></li>book  city movie 都是 url的名字 path('book/',views.book,name='book'),  先根据 name  将url反转也就是 取出 /book/然后交给 index.htmlpath('movie/',views.movie,name='movie'),path('city/',views.city,name='city'),如果 跳转 需要添加参数  python: path('book/detail/<book_id>/<category>/',views.book_detail,name='detail'),DTL:<li><a href="{% url 'detail' book_id=1 %}">转发锦鲤转给属龙的人</a></li>
传递参数  空格 隔开 即可 <li><a href="{% url 'detail' book_id=1 category=2 %}">转发锦鲤转给属龙的人</a></li>
加 ?next   查询字符串的形式  <li><a href="{% url 'login' %}?next=/">登陆</a></li>

5.7 with 标签

模板中想要定义变量  可以使用with语句    {#    <p>{{ people.0 }}</p>#}
{#    <p>{{ people.0 }}</p>#}
{#    <p>{{ people.0 }}</p>#}
这个方式 有点 麻烦   可以使用下面两种方法   {% with zelinx=people.0 %}   要求  =号前后 不能有 空格   <p>{{ zelinx }}</p><p>{{ zelinx }}</p><p>{{ zelinx }}</p>{% endwith %}{% with people.0 as qulinx %}<p>{{ qulinx }}</p><p>{{ qulinx }}</p><p>{{ qulinx }}</p>{% endwith %}

5.8 verbatim 标签

后期做项目 可能不止 DTL模板引擎 还有 https://github.com/aui/art-template 或者 vue verbatim 标签 能够让 两个模板引擎共存

项目过程中 模板引擎 可能不止django自带的 DTL 还有 VUE VUE也是{{ }} 为了让部分代码  使用vue 解析方法    可以对 这部分代码  用  {% verbatim %}{{ 这里的代码用vue或者 art-template引擎 解析    }}{% endverbatim %}其它 用DTL来解析  {{ person.0 }}      这个输出的是  zhangsan   {% verbatim %}{{ kangbazi }}   这个输出的 是    {{kangbazi}}{% endverbatim %}

5.9 模板结构优化

每个页面都包含 head  底部部分  如果说 N页面 全部在重新写一遍 有点浪费时间 如果修改 全部都要改
现在 一个办法是  把公共的部分抽离出来    其它页面只需要引入这个公共部分即可   修改的话 只需要修改公共部分一个文件即可    1.首先将相同的代码 比如 header  footer 统统单独写到一个文件中  比如 header.html  其它页面只需要
{% include '文件的名字' %}{% include '文件的名字' %}如果想要把 原本只有 固定的页面显示的变量 在 其它页面也显示   {% include '文件的名字' with 变量名=‘值’%}示例代码 :
{% include 'header.html' with username='haha'%}<div class="content">这是公司信息</div>{% include 'footer.html' %}

模板继承

将所有相同部分的代码 放到一个base.html中
其它页面{%extends 'base.html'%}
如果想要 显示base.html中的  特殊内容
{{block.super}}示例代码
base.html <!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><header><ul><li><a href="/">首页</a></li><li><a href="{% url 'company' %}">公司</a></li><li><a href="{% url 'talk' %}">言职</a></li>{{ username }}</ul>
</header>
<div class="content">{% block t0 %}今天下午五点开校委会  #除了这里不同 其它全部相同  {% endblock %}
</div>
<footer>欢迎下次再来 这是页脚部分
</footer>
</body>
</html>index.html {% extends 'base.html' %}   #继承模板网页{% block t0 %}我是首页的代码<p>{{ block.super }}</p>  继承于 base.html 特殊的内容(今天下午五点开校委会)  {% endblock %}

5.10 模板template加载静态文件

在 DTL中 使用 static 来加载 静态文件 如果想要使用 还必须 {% load static%}

加载静态文件的步骤如下:

1.确保 ‘django.contrib.staticfiles’ 已经加入到了 INSTALLED_APPS

2.STATIC_URL 确保setting.py中设置了 #127.0.0.1/static/1.jpg #在浏览器中请求 静态文件的url

3.首先在应用下面 新建一个 目录 叫 static 在static 目录下面 再创建一个 以 app 名字 命名的 目录

4.到 INSTALLED_APPS 注册 你的应用

5.10.1 主目录中加载static

当static 不跟 所有的应用挂钩 需要这么操作

settings.py 加入
STATICFILES_DIRS  = [os.path.join(BASE_DIR, 'static')
]static 跟  templates 同级  

如果不想每次加载静态文件 都 {%load static %} 在 settings.py中 加入

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',],'builtins':['django.templatetags.static'],  #在这里加入  },},
]

6、过滤器

其实就是 函数  我们可以通过在 模板中定义函数 来实现  我们的 目的
在 DTL模板中  不支持  圆括号传递参数
{{greet('ee')}}
正确的语法是 :
{{value|函数名字:值}}

6.1 常用的过滤器

add 过滤器

将值和参数转成整型以后进行相加 如果不能转成整型 将值和参数 进行拼接

{{value|add:参数}}

源代码 : def add(value, arg):"""Add the arg to the value."""try:return int(value) + int(arg)except (ValueError, TypeError):try:return value + argexcept Exception:return ''示例代码: context = {'num1':4,'num2':10,}{{ num1|add:num2}}  返回14

cut 过滤器

类似于 python中的replace 移除值中指定的字符串

{{value|cut:""}}

{{ "hello world"|cut:"hello" }}  #返回的结果 world

date 时间过滤器

{{value|date:""}}{{ today|date:"Y-m-d h:i:s" }}Y 四位数年份 m 两位数月份  1-9  01-09 n 1-9 前面不加0  d 两位数字的天  h 小时  12小时  0前缀  i 分钟  0前缀s 秒  0前缀  H 小时  24小时制  0前缀  G 小时  前面没有 0前缀

default 过滤器

{{value|default:"默认值"}}判断 value 是否为真   如果真 显示 value的值  如果为假 显示默认值     为false的情况  [] "" None {}

default_if_none过滤器

{{value|default_if_none:"默认值"}}只有 value 值为   None的时候  才显示默认值  如果 "" [] {} 认定为真   显示空字符串 等   context = {'value':None}

first 过滤器

返回 列表 元组 字符串的第一个元素

{{value|first}}

last 过滤器

返回 列表 元组 字符串的最后一个元素

{{value|last}}

floatformat 过滤器

使用四舍五入的方式 格式化 一个浮点类型

{{value|floatformat}} 默认保留一位小数点
3.00000  返回 3
3.1415  返回3.1
3.15 返回3.2 四舍五入 保留一位小数点  {{ value|floatformat:3 }} 保留3位小数点

join 过滤器

将列表 元组 字符串 使用 符号进行拼接

{{value|join:"@"}}

length 过滤器

列表 元组 字符串 字典的长度

{{value|length}}

lower 过滤器

所有的字符串 转成小写

{{value|lower}}

upper过滤器

所有的字符串转成大写

{{value|upper}}

random 过滤器

随机取出 列表 元组 字符串中的任意一值 点名器

{{value|random }}

safe 过滤器

{{ value|safe }}

标记 里边的字符串是安全的  你不用给我转义

slice 过滤器

类似于python中的切片操作

{{ value|slice:"0:"}}
从0开始截取到最后
{{ value|slice:"0::2"}} context = {'value':"0123456789"}输出02468

striptags过滤器

删除字符串中所有的 html标签

{{ value|striptags }}context = {'value':"<script>alert('我的每一支笔都知道你的名字')</script>"}
最后输出 alert('我的每一支笔都知道你的名字')

truncatechars 过滤器

如果给定的字符串超过了 过滤器要求的长度 那么就会进行切割 然后拼接 … 作为省略号

{{value|truncatechars:5}} 5这里 合理是指  2个字符+3个点 context = {'value':"<p>小爷,欢迎下次光临</p>"}
最后结果: <p...

truncatechars_html 过滤器

如果给定的字符串超过了 过滤器要求的长度 那么就会进行切割 然后拼接 … 作为省略号 跟上面的区别是 它不切割 html标签 只切割字符串

context = {'value':"<p>小爷,欢迎下次光临</p>"}{{ value|truncatechars_html:6 }}最终结果
<p>小爷,...</p>

-------------------以上都是 DTL内置过滤器 相当于系统提前给你写好的函数 但是有时候还不能满足所有的需求 这个时候需要自定义 过滤器 也叫自定义函数 记住 过滤器最多两个参数 ----------

6.2 自定义 过滤器

  • 模板的过滤器必须放在 app中 然后在app下面创建一个 python包 这个包的名字 必须叫 templatetags 在包里再创建一个python文件

    目录结构: bookmodels.pyviews.pyurls.pytemplatetags my_filter.py
    
  • 到settings.py INSTALLED_APPS写上应用的名字

  • 接下来 就是在my_filter.py写过滤器代码

  • 过滤器最多两个参数 (一般为 value ,XXXX)

  • 过滤器的第一个参数 永远是被过滤的那个参数 也就是 | 左边 那个 (value)

  • 然后将其注册到 模板中

      register = template.Library()register.filter("kangbaba",kangbazi)
    
  • 最后在页面上 要 {%load my_filter%}

    示例代码

from django import  template
register = template.Library()   #这里是产生一个注册对象
def kangbazi(value,word):return value+wordregister.filter("kangbazi",kangbazi)  #将其注册进去这是在页面上显示的名字   后面是自定义的名字  {% load my_filter %}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>自定义过滤器 自定义函数 </title>
</head>
<body>{{ value|kangbazi:"下次再来" }}
</body>
</html>from django.shortcuts import render
def index(request):context = {'value':'qulinx'}return render(request,'index.html',context=context)
# Create your views here.

6.3 自定义过滤器实战

发表微博 或者 空间动态的时候  一般不会直接显示发布的精确时间  而是 刚刚   *分钟之前  *小时之前 *天前
超过这些时间  显示  年月日 时分秒  from django import  template
from datetime import datetime
register = template.Library()
def kangbazi(value,word):return value+wordregister.filter("kangbaba",kangbazi)def time_since(value):'''刚刚  1分钟以内大于1分钟 小于1小时  多少分钟之前大于1小时 小于 24小时 几个小时之前大于24小时 小于30天   几天前否则 显示具体时间  2018/09/13 10:12:13'''now = datetime.now() #获取当前的时间timestamp = (now-value).total_seconds() #当前时间 减去 发表时间的  间隔秒数if timestamp < 60:return '刚刚'elif timestamp>=60 and timestamp<= 3600:minutes = int(timestamp/60)return '%s分钟之前' % minuteselif timestamp>=60*60 and timestamp<= 60*60*24:hours = int(timestamp/60/60)return '%s小时之前' % hourselif timestamp>=60*60*24 and timestamp<= 60*60*24*30:days = int(timestamp/60/60/24)return '%s天之前' % dayselse:return value.strftime("%Y-%m-%d %H:%M")
register.filter("times",time_since)times是在页面上显示的过滤器名称views.pyfrom django.shortcuts import render
from datetime import datetime
def index(request):context = {'value':'qulinx','mytime':datetime(year=2018,month=9,day=13,hour=15,minute=56,second=10)}return render(request,'index.html',context=context)index.html {% load my_filter %}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>自定义过滤器 自定义函数 </title>
</head>
<body>{{ value|kangbaba:"下次再来" }}{{ mytime|times }}
</body>
</html>

7、数据库

django 操作 mysql  底层还是python操作mysql  想操作没问题  必须安装扩展或者叫做 驱动程序  pymysql  纯python 写的  执行效率不如 MySQL-python 高
MySQL-python c语言写的  只支持puthon2  不支持python3
mysqlclient MySQL-python 的一个分支  修复了bug 支持了python3 综上考虑  选择 mysqlclient  https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient 下载下来之后 放到 一个  目录下面
然后 pip install mysqlclient-1.3.13-cp36-cp36m-win_amd64.whl  即可 

7.1 django 操作数据库

settings.py中 有一个 DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'djangodb', #数据库的名字'USER':'root', #用户名 'PASSWORD':'123456', #密码'HOST':'127.0.0.1', #连接地址'PORT':'3306' #端口号 }
}from django.shortcuts import render
from django.db import connectiondef index(request):cursor = connection.cursor()#cursor.execute("insert into book(name,author) values('三国演义','罗贯中'),('红楼梦','曹雪芹')")cursor.execute("select * from book")rows = cursor.fetchall()for row in rows:print(row)#rows = cursor.fetone()#rows = cursor.fetchmany(2)return render(request,'index.html')

7.2 mysql引擎

show engines;myisam  1.   frm  表结构文件2.   myd  数据文件3.   myi  索引文件 当 对 读 和插入要求 高的话 一般选择   myisam    myisam 支持全文索引 不支持事务  表锁  不支持外键
innodb 1.ibd  数据和索引 共享一个表空间 2.frm  表结构文件  如果对 数据 安全性要求很高 也就是 经常对数据 更新 和删除操作   要使用innodb mysql5.6 以后 默认的引擎是  innodb  mysql5.7 以后 innodb 也支持全文索引  支持事务  行锁 支持外键

8、实战项目

bookmanager

9、ORM 模型

项目增大 sql语句越来越复杂  大量采用 原声sql语句 会出现以下问题  : 1.重复利用率不高  需要写多个重复的sql语句  2.好多程序员 容易忽略 安全问题容易导致 sql注入 3.如果哪天业务逻辑放生变化  我们需要修改多条sql语句  容易遗漏某些sql语句  导致报错   为了解决这个问题 就出现了 ORM object relational Mapping  对象关系映射 通过ORM 我们可以使用 类来操作mysql 不用写原生sql语句   把每个数据表映射为一个类 数据行映射为一个个实例 字段映射为属性    1.这样 减少重复利用的概率   看起来直观清晰
2. ORM归根到底 是 转化为原声sql语句去操作数据库  有人觉得 转化过程容易出现损耗  损耗不会超过5%
3.写复杂的查询 更简单
4.可以将ORM移植到 sqlite mysql等

9.1 创建 ORM 模型

创建 应用  并到 settings.py中 注册应用  INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','book',
]
到应用下面 的 models.py  from django.db import models#如果想要将普通的一个类 映射为 数据库中的ORM模型
# 必须继承  models.Model
class Book(models.Model):#1.id int(11)  自增长id = models.AutoField(primary_key=True)#2.name varchar(100) not nullname = models.CharField(max_length=100,null=False)#3.author varchar(100) not nullauthor = models.CharField(max_length=100,null=False)#4.price float not nullprice = models.FloatField(null=False,default=0)接下来
python manage.py makemigrations   #生成迁移脚本 Migrations for 'DBrom':DBrom\migrations\0001_initial.py- Create model Book(my_env) c:\www\db\_intr_demo>python manage.py migrate #映射到数据库中
Operations to perform:Apply all migrations: admin, auth, book, contenttypes, sessions
Running migrations:Applying contenttypes.0001_initial... OKApplying auth.0001_initial... OKApplying admin.0001_initial... OKApplying admin.0002_logentry_remove_auto_add... OKApplying contenttypes.0002_remove_content_type_name... OKApplying auth.0002_alter_permission_name_max_length... OKApplying auth.0003_alter_user_email_max_length... OKApplying auth.0004_alter_user_username_opts... OKApplying auth.0005_alter_user_last_login_null... OKApplying auth.0006_require_contenttypes_0002... OKApplying auth.0007_alter_validators_add_error_messages... OKApplying auth.0008_alter_user_username_max_length... OKApplying auth.0009_alter_user_last_name_max_length... OKApplying book.0001_initial... OKApplying sessions.0001_initial... OKsettings.py中  INSTALLED_APPS 写了多少 就创建多少模型

9.2 ORM 增删该查

from django.shortcuts import render
from .models import Book
from django.http import HttpResponse# Create your views here.
def index(request):#添加数据到数据库中# book = Book(name="金瓶梅",author='兰陵笑笑生',price='1000')# book.save()#查询数据库# book = Book.objects.get(pk=1)# print(book)# books = Book.objects.filter(name="金瓶梅").first()# print(books.price)#删除数据# book = Book.objects.get(pk=1)# book.delete()#修改数据book = Book.objects.get(pk=1)book.price = 10.1book.save()return HttpResponse('图书添加成功')

orm 增删该查 扩展

book = Book.objects.filter(name="三国演义",desc='name').first()books = Book.objects.order_by("-price")  #从高到低进行排序  for book in books:print(book.name)return HttpResponse("图书保存成功")books = Book.objects.order_by("price")  #从低到高进行排序  for book in books:print(book.name)return HttpResponse("图书保存成功")

9.3 外键 及表关系

class User(models.Model):username = models.CharField(max_length=20,null=False)password = models.CharField(max_length=100)class Article(models.Model):title = models.CharField(max_length=100,null=False)content = models.TextField()author = models.ForeignKey("User",on_delete=models.CASCADE)参数: on_delete=1.on_delete=models.CASCADE  删除掉user表里边的 那么 article 与其关联的数据 一并删除 2.on_delete=models.PROTECT 受保护  不让你删除    3.on_delete=models.SET_NULL,null=True4.on_delete=models.SET_DEFAULT,default=User.objects.get(pk=2) 关联的用户删除以后 这个字段会立马变成默认值  5.on_delete=models.DO_NOTHING 1  2 4  用的比较多    以上 这些 是  django级别  最终还要看数据库级别

表关系 :

==一对一== ==一对多== ==多对多==

9.3.1 一对 多

一篇文章  一个作者写       一个作者可以写多篇文章  作者 和文章之间的关系  就是   一对多  def one_to_many_view(request):# article = Article(title="红楼梦",content="12213阿达")# category = Category.objects.first()# author = FrontUser.objects.first()# article.category = category# article.author = author## article.save()# return HttpResponse("添加成功")#获取某个分类下面的所有的文章# category = Category.objects.first() #获取指定的分类 # articles = category.article_set.all() #自动的产生一个article_set# for article in articles:#     print(article.title)#将文章添加到指定的分类中category = Category.objects.first() #先获取到分类article = Article(title="ccc",content="hahaha") #创建文章article.author = FrontUser.objects.filter(id="2").first() #选中作者category.article_set.add(article,bulk=False)return HttpResponse("添加成功")

9.3.2 一对一

一个用户只有一个身份证号   只有一个用户详情
在django 中  一对一是通过  models.OneToOneFiled 第一步 我们在 frontuser 里边  新添加了一个模型 叫用户详情
class FrontUser(models.Model):name = models.CharField(max_length=100,null=False)def __str__(self):return "<FrontUser:(id:%s,name:%s)>" % (self.id,self.name)class UserExtension(models.Model):school = models.CharField(max_length=100)user = models.OneToOneField("FrontUser",on_delete=models.CASCADE)def __str__(self):return "<UserExtension:(id:%s,school:%s,user:%s)>" % (self.id,self.school,self.user.id)2.在article 模型中  创建  控制器  def one_to_one(request):user = FrontUser.objects.filter(id="2").first() 首先将 id为2的 用户查出来   extension = UserExtension(school="千锋") #然后给他添加详情  extension.user = user 赋值外键的内容   extension.save()  #保存  return HttpResponse("添加成功")

9.3.3 多 对 多

一个文章  可以 拥有多个标签    一个标签可以被 多篇文章使用    class Article(models.Model):title = models.CharField(max_length=100,null=False)content = models.TextField()category = models.ForeignKey("Category",on_delete=models.CASCADE,null=True)author = models.ForeignKey("frontuser.FrontUser",on_delete=models.CASCADE)class Tag(models.Model):name = models.CharField(max_length=100,null=False)article = models.ManyToManyField("Article")views.py def many_to_many(request):# article = Article.objects.first() #获取第一篇文章# tag = Tag(name="热门文章")# tag.save() #先将标签保存# #然后将标签添加到文章上面# article.tag_set.add(tag)tag =Tag.objects.get(pk=2)article = Article.objects.get(pk=2)tag.article.add(article)return HttpResponse("添加成功")

9.4 navie 和 aware 时间

  • navie 时间 幼稚 不知道 自己 位于 哪个时区
  • aware 时间 清除的知道自己 位于哪个时区
  • pytz 管理时区的库 自动更新时区数据 没有安装的话 pip install pytz
  • astimezone 将一个时区转为 另外一个时区的方法 这个方法 只能被 aware 时间 不能被 navie 时间调用
>>> import pytz
>>> from datetime import datatime
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ImportError: cannot import name 'datatime'
>>> from datetime import datetime
>>> now = datetime.now()  #这个时间是  navie 时间
>>> print(now)
2018-09-17 10:21:46.822158
>>> utc_timezone = pytz.timezone("UTC") #定义一个世界统一时间的 对象
>>> utc_now = now.astimezone(utc_timezone)#将当前的时间转为 UTC时间
>>> print(utc_now)

9.5 模型常用的字段

1.AutoField int类型 自动增长

id = models.BigAutoField(primary_key=True)  要么不写 自动创建一个主键  既然自定主键的字段   必须要加上  primary_key=True

2.BigAutoField 长整型 范围更大

3.BooleanField bool 类型

4.CharField 字符串类型 必须要指定 max_length

5.FloatField

6.DateFiled 日期

  create_time = models.DateField(auto_now=True)# auto_now 每一次更新 都会获取到当前的时间     更新时间   # auto_now_add 第一次添加的时候 获取当前时间     创建时间

7.DateTimeFiled 日期加时间

 create_time = models.DateTimeField(auto_now=True)

8.TimeFiled 时间

create_time = models.DateTimeField(auto_now=True)

9.EmailFailed

email = model.EmailFiled(max_length=100,null=False)
在数据库中  会以 varchar的形式  但是 后期 django 自带的表单验证类库 会识别你这个字段是否是 email 要求用户只能输入要 邮箱格式

10.TextFiled

文章内容 用varchar  char 不够用 这个时候 可以使用 TextFiled  signature = models.TextField()

11.整型

 num = models.IntegerField() 整型num1 = models.BigIntegerField() 长整型num2 = models.PositiveIntegerField() 正整型 num3 = models.SmallIntegerField() 小整型num4 = models.PositiveSmallIntegerField()正小整型

12.URLFiled

存放url的字符串

13 .UUIDField

只能存放  uuid 类型

9.6 字段的参数

  • null 是否为空

    • null=False
  • default 默认值
  • primary key = True 是否为主键
  • unique =True 是否唯一
  • db_column
 username = models.CharField(max_length=100,null=True)
age = models.IntegerField(null=True,db_column='author_age',default=0)
create_time = models.DateTimeField(default=now)
tele = models.CharField(max_length=100,unique=True,null=True)

10、视图高级

  • django 限制请求
  • 页面重定向
  • HttpRequest对象
  • 生成CSV文件
  • 类视图

10.1 django 限制请求

  • get 一般用来像服务器索取数据 不会对服务器的状态进行修改 不会像服务器提交数据
  • post 一般用来像服务器提交数据 会对服务器的状态进行修改 比如提交一片文章给服务器
from django.views.decorators.http import require_http_methods
from django.views.decorators.http import require_GET,require_POST,require_safe#@require_http_methods(['POST','GET'])   这样post和get都能请求过来
#@require_http_methods(['POST'])   这样的话只能 POST请求过来  ==@require_POST
@require_http_methods(['GET'])     这样的话只能 GET请求过来  ==@require_GET
def index(request):#首页用来展示数据#一般用get请求 展示数据articles = Article.objects.all()return render(request,'index.html',context={"articles":articles})

10.2 页面重定向

  • 301 永久性重定向 比如 t.tt 跳转到 https://www.smartisan.com/ www.jingdong.com 跳转到 www.jd.com
  • 302暂时性的重定向 https://www.zhihu.com 如果没有登陆 跳转到 https://www.zhihu.com/signup?next=%2F

10.2 HttpRequest对象

django在接收到http请求以后  会根据请求携带的参数 及 报文 创建 一个 WSGIRequest对象  这个对象会作为视图函数的第一个参数传递给 视图函数  也就是经常看到的request

WSGIRequest 属性

  • path request.path 请求服务器的完整的路径 注意这里不包括域名和端口号
  • method request.method 请求的方法 GET 或者 POST
  • request.get_raw_uri() 包含域名和端口
  • http://127.0.0.1:9000/signup/?username=1111&password=12345
  • request.get_host() 返回的是127.0.0.1

10.3 将数据到处到 CSV 文件

from django.http import HttpResponse
import csvdef index(request):response = HttpResponse(content_type='text/csv') #告诉浏览器这是一个  csv 格式的文件 不是html 如果不指定 就是html response['Content-Disposition']="attechment;filename='abc.csv'"# 在 response 添加一个 'Content-Disposition'头  这样的话 是为了告诉他 该怎么样处理这个csv文件 attechment 说明这是一个附件 这样浏览器就不会显示它 而是下载  filename='abc.csv' 是指定附件的名字  writer = csv.writer(response) writer 将 内容写进responewriter.writerow(['username','age','height'])writer.writerow(['qulinx', '18', '181cm'])return response

10.3.1 根据模板 导出 csv 文件

def template_csv_view(request):response = HttpResponse(content_type='text/csv')response['Content-Disposition'] = "attechment;filename='linzhen.csv'"context = {'rows':[['username','age','height','weight'],['zelinx',18,'181cm','90kg']]}template= loader.get_template('python1806.txt')csv_template = template.render(context)response.content = csv_templatereturn response

10.3.2 大文件处理

StreamingHttpResponse 在处理大文件的时候 不会因为 服务器响应时间长而超时 这个类不继承于 HttpResponse 类似于 大视频 边看边下载

def large_csv_view(request):# response = HttpResponse(content_type='text/csv')# response['Content-Disposition'] = "attechment;filename='linzhen.csv'"# writer = csv.writer(response)# for row in range(0,1000000):#     writer.writerow(['Row {}'.format(row),'{}'.format(row)])# return responseresponse = StreamingHttpResponse(content_type='text/csv')response['Content-Disposition'] = "attechment;filename='kangbazi.csv'"rows = ("第{},{}\n".format(row,row) for row in range(0,10000000))response.streaming_content=rowsreturn response

10.4 类视图

在写视图的时候,Django除了使用函数作为视图,也可以使用类作为视图。使用类视图可以使用类的一些特性,比如继承等。

10.4.1 View:

django.views.generic.base.View是主要的类视图,所有的类视图都是继承自他。如果我们写自己的类视图,也可以继承自他。然后再根据当前请求的method,来实现不同的方法。比如这个视图只能使用get的方式来请求,那么就可以在这个类中定义get(self,request,*args,**kwargs)方法。以此类推,如果只需要实现post方法,那么就只需要在类中实现post(self,request,*args,**kwargs)。示例代码如下:

from django.views import View
def index(request):return HttpResponse("")
class BookDetailView(View):def get(self,request,*args,**kwargs):return render(request,'detail.html')

类视图写完后,还应该在urls.py中进行映射,映射的时候就需要调用View的类方法as_view()来进行转换。示例代码如下:

urlpatterns = [    path("",views.index)path("detail/<book_id>/",views.BookDetailView.as_view(),name='detail')
]

除了get方法,View还支持以下方法['get','post','put','patch','delete','head','options','trace']

如果用户访问了View中没有定义的方法。比如你的类视图只支持get方法,而出现了post方法,那么就会把这个请求转发给http_method_not_allowed(request,*args,**kwargs)。示例代码如下:

class AddBookView(View):def post(self,request,*args,**kwargs):return HttpResponse("书籍添加成功!")def http_method_not_allowed(self, request, *args, **kwargs):return HttpResponse("您当前采用的method是:%s,本视图只支持使用post请求!" % request.method)

urls.py中的映射如下:

path("addbook/",views.AddBookView.as_view(),name='add_book')

如果你在浏览器中访问addbook/,因为浏览器访问采用的是get方法,而addbook只支持post方法,因此以上视图会返回您当前采用的method是:GET,本视图只支持使用post请求!。

其实不管是get请求还是post请求,都会走dispatch(request,*args,**kwargs)方法,所以如果实现这个方法,将能够对所有请求都处理到。

10.4.2 TemplateView:

django.views.generic.base.TemplateView,这个类视图是专门用来返回模版的。在这个类中,有两个属性是经常需要用到的,一个是template_name,这个属性是用来存储模版的路径,TemplateView会自动的渲染这个变量指向的模版。另外一个是get_context_data,这个方法是用来返回上下文数据的,也就是在给模版传的参数的。示例代码如下:

from django.views.generic.base import TemplateViewclass HomePageView(TemplateView):template_name = "home.html"def get_context_data(self, **kwargs):context = {"phone":"123456"}return context{{phone}}

urls.py中的映射代码如下:

from django.urls import pathfrom myapp.views import HomePageViewurlpatterns = [path('', HomePageView.as_view(), name='home'),
]

如果在模版中不需要传递任何参数,那么可以直接只在urls.py中使用TemplateView来渲染模版。示例代码如下:

from django.urls import path
from django.views.generic import TemplateViewurlpatterns = [path('about/', TemplateView.as_view(template_name="about.html")),
]

10.4.3 ListView:

在网站开发中,经常会出现需要列出某个表中的一些数据作为列表展示出来。比如文章列表,图书列表等等。在Django中可以使用ListView来帮我们快速实现这种需求。示例代码如下:

from django.views.generic import TemplateView,ListView
class ArticleListView(ListView):model = Article   #你的模型  template_name = 'article_list.html' #渲染的页面 paginate_by = 10  #每页显示多少条   context_object_name = 'articles' #在页面上 进行  遍历 for article in articlesordering = 'id' #结果根据什么进行排序  page_kwarg = 'p'   #http://127.0.0.1:9000/article/?p=2def get_context_data(self, **kwargs):#获取上下文的信息    分页的相关信息  context = super(ArticleListView, self).get_context_data(**kwargs)print(context.count)print(context)"""{'paginator': <django.core.paginator.Paginator object at 0x00000277042B40F0>, 'page_obj': <Page 2 of 8>, 'is_paginated': True,"""return contextdef get_queryset(self):return Article.objects.filter(id__lte=89)

对以上代码进行解释:

  1. 首先ArticleListView是继承自ListView
  2. model:重写model类属性,指定这个列表是给哪个模型的。
  3. template_name:指定这个列表的模板。
  4. paginate_by:指定这个列表一页中展示多少条数据。
  5. context_object_name:指定这个列表模型在模板中的参数名称。
  6. ordering:指定这个列表的排序方式。
  7. page_kwarg:获取第几页的数据的参数名称。默认是page
  8. get_context_data:获取上下文的数据。
  9. get_queryset:如果你提取数据的时候,并不是要把所有数据都返回,那么你可以重写这个方法。将一些不需要展示的数据给过滤掉。

注意:PaginatorPage类都是用来做分页的。他们在Django中的路径为django.core.paginator.Paginatordjango.core.paginator.Page。以下对这两个类的常用属性和方法做解释:

10.4.3.1Paginator常用属性和方法:

  1. paginator.count:总共有多少条数据。
  2. paginator.num_pages:总共有多少页。
  3. paginator.page_range:页面的区间。比如有三页,那么就range(1,4)
class ArticleListView(ListView):model = Articletemplate_name = 'article_list.html'paginate_by = 20context_object_name = 'articles'ordering = 'create_time'page_kwarg = 'p'def get_context_data(self,**kwargs):#要获取上下文的数据 需要继承于父类context = super(ArticleListView,self).get_context_data(*kwargs)print("="*30)print(context)print("=" * 30)paginator = context.get('paginator') #获取context中的 paginatorprint(paginator.count) #151条数据print(paginator.num_pages) #8 分 8页print(paginator.page_range) #range(1, 9) 包含第一页 不包含第九页

10.4.3.1Page常用属性和方法:

  1. page_obj.has_next:是否还有下一页。
  2. page_obj.has_previous:是否还有上一页。
  3. page_obj.next_page_number:下一页的页码。
  4. page_obj.previous_page_number:上一页的页码。
  5. page_obj.number:当前页。
  6. page_obj.start_index:当前这一页的第一条数据的索引值。
  7. page_obj.end_index:当前这一页的最后一条数据的索引值。
class ArticleListView(ListView):model = Articletemplate_name = 'article_list.html'paginate_by = 20context_object_name = 'articles'ordering = 'create_time'page_kwarg = 'p'def get_context_data(self,**kwargs):#要获取上下文的数据 需要继承于父类context = super(ArticleListView,self).get_context_data(*kwargs)page_obj = context.get('page_obj')  从 context 中 获取 page_obj#http://127.0.0.1:9000/article/list/?p=7print(page_obj.has_next()) 是否还有下一页  Trueprint(page_obj.has_previous()) 是否还有上一页 Trueprint(page_obj.next_page_number()) 下一页的页码  8

11、返回 json 类型

def jsonresponse_view(request):person = {'username':'kangbazi1806','age':18,'height':'181cm'}person_str = json.dumps(person)  #将数据dump转化为  json字符串  response = HttpResponse(person_str, content_type='application/json')
# 封装成 Response返回给浏览器 并告诉浏览器 这是 json 类型  return response #最后返回  response以上的可以换成  def jsonresponse_view(request):person = {'username':'kangbazi1806','age':18,'height':'181cm'}response = JsonResponse(person,safe=False)   JsonResponse 只能转化字典类型的  非字典类型  必须加上 safe=Falsereturn response

12、django错误处理

12.1 常见的错误

2开头 一般是成功的

3 开头 重定向

301

301302

4开头 一般是客户端的错误

404 找不到目标url 403 你没有权限访问相关的数据405 请求方法不允许 限制请求的过程中 只允许你 get请求  但是 你很调皮  非得 post传过去  这个时候报405错误400 请求的参数有错误

5开头 一般是服务器的错误

500 服务器内部错误  代码有 bug  502    一般是 服务器部署错误  比如  nginx启动 但是  uwsgi 有无 没法完成正常的请求

生产环境 也就是 线上 上线以后 会把debug 关闭 settings.py

DEBUG = FalseALLOWED_HOSTS = ['127.0.0.1']

常见的错误 比如 404 500 直接在 templates 下面 新建 404.html 500.html如果出现 404 500 错误 会自动的显示这个页面的内容

12.2 其他错误

其它错误 比如 400 403

专门 定义一个 app 名字叫 errors

在errors 下面 新建一个 templates 下面再建一个 errors 里边 创建 页面 400.html或者 403.html 502.html

在errors 应用下面

vim views.py

from django.shortcuts import render
def view_400(request):return render(request,'errors/400.html')
# Create your views here.
def view_403(request):return render(request,'errors/403.html')
def view_502(request):return render(request,'errors/502.html')

vim urls.py

from django.urls import path
from . import  views
app_name = 'errors'
urlpatterns = [path('400.html',views.view_400,name='400'),path('403.html',views.view_403,name='403'),path('502.html',views.view_502,name='502'),
]

在 项目总的 urls.py 下面

 path('errors/',include('errors.urls'))

其它 应用 比如 front 或者 book 或者其它 通过redirect 重定向

def index(request):if not request.GET.get('username'):#400请求参数有错误return redirect(reverse('errors:502'))return HttpResponse("首页")

13、表单

13.1 HTML中的表单:

单纯从前端的html来说,表单是用来提交数据给服务器的,不管后台的服务器用的是Django还是PHP语言还是其他语言。只要把input标签放在form标签中,然后再添加一个提交按钮,那么以后点击提交按钮,就可以将input标签中对应的值提交给服务器了。

13.2 Django中的表单:

Django中的表单丰富了传统的HTML语言中的表单。在Django中的表单,主要做以下两件事:

  1. 渲染表单模板。
  2. 表单验证数据是否合法。

13.3 Django中表单使用流程:

在讲解Django表单的具体每部分的细节之前。我们首先先来看下整体的使用流程。这里以一个做一个留言板为例。首先我们在后台服务器定义一个表单类,继承自django.forms.Form。示例代码如下:

# forms.py
class MessageBoardForm(forms.Form):title = forms.CharField(max_length=3,label='标题',min_length=2,error_messages={"min_length":'标题字符段不符合要求!'})content = forms.CharField(widget=forms.Textarea,label='内容')email = forms.EmailField(label='邮箱')reply = forms.BooleanField(required=False,label='回复')

然后在视图中,根据是GET还是POST请求来做相应的操作。如果是GET请求,那么返回一个空的表单,如果是POST请求,那么将提交上来的数据进行校验。示例代码如下:

# views.py
class IndexView(View):def get(self,request):form = MessageBoardForm() #实例化一个表单对象  return render(request,'index.html',{'form':form})def post(self,request):form = MessageBoardForm(request.POST)if form.is_valid(): #这里是为了验证 froms.py中的规则 title = form.cleaned_data.get('title')content = form.cleaned_data.get('content')email = form.cleaned_data.get('email')reply = form.cleaned_data.get('reply')return HttpResponse('success')else:print(form.errors)return HttpResponse('fail')

在使用GET请求的时候,我们传了一个form给模板,那么以后模板就可以使用form来生成一个表单的html代码。在使用POST请求的时候,我们根据前端上传上来的数据,构建一个新的表单,这个表单是用来验证数据是否合法的,如果数据都验证通过了,那么我们可以通过cleaned_data来获取相应的数据。在模板中渲染表单的HTML代码如下:

<form action="" method="post"><table>{{form.as_table}}<tr><td></td><td><input type="submit" value="提交"></td></tr></table>
</form>

我们在最外面给了一个form标签,然后在里面使用了table标签来进行美化,在使用form对象渲染的时候,使用的是table的方式,当然还可以使用ul的方式(as_ul),也可以使用p标签的方式(as_p),并且在后面我们还加上了一个提交按钮。这样就可以生成一个表单了

13.4 用表单验证数据

13.4.1 常用的Field:

使用Field可以是对数据验证的第一步。你期望这个提交上来的数据是什么类型,那么就使用什么类型的Field

CharField:

用来接收文本。
参数:

  • max_length:这个字段值的最大长度。
  • min_length:这个字段值的最小长度。
  • required:这个字段是否是必须的。默认是必须的。
  • error_messages:在某个条件验证失败的时候,给出错误信息。

EmailField:

用来接收邮件,会自动验证邮件是否合法。
错误信息的key:requiredinvalid

FloatField:

用来接收浮点类型,并且如果验证通过后,会将这个字段的值转换为浮点类型。
参数:

  • max_value:最大的值。
  • min_value:最小的值。

错误信息的key:requiredinvalidmax_valuemin_value

IntegerField:

用来接收整形,并且验证通过后,会将这个字段的值转换为整形。
参数:

  • max_value:最大的值。
  • min_value:最小的值。

错误信息的key:requiredinvalidmax_valuemin_value

URLField:

用来接收url格式的字符串。
错误信息的key:requiredinvalid


13.4.2 常用验证器:

在验证某个字段的时候,可以传递一个validators参数用来指定验证器,进一步对数据进行过滤。验证器有很多,但是很多验证器我们其实已经通过这个Field或者一些参数就可以指定了。比如EmailValidator,我们可以通过EmailField来指定,比如MaxValueValidator,我们可以通过max_value参数来指定。以下是一些常用的验证器:

  1. MaxValueValidator:验证最大值。

  2. MinValueValidator:验证最小值。

  3. MinLengthValidator:验证最小长度。

  4. MaxLengthValidator:验证最大长度。

  5. EmailValidator:验证是否是邮箱格式。

  6. URLValidator:验证是否是URL格式。

  7. RegexValidator
    

    :如果还需要更加复杂的验证,那么我们可以通过正则表达式的验证器:

    RegexValidator
    

    。比如现在要验证手机号码是否合格,那么我们可以通过以下代码实现:

     class MyForm(forms.Form):telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')])
    

注意变量的统一

forms.py telephone = forms.CharField(validators=[validators.RegexValidator(r'1[3456789]\d{9}',message="请输入正确的手机号")],error_messages={"required":"请输入您的手机号"})views.py def post(self,request):form = MyForm(request.POST)if form.is_valid():telephone = form.cleaned_data.get('telephone')return HttpResponse("成功")else:print(form.errors.get_json_data())return HttpResponse('fail')index.html   <input type="text" name="telephone">三个telephone 必须要 统一

13.4.3 自定义验证:

有时候对一个字段验证,不是一个长度,一个正则表达式能够写清楚的,还需要一些其他复杂的逻辑,那么我们可以对某个字段,进行自定义的验证。比如在注册的表单验证中,我们想要验证手机号码是否已经被注册过了,那么这时候就需要在数据库中进行判断才知道。对某个字段进行自定义的验证方式是,定义一个方法,这个方法的名字定义规则是:clean_fieldname。如果验证失败,那么就抛出一个验证错误。比如要验证用户表中手机号码之前是否在数据库中存在,那么可以通过以下代码实现:

class MyForm(forms.Form):telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')])def clean_telephone(self):telephone = self.cleaned_data.get('telephone')exists = User.objects.filter(telephone=telephone).exists()if exists:raise forms.ValidationError("手机号码已经存在!")return telephone

以上是对某个字段进行验证,如果验证数据的时候,需要针对多个字段进行验证,那么可以重写clean方法。比如要在注册的时候,要判断提交的两个密码是否相等。那么可以使用以下代码来完成:

class MyForm(forms.Form):telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')])pwd1 = forms.CharField(max_length=12)pwd2 = forms.CharField(max_length=12)def clean(self):cleaned_data = super().clean()pwd1 = cleaned_data.get('pwd1')pwd2 = cleaned_data.get('pwd2')if pwd1 != pwd2:raise forms.ValidationError('两个密码不一致!')

13.5 文件上传

文件上传是网站开发中非常常见的功能。这里详细讲述如何在Django中实现文件的上传功能。

13.5.1 python实现文件上传

13.5.1.1 前端代码实现:

  1. 在前端中,我们需要填入一个form标签,然后在这个form标签中指定enctype="multipart/form-data",不然就不能上传文件。
  2. form标签中添加一个input标签,然后指定input标签的name,以及type="file"

以上两步的示例代码如下:

<form action="" method="post" enctype="multipart/form-data"><input type="file" name="myfile">
</form>

13.5.1.2 后端代码实现:

后端的主要工作是接收文件。然后存储文件。接收文件的方式跟接收POST的方式是一样的,只不过是通过FILES来实现。示例代码如下:

def save_file(file):with open('somefile.txt','wb') as fp:for chunk in file.chunks():fp.write(chunk)def index(request):if request.method == 'GET':form = MyForm()return render(request,'index.html',{'form':form})else:myfile = request.FILES.get('myfile')save_file(myfile)return HttpResponse('success')

以上代码通过request.FILES接收到文件后,再写入到指定的地方。这样就可以完成一个文件的上传功能了。

13.5.2 django实现文件上传

在定义模型的时候,我们可以给存储文件的字段指定为FileField,这个Field可以传递一个upload_to参数,用来指定上传上来的文件保存到哪里。比如我们让他保存到项目的files文件夹下,那么示例代码如下:

# models.py
from django.db import models
class Upfile_modles(models.Model):title=models.CharField(max_length=100)content=models.CharField(max_length=100)thumbnail=models.FileField(upload_to='upfiles/%Y/%m/%d/')class Meta:db_table = 'Upfile'#forms.py
from django import forms
from .models import Upfile_modles
class Upfile_forms(forms.ModelForm):class Meta:model=Upfile_modlesfields='__all__'error_messages = {  # 错误信息"thumbnail": {# 'invalid_extension':'请上传正确格式的文件''required': '请上传正确格式的图片'}}# views.py
from django.shortcuts import render,HttpResponse
from django.views import View
from .forms import Upfile_formsclass Upload_file(View):def get(self,request):return  render(request,'Upload_file.html')def post(self,request):form=Upfile_forms(request.POST,request.FILES)if form.is_valid():form.save()return HttpResponse('成功了 成功')else:print(form.errors.get_json_data())return HttpResponse('失败了')def Upload_file_def(request):if request.method=="GET":return render(request, 'Upload_file.html')elif request.method=="POST":form=Upfile_forms(request.POST,request.FILES)if form.is_valid():form.save()return HttpResponse("成功了,上传")else:print(form.errors.get_json_data())return HttpResponse("上传失败")

调用完form.save()方法,就会把文件保存到files`下面,并且会将这个文件的路径存储到数据库中。

13.5.3 指定上传文件位置

#在seetings.py最下面添加上
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = '/media/'#在models.py thumbnail = models.FileField()#默认在主目录:mediathumbnail = models.FileField(upload_to="files/")  #默认在主目录 media/files#未在在seetings.py最下面添加上
thumbnail = models.FileField(upload_to="files/")#默认在主目录:files

13.5.4 访问路径访问上传文件

然后我们可以在urls.py中添加MEDIA_ROOT目录下的访问路径。示例代码如下:

http://127.0.0.1:9000/media/upfiles/django%E9%85%8D%E7%BD%AE.txt从数据库中读取文件的url 然后在页面上能访问

#在主settings.py中  写入
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = '/media/'#然后在主urls.py中 映射 的
from django.contrib import admin
from django.urls import path,include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [path('admin/', admin.site.urls),path('doc/',include('Uploading_documents.urls'),name='doc'),path('img/',include('Uploading_image.urls'),name='img' ),
]+ static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT) #全局路径能访问上传的文件

如果我们同时指定MEDIA_ROOTupload_to,那么会将文件上传到MEDIA_ROOT下的upload_to文件夹中。示例代码如下:

# models.py
from django.db import models
class Upfile_modles(models.Model):title=models.CharField(max_length=100)content=models.CharField(max_length=100)thumbnail=models.FileField(upload_to='upfiles/%Y/%m/%d/')class Meta:db_table = 'Upfile'

13.5.5 限制上传文件拓展名

如果想要限制上传的文件的拓展名,那么我们就需要用到表单来进行限制。我们可以使用普通的Form表单,也可以使用ModelForm,直接从模型中读取字段。示例代码如下:

# models.py
from django.db import models
from django.core import validators
class Upfile_modles(models.Model):title=models.CharField(max_length=100)content=models.CharField(max_length=100)thumbnail=models.FileField(upload_to='upfiles/%Y/%m/%d/',validators=[validators.FileExtensionValidator(['txt','pdf'])])class Meta:db_table = 'Upfile'

13.5.6 上传图片

上传图片跟上传普通文件是一样的。只不过是上传图片的时候Django会判断上传的文件是否是图片的格式(除了判断后缀名,还会判断是否是可用的图片)。如果不是,那么就会验证失败。我们首先先来定义一个包含ImageField的模型。示例代码如下:

#前端页面和上传文件相同# models.py
from django.db import models
class upfile_img(models.Model):title=models.CharField(max_length=100)content=models.CharField(max_length=100)thumbnail=models.ImageField(upload_to="img/%Y/%m/%d")class Meta:db_table='upimg'#forms.py
from django import forms
from .models import upfile_img
class upimg_form(forms.ModelForm):class Meta:model=upfile_imgfields="__all__"error_messages={"thumbnail":{'invalid_image':"请上传图片"}}# views.py
from django.shortcuts import render,HttpResponse
from django.views import View
from .forms import upimg_formclass Upfile_img(View):def get(self,request):return render(request,'Upload_img.html')def post(self,request,*args,**kwargs):form=upimg_form(request.POST,request.FILES)if form.is_valid():form.save()return HttpResponse("成功")else:print(form.errors.get_json_data())return HttpResponse("失败")

注意:使用ImageField,必须要先安装Pillow库:pip install pillow

14、cookie和session

  1. cookie:在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。
  2. session: session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,session是一个思路、一个概念、一个服务器存储授权信息的解决方案,不同的服务器,不同的框架,不同的语言有不同的实现。虽然实现不一样,但是他们的目的都是服务器为了方便存储数据的。session的出现,是为了解决cookie存储数据不安全的问题的。
  3. cookie和session使用:web开发发展至今,cookiesession的使用已经出现了一些非常成熟的方案。在如今的市场或者企业里,一般有两种存储方式:
    • 存储在服务端:通过cookie存储一个sessionid,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个sessionid,下次再次请求的时候,会把该sessionid携带上来,服务器根据sessionidsession库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side sessionDjangosession信息默认存储到数据库中,当然也可以存储到其他地方,比如缓存中,文件系统中等。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。
    • session数据加密,然后存储在cookie中。这种专业术语叫做client side sessionflask框架默认采用的就是这种方式,但是也可以替换成其他形式。

14.1 操作cookie:

14.1.1 设置cookie:

设置cookie是设置值给浏览器的。因此我们需要通过response的对象来设置,设置cookie可以通过response.set_cookie来设置,这个方法的相关参数如下:

   def set_cookie(self, key, value='', max_age=None, expires=None, path='/',domain=None, secure=False, httponly=False):
  1. key:这个cookiekey
  2. value:这个cookievalue
  3. max_age:最长的生命周期。单位是秒。
  4. expires:过期时间。跟max_age是类似的,只不过这个参数需要传递一个具体的日期,比如datetime或者是符合日期格式的字符串。如果同时设置了expiresmax_age,那么将会使用expires的值作为过期时间。默认是 navie时间 不知道位于哪个时区 可以将其转为aware时间
 from django.utils.timezone import make_aware   expire_time = datetime(year=2018,month=9,day=25,hour=20,minute=30,second=0)expire_time1 = make_aware(expire_time)
  1. 如果max_ageexpires同时存在 以 expires为准

    user_id=kangbazi1806; expires=Tue, 25-Sep-2018 20:30:00 GMT; Max-Age=480381; Path=/cms/

  2. path:对域名下哪个路径有效。默认是对域名下所有路径都有效。 上面 只对 /cms/路径有效

  3. domain:针对哪个域名有效。默认是针对主域名下都有效,如果只要针对某个子域名才有效,那么可以设置这个属性.

  4. secure:是否是安全的,如果设置为True,那么只能在https协议下才可用。默认 secure 为false 也就是 http协议能用

  5. httponly:默认是False。如果为True,那么在客户端不能通过JavaScript进行操作。

14.1.2 删除cookie:

通过delete_cookie即可删除cookie。实际上删除cookie就是将指定的cookie的值设置为空的字符串,然后使用将他的过期时间设置为0,也就是浏览器关闭后就过期。

14.1.3 获取cookie:

获取浏览器发送过来的cookie信息。可以通过request.COOKIES来或者。这个对象是一个字典类型。比如获取所有的cookie,那么示例代码如下:

cookies = request.COOKIES
for cookie_key,cookie_value in cookies.items():print(cookie_key,cookie_value)

14.2 操作session:

django中的session默认情况下是存储在服务器的数据库中的,在表中会根据sessionid来提取指定的session数据,然后再把这个sessionid放到cookie中发送给浏览器存储,浏览器下次在向服务器发送请求的时候会自动的把所有cookie信息都发送给服务器,服务器再从cookie中获取sessionid,然后再从数据库中获取session数据。但是我们在操作session的时候,这些细节压根就不用管。我们只需要通过request.session即可操作。示例代码如下:

def index(request):request.session.get('username')return HttpResponse('index')def session_view(request):request.session['username'] = "kangbazi1806" #如果登录 验证通过  同时将信息存到session中 一份   request.session['userid'] = '1806'# username = request.session.pop("username")# userid = request.session.pop("userid")request.session.clear()request.session.flush()username = request.session.get("username")print(username)# expire =  datetime(year=2018,month=9,day=25,hour=20,minute=30,second=0)# expires = make_aware(expire)# request.session.set_expiry(expires)return HttpResponse("session view")

session常用的方法如下:

  1. get:用来从session中获取指定值。
  2. pop:从session中删除一个值。
  3. keys:从session中获取所有的键。
  4. items:从session中获取所有的值。
  5. clear:清除当前这个用户的session数据。
  6. flush:删除session并且删除在浏览器中存储的session_id,一般在注销的时候用得比较多。
  7. set_expiry(value):设置过期时间。
    • 整形:代表秒数,表示多少秒后过期。
    • 0:代表只要浏览器关闭,session就会过期。
    • None:会使用全局的session配置。在settings.py中可以设置SESSION_COOKIE_AGE来配置全局的过期时间。默认是1209600秒,也就是2周的时间。
  8. clear_expired:清除过期的sessionDjango并不会清除过期的session,需要定期手动的清理,或者是在终端,使用命令行python manage.py clearsessions来清除过期的session

修改session的存储机制:下面这些 在 settings.py 中进行设置 session存储方式 很多 不止一种

最后一行 加入 SESSION_ENGINE = “django.contrib.sessions.backends.cache”

默认情况下,session数据是存储到数据库中的。当然也可以将session数据存储到其他地方。可以通过设置SESSION_ENGINE来更改session的存储位置,这个可以配置为以下几种方案:

  1. django.contrib.sessions.backends.db:使用数据库。默认就是这种方案。django_session
  2. django.contrib.sessions.backends.file:使用文件来存储session。
  3. django.contrib.sessions.backends.cache:使用缓存来存储session。想要将数据存储到缓存中,前提是你必须要在settings.py中配置好CACHES,并且是需要使用Memcached,而不能使用纯内存作为缓存。
  4. django.contrib.sessions.backends.cached_db:在存储数据的时候,会将数据先存到缓存中,再存到数据库中。这样就可以保证万一缓存系统出现问题,session数据也不会丢失。在获取数据的时候,会先从缓存中获取,如果缓存中没有,那么就会从数据库中获取。
  5. django.contrib.sessions.backends.signed_cookies:将session信息加密后存储到浏览器的cookie中。这种方式要注意安全,建议设置SESSION_COOKIE_HTTPONLY=True,那么在浏览器中不能通过js来操作session数据,并且还需要对settings.py中的SECRET_KEY进行保密,因为一旦别人知道这个SECRET_KEY,那么就可以进行解密。另外还有就是在cookie中,存储的数据不能超过4k

15、上下文处理器

  • 重点

    • debug 后期开发 查看 代码 数据库查询是否有问题
    • message 前端用户输入内容的错误信息 直接在页面上显示

15.1 django上下文处理器

上下文处理器是可以返回一些数据,在全局模板中都可以使用。比如登录后的用户信息,在很多页面中都需要使用,那么我们可以放在上下文处理器中,就没有必要在每个视图函数中都返回这个对象。

settings.TEMPLATES.OPTIONS.context_processors中,有许多内置的上下文处理器。这些上下文处理器的作用如下:

  1. django.template.context_processors.debug:增加一个debugsql_queries变量。在模板中可以通过他来查看到一些数据库查询。

    现在  settings.py中设置  INTERNAL_IPS = ['127.0.0.1'] 在页面 上   {{debug}}  或者  {{sql_queries}}首页 True ::::[{'sql': 'SELECT "front_user"."id", "front_user"."username", "front_user"."password", "front_user"."telephone" FROM "front_user"', 'time': '0.001'}]
    
  2. django.template.context_processors.request:增加一个request变量。这个request变量也就是在视图函数的第一个参数。

  3. django.contrib.auth.context_processors.authDjango有内置的用户系统,这个上下文处理器会增加一个user对象。

  4. django.contrib.messages.context_processors.messages:增加一个messages变量。
    level的四个级别 info debug Warnning erro

    如果想让 错误信息直接呈现在页面上
    views.py  from django.contrib import messages #先引入class LoginView(View):def get(self,request):return render(request,'login.html')def post(self,request):form = LoginForm(request.POST)if form.is_valid():username = form.cleaned_data.get('username')password = form.cleaned_data.get('password')user = User.objects.filter(username=username,password=password).first()if user:request.session['user_id'] = user.id #登录成功以后 将这个用户的id存到sessionreturn redirect(reverse('index'))else:print("用户名或者密码错误")# messages.add_message(request,messages.INFO,"用户名或者密码错误")messages.info(request, "用户名或者密码错误") #因为内置上下文处理器  上面方式效果一样   这是 用户名或者密码错误级别  return redirect(reverse('login'))外边是 用户名  密码 不能少于6位 错误级别  这个最开始是交给 forms.py来做的   所以需要在里边 写一个 方法   else:# errors = form.errors.get_json_data()# #'username': [{'message': '用户名不能少于6位', 'code': 'min_length'}], 'password': [{'message': '密码不能少于6位', 'code': 'min_length'}]}# for messages1 in errors.values():#     #print(messages1)#     #[{'message': '用户名不能少于6位', 'code': 'min_length'}]#     #[{'message': '密码不能少于6位', 'code': 'min_length'}]#     for messages_dict in messages1:#         for key,value in messages_dict.items():#             if key == "message":#                 print(value) erros = form.get_errors()  #这里调用的是   下面forms.py中的方法  for erro in erros:messages.info(request,erro)   return redirect(reverse('login'))forms.py    最终我们只想输出  用户名不能少于 6位用户名不能少于6位def get_errors(self):new_error = []errors = self.errors.get_json_data()# 'username': [{'message': '用户名不能少于6位', 'code': 'min_length'}], 'password': [{'message': '密码不能少于6位', 'code': 'min_length'}]}for messages1 in errors.values():# print(messages1)# [{'message': '用户名不能少于6位', 'code': 'min_length'}]# [{'message': '用户名不能少于6位', 'code': 'min_length'}]for messages_dict in messages1:for key, value in messages_dict.items():if key == "message":new_error.append(value)return new_error在 login.html 上   <ul>{% for message in messages %}<li>{{ message }}</li>{% endfor %}</ul>
  5. django.template.context_processors.media:在模板中可以读取 MEDIA_URL。比如想要在模板中使用上传的文件,那么这时候就需要使用settings.py中设置的MEDIA_URL来拼接url。示例代码如下:

    1.在 settings.py OPTIONS  中  写入 'django.template.context_processors.media',
    然后制定    MEDIA_ROOT = os.path.join(BASE_DIR,'medias')MEDIA_URL = '/medias/'
    urls.py 映射 from django.conf.urls.static import static
    from django.conf import settings
    urlpatterns = [path('', views.index,name='index'),
    ]+ static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)<img src="{{MEDIA_URL}}aa.png" />
  6. django.template.context_processors.static:在模板中可以使用STATIC_URL

  7. django.template.context_processors.csrf:在模板中可以使用csrf_token变量来生成一个csrf token

    如果表单 post请求 在  form标签里边 加入下面两个 两个效果都可以
    {#        <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">#}{% csrf_token %}如果 不是 表单  可以在 head 上面 加一个 {% block head %}<meta name="csrf-coken" content="{{ csrf_token }}">
    {% endblock %}base页面上  得有  {% block head %}
    {% endblock %}
    

15.2 自定义上下文处理器

有时候我们想要返回自己的数据。那么这时候我们可以自定义上下文处理器。自定义上下文处理器的步骤如下:

  1. 你可以根据这个上下文处理器是属于哪个app,然后在这个app中创建一个文件专门用来存储上下文处理器。比如context_processors.py。(或者是你也可以专门创建一个Python包,用来存储所有的上下文处理器。没讲到)

  2. 在你定义的上下文处理器文件中,定义一个函数,这个函数只有一个request参数。这个函数中处理完自己的逻辑后,把需要返回给模板的数据,通过字典的形式返回。如果不需要返回任何数据,那么也必须返回一个空的字典。示例代码如下:

  3. 在settings.py下 让 系统识别你写的 context_processors.py

    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','front.context_processors.front_user'  #这一步就是让context_processors 生效],},},
    ]
    
    from .models import Userdef front_user(request):userid = request.session.get('user_id') #从session中读取user_idcontext = {}if userid:try:user = User.objects.get(pk=userid)context['front_user'] = userexcept:passreturn context在 页面上  {% if front_user %}<li><a href="#">欢迎你,{{ front_user.username }}</a></li><li><a href="#">注销</a></li>{% else %}<li><a href="{% url 'login' %}">登录</a></li><li><a href="{% url 'register' %}">注册</a></li>{% endif %}
    

16、emcached缓存

16.1 什么是memcached:

  1. memcached之前是danga的一个项目,最早是为LiveJournal服务的,当初设计师为了加速LiveJournal访问速度而开发的,后来被很多大型项目采用。官网是www.danga.com或者是memcached.org
  2. Memcached是一个高性能的分布式内存对象缓存系统,全世界有不少公司采用这个缓存项目来构建大负载的网站,来分担数据库的压力。Memcached是通过在内存里维护一个统一的巨大的hash表,memcached能存储各种各样的数据,包括图像、视频、文件、以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。
  3. 哪些情况下适合使用Memcached:存储验证码(图形验证码、短信验证码)、登录session等所有不是至关重要的数据。

16.2 安装和启动memcached

  1. windows:

    • 安装:memcached.exe -d install
    • 启动:memcached.exe -d start
  2. linux(ubuntu):

    • 安装:sudo apt install memcached

    • 启动:

      cd /usr/local/memcached/bin
      ./memcached -d start
      
  3. 可能出现的问题:

    • 提示你没有权限:在打开cmd的时候,右键使用管理员身份运行。
    • 提示缺少pthreadGC2.dll文件:将pthreadGC2.dll文件拷贝到windows/System32.
    • 不要放在含有中文的路径下面。
  4. 启动memcached

    • -d:这个参数是让memcached在后台运行。
    • -m:指定占用多少内存。以M为单位,默认为64M
    • -p:指定占用的端口。默认端口是11211
    • -l:别的机器可以通过哪个ip地址连接到我这台服务器。如果是通过service memcached start的方式,那么只能通过本机连接。如果想要让别的机器连接,就必须设置-l 0.0.0.0

    如果想要使用以上参数来指定一些配置信息,那么不能使用service memcached start,而应该使用/usr/bin/memcached的方式来运行。比如/usr/bin/memcached -u memcache -m 1024 -p 11222 start

16.3 telnet操作memcached

telnet ip地址 [11211]
  1. 添加数据:

    • set
      

      • 语法:

          set key flas(是否压缩) timeout value_lengthvalue
        
      • 示例:

          set username 0 60 7zhiliao
        
    • add

      • 语法:

          add key flas(0) timeout value_lengthvalue
        
      • 示例:

          add username 0 60 7xiaotuo
        

        setadd的区别:add是只负责添加数据,不会去修改数据。如果添加的数据的key已经存在了,则添加失败,如果添加的key不存在,则添加成功。而set不同,如果memcached中不存在相同的key,则进行添加,如果存在,则替换。

  2. 获取数据:

    • 语法:

        get key
      
    • 示例:

        get username
      
  3. 删除数据:

    • 语法:

        delete key
      
    • 示例:

        delete username
      
    • flush_all:删除memcached中的所有数据。

  4. 查看memcached的当前状态:

    • 语法:stats

16.4 通过python操作memcached

  1. 安装:python-memcachedpip install python-memcached

  2. 建立连接:

     import memcachemc = memcache.Client(['127.0.0.1:11211','192.168.174.130:11211'],debug=True)
    
  3. 设置数据:

     mc.set('username','hello world',time=60*5)mc.set_multi({'email':'xxx@qq.com','telphone':'111111'},time=60*5)
    
  4. 获取数据:

     mc.get('telphone')
    
  5. 删除数据:

     mc.delete('email')
    
  6. 自增长:

     mc.incr('read_count')
    
  7. 自减少:

     mc.decr('read_count')
    

16.5 memcached的安全性:

memcached的操作不需要任何用户名和密码,只需要知道memcached服务器的ip地址和端口号即可。因此memcached使用的时候尤其要注意他的安全性。这里提供两种安全的解决方案。分别来进行讲解:

  1. 使用-l参数设置为只有本地可以连接:这种方式,就只能通过本机才能连接,别的机器都不能访问,可以达到最好的安全性。
  2. 使用防火墙,关闭11211端口,外面也不能访问。
  ufw enable # 开启防火墙ufw disable # 关闭防火墙ufw default deny # 防火墙以禁止的方式打开,默认是关闭那些没有开启的端口ufw deny 端口号 # 关闭某个端口ufw allow 端口号 # 开启某个端口

16.6 在Django中使用memcached:

首先需要在settings.py中配置好缓存:

CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': '127.0.0.1:11211',}
}

如果想要使用多台机器,那么可以在LOCATION指定多个连接,示例代码如下:

CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': ['172.19.26.240:11211','172.19.26.242:11211',]}
}

配置好memcached的缓存后,以后在代码中就可以使用以下代码来操作memcached了:

from django.core.cache import cachedef index(request):cache.set('abc','zhiliao',60)print(cache.get('abc'))response = HttpResponse('index')return response

需要注意的是,django在存储数据到memcached中的时候,不会将指定的key存储进去,而是会对key进行一些处理。比如会加一个前缀,会加一个版本号。如果想要自己加前缀,那么可以在settings.CACHES中添加KEY_FUNCTION参数:

CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': '127.0.0.1:11211','KEY_FUNCTION': lambda key,prefix_key,version:"django:%s"%key}
}

===================================================================

一、项目准备阶段

前端开发环境配置

nvm->node.js -> npm

nvm安装

nvm(Node Version Manager)是一个用来管理node版本的工具。我们之所以需要使用node,是因为我们需要使用node中的npm(Node Package Manager),使用npm的目的是为了能够方便的管理一些前端开发的包!nvm的安装非常简单,步骤如下:

  1. 到这个链接下载nvm的安装包:https://github.com/coreybutler/nvm-windows/releases
  2. 然后点击一顿下一步,安装即可!
  3. 安装完成后,还需要配置环境变量。在我的电脑->属性->高级系统设置->环境变量->系统环境变量->Path下新建一个,把nvm所处的路径填入进去即可!
  4. 打开cmd,然后输入nvm,如果没有提示没有找不到这个命令。说明已经安装成功!
  5. Mac或者Linux安装nvm请看这里:https://github.com/creationix/nvm。也要记得配置环境变量。

nvm常用命令:

  1. nvm install node:默认安装最新版的node.js。nvm i == nvm install。
  2. nvm install [version]:安装指定版本的node.js
  3. nvm use [version]:使用某个版本的node
  4. nvm list:列出当前安装了哪些版本的node
  5. nvm uninstall [version]:卸载指定版本的node
  6. nvm node_mirror [url]:设置nvm的镜像。
  7. nvm npm_mirror [url]:设置npm的镜像。

node安装

安装完nvm后,我们就可以通过nvm来安装node了。这里我们安装6.4.0版本的的node.js就可以。因为最新版的node.jsnpm5.0的,上面还有很多坑。安装命令如下:

nvm install 6.4.0

如果你的网络够快,那以上命令在稍等片刻之后会安装成功。如果你的网速很慢,那以上命令可能会发生超时。因为node的服务器地址是https://nodejs.org/dist/,这个域名的服务器是在国外。因此会比较慢。因此我们可以设置一下nvm的源。

nvm node_mirror https://npm.taobao.org/mirrors/node/
nvm npm_mirror https://npm.taobao.org/mirrors/npm/

npm

npm(Node Package Manager)在安装node的时候就会自动的安装了。当时前提条件是你需要设置当前的node的版本:nvm use 6.4.0。然后就可以使用npm了.
关于npm常用命令以及用法,请看下文。

安装包

安装包分为全局安装和本地安装。全局安装是安装在当前node环境中,在可以在cmd中当作命令使用。而本地安装是安装在当前项目中,只有当前这个项目能使用,并且可以通过require引用。安装的方式只有-g参数的区别:npm install express

npm install express      # 本地安装(在打算上线的系统文件主目录里安装)
npm install express -g   # 全局安装(接着上面的安装)

本地安装

  1. 将安装包放在./node_modules下(运行 npm 命令时所在的目录),如果没有node_modules目录,会在当前执行npm命令的目录下生成node_modules目录。
  2. 可以通过require()来引入本地安装的包。

全局安装

  1. 将安装包放在/usr/local下或者你node的安装目录。
  2. 可以直接在命令行里使用。

卸载包

npm uninstall [package]

更新包

npm update [package]

搜索包

npm search [package]

使用淘宝镜像

npm install -g cnpm --registry=https://registry.npm.taobao.org那么以后就可以使用cnpm来安装包了!

cnpm install update uninstall search

前端项目搭建

1》npm install express
2》npm install -g cnpm --registry=https://registry.npm.taobao.org(非必须,使用国内源,加速)
3》npm install gulp --save-dev   #本地项目中安装glup
4》npm init            #命令在本地生成一个`package.json`文件
5》cnpm install        #自动安装`package.json`下`devDependencies`中指定的依赖包

前端优化:

1.减少http请求 2.雪碧图  并且压缩图片3.合并css js文件并且压缩

前端我们使用gulp来自动化开发流程。配置好gulp后,可以自动给我们处理好一些工作。比如写完css后,要压缩成.min.css,写完js后,要做混淆和压缩,图片压缩等。这些工作都可以让gulp帮我们完成。

安装gulp

1. 创建本地包管理环境

使用npm init命令在本地生成一个package.json文件,package.json是用来记录你当前这个项目依赖了哪些包,以后别人拿到你这个项目后,不需要你的node_modules文件夹(因为node_moduels中的包实在太庞大了)。只需要执行npm install命令,即会自动安装package.jsondevDependencies中指定的依赖包。

2. 安装gulp

gulp的安装非常简单,只要使用npm命令安装即可。但是因为gulp需要作为命令行的方式运行,因此需要在安装在系统级别的目录中。

npm install gulp -g

因为在本地需要使用require的方式gulp。因此也需要在本地安装一份:

切换到项目目录
npm install gulp --save-dev

以上的--save-dev是将安装的包的添加到package.json下的devDependencies依赖中。以后通过npm install即可自动安装。devDependencies这个是用来记录开发环境下使用的包,如果想要记录生产环境下使用的包,那么在安装包的时候使用npm install xx --save就会记录到package.json下的dependencies中,dependencies是专门用来记录生产环境下的依赖包的!

3. 创建gulp任务

要使用gulp来流程化我们的开发工作。首先需要在项目的根目录下创建一个gulpfile.js文件。然后在gulpfile.js中填入以下代码:

var gulp = require("gulp")  #引入gulp gulp.task("kangbazi",function () {console.log('hello world');
});

这里对代码进行一一解释:

  1. 通过require语句引用已经安装的第三方依赖包。这个require只能是引用当前项目的,不能引用全局下的。require语法是node.js独有的,只能在node.js环境下使用。

  2. gulp.task是用来创建一个任务。gulp.task的第一个参数是命令的名字,第二个参数是一个函数,就是执行这个命令的时候会做什么事情,都是写在这个里面的。

  3. 写完以上代码后,以后如果想要执行greet命令,那么只需要进入到项目所在的路径,然后终端使用

    gulp kangbazi即可执行。

4. 创建处理css文件的任务

gulp只是提供一个框架给我们。如果我们想要实现一些更加复杂的功能,比如css压缩,那么我们还需要安装一下gulp-cssnano插件。gulp相关的插件安装也是通过npm命令安装,安装方式跟其他包是一模一样的(gulp插件本身就是一个普通的包)。
css文件的处理,需要做的事情就是压缩,然后再将压缩后的文件放到指定目录下(不要和原来css文件重合了)!这里我们使用gulp-cssnano来处理这个工作:

npm install gulp-cssnano --save-dev

然后在gulpfile.js中写入以下代码:

var gulp = require("gulp")
var cssnano = require("gulp-cssnano")// 定义一个处理css文件改动的任务
gulp.task("css",function () {gulp.src("./css/*.css").pipe(cssnano()).pipe(gulp.dest("./css/dist/"))
});

以上对代码进行详细解释:

  1. gulp.task:创建一个css处理的任务。
  2. gulp.src:找到当前css目录下所有以.css结尾的css文件。
  3. pipe:管道方法。将上一个方法的返回结果传给另外一个处理器。比如以上的cssnano
  4. gulp.dest:将处理完后的文件,放到指定的目录下。不要放在和原文件相同的目录,以免产生冲突,也不方便管理。

5. 修改文件名

像以上任务,压缩完css文件后,最好是给他添加一个.min.css的后缀,这样一眼就能知道这个是经过压缩后的文件。这时候我们就需要使用gulp-rename来修改了。当然首先也需要安装npm install gulp-rename --save-dev。示例代码如下:

var gulp = require("gulp")
var cssnano = require("gulp-cssnano")
var rename = require("gulp-rename")
gulp.task("css",function () {gulp.src("./css/*.css").pipe(cssnano()).pipe(rename({"suffix":".min"})).pipe(gulp.dest("./css/dist/"))
});

在上述代码中,我们增加了一行.pipe(rename({"suffix":".min"})),这个我们就是使用rename方法,并且传递一个对象参数,指定修改名字的规则为添加一个.min后缀名。这个gulp-rename还有其他的指定文件名的方式,比如可以在文件名前加个前缀等。更多的教程可以看这个:https://www.npmjs.com/package/gulp-rename

6. 创建处理js文件的任务

处理js文件,我们需要使用到gulp-uglify插件。安装命令如下:

npm install gulp-uglify --save-dev

安装完后,我们就可以对js文件进行处理了。示例代码如下:

var gulp = require("gulp")
var rename = require("gulp-rename")
var uglify = require('gulp-uglify');
gulp.task('script',function(){gulp.src("./js/*.js").pipe(uglify()).pipe(rename({suffix:'.min'})) #这个是 压缩后 自动在 css前面加min.pipe(gulp.dest('js/')); #压缩以后放到指定的目录下面
});

这里就是增加了一个.pipe(uglify())的处理,对js文件进行压缩和丑化(修改变量名)等处理。更多关于gulp-uglify的教程。请看:https://github.com/mishoo/UglifyJS2#minify-options

7. 合并多个文件

在网页开发中,为了加快网页的渲染速度,有时候我们会将多个文件压缩成一个文件,从而减少请求的次数。要拼接文件,我们需要用到gulp-concat插件。安装命令如下:

npm install gulp-concat --save-dev

比如我们现在有一个nav.js文件用来控制导航条的。有一个index.js文件用来控制首页整体内容的。那么我们可以使用以下代码将这两个文件合并成一个文件:

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
gulp.task('vendorjs',function(){gulp.src(['./js/nav.js','./js/index.js'  #指定目标文件进行压缩]).pipe(concat('index.min.js')).pipe(uglify()).pipe(gulp.dest('dist/js/'));
});

8. 压缩图片

图片是限制网站加载速度的一个主要原因。图片越大,从网站上下载所花费的时间越长。因此对于一些图片,我们可以采取无损压缩,即在不改变图片质量的基础之上进行压缩。在gulp中我们可以通过gulp-imagemin来帮我们实现。安装命令如下:

npm install gulp-imagemin --save-dev

压缩图片也是一个比较大的工作量,对于一些已经压缩过的图片,我们就没必要再重复压缩了。这时候我们可以使用gulp-cache来缓存那些压缩过的图片。安装命令如下:

npm install gulp-cache --save-dev

两个插件结合使用的代码如下:

var imagemin = require('gulp-imagemin');
var cache = require('gulp-cache');
gulp.task('image',function(){gulp.src("./images/*.*").pipe(cache(imagemin())).pipe(gulp.dest('dist/images/'));
});

9. 检测代码修改,自动刷新浏览器

以上所有的任务,我们都是需要手动的在终端去执行。这样很不方便我们开发。最好的方式就是我修改了代码后,gulp会自动的执行相应的任务。这个工作我们可以使用gulp内置的watch方法帮我们完成:

var gulp = require("gulp")
var cssnano = require("gulp-cssnano")
var rename = require("gulp-rename")// 定义一个处理css文件改动的任务
gulp.task("css",function () {gulp.src("./css/*.css").pipe(cssnano()).pipe(rename({"suffix":".min"})).pipe(gulp.dest("./css/dist/")).pipe(connect.reload())
});// 定义一个监听的任务
gulp.task("watch",function () {// 监听所有的css文件,然后执行css这个任务gulp.watch("./css/*.css",['css'])
});

以后只要在终端执行gulp watch命令即可自动监听所有的css文件,然后自动执行css的任务,完成相应的工作。

10. 更改文件后,自动刷新浏览器

以上我们实现了更改一些css文件后,可以自动执行处理css的任务。但是我们还是需要手动的去刷新浏览器,才能看到修改后的效果。有什么办法能在修改完代码后,自动的刷新浏览器呢。答案是使用browser-syncbrowser-sync安装的命令如下:

cnpm install browser-sync --save-dev

browser-sync使用的示例代码如下:

var gulp = require("gulp")
var cssnano = require("gulp-cssnano")
var rename = require("gulp-rename")
var bs = require("browser-sync").create()gulp.task("bs",function () {bs.init({'server': {'baseDir': './'}});
});// 定义一个处理css文件改动的任务
gulp.task("css",function () {gulp.src("./css/*.css").pipe(cssnano()).pipe(rename({"suffix":".min"})).pipe(gulp.dest("./css/dist/")).pipe(bs.stream())
});// 定义一个监听的任务
gulp.task("watch",function () {gulp.watch("./css/*.css",['css'])
});// 执行gulp server开启服务器
gulp.task("server",['bs','watch'])

以上我们创建了一个bs的任务,这个任务会开启一个3000端口,以后我们在访问html页面的时候,就需要通过http://127.0.0.1:3000的方式来访问了。然后接下来我们还定义了一个server任务。这个任务会去执行bswatch任务,只要修改了css文件,那么就会执行css的任务,然后就会自动刷新浏览器。
browser-sync更多的教程请参考:http://www.browsersync.cn/docs/gulp/

Sass语法

众所周知,css不是一门编程语言。他没法像jspython那样拥有逻辑处理的能力,甚至导入其他的css文件中的样式都做不到。而Sass就是为了解决css的这些问题。他它允许你使用变量、嵌套规则、 mixins、导入等众多功能,并且完全兼容css语法。Sass文件不能直接被网页所识别,写完Sass后,还需要专门的工具转化为css才能使用。

Sass文件的后缀名

Sass文件有两种后缀名,一个是scss,一个是sass。不同的后缀名,相应的语法也不一样。这里我们使用scss的后缀名。包括后面讲到的Sass语法,也都是scss的后缀名的语法。

使用gulp将Sass转换为css

Sass文件转换为css文件的工具有很多。这里我们就使用之前讲过的gulp来实现。这里我们需要使用gulp-sass插件来帮我们完成。安装方式非常简单:npm install gulp-sass --save-dev。那么处理sass的代码如下:

var gulp = require("gulp");
var sass = require("gulp-sass");
// 处理css的任务
gulp.task('css',function () {gulp.src(path.css + '*.scss').pipe(sass().on("error",sass.logError)).pipe(cssnano()).pipe(rename({"suffix":".min"})).pipe(gulp.dest(path.css_dist))
});

Sass基本语法

注释

支持/* comment */// 注释两种方式。

嵌套

Sass语法允许嵌套。比如#main下有一个类为.header,那么我们可以写成以下的形式:

#main{background: #ccc;.header{width: 20px;height: 20px;}
}

这样写起来更加的直观。一看就知道.header是在#main下的。

引用父选择器(&

有时候,在嵌套的子选择器中,需要使用父选择器,那么这时候可以通过&来表示。示例代码如下:

a{font-weight: bold;text-decoration: none;&:hover{color: #888;}
}

定义变量

是的,你没听错。在Sass中可以定义变量。对于一些比较常用的值,我们可以通过变量存储起来,以后想要使用的时候就直接用就可以了。定义变量使用$符号。示例代码如下:

$mainWidth: 980px;
#main{width: $mainWidth;
}

运算

Sass中支持运算。比如现在有一个容器总宽度是900,要在里面平均放三个盒子,那么我们可以通过变量来设置他们的宽度。示例代码如下:

$mainWidth: 900px;
.box{width: $mainWidth/3;
}

@import语法

css@import只能导入css文件,而且对网站性能有很大的影响。而Sass中的@import则是完全实现了一套自己的机制。他可以直接将指定文件的代码拷贝到导入的地方。示例代码如下:

@import "init.scss";

@extend语法

有时候我们一个选择器中,可能会需要另外一个选择器的样式,那么我们就可以通过extend来直接将指定选择器的样式加入进来。示例代码如下:

.error{background-color: #fdd;border: 1px solid #f00;
}
.serious-error{@extend .error;border-width: 3px;
}

@mixin语法

有时候一段样式代码。我们可能要用很多地方。那么我们可以把他定义i成mixin。需要用的时候就直接引用就可以了。示例代码如下:

@mixin large-text {font: {family: Arial;size: 20px;weight: bold;}color: #ff0000;
}

如果其他地方想要使用这个mixin的时候,可以通过@include来包含进来。示例代码如下:

.page-title {@include large-text;padding: 4px;margin-top: 10px;
}

@mixin也可以使用参数。示例代码如下:

@mixin sexy-border($color, $width) {border: {color: $color;width: $width;style: dashed;}
}

那么以后在include的时候,就需要传递参数了。示例代码如下:

p { @include sexy-border(blue, 1px);
}

更详细的教程

更详细的教程可以参考:http://sass.bootcss.com/docs/sass-reference/

二、项目开发阶段

User模型(2018.9.27)

User模型是这个框架的核心部分。他的完整的路径是在django.contrib.auth.models.User。以下对这个User对象做一个简单了解:

user模型内置字段规则

内置的User模型拥有以下的字段:

  1. username: 用户名。150个字符以内。可以包含数字和英文字符,以及_@+.-字符。不能为空,且必须唯一!
  2. first_name:歪果仁的first_name,在30个字符以内。可以为空。
  3. last_name:歪果仁的last_name,在150个字符以内。可以为空。
  4. email:邮箱。可以为空。
  5. password:密码。经过哈希过后的密码。
  6. groups:分组。一个用户可以属于多个分组,一个分组可以拥有多个用户。groups这个字段是跟Group的一个多对多的关系。
  7. user_permissions:权限。一个用户可以拥有多个权限,一个权限可以被多个用户所有用。和Permission属于一种多对多的关系。
  8. is_staff:是否可以进入到admin的站点。代表是否是员工。
  9. is_active:是否是可用的。对于一些想要删除账号的数据,我们设置这个值为False就可以了,而不是真正的从数据库中删除。
  10. is_superuser:是否是超级管理员。如果是超级管理员,那么拥有整个网站的所有权限。
  11. last_login:上次登录的时间。
  12. date_joined:账号创建的时间。

User模型的基本用法

创建用户

通过create_user方法可以快速的创建用户。这个方法必须要传递usernameemailpassword。示例代码如下:

#views.pyfrom django.contrib.auth.models import User
user = User.objects.create_user('zhiliao','hynever@zhiliao.com','111111')
# 此时user对象已经存储到数据库中了。当然你还可以继续使用user对象进行一些修改
user.last_name = 'abc'
user.save()

创建超级用户

创建超级用户有两种方式。第一种是使用代码的方式。用代码创建超级用户跟创建普通用户非常的类似,只不过是使用create_superuser。示例代码如下:

#views.py
from django.contrib.auth.models import User
User.objects.create_superuser('admin','admin@163.com','111111')

也可以通过命令行的方式。命令如下:

python manage.py createsuperuser

后面就会提示你输入用户名、邮箱以及密码。

修改密码

因为密码是需要经过加密后才能存储进去的。所以如果想要修改密码,不能直接修改password字段,而需要通过调用set_password来达到修改密码的目的。示例代码如下:

#views.py
from django.contrib.auth.models import User
user = User.objects.get(pk=1)
user.set_password('新的密码')
user.save() #修改密码 别忘了 保存

登录验证(全局使用用户信息)

Django的验证系统已经帮我们实现了登录验证的功能。通过django.contrib.auth.authenticate即可实现。这个方法只能通过usernamepassword来进行验证。示例代码如下:

views.py
from django.contrib.auth import authenticate
user = authenticate(username='zhiliao', password='111111')
# 如果验证通过了,那么就会返回一个user对象。可以全局使用,类似于自定义上下文处理器
if user is not None:# 执行验证通过后的代码
else:# 执行验证没有通过的代码。

扩展用户模型:

Django内置的User模型虽然已经足够强大了。但是有时候还是不能满足我们的需求。比如在验证用户登录的时候,他用的是用户名作为验证,而我们有时候需要通过手机号码或者邮箱来进行验证。还有比如我们想要增加一些新的字段。那么这时候我们就需要扩展用户模型了。扩展用户模型有多种方式。这里我们来一一讨论下。

1. 设置Proxy模型: 操作 代理模型 就是操作 User模型

如果你对Django提供的字段,以及验证的方法都比较满意,没有什么需要改的。但是只是需要在他原有的基础之上增加一些操作的方法。那么建议使用这种方式。示例代码如下:

class Person(User):telephone = models.CharField(max_length=11) #代理模型不能创建字段    这一步肯定报错Person.objects.all() #跟  User.objects.all() #效果一样class Meta:proxy = True@classmethod   #这个叫做类方法     def get_blacklist(self):return self.objects.filter(is_active=False)views.py def proxy_view(request):# blacklists= Person.get_blacklist()#获取黑名单# for blacklist in blacklists:#     print(blacklist.username)# users = Person.objects.all()#获取所有用户# for user in users:#     print(user.username)stafflists = Person.get_stafflist()for stafflist in stafflists:print(stafflist.username)return HttpResponse("成功")

在以上,我们定义了一个Person类,让他继承自User,并且在Meta中设置proxy=True,说明这个只是User的一个代理模型。他并不会影响原来User模型在数据库中表的结构。以后如果你想方便的获取所有黑名单的人,那么你就可以通过Person.get_blacklist()就可以获取到。并且User.objects.all()Person.objects.all()其实是等价的。因为他们都是从User这个模型中获取所有的数据。

2. 一对一外键:

如果你对用户验证方法authenticate没有其他要求,就是使用usernamepassword即可完成。但是想要在原来模型的基础之上添加新的字段,那么可以使用一对一外键的方式。示例代码如下:User模型默认的验证是通过username和 password

#models.py
from django.contrib.auth.models import User
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import post_saveclass UserExtension(models.Model):user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='extension')birthday = models.DateField(null=True,blank=True)school = models.CharField(max_length=100)  #别忘了映射(python menger.py  XXX)#reveiver 表示收到信号 如果信号是 User发过来的 save
@receiver(post_save,sender=User)
def create_user_extension(sender,instance,created,**kwargs):if created:UserExtension.objects.create(user=instance) #新建  else:instance.extension.save() #删除和修改#views.py
#传统的   authenticate 只能验证  username 和password   返回的是 user
#自定义方法    需要传两个参数   第一个参数 表示  telephone  第二个参数  密码
def my_authenticate(telephone,password):   user = User.objects.filter(extension__telephone=telephone).first()#根据你传过来的 telephone从 User关联的表中查询 返回第一条结果  if user:  #如果有  is_true = user.check_password(password)if is_true:return user  else:return Nonedef one_view(request):# user = User.objects.create_user(username="dingkou",email="haha@aliyun.com",password="sh1806")# user.extension.telephone = '13888888888'# user.save()telephone = request.GET.get('telephone')password = request.GET.get('password')user = my_authenticate(telephone,password) #接收完用户的提交之后 然后 调用自定义的方法      if user:print("验证成功 %s" % user.username)else:print("验证失败")return HttpResponse("一对一扩展模型")

以上定义一个UserExtension的模型,并且让她和User模型进行一对一的绑定,以后我们新增的字段,就添加到UserExtension上。并且还写了一个接受保存模型的信号处理方法,只要是User调用了save方法,那么就会创建一个UserExtensionUser进行绑定。

3. 继承自AbstractUser

对于authenticate不满意,并且不想要修改原来User对象上的一些字段,但是想要增加一些字段,那么这时候可以直接继承自django.contrib.auth.models.AbstractUser,其实这个类也是django.contrib.auth.models.User的父类。比如我们想要在原来User模型的基础之上添加一个telephoneschool字段。示例代码如下:

from django.contrib.auth.models import AbstractUser
class User(AbstractUser):telephone = models.CharField(max_length=11,unique=True)school = models.CharField(max_length=100)# 指定telephone作为USERNAME_FIELD,以后使用authenticate# 函数验证的时候,就可以根据telephone来验证# 而不是原来的usernameUSERNAME_FIELD = 'telephone'REQUIRED_FIELDS = []# 重新定义Manager对象,在创建user的时候使用telephone和# password,而不是使用username和passwordobjects = UserManager()原来的UserManager 验证的是 def _create_user(self, username, email, password, **extra_fields):authenticate只验证 用户名和密码   现在验证  手机号
class UserManager(BaseUserManager):use_in_migrations = Truedef _create_user(self,telephone,password,**extra_fields): #这个受保护的  只能类内部使用if not telephone:raise ValueError("请填入手机号码!")user = self.model(telephone=telephone,*extra_fields)user.set_password(password)user.save()return userdef create_user(self,telephone,password,**extra_fields): #创建普通用户extra_fields.setdefault('is_superuser',False)return self._create_user(telephone,password)def create_superuser(self,telephone,password,**extra_fields): #创建超级用户extra_fields['is_superuser'] = Truereturn self._create_user(telephone,password)

然后再在settings中配置好AUTH_USER_MODEL=youapp.User

这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好。

4. 继承自AbstractBaseUser模型:

如果你想修改默认的验证方式,并且对于原来User模型上的一些字段不想要,那么可以自定义一个模型,然后继承自AbstractBaseUser,再添加你想要的字段。这种方式会比较麻烦,最好是确定自己对Django比较了解才推荐使用。步骤如下:

  1. 创建模型。示例代码如下:

     class User(AbstractBaseUser,PermissionsMixin):email = models.EmailField(unique=True)username = models.CharField(max_length=150)telephone = models.CharField(max_length=11,unique=True)is_active = models.BooleanField(default=True)USERNAME_FIELD = 'telephone'REQUIRED_FIELDS = []objects = UserManager()def get_full_name(self):return self.usernamedef get_short_name(self):return self.username
    

    其中passwordlast_login是在AbstractBaseUser中已经添加好了的,我们直接继承就可以了。然后我们再添加我们想要的字段。比如emailusernametelephone等。这样就可以实现自己想要的字段了。但是因为我们重写了User,所以应该尽可能的模拟User模型:

    • USERNAME_FIELD:用来描述User模型名字字段的字符串,作为唯一的标识。如果没有修改,那么会使用USERNAME来作为唯一字段。
    • REQUIRED_FIELDS:一个字段名列表,用于当通过createsuperuser管理命令创建一个用户时的提示。
    • is_active:一个布尔值,用于标识用户当前是否可用。
    • get_full_name():获取完整的名字。
    • get_short_name():一个比较简短的用户名。
  2. 重新定义UserManager:我们还需要定义自己的UserManager,因为默认的UserManager在创建用户的时候使用的是usernamepassword,那么我们要替换成telephone。示例代码如下:

     class UserManager(BaseUserManager):use_in_migrations = Truedef _create_user(self,telephone,password,**extra_fields):if not telephone:raise ValueError("请填入手机号码!")user = self.model(telephone=telephone,*extra_fields)user.set_password(password)user.save()return userdef create_user(self,telephone,password,**extra_fields):extra_fields.setdefault('is_superuser',False)return self._create_user(telephone,password)def create_superuser(self,telephone,password,**extra_fields):extra_fields['is_superuser'] = Truereturn self._create_user(telephone,password)
    
  3. 在创建了新的User模型后,还需要在settings中配置好。配置AUTH_USER_MODEL='appname.User'

  4. 如何使用这个自定义的模型:比如以后我们有一个Article模型,需要通过外键引用这个User模型,那么可以通过以下两种方式引用。
    第一种就是直接将User导入到当前文件中。示例代码如下:

     from django.db import modelsfrom myauth.models import Userclass Article(models.Model):title = models.CharField(max_length=100)content = models.TextField()author = models.ForeignKey(User, on_delete=models.CASCADE)
    

    这种方式是可以行得通的。但是为了更好的使用性,建议还是将User抽象出来,使用settings.AUTH_USER_MODEL来表示。示例代码如下:

     from django.db import modelsfrom django.conf import settingsclass Article(models.Model):title = models.CharField(max_length=100)content = models.TextField()author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    

这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好。

项目中的 user模型

1.使用django 自带的用户系统
2.需要重新定制
3.前后台使用同一个user系统 #自定义user模型
1.单独创建一个应用app  用来存放 用户操作系统  xiaofanzhuoauth
2.全部重写   AbstactBaseUser
3.UserManager 需要重新写
4.在settings.py中设置 AUTH_USER_MODEL = "应用.User"
5.映射到数据库中
6.自增长的id 使用 shortuuid  前提需要  pip install django-shortuuidfield# 登录逻辑
1.先用我们准备好的那个 登录页面 测试
然后 后期  前后都用一个登录页面   2.因为前后都用一个user系统  前台ajax 提交过来 提交过来 是 json 数据 3.所有的登录逻辑全部写在 xiaofanzhuoauth

权限和分组

登录、注销和登录限制:

登录

在使用authenticate进行验证后,如果验证通过了。那么会返回一个user对象,拿到user对象后,可以使用django.contrib.auth.login进行登录。示例代码如下:

#views.py
user = authenticate(username=username, password=password)
if user is not None:if user.is_active:login(request, user)

注销:

注销,或者说退出登录。我们可以通过django.contrib.auth.logout来实现。他会清理掉这个用户的session数据以及user对象。

def logout_view(request):logout(request)return redirect(reverse('news:index'))

登录限制:

有时候,某个视图函数是需要经过登录后才能访问的。那么我们可以通过django.contrib.auth.decorators.login_required装饰器来实现。示例代码如下:

from django.contrib.auth.decorators import login_required# 在验证失败后,会跳转到/login/这个url页面
@login_required(login_url="/login/")
def profile(request):return HttpResponse("登錄成功以後才可以查看的個人中心")

def my_login(request):if request.method == 'GET':return render(request,'login.html')else:forms = LoginForm(request.POST)if forms.is_valid():telephone = forms.cleaned_data.get("telephone")password = forms.cleaned_data.get("password")remember = forms.cleaned_data.get("remember")user = authenticate(request,username=telephone,password=password)if user:login(request,user)if remember:request.session.set_expiry(None)else:request.session.set_expiry(0)next_url = request.GET.get('next')#判断后边是否有nextif next_url:return redirect(next_url) #如果有跳转到 next下面else:return HttpResponse("登錄成功")else:return HttpResponse("用戶名或者密碼錯誤")else:print(forms.errors.get_json_data())return redirect(reverse('login'))
def my_logout(request):logout(request)  把请求传给  logout 就是退出登录    return HttpResponse("成功退出登錄")#这个装饰器为了 想要查看 个人中心  必须先登录
#login_url="/login/" 如果你没有登录 先要跳转到登录页面 但是 跳转的登录地址 不是咱们想要的  如果指定登录的地址  必须加上 login_url="/login/
@login_required(login_url="/login/")
def profile(request):return HttpResponse("登錄成功以後才可以查看的個人中心")

项目中的 user模型

1.使用django 自带的用户系统
2.需要重新定制
3.前后台使用同一个user系统 #自定义user模型
1.单独创建一个应用app  用来存放 用户操作系统  xiaofanzhuoauth
2.全部重写   AbstactBaseUser
3.UserManager 需要重新写
4.在settings.py中设置 AUTH_USER_MODEL = "应用.User"
5.映射到数据库中
6.自增长的id 使用 shortuuid  前提需要  pip install django-shortuuidfield# 登录逻辑
1.先用我们准备好的那个 登录页面 测试
然后 后期  前后都用一个登录页面   2.因为前后都用一个user系统  前台ajax 提交过来 提交过来 是 json 数据 3.所有的登录逻辑全部写在 xiaofanzhuoauth

表单验证出现错误信息 需要传给前端

如果验证失败了,那么有一些错误信息是我们需要传给前端的。这时候我们可以通过以下属性来获取:

form.errors:这个属性获取的错误信息是一个包含了html标签的错误信息。
form.errors.get_json_data():这个方法获取到的是一个字典类型的错误信息。将某个字段的名字作为key,错误信息作为值的一个字典。
form.as_json():这个方法是将form.get_json_data()返回的字典dump成json格式的字符串,方便进行传输。
上述方法获取的字段的错误值,都是一个比较复杂的数据。比如以下:

{'username': [{'message': 'Enter a valid URL.', 'code': 'invalid'}, {'message': 'Ensure this value has at most 4 characters (it has 22).', 'code': 'max_length'}]}

那么如果我只想把错误信息放在一个列表中,而不要再放在一个字典中。这时候我们可以定义一个方法,把这个数据重新整理一份。实例代码如下:

class MyForm(forms.Form):username = forms.URLField(max_length=4)def get_errors(self):errors = self.errors.get_json_data()new_errors = {}for key,message_dicts in errors.items():messages = []for message in message_dicts:messages.append(message['message'])new_errors[key] = messagesreturn new_errors

这样就可以把某个字段所有的错误信息直接放在这个列表中。

restful API 就是一个接口标准

丢给前端一个地址 地址里边是 json数据 前端将json转为 js 然后 显示在页面上

前端通过接口地址 提交数据 给 后台完成数据的增删改查

GET 查

PUT 改

POST 增

DELETE 删除

我们需要判断 用户过来的请求方式 :

权限:

Django中内置了权限的功能。他的权限都是针对表或者说是模型级别的。比如对某个模型上的数据是否可以进行增删改查操作。他不能针对数据级别的,比如对某个表中的某条数据能否进行增删改查操作(如果要实现数据级别的,考虑使用django-guardian)。创建完一个模型后,针对这个模型默认就有三种权限,分别是增/删/改/。可以在执行完migrate命令后,查看数据库中的auth_permission表中的所有权限。

如何获取settings.py中的  AUTH_USER_MODEL = 'news.User' 

其中的codename表示的是权限的名字。name表示的是这个权限的作用。

通过定义模型添加权限:

如果我们想要增加新的权限,比如查看某个模型的权限,那么我们可以在定义模型的时候在Meta中定义好。示例代码如下:

class Article(models.Model):title = models.CharField(max_length=100)content = models.TextField()author = models.ForeignKey(get_user_model(),on_delete=models.CASCADE)class Meta:permissions = (('view_article','can view article'),)

通过代码添加权限:

权限都是django.contrib.auth.Permission的实例。这个模型包含三个字段,namecodename以及content_type,其中的content_type表示这个permission是属于哪个app下的哪个models。用Permission模型创建权限的代码如下:

from django.contrib.auth.models import Permission,ContentType
from .models import Articledef add_permissions(request):contenttype =  ContentType.objects.get_for_model(Article)#告诉 这是哪个app下面的  哪个model 的权限permission = Permission.objects.create(codename="black_list",name="文章拉黑",content_type=contenttype)return HttpResponse("添加权限成功")def do_permissions(request):user =  User.objects.filter(pk=2).first()contenttype = ContentType.objects.get_for_model(Article)#Article模型 content_type为7   前边获取到了  7#给用户添加 content_type为7 的所有权限permissions = Permission.objects.filter(content_type=contenttype)for permission in permissions:print(permission)user.user_permissions.set(permissions)#user.user_permissions.clear()#user.user_permissions.add(permissions[0],permissions[1])#user.user_permissions.remove(permissions[0],permissions[1])if request.user.has_perm('news.view_article'):print("拥有权限")else:print("没有权限")return HttpResponse("添加权限成功")

用户与权限管理:

权限本身只是一个数据,必须和用户进行绑定,才能起到作用。User模型和权限之间的管理,可以通过以下几种方式来管理:news_user_user_permissions 其实就是这个表的操作

  1. myuser.user_permissions.set(permission_list):直接给定一个权限的列表。
  2. myuser.user_permissions.add(permission,permission,...):一个个添加权限。
  3. myuser.user_permissions.remove(permission,permission,...):一个个删除权限。
  4. myuser.user_permissions.clear():清除权限。
  5. myuser.has_perm('<app_name>.<codename>'):判断是否拥有某个权限。权限参数是一个字符串,格式是app_name.codename
  6. myuser.get_all_permissons():获取所有的权限。

权限限定装饰器:

使用django.contrib.auth.decorators.permission_required可以非常方便的检查用户是否拥有这个权限,如果拥有,那么就可以进入到指定的视图函数中,如果不拥有,那么就会报一个400错误。示例代码如下:

from django.contrib.auth.decorators import permission_required@permission_required(['news.delete_article'],login_url="/login/",raise_exception=True)
def my_view(request):...def add_article(request):if request.user.is_authenticated:print("已经登录")if request.user.has_perm("news.view_article"):return HttpResponse("这是添加文章的页面")else:return HttpResponse("您没有访问权限",status=403)else:return redirect(reverse('login'))return HttpResponse("这是添加文章的页面")

分组:

权限有很多,一个模型就有最少三个权限,如果一些用户拥有相同的权限,那么每次都要重复添加。这时候分组就可以帮我们解决这种问题了,我们可以把一些权限归类,然后添加到某个分组中,之后再把和把需要赋予这些权限的用户添加到这个分组中,就比较好管理了。分组我们使用的是django.contrib.auth.models.Group模型, 每个用户组拥有idname两个字段,该模型在数据库被映射为auth_group数据表。

分组操作:

  1. Group.object.create(group_name):创建分组。
  2. group.permissions:某个分组上的权限。多对多的关系。
    • group.permissions.add:添加权限。
    • group.permissions.remove:移除权限。
    • group.permissions.clear:清除所有权限。
    • user.get_group_permissions():获取用户所属组的权限。
  3. user.groups:某个用户上的所有分组。多对多的关系。

在模板中使用权限:

settings.TEMPLATES.OPTIONS.context_processors下,因为添加了django.contrib.auth.context_processors.auth上下文处理器,因此在模板中可以直接通过perms来获取用户的所有权限。

图片验证码

(2018.9.28)

通过urls—views—调用生成图片方法—实现验证码保存为图片—通过html文件将图片放置指定位置—通过js、css实现同台动态效果(点击更行验证码)。

生成图片函数

在项目中,单独存放一些通用功能,所以生成图片验证方式放在通用的python文件,

import random
# pip install Pillow
# Image:是一个画板(context),ImageDraw:是一个画笔, ImageFont:画笔的字体
from PIL import Image,ImageDraw,ImageFont
import time
import os
import string# Captcha验证码class Captcha(object):# 把一些常量抽取成类属性#字体的位置(将字体文件verdana.ttf放置在此py文件同一级目录)font_path = os.path.join(os.path.dirname(__file__),'verdana.ttf')# font_path = 'utils/captcha/verdana.ttf'#生成几位数的验证码number = 4#生成验证码图片的宽度和高度size = (100,40)#背景颜色,默认为白色 RGB(Re,Green,Blue)bgcolor = (0,0,0)#随机字体颜色random.seed(int(time.time()))fontcolor = (random.randint(200,255),random.randint(100,255),random.randint(100,255))# 验证码字体大小fontsize = 20#随机干扰线颜色。linecolor = (random.randint(0,250),random.randint(0,255),random.randint(0,250))# 是否要加入干扰线draw_line = True# 是否绘制干扰点draw_point = True# 加入干扰线的条数line_number = 3SOURCE = list(string.ascii_letters) #随机生成字符串      a-zA-Zfor index in range(0, 10):SOURCE.append(str(index))#用来随机生成一个字符串(包括英文和数字)# 定义成类方法,然后是私有的,对象在外面不能直接调用@classmethoddef gene_text(cls):return ''.join(random.sample(cls.SOURCE,cls.number))#number是生成验证码的位数#用来绘制干扰线@classmethoddef __gene_line(cls,draw,width,height):begin = (random.randint(0, width), random.randint(0, height))end = (random.randint(0, width), random.randint(0, height))draw.line([begin, end], fill = cls.linecolor)# 用来绘制干扰点@classmethoddef __gene_points(cls,draw,point_chance,width,height):chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]for w in range(width):for h in range(height):tmp = random.randint(0, 100)if tmp > 100 - chance:draw.point((w, h), fill=(0, 0, 0))#生成验证码@classmethoddef gene_code(cls):width,height = cls.size #宽和高image = Image.new('RGBA',(width,height),cls.bgcolor) #创建画板font = ImageFont.truetype(cls.font_path,cls.fontsize) #验证码的字体draw = ImageDraw.Draw(image)  #创建画笔text = cls.gene_text() #生成字符串font_width, font_height = font.getsize(text)draw.text(((width - font_width) / 2, (height - font_height) / 2),text,font= font,fill=cls.fontcolor) #填充字符串#填充到上面 draw的 image中# 如果需要绘制干扰线if cls.draw_line:# 遍历line_number次,就是画line_number根线条for x in range(0,cls.line_number):cls.__gene_line(draw,width,height)# 如果需要绘制噪点if cls.draw_point:cls.__gene_points(draw,10,width,height)return (text,image)

生成图片验证码

通过视图来调用函数执行生成图片验证的相关操作。其本质是将验证码保存为一张图片,并同时将验证码中的字符暂时保存在缓存中,以便于之后验证提交的验证码。

#views.py
def img_captcha(request):#第一步 实例化对象 调用text,image = Captcha.gene_code()#因为 图片不能直接放到  HttpResponse 直接返回out = BytesIO() #可以先放到管道中 BytesIO相当于一个管道 存放图片流数据image.save(out,'png')#调用image save方法 将图片流数据保存在BytesIO中 指定图片为pngout.seek(0) #将指针回0response = HttpResponse(content_type="image/png") #指定返回内容的类型response.write(out.read()) #从管道中读取数据  放到 HttpResponse中#这个地方如果 管道指针 不回  0 它会从最后的位置开始往后读  会反回空#所以上一步将指针回0response['Content-length'] = out.tell() #也就是返回指针的位置cache.set(text.lower(),text.lower(),300) #把图形验证码放到缓存中return response

Html调用图片验证码

对于以上操作是将生成的验证码添加生干扰点以及干扰线并保存为png的图片,其路径为:"{% url ‘xiaofanzhuoauth:img_captcha’ %}",通过js实现绑定监听,一旦点击图片,发出请求,请求地址为验证码生成图片的地址,从而实现验证码的更新操作。

html文件

{# 在html文件中使用方式 #}
<div class="input-group"><div class="short-input-group"><input type="text"class="form-control" name="img_captcha" placeholder="图形验证码"></div><div class="input-group-addon"><img src="{% url 'xiaofanzhuoauth:img_captcha' %}" alt="" class="img_captcha" style="cursor: pointer"></div>
</div>

js文件

 var img_captcha = $(".img_captcha")img_captcha.click(function () {img_captcha.attr("src","/account/img_captcha"+"?num="+Math.random())})#类似于请求后面带随机参数,后台认定它将是不同的请求,从而达到无缓存目而且更新验证码,

短信验证码

(2018.9.28)

短信验证码服务商有很多。这里我们选择一个阿里通信来作为短信服务平台。通过这个平台,中小企业及开发者可以在最短的时间内实现短信验证码发送、短信服务提醒、语音验证码、语音服务通知、IVR及呼叫中心、码号、后向流量、隐私保护相关的能力,实现互联网电信化。

官方文档:

https://help.aliyun.com/document_detail/59210.html

登录阿里通信:

  1. 链接:https://www.aliyun.com/product/sms
  2. 然后用淘宝账号进行登录。
    1. 获取AccessKey和ACCESS_KEY_SECRET:

在右上角的头像下,选择AccessKey:

创建签名:

https://dysms.console.aliyun.com/dysms.htm?spm=5176.8195934.907839.sms8.3a574183gWSBsQ#/domestic/text/sign

添加短信模板:

  1. https://dysms.console.aliyun.com/dysms.htm?spm=5176.8195934.907839.sms8.3a574183gWSBsQ#/domestic/text/sign

  2. 模板内容 :

    您的验证码为:${code}

Python发送短信验证码:

  1. 在这里下载PythonSDK

  2. 在这里查看文档。

  3. 下载完SDK后,然后进入到目录中,再进入到你的django开发的虚拟环境中,然后执行命令python setup.py install即可安装SDK

  4. 在下载的SDK文件中,有一个demo_sms_send.py的文件,修改里面相关的参数,然后再修改const.py中的AccessKeySecretKey为之前获取的参数。运行这个文件就可以发送成功了。

     # const.py文件# ACCESS_KEY_ID/ACCESS_KEY_SECRET 根据实际申请的账号信息进行替换ACCESS_KEY_ID = "你的用户的AccessKey"ACCESS_KEY_SECRET = "你的用户的Secretkey"
    

    然后修改demo_sms_send.py的代码:

     import sysfrom aliyunsdkdysmsapi.request.v20170525 import SendSmsRequestfrom aliyunsdkdysmsapi.request.v20170525 import QuerySendDetailsRequestfrom aliyunsdkcore.client import AcsClientimport uuidfrom aliyunsdkcore.profile import region_providerfrom aliyunsdkcore.http import method_type as MTfrom aliyunsdkcore.http import format_type as FTimport constimport json"""短信业务调用接口示例,版本号:v20170525Created on 2017-06-12"""# 注意:不要更改REGION = "cn-hangzhou"PRODUCT_NAME = "Dysmsapi"DOMAIN = "dysmsapi.aliyuncs.com"acs_client = AcsClient(const.ACCESS_KEY_ID, const.ACCESS_KEY_SECRET, REGION)region_provider.add_endpoint(PRODUCT_NAME, REGION, DOMAIN)def send_sms(business_id, phone_numbers, sign_name, template_code, template_param=None):smsRequest = SendSmsRequest.SendSmsRequest()# 申请的短信模板编码,必填smsRequest.set_TemplateCode(template_code)# 短信模板变量参数if template_param is not None:smsRequest.set_TemplateParam(template_param)# 设置业务请求流水号,必填。smsRequest.set_OutId(business_id)# 短信签名smsRequest.set_SignName(sign_name)# 数据提交方式# smsRequest.set_method(MT.POST)# 数据提交格式# smsRequest.set_accept_format(FT.JSON)# 短信发送的号码列表,必填。smsRequest.set_PhoneNumbers(phone_numbers)# 调用短信发送接口,返回jsonsmsResponse = acs_client.do_action_with_exception(smsRequest)# TODO 业务处理return smsResponseif __name__ == '__main__':__business_id = uuid.uuid1()#print(__business_id)params = {'code': 1234}#params = u'{"name":"wqb","code":"12345678","address":"bz","phone":"13000000000"}'print(send_sms(__business_id, "你的手机号", "应用名称", "你申请的模板", json.dumps(params)))
    

实现流程

在html文件中点击控件,触发js绑定的事件,判断是否有号码,再通过ajax.get请求实现views.py中的方法,在views.py的方法中生成验证码,并将其保存到缓存中,调用之前安装的sms模块方法,传递参数(验证码),实现发送带有验证码的短信给用户,并返回结果,views.py的方法返回结果,js根据返回结果执行相应的操作。

html文件

<div class="input-group"><div class="short-input-group"><input type="text" class="form-control" name="sms_captcha" placeholder="短信验证码"></div><div class="input-group-addon"><span class="sms-captcha-btn">发送验证码</span></div>
</div>

js文件

function sms() {var smsCaptcha = $(".sms-captcha-btn");var telephoneInput = $(".signup-group input[name='telephone']");smsCaptcha.click(function () {var telephone = telephoneInput.val();if(!telephone){messageBox.showInfo('请输入手机号码!'); }xfzajax.get({'url': '/account/sms_captcha/','data':{'telephone': telephone},'success': function (result) {if(result['code'] == 200){messageBox.showSuccess('短信验证码发送成功!');smsCaptcha.addClass('disabled');var count = 60;smsCaptcha.unbind('click');var timer = setInterval(function () {smsCaptcha.text(count+'s');count -= 1;if(count <= 0){clearInterval(timer);smsCaptcha.removeClass('disabled');smsCaptcha.text('发送验证码');sms();}},1000);}},'fail': function (error) {console.log(error);}});});
}

views.py文件

from django.core.cache import cache
from utils.aliyunsdk import demo_sms_send
def sms_captcha(request):#http://127.0.0.1:9000/sms_captcha?telephone=13777777777telephone = request.GET.get('telephone') #接收手机号code = Captcha.gene_text() #调用图片验证的生成随机的字符方法cache.set(telephone, code, 5 * 60) #然后放到了缓存中print('短信验证码:', code)result = demo_sms_send.send_sms(telephone,code)#发送给用户return restful.success()

调用现成方法

import sys
from aliyunsdkdysmsapi.request.v20170525 import SendSmsRequest
from aliyunsdkdysmsapi.request.v20170525 import QuerySendDetailsRequest
from aliyunsdkcore.client import AcsClient
import uuid
from aliyunsdkcore.profile import region_provider
from aliyunsdkcore.http import method_type as MT
from aliyunsdkcore.http import format_type as FT
import json"""
短信业务调用接口示例,版本号:v20170525Created on 2017-06-12"""ACCESS_KEY_ID = "你的key"
ACCESS_KEY_SECRET = "你的密钥"# 注意:不要更改
REGION = "cn-hangzhou"
PRODUCT_NAME = "Dysmsapi"
DOMAIN = "dysmsapi.aliyuncs.com"acs_client = AcsClient(ACCESS_KEY_ID, ACCESS_KEY_SECRET, REGION)
region_provider.add_endpoint(PRODUCT_NAME, REGION, DOMAIN)def send_sms(phone_numbers,code):business_id = uuid.uuid1()sign_name = '小饭桌管理平台'template_code = 'SMS_146806055'template_param = json.dumps({"code":code})smsRequest = SendSmsRequest.SendSmsRequest()# 申请的短信模板编码,必填smsRequest.set_TemplateCode(template_code)# 短信模板变量参数if template_param is not None:smsRequest.set_TemplateParam(template_param)# 设置业务请求流水号,必填。smsRequest.set_OutId(business_id)# 短信签名smsRequest.set_SignName(sign_name)# 短信发送的号码列表,必填。smsRequest.set_PhoneNumbers(phone_numbers)# 调用短信发送接口,返回jsonsmsResponse = acs_client.do_action_with_exception(smsRequest)return smsResponse

三、项目保存以及上传阶段

Git

Git 是世界上最牛的 分布式版本管理工具 没有之一

版本管理工具有哪些 ?

  • git
  • svn

安装 windows、Linux

  • Linux apt-get install git
  • windows需要下载 .exe 安装文件

推荐学习网站 : https://backlog.com/git-tutorial/cn/

https 和 ssh 的区别:

  1. https ssh 都能实现 从远程仓库 拉取 推送上去 clone
  2. 不同的是 https 上传拉取的时候 可能需要你输入密码
  3. ssh 配置好了密钥之后 不会每次让你输入密码

git 专用名词

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O73haUzm-1592464959733)(F:/Python%E5%AD%A6%E4%B9%A0/python%E4%B8%8A%E8%AF%BE%E8%AF%BE%E4%BB%B6/%E7%AC%AC3%E9%98%B6%E6%AE%B5%EF%BC%9Adjango%E6%A1%86%E6%9E%B6/2018.9.29/day35/%E7%AC%94%E8%AE%B0/Git%E6%B5%81%E7%A8%8Bpng.png)]

1.新建代码库

新建一个目录 然后 进入
git init  在当前目录创建一个代码库
git init 目录名 #创建一个目录并且将它设定为 代码库
git clone url  

2.配置git

git config --list #显示当前的设置
git config -e --global  #修改 配置文件
#设置代码提交的时候 用户的信息
git config --global user.name "gaohj"
git config --global user.email "gaohj@126.cn" 

3.添加删除文件

git add 文件名1 文件名2  #将文件添加到暂存区
git add . #.代表所有的文件
git add 目录名
git rm -f 删除工作区的文件  但是文件会保存到暂存区
git mv  原文件 名  新文件名   改名字   

4.代码提交

 git commit -m "新增用户模型"  #将暂存区下面所有的文件提交到本地仓库git commit 文件名1 文件名2 -m "你的备注信息" #提交指定的文件到本地仓库  git commit -a  提交的是工作区从上次提交之后的变化的代码  git commit -v  提交的时候显示 所有的不同信息 也就是说 上次提交到这次提交 不同的地方   

5.分支

远处分支本地分支  如果本地分支push到远程  远程才有远程分支 
git branch  #列出所有的本地分支
git branch -r #列出所有的远程分支
git branch -a #列出所有的本地和远程分支
git branch 分支名  #新建分支  但是你还是在原来的分支下面
git checkout 分支名  #切换到指定的分支 并且更新 工作区
git checkout - #切换到上一个分支
git branch -d python1806 #删除分支  注意这里是删除本地分支
git push origin --delete 分支名   #如果是https 克隆到本地仓库 需要 输入你的github 用户名和密码 如果创建了 两个分支  在其中一个分支下a写代码  另外一个分支b不写  a分支如果将这些代码 add 到暂存区 b分支还是能看到代码  只有当a分支将代码 commit到 本次仓库 这个时候 b就看不到  a分支下写的那个代码了   工作区 和暂存区 代码 可以被其它分支 看到
只有将代码 放入本地仓库  这个时候  其它分支看不到了  如果 已经到了本地仓库 其它分支相看代码 这个时候 就要  合并分支   合并分支
git merge 要合并的分支名字
合并分支之前 需要将分支  切换到 没有代码的那个分支   

6.查看信息

git status 显示有变更的文件
git log 当前分支下的代码版本历史
git diff 显示暂存区和工作区代码的区别是啥  

7.远程同步

git pull origin master  #将制定远程分支下面的代码更新本地
git fetch --all  #下载远程仓库下面所有的变动
git push origin --all #将本地仓库下面所有的变动 推送到远程仓库
增加 一个远程仓库的名字  

8.标签

git tag 列出所有的标签
git tag 标签名 在当前分支下面创建一个标签
git tag -d 标签名 删除指定的标签
git push origin 标签名  将标签提交到远程仓库 git push origin --tags  将所有的标签提交到远程仓库  

9.总结

git init 将当前目录初始化为本地仓库  git clone 将远程仓库的代码 同步到本地仓库
git pull origin 分支名 从指定的分支下面 拉取下来代码  到工作区
git add . 先把它加入到 暂存区
git commit -a -m ""  将其放入本地仓库
git push origin 分支名  将将本地仓库的代码 推送到远程仓库  

Django的个人笔记相关推荐

  1. Django:学习笔记(2)——创建第一个应用

    Django:学习笔记(2)--创建第一个应用 创建应用 在 Django 中,每一个应用都是一个 Python 包,并且遵循着相同的约定.Django 自带一个工具,可以帮你生成应用的基础目录结构, ...

  2. django系统学习笔记

    转自:http://hi.baidu.com/derris/item/7ca6013e330563fede2221ab 2010-02-07 00:12 django系统学习笔记--(1)hello ...

  3. python django XX在线笔记系统

    python django XX在线笔记系统 基于python Django的在线笔记系统 基于python Django的情感树洞平台 基于python Django的社交随笔平台 基于Python ...

  4. django源码笔记-【1】

    前言 Django是一个开放源代码的Web应用框架,她像一个工具箱,包括了整个web开发中的各种技术,例如ORM,Template等.这是在阅读django的源码中作的笔记. 首先需要简要介绍以下WS ...

  5. 【Python实战】Django建站笔记

    前一段时间,用Django搭建一个报表分析的网站:借此正好整理一下笔记. 1. 安装 python有包管理工具pip,直接cd Python27/Scripts,输入 pip install djan ...

  6. Django REST Framework笔记(六)重写序列化器的update和create方法

    序列化器中update.create和视图中的update.create 在序列化器中和视图中,都可以重写update和create方法,但两者有什么区别呢? 说实话,我还未搞懂,看不懂源码.这里我可 ...

  7. Django使用缓存笔记

    2019独角兽企业重金招聘Python工程师标准>>> Django设置缓存需要在settings.py文件中进行设置,缓存配置是通过setting文件的CACHES 配置来实现的. ...

  8. Django使用MySQL笔记

    Django settings文件设置: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'aisir ...

  9. Python:Django开发函数笔记:

    1:django中的objects.get和objects.filter方法的区别 为了说明它们两者的区别定义2个models class Student(models.Model): name = ...

  10. Django第二天笔记

    一.安装虚拟环境 安装虚拟环境软件 pip3 install virtualenv 安装虚拟环境扩展包 pip3 install virtualenvwrapper 配置家目录下的环境配置文件 .ba ...

最新文章

  1. css选择器及float(浮动)
  2. Git学习笔记:分支管理(1)
  3. WPF 中Frame + Page 的使用
  4. SAP CRM和Cloud for Customer的数据同步一例
  5. 你穿衣品味还不如AI,这有一款时尚着装网络模型
  6. 【牛客 - 371牛客OI周赛7-提高组A】小睿睿的等式(dp,暴力 )
  7. html二叉树遍历,二叉树的遍历(前序、中序、后序、层次)
  8. 免费素材下载:200个超棒的矢量图标下载
  9. Windows防火墙增加访问端口
  10. linux软件包管理rpm
  11. linux备份系统iso,将Linux系统转换为ISO镜像文件以实现备份
  12. 计算机网络奇偶校验码题目,计算机网络试题(考试必备)分解.doc
  13. 正菱台体积在线计算机,棱台体积公式
  14. Java HashMap底层实现和原理分析(一)
  15. 物联网4G工业路由器在森林烟火监测的应用
  16. Stream流和Optional
  17. 我手机中的舍不得删除的48条笑话,条条经典!! (转自猫扑)
  18. 开放平台支持的签名算法
  19. 神奇的开关 — 可控硅
  20. 510758-19-7,5-FAM-Alkyne高选择性和灵敏的荧光生物标记物,可用于标记碱性磷酸酶 (ALP)

热门文章

  1. 【Nginx】Nginx相关知识整理
  2. 需求及政策加速电子劳动合同应用,君子签助推企业优化用工管理
  3. win7隐藏linux分区工具,Win7学院:隐藏磁盘分区方法汇总
  4. 电话销售实战经验总结
  5. 【推荐】智慧教育,智慧课堂研究分析资料整理合集
  6. 电脑平时好好的,今天连上了wifi,浏览器却加载不出网页,解决措施。
  7. 人脸识别协助实名认证 让游戏实名环节不再是摆设
  8. excel表格如何设置下拉选项?
  9. dejavu sans_新的Google徽标真的看起来像Comic Sans吗?
  10. 深度学习-在自带显卡GeForce RTX 2070的研华MIC-770工控机上安装Ubuntu18.04及显卡驱动过程记录