Django 学习

Python的WEB框架有Django、Tornado、Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等诸多功能。

基本配置

创建Django程序

  1. 终端命令:django-admin startproject sitename
  2. 通过IDE(pycharm)创建Django程序,本质上都是自动执行上述命令

创建Django

上述的sitename是自己定义的项目名称!
其他常用命令:

python manage.py runserver 0.0.0.0:port     # 运行django,指定端口
python manage.py startapp appname           # 创建app
python manage.py syncdb
python manage.py makemigrations             # 建立数据库
python manage.py migrate                    # 初始化数据库
python manage.py createsuperuser            # 创建admin后台用户

程序目录

Django目录

settings.py 配置文件

urls.py 存放路由系统(映射)

wsgi.py 让你做配置:wsgi有多重一种uwsgi和wsgi,你用那种wsgi来运行Django,一般不用改只有你用到的时候在改

manage.py 就是Django的启动管理程序

以上配置文件,如果是初学者当创建完project后都不要修改,因为涉及到很多配置文件需要修改

Project和App概念

咱们目前创建的是Project,Project下面可以有很多app,原理是什么呢!

我们创建的Project是一个大的工程,下面有很多功能:(一个Project有多个App,其实他就是对你大的工程的一个分类

Project
–web (前台功能)
–admin (后台管理功能)

一个Project有多个app,其实他就是对你大的工程的一个分类

创建App

python manage.py startapp app01

如果在创建一个App,我们可以理解为App是手机里的App程序他们之间是完全独立的,好处是降低他们之间的耦合性,不到万不得已不要让他们之间建立关系

Django目录

注册App
创建的App,需要在工程的settings.py文件里面注册

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
]

配置文件

settings.py

1、数据库

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

# 由于Django内部连接MySQL时使用的是MySQLdb模块,而Python中还无此模块,所以需要使用pymysql来代替

# 如下设置放置的与project同名的配置的 __init__.py文件中

import pymysql
pymysql.install_as_MySQLdb() 

2、模板

TEMPLATE_DIRS = (
    os.path.join(BASE_DIR,'templates'),
)

3、静态文件

# 实际调用得名称
STATIC_URL = '/static/'

# 真实路径
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'statics'),
)

路由系统

1、每个路由规则对应view中的一个函数,(处理发来的请求)

url(r'^index/(\d*)', views.index),
url(r'^manage/(?P<name>\w*)/(?P<id>\d*)', views.manage),
url(r'^manage/(?P<name>\w*)', views.manage,{'id':333}),

2、根据app对路由规则进行一次分类

from django.conf.urls import url, include

url(r'^web/',include('web.urls')),

django中的路由系统和其他语言的框架有所不同,在django中每一个请求的url都要有一条路由映射,这样才能将请求交给对一个的view中的函数去处理。其他大部分的Web框架则是对一类的url请求做一条路由映射,从而是路由系统变得简洁。

通过反射机制,为django开发一套动态的路由系统:

    ('^(?P<app>(\w+))/(?P<function>(\w+))/(?P<page>(\d+))/(?P<id>(\d+))/$',process),
    ('^(?P<app>(\w+))/(?P<function>(\w+))/(?P<id>(\d+))/$',process),
    ('^(?P<app>(\w+))/(?P<function>(\w+))/$',process),
    ('^(?P<app>(\w+))/$',process,{'function':'index'}),

MTV操作(Models、Templates、Views)

模板(Templates)

1、模版的执行
模版的创建过程,对于模版,其实就是读取模版(其中嵌套着模版标签),然后将 Model 中获取的数据插入到模版中,最后将模板和数据渲染后最终结果返回给用户。

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)
from django import template
t = template.Template('My name is {{ name }}.')
c = t.Context({'name': 'Adrian'})
print(t.render(c))
import datetime
from django import template
import DjangoDemo.settings

now = datetime.datetime.now()
fp = open(settings.BASE_DIR+'/templates/Home/Index.html')
t = template.Template(fp.read())
fp.close()
html = t.render(template.Context({'current_date': now}))
return HttpResponse(html)
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    t = get_template('current_datetime.html')
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)
return render_to_response('Account/Login.html',data,context_instance=RequestContext(request))

2、模版语言

模板中也有自己的语言,该语言可以实现数据展示

# 引用后台传传来item的值
{{ item }}

# 获取item对象中value的值
{{ item.value }}

# 获取item对象如果是个数组.1 获取索引1对象中value的值
{{ item.1.value }}

# for循环
{% for item in item_list %}
    {{ item }}  # 每一个都创建这样一个A标签
{% endfor %}

    forloop.counter  # for循环的次数
    forloop.first    # 循环的第一个
    forloop.last     # 循环的最后一个

# 条件判断
{% if ordered_warranty %}
    ture
{% else %}
    false
{% endif %}

母板:{% block title %}{% endblock %}
子板:{% extends "base.html" %}
   {% block title %}{% endblock %}

内置方法:
{{ item.event_start|date:"Y-m-d H:i:s"}}
{{ bio|truncatewords:"30" }}
{{ my_list|first|upper }}
{{ name|lower }}

_模板语言中除了内置方法,也有两种方式自己定义方法_

3、自定义模板语言方法

1) 在app中创建templatetags模块
2) 创建任意 .py 文件,如:xx.py

from django import template
from django.utils.safestring import mark_safe
from django.template.base import Node, TemplateSyntaxError

# 这个是不可变的,必须要写
register = template.Library()

@register.simple_tag
def f1(s1, s2, s3):
    return s1 + s2 + s3

3) 在使用自定义simple_tag的html文件中导入之前创建的 xx.py 文件

{% load xx %}

4) 使用simple_tag

执行自己定义的simple_tag类型方法 f1接收3个参数,传3个参数
{% f1 1 2 3 %}

5) 自定义filter方法

@register.filter
def f2(value):
    if value == 'vvv':
        return True
    return False

6) 使用filter

将item值当作参数传递给f2方法
{{ item|f2  }}
额外给f2再传递一个参数
{{ item|f2:"123" }}

filter方法和simple_tag方法使用的方式不一样,filter只能接收最多两个参数,simple_tag可以接收任意个参数,他们最大的区别是,filter还能支持if语句条件

{% if k1|f3 %}

7) 在settings中配置当前app,不然django无法找到自定义的方法 

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
)

4、母板
首先了解下母板是什么概念?首先已博客园为例:
看下面的图片,在点击首页、精华、候选、新闻、管住、我评、我赞的时候 上面、左侧的红色框体都没有变,变得是中间的内容是怎么实现的呢?就是通过母版来实现的

网页母板

创建一个母板,母板中确定下不变的内容和变动得内容,然后再创建子板,子板继承母板,子板中只需要写变动得内容那块得代码即可.其他的内容都套用母板的.

在templates目录下面创建一个master目录(统一用他吧),然后在master目录下创建一个master_templates.html

创建母板

母板代码:

母板代码

母板一般确定三块变动得区域:
1) 活动css样式代码块,这样子板和子板即可以即有从母板继承下来的代码和样式还有自己的样式
2) 活动主要内容代码块,子板自己的内容
3) 活动js代码块,子板中自己的js代码

子板代码:
子板中引用母板,然后只要写上子板中要写的内容写在母板对应的block语法名称中
子板代码

最后实现的情况如下图,,页眉页脚和左侧菜单不变,变得只是右边绿色框中的内容.

母板示例

5、小组件
还可以单独在一个文件中写一段代码,然后以小组件得形式提供给别的页面使用,这样可以做到一个经常用到的一段代码写成小组件,达到复用得情况:

1) 在templates下新建一个模板文件,然后在里面写上一段代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>小黄人</title>
    <style>
        .huang{
            position: fixed;
            top: 50%;
            margin-top: -240px;
        }
    </style>
</head>
<body>
    <!--这是一个永远水平居中停靠在窗口左边的小黄人插件-->
    <div>
        <img class="huang" src="/static/images/小黄人.jpg" />
    </div>
</body>
</html>

2) 如果要使用这段代码(这个组件),只需要在要使用的html中写:

{% include '小黄人.html' %}

实际效果:

模板插件

Models

到目前为止,当我们的程序涉及到数据库相关操作

  • 创建数据库,设计表结构和字段
  • 使用 MySQLdb 来连接数据库,并编写数据访问层代码
  • 业务逻辑层去调用数据访问层执行数据库操作

django为使用一种新的方式,即:关系对象映射(Object Relational Mapping,简称ORM)。

   PHP:activerecord

  Java:Hibernate

   C#:Entity Framework

django中遵循 Code Frist 的原则,即:根据代码中定义的类来自动生成数据库表。
django默认使用自带的db.sqlite3
1、创建数据库

1) 创建model类

from django.db import models

class userinfo(models.Model):
    name = models.CharField(max_length=30)
    email = models.EmailField()
    memo = models.TextField()

2) 执行命令

  python manage.py makemigrations
  python manage.py migrate

其他字段

models.AutoField  # 自增列 = int(11)
# 如果没有的话,默认会生成一个名称为 id 的列,如果要显示的自定义一个自增列,必须将给列设置为主键 primary_key=True。
models.CharField  # 字符串字段
# 必须 max_length 参数
models.BooleanField  # 布尔类型=tinyint(1)
# 不能为空,Blank=True
models.ComaSeparatedIntegerField  # 用逗号分割的数字=varchar
# 继承CharField,所以必须 max_lenght 参数
models.DateField  # 日期类型 date
# 对于参数,auto_now = True   # 则每次更新都会更新这个时间;auto_now_add 则只是第一次创建添加,之后的更新不再改变。
models.DateTimeField  # 日期类型 datetime
# 同DateField的参数
models.Decimal  # 十进制小数类型 = decimal
# 必须指定整数位max_digits和小数位decimal_places
models.EmailField  # 字符串类型(正则表达式邮箱) =varchar
# 对字符串进行正则表达式
models.FloatField  # 浮点类型 = double
models.IntegerField  # 整形
models.BigIntegerField  # 长整形
integer_field_ranges = {
  'SmallIntegerField': (-32768, 32767),
  'IntegerField': (-2147483648, 2147483647),
  'BigIntegerField': (-9223372036854775808, 9223372036854775807),
  'PositiveSmallIntegerField': (0, 32767),
  'PositiveIntegerField': (0, 2147483647),
}
models.IPAddressField  # 字符串类型(ip4正则表达式)
models.GenericIPAddressField  # 字符串类型(ip4和ip6是可选的)
# 参数protocol可以是:both、ipv4、ipv6
# 验证时,会根据设置报错
models.NullBooleanField  # 允许为空的布尔类型
models.PositiveIntegerFiel  # 正Integer
models.PositiveSmallIntegerField  # 正smallInteger
models.SlugField  # 减号、下划线、字母、数字
models.SmallIntegerField  # 数字
# 数据库中的字段有:tinyint、smallint、int、bigint
models.TextField  # 字符串=longtext
models.TimeField  # 时间 HH:MM[:ss[.uuuuuu]]
models.URLField  # 字符串,地址正则表达式
models.BinaryField    # 二进制
models.ImageField      # 图片
models.FilePathField   # 文件

更多参数

null=True
# 数据库中字段是否可以为空
blank=True
# django的 Admin 中添加数据时是否可允许空值
primary_key = False
# 主键,对AutoField设置主键后,就会代替原来的自增 id 列
auto_now 和 auto_now_add
  auto_now   # 自动创建---无论添加或修改,都是当前操作的时间
  auto_now_add  # 自动创建---永远是创建时的时间
choices
GENDER_CHOICE = (
        (u'M', u'Male'),
        (u'F', u'Female'),
    )
gender = models.CharField(max_length=2,choices = GENDER_CHOICE)
max_length
default  # 默认值
verbose_name  # Admin中字段的显示名称
name|db_column  # 数据库中的字段名称
unique=True  # 不允许重复
db_index = True  # 数据库索引
editable=True  # 在Admin里是否可编辑
error_messages=None  # 错误提示
auto_created=False  # 自动创建
help_text  # 在Admin中提示帮助信息
validators=[]
upload-to

2、连表结构

  • 一对多:models.ForeignKey(其他表)
  • 多对多:models.ManyToManyField(其他表)
  • 一对一:models.OneToOneField(其他表)

应用场景:

  • 一对多:当一张表中创建一行数据时,有一个单选的下拉框(可以被重复选择)
    例如:创建用户信息时候,需要选择一个用户类型【普通用户】【金牌用户】【铂金用户】等。
  • 多对多:在某表中创建一行数据是,有一个可以多选的下拉框
    例如:创建用户信息,需要为用户指定多个爱好
  • 一对一:在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了
    例如:原有含10列数据的一张表保存相关信息,经过一段时间之后,10列无法满足需求,需要为原来的表再添加5列数据

3、操作表
1) 基本操作

# 增
models.Tb1.objects.create(c1='xx', c2='oo')  # 增加一条数据,可以接受字典类型数据 **kwargs

obj = models.Tb1(c1='xx', c2='oo')
obj.save()

# 查
models.Tb1.objects.get(id=123)         # 获取单条数据,不存在则报错(不建议)
models.Tb1.objects.all()               # 获取全部
models.Tb1.objects.filter(name='seven') # 获取指定条件的数据

# 删
models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据

# 改
models.Tb1.objects.filter(name='seven').update(gender='0')  # 将指定条件的数据更新,均支持 **kwargs
obj = models.Tb1.objects.get(id=1)
obj.c1 = '111'
obj.save()                                                 # 修改单条数据

2) 进阶操作(了不起的双下划线)

利用双下划线将字段和对应的操作连接起来

# 获取个数
models.Tb1.objects.filter(name='seven').count()

# 大于,小于
models.Tb1.objects.filter(id__gt=1)              # 获取id大于1的值
models.Tb1.objects.filter(id__lt=10)             # 获取id小于10的值
models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值

# in
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in

# contains
models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.exclude(name__icontains="ven")

# range
models.Tb1.objects.filter(id__range=[1, 2])   # 范围bettwen and

# 其他类似
startswith,istartswith, endswith, iendswith,

# 排序
models.Tb1.objects.filter(name='seven').order_by('id')    # 生序
models.Tb1.objects.filter(name='seven').order_by('-id')   # 降序

# limit 、offset
models.Tb1.objects.all()[10:20]

# 返回list中就不再是对象了,而是键值对(字典),
models.Tb1.objects.all().values('user')
# [{'user: 'alex', 'user': 'eric'}]

# 返回的list是一个一个元祖,元祖里面就是一个个值.
models.Tb1.objects.all().values_list('user')
# [('ales'), ('eric')]

# group by
from django.db.models import Count, Min, Max, Sum
models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
# SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"

3) 连表操作(了不起的双下划线)

class UserProfile(models.Model):
    user_info = models.OneToOneField('UserInfo')
    username = models.CharField(max_length=64)
    password = models.CharField(max_length=64)

    def __unicode__(self):
        return self.username


class UserInfo(models.Model):
    user_type_choice = (
        (0, u'普通用户'),
        (1, u'高级用户'),
    )
    # choices设置别名
    user_type = models.IntegerField(choices=user_type_choice)
    name = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
    address = models.CharField(max_length=128)

    def __unicode__(self):
        return self.name


class UserGroup(models.Model):

    caption = models.CharField(max_length=64)

    user_info = models.ManyToManyField('UserInfo')

    def __unicode__(self):
        return self.caption


class Host(models.Model):
    hostname = models.CharField(max_length=64)
    ip = models.GenericIPAddressField()
    user_group = models.ForeignKey('UserGroup')

    def __unicode__(self):
        return self.hostname

一对一操作:

import os
import django
from mysite import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django.setup()


from app01 import models

models.UserInfo.objects.create(name='liang',user_type=1,email='liang@163.com')
user_info_obj = models.UserInfo.objects.filter(id=1).first()

models.UserProfile.objects.create(user_info=user_info_obj, username='liang', password='123',)


print(user_info_obj.user_type)
print(user_info_obj.get_user_type_display())
print(user_info_obj.userprofile.password)

user_info_obj = models.UserInfo.objects.filter(id=1).values('email', 'userprofile__username').first()   # 双下划线跨表链接另外一个表,然后加字段就可以跨表查询
print(list(user_info_obj.keys()))
print(list(user_info_obj.values()))

多对多操作:
利用双下划线和 _set 将表之间的操作连接起来

import os
import django
from mysite import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django.setup()


from app01 import models

user_info_obj = models.UserInfo.objects.filter(name='liang')[0]
user_info_objs = models.UserInfo.objects.all()


models.UserGroup.objects.create(caption='liang')
models.UserGroup.objects.create(caption='lianglian')


group_obj = models.UserGroup.objects.get(caption='liang')
group_objs = models.UserGroup.objects.all()

# 添加数据
group_obj.user_info.add(user_info_obj)
group_obj.user_info.add(*user_info_objs)  # '*kwarg'

# 删除数据
group_obj.user_info.remove(user_info_obj)
group_obj.user_info.remove(*user_info_objs)

# 添加数据
user_info_obj.usergroup_set.add(group_obj)
user_info_obj.usergroup_set.add(*group_objs)

# 删除数据
user_info_obj.usergroup_set.remove(group_obj)
user_info_obj.usergroup_set.remove(*group_objs)

# 获取数据
print(group_obj.user_info.all())
print(group_obj.user_info.all().filter(id=1))

# 获取数据
print(user_info_obj.usergroup_set.all())
print(user_info_obj.usergroup_set.all().filter(caption='liang'))
print(user_info_obj.usergroup_set.all().filter(caption='lianglian'))

注意:xx_set中的多对多中的隐含字段,用于反向查询

跨表操作:

class UserType(models.model):
    caption = models.CharField(max_length=32)
    # 普通用户,超级用户,测试用户

class Somthing(models.Model):
    name = models.CharField(max_length=32)
    s = models.ForignKey('Somthing')

class UserInfo(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    user_type = models.ForignKey('UserType')
    # 在Django数据库表中ForignKey字段其实就是 user_type_id ,带指的就是'UserType'表的id字段

# 双下划线进行跨表查询,UserInfo的user_type字段对应的就是UserType表,再加'__'双下划线就可以跨表查询了.
querset = UserInfo.objects.filter(user_type__caption='普通用户')

querset = UserInfo.objects.filter(user_type__s__name='xx')

querset = UserInfo.objects.filter(user_type__caption='普通用户').values('user', 'user_type_caption')

  • 1、搜索条件使用 __ 连接
  • 2、获取值时使用 . 连接

Note: 创建外键数据的时候, UserInfo.objects.create(user='alex', pwd='123', user_type=UserType.objects.get(id=2)) ,这样插一条数据相当于要做两次数据库查询,这样不好对吧, 在Django数据库表中ForignKey字段其实user_type_id带指的就是UserType表的id字段,所以我们直接UserInfo.objects.create(user='alex', pwd='123', user_type_id=2)就可以了.

其他操作

# F 使用查询条件的值
from django.db.models import F
models.Tb1.objects.update(num=F('num')+1)

# Q 构建搜索条件
from django.db.models import Q
con = Q()

q1 = Q()
q1.connector = 'OR'
q1.children.append(('id', 1))
q1.children.append(('id', 10))
q1.children.append(('id', 9))

q2 = Q()
q2.connector = 'OR'
q2.children.append(('c1', 1))
q2.children.append(('c1', 10))
q2.children.append(('c1', 9))

con.add(q1, 'AND')
con.add(q2, 'AND')

models.Tb1.objects.filter(con)

# SQL
from django.db import connection
cursor = connection.cursor()
cursor.execute("SELECT * from tb where name = %s", ['Lennon'])
row = cursor.fetchone()

4) 扩展

高级操作

Form

django中的Form一般有两种功能:

  • 验证用户输入
  • 自动插入html表单

Djangoforms模块提供表单验证,前端js只能做简单判断,最后还是都要到后端来处理,forms模块提供


1、验证用户输入
前端Ajax发送表单内容到后端验证,后端验证后将结果返回给ajax,显示到页面上.

views:

from django.shortcuts import render, HttpResponse

# Create your views here.
import json
import re
from django import forms
from django.core.exceptions import ValidationError


# 自己写一个验证规则函数
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')  # 将正则表达式编译成对象,方法直接用这个正则去套结果
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')


# 写一个Form表单验证规则
class LoginForm(forms.Form):
    # 每个字段名要是前端form表单中的name数据值,一一对应,不然分辨不了,无法做验证.
    user = forms.CharField(required=True, error_messages={'required': '用户名不能为空'})
    password = forms.CharField(required=True,
                               min_length=6,
                               max_length=12,
                               # 每个key对应一个触发条件,value则是消息
                               error_messages={'required': '密码不能为空', 'min_length': '最少6位', 'max_length': '最多12位'})
    phone = forms.CharField(required=True,
                               validators=[mobile_validate, ],   # 指定验证器为自己定义的验证规则函数
                               error_messages={'required': '手机号不能为空'})

    url = forms.URLField(required=True, error_messages={'required': 'url不能为空','invalid': '请输入url'})

    email = forms.EmailField(required=True, error_messages={'required': 'email不能为空', 'invalid': '请输入邮箱格式'})


def login(request):
    if request.method == 'POST':
        result = {'status': False, 'message': None}
        obj = LoginForm(request.POST)  # 前端提交的POST请求数据是创建一个对象
        ret = obj.is_valid()   # 验证用户输入,只要有一个为空就是False
        if ret:
            print(obj.clean())   #返回字典对象
            result['status'] = True
        else:
            result['status'] = False
            print(type(obj.errors), obj.errors.as_json())  # 字典对象,转换成json格式
            error_str = obj.errors.as_json()
            result['message'] = json.loads(error_str)
        return HttpResponse(json.dumps(result))

    return render(request, 'login.html')

templates:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
    <style>
        .error-msg{
            color: red;
        }
    </style>
</head>
<body>

    <div>
        <div>
            <input type="text" name="user" />
        </div>
        <div>
            <input type="password" name="password" />
        </div>
        <div>
            <input type="text" name="phone" />
        </div>
        <div>
            <input type="text" name="url" />
        </div>
        <div>
            <input type="text" name="email" />
        </div>
        <input type="button" value="提交" onclick="DoSubmit();" />
    </div>

    <script src="/static/js/jquery-1.12.4.js"></script>
    <script>
        function DoSubmit() {
            var input_dict = {};
            $('input').each(function () {
               var v = $(this).val();
                var n = $(this).attr('name');
                input_dict[n] = v;
            });
            console.log(input_dict);
            $('.error-msg').remove();  //在每次提交(验证)之前把之前的错误信息先删除掉
            $.ajax({
                url: '/login/',
                type: 'POST',
                data: input_dict,
                dataType: 'json',
                success: function (result) {
                    console.log(result);
                    if(result.status){
                        location.href = '/index/'
                    }else {
                        $.each(result.message, function (k, v) {
                            console.log(k,v[0].message);
                            // <span class="error-msg">错误信息</span>
                            var tag = document.createElement('span');
                            tag.className = 'error-msg';
                            tag.innerText = v[0].message;
                            // input[name="user"]
                            $('input[name="' + k + '"]').after(tag);
                        })
                    }
                },
                error: function () {

                }
            })
        }
    </script>
</body>
</html>

2、自动插入html表单
前端不用写html表单代码和js,ajax代码,只需要遵循django的模板语言编写对应的内容,就可以实现表单验证.

views:

from django.shortcuts import render, HttpResponse

# Create your views here.
import json
import re
from django import forms
from django.core.exceptions import ValidationError


# 自己写一个验证规则函数
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')  # 将正则表达式编译成对象,方法直接用这个正则去套结果
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')


# 写一个Form表单验证规则
class LoginForm(forms.Form):
    # 每个字段名要是前端form表单中的name数据值,一一对应,不然分辨不了,无法做验证.
    user = forms.CharField(required=True, error_messages={'required': '用户名不能为空'})
    password = forms.CharField(required=True,
                               min_length=6,
                               max_length=12,
                               # 每个key对应一个触发条件,value则是消息
                               error_messages={'required': '密码不能为空', 'min_length': '最少6位', 'max_length': '最多12位'})

    phone = forms.CharField(required=True,
                               validators=[mobile_validate, ],   # 指定验证器为自己定义的验证规则函数
                               error_messages={'required': '手机号不能为空'})

    url = forms.URLField(required=True, error_messages={'required': 'url不能为空', 'invalid': '请输入url'})

    email = forms.EmailField(required=True, error_messages={'required': 'email不能为空', 'invalid': '请输入邮箱格式'})

    countries_choices = (
        (0, '中国'),
        (1, '美国'),
    )

    # widget添加小功能,froms.Select添加select标签
    countries = forms.IntegerField(widget=forms.Select(choices=countries_choices, attrs={'test': '国家'}))
    # widget添加小功能,froms.Select添加Textarea标签
    note = forms.CharField(widget=forms.Textarea(attrs={'class': 'note', 'test': '消息'}))


def login(request):
    if request.method == 'POST':
        # 用户Post传到后台的数据.创建的对象带用户得内容和html,这样用户不会每次点提交表格中的内容都要重新输入了
        objPost = LoginForm(request.POST)
        ret = objPost.is_valid()   # 验证用户输入,只要有一个为空就是False
        if ret:
            print(objPost.clean())   #返回字典对象
        else:
            # from django.forms.utils import ErrorDict;  objPost继承ErrorDict,继承了字典.
            for k,v in objPost.errors.items():
                print(k, v)

        return render(request, 'login.html', {'obj': objPost})
    else:
        # 用户Get页面的时候,这个没参数,LoginForm默认只是给我生成了html标签
        objGet = LoginForm()
        return render(request, 'login.html', {'obj': objGet})

templates:

templates

点击下载源码

自动插入html的这种方法前端不用写js代码,html和ajax代码,很大程度得减少了前端代码量,也可以避免js被用户摘掉,在前端看不到form表单验证方式,但是前端提交智能使用表单提交,不能用ajax;第一种就和常规的写法没什么却别,只是后端验证方面提供了一个模块,更方便了些,这两种方法都可以使用,在使用前根据利弊关系选择(我在栽跟头了,都用第二种方式写,后端逻辑都写完了,前端有个地方需要用ajax,结果那块地方重新用第一种方式写了遍.)


3、扩展:ModelForm

在使用Model和Form时,都需要对字段进行定义并定义类型,通过ModelFrom可以根据model中的定义省去From中字段的定义

class AdminModelForm(forms.ModelForm):

    class Meta:
        model = models.Admin
        #fields = '__all__'
        fields = ('username', 'email')

        widgets = {
            'email' : forms.PasswordInput(attrs={'class':"alex"}),
        }

这种方式可能更简便,但是不利于解藕,与后端数据库绑定死了;如有数据库字段变更,那么前端提交数据对不上提交则会失败.

跨站请求(CSRF)

一、简介

django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。

全局:
settings:
  中间件 django.middleware.csrf.CsrfViewMiddleware

局部:
装饰器:
@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

注:from django.views.decorators.csrf import csrf_exempt,csrf_protect

二、应用

1、普通表单

CSRF

Note: views 中必须用render返回,如果用HttpResponse的话,那再次发POST请求的时候没有Token,会被拒绝连接.

def csrf(request):

    return render(request, 'csrf.html')

2、Ajax

对于传统的form,可以通过表单的方式将token再次发送到服务端,而对于ajax的话,使用如下方式。

views:

def csrf(request):

    return render(request, 'csrf.html')

templates:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>csrf</title>
</head>
<body>
    <input type="button" value="Ajax提交" onclick="DoAjax();" />

    <script src="/static/js/jquery-1.12.4.js"></script>
    <script src="/static/js/jquery.cookie.js"></script>
    <script>
        // 去cookie中获取值
        var csrftoken = $.cookie('csrftoken');

        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/).test(method);
        }

        // 写上这段代码,那么ajax请求都会带着这段代码执行,就不需要分别在每个ajax请求函数里面写token操作了
        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if(!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });

        function DoAjax() {
            $.ajax({
                url: '/csrf/',
                type: 'POST',
                data: {'k1': 'v1'},
                success: function (data) {
                    console.log(data);
                }
            })
        }
    </script>
</body>
</html>

更多:https://docs.djangoproject.com/en/dev/ref/csrf/#ajax

网站为了辨别用户身份而储存在用户本地终端上的数据,用户通过浏览器请求网站,网站返回用户信息之后,还会偷偷给用户塞给用户网站生成的一个身份证明,上面记录了用户的信息,下次用户来的时候带着这个网站就知道你是谁,就会”智能”得在页面上显示你是谁.登录了关闭浏览器再打开不用再重新登录.

1、获取Cookie:

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

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

2、设置Cookie:

rep = HttpResponse(...) 或 rep = render(request, ...)

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

由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。

    <script src='/static/js/jquery.cookie.js'></script>
    $.cookie("list_pager_num", 30,{ path: '/' });

Session

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:

  • 数据库(默认)
  • 缓存
  • 文件
  • 缓存 + 数据库
  • 加密cookie

Django的Sessio保存在数据库中,使用之前要记得初始化数据库
1、数据库Session:

Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。

a. 配置 settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

b. 使用

def index(request):
    # 获取、设置、删除Session中数据
    request.session['k1']
    request.session.get('k1',None)
    request.session['k1'] = 123
    request.session.setdefault('k1',123) # 存在则不设置
    del request.session['k1']

    # 所有 键、值、键值对
    request.session.keys()
    request.session.values()
    request.session.items()
    request.session.iterkeys()
    request.session.itervalues()
    request.session.iteritems()


    # 用户session的随机字符串
    request.session.session_key

    # 将所有Session失效日期小于当前日期的数据删除
    request.session.clear_expired()

    # 检查 用户session的随机字符串 在数据库中是否
    request.session.exists("session_key")

    # 删除当前用户的所有Session数据
    request.session.delete("session_key")

    ...

2、缓存Session:

a. 配置 settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次请求都保存Session,默认修改之后才保存

b. 使用

同上

3、文件Session:

a. 配置 settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                                                                             # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T


SESSION_COOKIE_NAME = "sessionid"                          # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/"                                  # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None                                # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False                               # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True                              # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600                                # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                     # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False                          # 是否每次请求都保存Session,默认修改之后才保存

b. 使用

同上

4、缓存+数据库Session:

数据库用于做持久化,缓存用于提高效率

a. 配置 settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

b. 使用

同上

5、加密cookie Session:

a. 配置 settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

b. 使用

同上

更多参考:猛击这里 设置session

扩展:Session用户验证

def login(func):
    def wrap(request, *args, **kwargs):
        # 如果未登陆,跳转到指定页面
        if request.path == '/test/':
            return redirect('http://www.baidu.com')
        return func(request, *args, **kwargs)
    return wrap

分页

1、Django内置分页

Paginator

2、自定义分页

分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置。

1、设定每页显示数据条数

2、用户输入页码(第一页、第二页…)

3、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

4、在数据表中根据起始位置取值,页面上输出数据


需求又来了,需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]
1、设定每页显示数据条数

2、用户输入页码(第一页、第二页…)

3、设定显示多少页号

4、获取当前数据总条数

5、根据设定显示多少页号和数据总条数计算出,总页数

6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

7、在数据表中根据起始位置取值,页面上输出数据

8、输出分页html,如:[上一页][1][2][3][4][5][下一页]

文章目录
  1. 1. 基本配置
    1. 1.1. 创建Django程序
    2. 1.2. 程序目录
    3. 1.3. Project和App概念
    4. 1.4. 创建App
    5. 1.5. 配置文件
    6. 1.6. 路由系统
  2. 2. MTV操作(Models、Templates、Views)
    1. 2.1. 模板(Templates)
    2. 2.2. Models
      1. 2.2.1. 其他字段
      2. 2.2.2. 更多参数
  3. 3. 高级操作
    1. 3.1. Form
    2. 3.2. 跨站请求(CSRF)
    3. 3.3. Cookie
    4. 3.4. Session
    5. 3.5. 分页
|