Django常用知识点
1,命令行语句
http://www.cnblogs.com/wupeiqi/articles/5237704.html
创建djangoproject:django-admin.py startproject sitename
创建app:python manage.py startapp appname
运行django server:python manage.py runserver 0.0.0.0:8000
(python manage.py createsuperuser 创建超级用户,登陆admin时使用)
2,数据库配置及命令:
a,创建数据库
b, 配置文件settings(django中)里DATABASES连接数据库,并将app添加到INSTALLED_APPS ('app.apps.AppConfig')
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'blog', 'USER': 'root', 'PASSWORD': '', 'HOST': '', 'PORT': '',
#'CHARSET':'utf8',
#'COLLATION':'utf8_general_ci',
#注意有中文时设置该utf8格式,同时建立数据库时:CREATE DATABASE mydb DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; } }
c, 文件model里创建类,继承models.Model类
静态字段 = models.CharField(max_lengtth=50)
d, 执行下面命令,创建表(每次更新表时需要执行)
python manage.py makemigrations
python manage.py migrate
(python manage.py sqlall appname 能打印出models中新建表对应的sql语句?)
3,静态文件路径配置及引用
a,首先保证 'django.contrib.staticfiles'安装:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rbac.apps.RbacConfig'
'app01.apps.App01Config'],
b,然后设置STATICFILES_DIRS:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static') ]
c, 最后可以引用jQuery:
<script type="text/javascript" src="/static/jquery-3.3.1.js"></script>
4. url路由配置
include()函数
动态路由参数
命名
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^register/', views.register), url(r'^blog/', include('blogHome.urls')), #引入blogHome.urls中的路由规则,url中blog/后面的部分由blogHome.urls进行匹配 url(r'^article/comment_tree/(\d+)/',views.getCommentTree), #(\d+)匹配数字作为视图函数的参数传入 url(r'^get_valid_img.png/', views.get_valid_img), url(r'^pc-geetest/register', views.pcgetcaptcha, name='pcgetcaptcha'), # url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}), url(r'(\w+)/article/(\d+)/',views.getArticle), ]
url反向解析
https://www.jianshu.com/p/e69b1a86c94b
使用:在定义url 时为include 定义namespace 属性,为url 定义name 属性
在模板中使用url 标签:{% url 'namespace_value:name_value'%}
在视图中使用reverse 函数:redirect(reverse('namespce_value:name_value’))
带参数的设置:<a href="{% url 'booktest:fan2' 2 3 %}">位置参数</a>
url反向解析 使用场景:模板中的超链接,视图中的重定向 使用:在定义url 时为include 定义namespace 属性,为url 定义name 属性 在模板中使用url 标签:{% url 'namespace_value:name_value'%} 在视图中使用reverse 函数:redirect(reverse('namespce_value:name_value’)) 根据正则表达式动态生成地址,减轻后期维护成本。 注意反向解析传参数,主要是在我们的反向解析的规则后面天界了两个参数,两个参数之间使用空格隔开:<a href="{% url 'booktest:fan2' 2 3 %}">位置参数</a> 1)在项目urls.py中为include定义namespace属性。 url(r'^',include('booktest.urls',namespace='booktest')), 2)在应用的urls.py中为url定义name属性,并修改为fan2。 url(r'^fan2/$', views.fan2,name='fan2'), 在模板中使用url标签做超链接 <a href="{%url 'booktest:fan2'%}">反向解析fan2</a> 视图函数中: return redirect(reverse('booktest:fan2')) 带位置参数 url(r'^fan(\d+)_(\d+)/$', views.fan2,name='fan2'), 反向解析:<a href="{%url 'booktest:fan2' 2 3%}">fan2</a> return redirect(reverse('booktest:fan', args=(2,3))) 带关键字参数 url(r'^fan(?P<id>\d+)_(?P<age>\d+)/$', views.fan2,name='fan2'), <a href="{%url 'booktest:fan2' id=100 age=18%}">fan2</a> return redirect(reverse('booktest:fan2', kwargs={'id':110,'age':26}))
7. django运行流程
当访问 URL /hello/ 时,Django 根据 ROOT_URLCONF 的设置装载 URLconf 。 然后按顺序逐个匹配URLconf里的URLpatterns,直到找到一个匹配的。 当找到这个匹配 的URLpatterns就调用相关联的view函数,并把 HttpRequest 对象作为第一个参数。总结一下:
-
进来的请求转入/hello/.
-
Django通过在ROOT_URLCONF配置来决定根URLconf.
-
Django在URLconf中的所有URL模式中,查找第一个匹配/hello/的条目。
-
如果找到匹配,将调用相应的视图函数
-
视图函数返回一个HttpResponse
6. Django转换HttpResponse为一个适合的HTTP response, 以Web page显示出来
5.模板系统Template
5.1 模板变量
"The name of function is {{foo.bar}} ."
模板进行渲染时,上面字符窜{{foo.bar}}中的变量会被替代,其中句点查找规则可概括为: 当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:
-
字典类型查找 (比如 foo["bar"]
- 属性查找 (比如 foo.bar )
-
方法调用 (比如 foo.bar() )
-
列表类型索引查找 (比如 foo[bar] )
系统使用找到的第一个有效类型。 这是一种短路逻辑
5.2 模板标签
{% if %} { % endif %}
#没有elif,用嵌套if语句
{% if today_is_weekend %} <p>Welcome to the weekend!</p> {% else %} <p>Get back to work.</p> {% endif %}
#支持and, or, not, 但不能同时使用
{% if athlete_list %} {% if coach_list or cheerleader_list %} We have athletes, and either coaches or cheerleaders! {% endif %} {% endif %}
{% ifequal %} {% endifequal %} :只有模板变量,字符串,整数和小数可以作为 {% ifequal %} 标签的参数,不支持python的字典类型、列表类型、布尔类型。
{% ifnotequal %} {% endifnotequal %}
#支持else语句 {% ifequal section 'sitenews' %} <h1>Site News</h1> {% else %} <h1>No News Here</h1> {% endifequal %}
{% for %} {% endfor %}
#支持嵌套的for,也可以和if嵌套
{% for athlete in athlete_list %} <h1>{{ athlete.name }}</h1> <ul> {% for sport in athlete.sports_played %} <li>{{ sport }}</li> {% endfor %} </ul> {% endfor %}
#empty语句表示列表为空时的输出
{% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% empty %} <p>There are no athletes. Only computer programmers.</p> {% endfor %}
在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性,包括counter, counter0, revcounter,revcounter0,first,last等。(嵌套循环时,forloop.parentloop 是一个指向当前循环的上一级循环的 forloop 对象的引用,如forloop.parentloop.counter)
# forloop.counter循环的第几项 {% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %} #forloop.first循环第一项是值为True {% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% endfor %}
5.3 过滤器
支持的过滤器参考:https://docs.djangoproject.com/en/2.1/ref/templates/builtins/
过滤器:模板过滤器是在变量被显示前修改它的值的一个简单方法,用管道符表示,支持连续的过滤处理
{{ my_list|first|upper }} #my_list中的第一项并大写
{{ value|date:"D d M Y" }} #按指定格式显示日期,格式参见https://docs.djangoproject.com/en/2.1/ref/templates/builtins/
前端时间格式化:create_date|date:"Y-m-d H:i:s"
后端时间格式化 create_date.strftime("%Y-%m-%d %H:%M:%S")
5.4模板存放位置
当模板进行渲染时,会去settings.py文件中查看TEMPLATES目录设置(模板文件存放在templates文件夹下),然后在templates文件夹中寻找模板文件。
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', ], }, }, ]
当templates文件夹中有子文件夹时,获取模板文件时加上子文件夹名称,后端获取如下:
#current_datetime.html存放在templates/dateapp文件夹下
t = get_template('dateapp/current_datetime.html') return render(request,'dateapp/current_datetime.html', {'current_date': now})
在网页前端中使用{% include %} 获取嵌套模板文件,如下:
{% include 'nav.html' %}
{% include "nav.html" %}
{% include 'dateapp/nav.html' %}
5.5 模板继承
除了使用{% include %} 嵌套模板文件外,还可以通过模板继承来复用模板文件。
母模板
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
子模板
{% extends "base.html" %}
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
注意事项:
{% extends "base.html" %} 必须放在第一行。
{% block name %} {% endblock %}语句,在同一个模板文件中不能出现同名.
{% block name %}<p>content</p> {% endblock %}, 对于其中间的内容,如果子模板不覆盖时,会按母模板内容显示
5.6自定义tag和filter
http://www.liujiangblog.com/course/django/150
https://docs.djangoproject.com/zh-hans/2.0/howto/custom-template-tags/
1. 首先得在相应的app下建立templatetags文件夹(包含__init__.py),再建立包含自定义tag或filter的代码文件。如下图中的poll_extras.py中定义自己的tag或filter。前端加载该文件格式: {% load poll_extras %}


2.自定义filter
#poll_extras.py文件内容 from django import template register = template.Library() #register名字固定 @register.filter(name='cut') def cut(value, arg): return value.replace(arg, '') @register.filter def lower(value): return value.lower() #或者按如下注册 #register.filter('cut', cut) #register.filter('lower', lower) #前端调用 {{ somevariable|cut:"0" }} # somevariable为需要过滤的字段,cut为自定义的filter名称,“0”为参数 # somevariable对应上面cut函数中value,“0”对应arg
3. 自定义tag: simple_tag 和inclusion_tag
#simple_tag @register.simple_tag(name='minustwo') def some_function(value): return value - 2 @register.simple_tag def my_tag(a, b, *args, **kwargs): warning = kwargs['warning'] profile = kwargs['profile'] ... return ... {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %} #inclusion_tag @register.inclusion_tag('results.html') def show_results(poll): choices = poll.choice_set.all() return {'choices': choices} #results.html <ul> {% for choice in choices %} <li> {{ choice }} </li> {% endfor %} </ul> #前端使用 {% show_results poll %}
4,数据库model操作
https://docs.djangoproject.com/en/2.1/topics/db/models/
每一个model继承django.db.models.Model,对应数据库中一张表,model的每一个属性对应表中一列。
field的种类:https://docs.djangoproject.com/en/2.1/ref/models/fields/#field-types
<1> CharField #字符串字段, 用于较短的字符串. #CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数. <2> IntegerField #用于保存一个整数. <3> FloatField # 一个浮点数. 必须 提供两个参数: # # 参数 描述 # max_digits 总位数(不包括小数点和符号) # decimal_places 小数位数 # 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: # # models.FloatField(..., max_digits=5, decimal_places=2) # 要保存最大值一百万(小数点后保存10位)的话,你要这样定义: # # models.FloatField(..., max_digits=19, decimal_places=10) # admin 用一个文本框(<input type="text">)表示该字段保存的数据. <4> AutoField # 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; # 自定义一个主键:my_id=models.AutoField(primary_key=True) # 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. <5> BooleanField # A true/false field. admin 用 checkbox 来表示此类字段. <6> TextField # 一个容量很大的文本字段. # admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> EmailField # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> DateField # 一个日期字段. 共有下列额外的可选参数: # Argument 描述 # auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳. # auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间. #(仅仅在admin中有意义...) <9> DateTimeField # 一个日期时间字段. 类似 DateField 支持同样的附加选项. <10> ImageField # 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field, # 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. <11> FileField # 一个文件上传字段. #要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, #该格式将被上载文件的 date/time #替换(so that uploaded files don't fill up the given directory). # admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . #注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: #(1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. # (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 # WEB服务器用户帐号是可写的. #(2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django # 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). # 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField # 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField # 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且 # 没有返回404响应). # admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 # admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField # "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs # 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 # 以前的 Django 版本,没有任何办法改变50 这个长度. # 这暗示了 db_index=True. # 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate # the slug, via JavaScript,in the object's admin form: models.SlugField # (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField #一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField # 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. # 参数 描述 # path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. # Example: "/home/images". # match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. # 注意这个正则表达式只会应用到 base filename 而不是 # 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. # recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. # 这三个参数可以同时使用. # match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: # FilePathField(path="/home/images", match="foo.*", recursive=True) # ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16># CommaSeparatedIntegerField # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
field的参数:https://docs.djangoproject.com/en/2.1/ref/models/fields/#field-options
<1> null : 数据库中字段是否可以为空 <2> blank: django的 Admin 中添加数据时是否可允许空值 <3> default:设定缺省值 <4> editable:如果为假,admin模式下将不能改写。缺省为真 <5> primary_key:设置主键,如果没有设置django创建表时会自动加上: id = meta.AutoField('ID', primary_key=True) primary_key=True implies blank=False, null=False and unique=True. Only one primary key is allowed on an object. <6> unique:数据唯一 <7> verbose_name Admin中字段的显示名称 <8> validator_list:有效性检查。非有效产生 django.core.validators.ValidationError 错误 <9> db_column,db_index 如果为真将为此字段创建索引 <10>choices:一个用来选择值的2维元组。第一个值是实际存储的值,第二个用来方便进行选择。 如SEX_CHOICES= (( ‘F’,'Female’),(‘M’,'Male’),) gender = models.CharField(max_length=2,choices = SEX_CHOICES)
图片和文件上传路径(upload_to): 对于upload_to参数的路径是相对路径(相对于MEDIA_ROOT的设置值,如MEDIA_ROOT="media", upload_to="avatar/",则最后保存的路径为 media/avatar/);
自定义上传路径:upload_to可以接受一个函数user_directory_path,来对路径进行自定义(由于所有用户的文件都上传到一个文件夹,会出现同名文件夹的覆盖)
参考: https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.FileField.upload_to https://blog.csdn.net/weixin_42134789/article/details/80753051 #示例一, user_directory_path函数必须接受两个参数,model实例和文件名 def user_directory_path(instance, filename): # file will be uploaded to MEDIA_ROOT/user_<id>/<filename> return 'user_{0}/{1}'.format(instance.user.id, filename) class MyModel(models.Model): upload = models.FileField(upload_to=user_directory_path) #示例二 from django.contrib.auth.models import User import uuid def user_directory_path(instance, filename): ext = filename.split('.')[-1] filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext) # return the whole path to the file return os.path.join(instance.user.id, "avatar", filename) class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') avatar = models.ImageField(upload_to=user_directory_path, verbose_name="头像") #示例三 def user_directory_path(instance, filename): ext = filename.split('.')[-1] filename = '{}.{}'.format(uuid.uuid4().hex[:8], ext) sub_folder = 'file' if ext.lower() in ["jpg", "png", "gif"]: sub_folder = "avatar" if ext.lower() in ["pdf", "docx"]: sub_folder = "document" return os.path.join(instance.user.id, sub_folder, filename)
另外对于用户上传的文件也可以在视图函数中进行重命名后再保存,来防止文件覆盖,如下:
@login_required def ajax_avatar_upload(request): user = request.user user_profile = get_object_or_404(UserProfile, user=user) if request.method == "POST": form = AvatarUploadForm(request.POST, request.FILES) if form.is_valid(): img = request.FILES['avatar_file'] # 获取上传图片 cropped_avatar = crop_image(img, user.id) user_profile.avatar = cropped_avatar # 将图片路径修改到当前会员数据库 user_profile.save() return HttpResponseRedirect(reverse('myaccount:profile')) def crop_image(file, uid): # 随机生成新的图片名,自定义路径。 ext = file.name.split('.')[-1] file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext) cropped_avatar = os.path.join(uid, "avatar", file_name) # 相对根目录路径 file_path = os.path.join("media", uid, "avatar", file_name) # 裁剪图片,压缩尺寸为200*200。 img = Image.open(file) crop_im = img.crop((50,50,300, 300)).resize((200, 200), Image.ANTIALIAS) crop_im.save(file_path) return cropped_avatar 参考:https://blog.csdn.net/weixin_42134789/article/details/80753051
model之间的三种关系
一对多关系:django.db.models.Foreign
https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ForeignKey
多对多关系:django.db.models.ManyToManyField
https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ManyToManyField
一对一关系:django.db.models.OneToOneField
https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.OneToOneField
model操作:增删改查
https://docs.djangoproject.com/en/2.1/topics/db/queries/
建立表:
from django.db import models<br> class Publisher(models.Model): name = models.CharField(max_length=30, verbose_name="名称") address = models.CharField("地址", max_length=50) city = models.CharField('城市',max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() class Meta: verbose_name = '出版商' verbose_name_plural = verbose_name def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=30) def __str__(self): return self.name class AuthorDetail(models.Model): sex = models.BooleanField(max_length=1, choices=((0, '男'),(1, '女'),)) email = models.EmailField() address = models.CharField(max_length=50) birthday = models.DateField() author = models.OneToOneField(Author) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2,default=10) def __str__(self): return self.title
每次创建一个对象,显示对应的sql语句,需要在settings.py中配置logging
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } } LOGGING
增:create, save
from app01.models import Author #create方式一: author = Author.objects.create(name='Alvin') #自动创建并保存 #create方式二: author = Author.objects.create(**{"name":"alex"}) #save方式一: author=Author(name="alvin") author.save() #save方式二: author=Author() author.name="alvin" author.save()
增加一对多和多对多数据:
#一对多(ForeignKey): #方式一: 由于绑定一对多的字段,比如publish,存到数据库中的字段名叫publish_id,所以我们可以直接给这个字段设定对应值: Book.objects.create(title='php', publisher_id=2, #这里的2是指为该book对象绑定了Publisher表中id=2的行对象 publication_date='2017-7-7', price=99) #方式二: <1> 先获取要绑定的Publisher对象: pub_obj=Publisher(name='河大出版社',address='保定',city='保定', state_province='河北',country='China',website='http://www.hbu.com') 或者 pub_obj=Publisher.objects.get(id=1) <2>将 publisher_id=2 改为 publisher=pub_obj #方式三: 分别获取两个对象,进行赋值 book_obj=Book.objects.get(name='河大出版社') pub_obj = Publisher.objects.get(id=2) book_obj.publish = pub_obj book.save() #多对多(ManyToManyField()): #正向添加,通过book表设置 author1=Author.objects.get(id=1) author2=Author.objects.filter(name='alvin')[0] book=Book.objects.get(id=1) book.authors.add(author1,author2) #等同于: book.authors.add(*[author1,author2]) 删除多对多关系:book.authors.remove(*[author1,author2]) #反向添加,通过authors表设置 book=models.Book.objects.filter(id__gt=1) authors=models.Author.objects.filter(id=1)[0] authors.book_set.add(*book) 删除多对多:关系authors.book_set.remove(*book) #注意: 如果第三张表是通过models.ManyToManyField()自动创建的,那么绑定关系只有上面一种方式 # 如果第三张表是自己创建的: class Book2Author(models.Model): author=models.ForeignKey("Author") Book= models.ForeignKey("Book") # 那么就还有一种方式: author_obj=models.Author.objects.filter(id=2)[0] book_obj =models.Book.objects.filter(id=3)[0] s=models.Book2Author.objects.create(author_id=1,Book_id=2) s.save() s=models.Book2Author(author=author_obj,Book_id=1) s.save()
删:delete
>>> Book.objects.filter(id=1).delete() # 删除id=1的所有book数据,由于级联删除,其对应的外键关联记录也会被删除 (3, {'app01.Book_authors': 2, 'app01.Book': 1}) #返回删除记录数,及删除的记录对象
删除一对多或多对多关系: remove和clear
https://docs.djangoproject.com/en/1.11/ref/models/relations/
#正向 book = models.Book.objects.filter(id=1) #删除第三张表中和id=1关联的所有关联信息 book.author.clear() #清空与book中id=1 关联的所有数据 book.author.remove(author_obj) book.author.remove(*obj) #可以为列表,前面加* #反向 author = models.Author.objects.filter(id=1) author.book_set.clear() #清空与author中id=1 关联的所有数据
改:update和save
https://docs.djangoproject.com/en/2.1/ref/models/querysets/#django.db.models.query.QuerySet.update
update
Publisher.objects.filter(id=3).update(name="zack") #filter不能替换为get 不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象 Book.objects.update(author__name='zack') # 会报错,不支持跨表更新,应该用下面filter跨表筛选再更新 Book.objects.filter(author__id=1).update(name="zack")
save
b = Book.objects.get(id=10) b.title = "python" b.save()
save方法会更新一行里的所有列,update只更新选定列,若只需要更新一行里的某几列,update更加高效,如下代码:
#---------------- update方法直接设定对应属性---------------- models.Book.objects.filter(id=3).update(title="PHP") ##sql: ##UPDATE "app01_book" SET "title" = 'PHP' WHERE "app01_book"."id" = 3; args=('PHP', 3) #--------------- save方法会将所有属性重新设定一遍,效率低----------- obj=models.Book.objects.filter(id=3)[0] obj.title="Python" obj.save() # SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", # "app01_book"."color", "app01_book"."page_num", # "app01_book"."publisher_id" FROM "app01_book" WHERE "app01_book"."id" = 3 LIMIT 1; # # UPDATE "app01_book" SET "title" = 'Python', "price" = 3333, "color" = 'red', "page_num" = 556, # "publisher_id" = 1 WHERE "app01_book"."id" = 3;
查:filter,all,exclude,get
https://docs.djangoproject.com/en/2.1/topics/db/queries/
https://docs.djangoproject.com/en/2.1/ref/models/querysets/#queryset-api
查询API:
# 查询相关API: # <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象,返回Queryset # <2>all(): 查询所有结果,返回Queryset # <3>get(**kwargs): 返回与所给筛选条件相匹配的model对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 #-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()-------- # <4>values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 # <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 # <6>order_by(*field): 对查询结果排序 # <7>reverse(): 对查询结果反向排序 # <8>distinct(): 从返回结果中剔除重复纪录 # <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 # <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。 # <11>first(): 返回第一条记录 # <12>last(): 返回最后一条记录 # <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False。
#扩展查询,有时候DJANGO的查询API不能方便的设置查询条件,提供了另外的扩展查询方法extra: #extra(select=None, where=None, params=None, tables=None,order_by=None, select_params=None (1) Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) (2) Blog.objects.extra( select=SortedDict([('a', '%s'), ('b', '%s')]), select_params=('one', 'two')) (3) q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) q = q.extra(order_by = ['-is_recent']) (4) Entry.objects.extra(where=['headline=%s'], params=['Lennon']) extra
查询数据排序:order_by
#根据单个字段排序 >>> Publisher.objects.order_by("state_province") [<Publisher: Apress>, <Publisher: O'Reilly>] #根据多个字段排序 >>> Publisher.objects.order_by("state_province", "address") [<Publisher: Apress>, <Publisher: O'Reilly>] #逆序 >>> Publisher.objects.order_by("-name") [<Publisher: O'Reilly>, <Publisher: Apress>] #设置默认排序字段 class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() def __unicode__(self): return self.name class Meta: ordering = ['name']
QuerySet惰性机制
所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
QuerySet特点:
<1> 可迭代的
<2> 可切片
#objs=models.Book.objects.all()#[obj1,obj2,ob3...] #QuerySet: 可迭代 # for obj in objs:#每一obj就是一个行对象 # print("obj:",obj) # QuerySet: 可切片 # print(objs[1]) #不支持objs[-1] # print(objs[1:4]) # print(objs[1:10:2])
QuerySet的缓存机制:
<1>Django的queryset是惰性的 Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得 到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave") 上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数, 这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。 <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql. 为了验证这些,需要在settings里加入 LOGGING(验证方式) obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) # if obj: # print("ok") <3>queryset是具有cache的 当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行 (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset, 你不需要重复运行通用的查询。 obj=models.Book.objects.filter(id=3) 第一次遍历时,查询数据库 # for i in obj: # print(i) 第二次遍历时,从cache中得到,不查询数据库 # for i in obj: # print(i) #LOGGING只会打印一次 # models.Book.objects.filter(id=3).update(title="GO")?? # obj_new=models.Book.objects.filter(id=3)?? <4> 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据: obj = Book.objects.filter(id=4) # exists()的检查可以避免数据放入queryset的cache。 if obj.exists(): print("hello world!") <5>当queryset非常巨大时,cache会成为问题 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。 objs = Book.objects.all().iterator() # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 for obj in objs: print(obj.name) #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 for obj in objs: print(obj.name) #当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询 总结: queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
对象查询:注意反向查找跨表book_set
条件查询(单表条件查询,跨表条件查询):注意__的含义(book__title__contains)
#--------------------对象形式的查找-------------------------- # 正向查找 ret1=models.Book.objects.first() print(ret1.title) print(ret1.price) print(ret1.publisher) print(ret1.publisher.name) #因为一对多的关系所以ret1.publisher是一个对象,而不是一个queryset集合 # 反向查找 ret2=models.Publish.objects.last() print(ret2.name) print(ret2.city) #如何拿到与它绑定的Book对象呢? print(ret2.book_set.all()) #ret2.book_set是一个queryset集合 #---------------了不起的双下划线(__)之单表条件查询---------------- # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # # startswith,istartswith, endswith, iendswith, #----------------了不起的双下划线(__)之多表条件关联查询--------------- # 正向查找(条件) # ret3=models.Book.objects.filter(title='Python').values('id') # print(ret3)#[{'id': 1}] #正向查找(条件)之一对多 ret4=models.Book.objects.filter(title='Python').values('publisher__city') print(ret4) #[{'publisher__city': '北京'}] #正向查找(条件)之多对多 ret5=models.Book.objects.filter(title='Python').values('author__name') print(ret5) ret6=models.Book.objects.filter(author__name="alex").values('title') print(ret6) #注意 #正向查找的publisher__city或者author__name中的publisher,author是book表中绑定的字段 #一对多和多对多在这里用法没区别 # 反向查找(条件) #反向查找之一对多: ret8=models.Publisher.objects.filter(book__title='Python').values('name') print(ret8)#[{'name': '人大出版社'}] 注意,book__title中的book就是Publisher的关联表名 ret9=models.Publisher.objects.filter(book__title='Python').values('book__authors') print(ret9)#[{'book__authors': 1}, {'book__authors': 2}] #反向查找之多对多: ret10=models.Author.objects.filter(book__title='Python').values('name') print(ret10)#[{'name': 'alex'}, {'name': 'alvin'}] #注意 #正向查找的book__title中的book是表名Book #一对多和多对多在这里用法没区别
查询参数对照: operators = { 'exact': '= %s', 'iexact': 'LIKE %s', 'contains': 'LIKE BINARY %s', 'icontains': 'LIKE %s', 'regex': 'REGEXP BINARY %s', 'iregex': 'REGEXP %s', 'gt': '> %s', 'gte': '>= %s', 'lt': '< %s', 'lte': '<= %s', 'startswith': 'LIKE BINARY %s', 'endswith': 'LIKE BINARY %s', 'istartswith': 'LIKE %s', 'iendswith': 'LIKE %s', }
聚合查询和分组查询
aggregate():通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
from django.db.models import Avg,Min,Sum,Max 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有 图书的集合。 >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中price字段的平均值 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的 标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定 一个名称,可以向聚合子句提供它: >>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35} 如果你也想知道所有图书价格的最大值和最小值,可以这样查询: >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
annotate():可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合.

F查询和Q查询
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
# F 使用查询条件的值,专门取对象中某列值的操作 # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # 查询书籍id大于\小于其价格的书籍 models.Book.objects.filter(id__gt=F("price")) <QuerySet []> models.Book.objects.filter(id__lt=F("price")) <QuerySet [<Book: 书一>, <Book: 书二>, <Book: 书三>, <Book: 书四>, <Book: 书五>, <Book: 书六>]> #Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作 models.Book.objects.filter(id__lt=F("price")/2) <QuerySet [<Book: 书一>, <Book: 书二>, <Book: 书三>, <Book: 书四>, <Book: 书五>]> #修改操作也可以使用F函数,比如将每一本书的价格提高30元 models.Book.objects.all().update(price=F("price")+30) #Concat连接 from django.db.models.functions import Concat from django.db.models import Value models.Book.objects.update(title=Concat(F("title"),Value("("),Value("第"),Value(")"))) #连接后为title=书名(第)
Q()构造查询条件
# Q 构建搜索条件 from django.db.models import Q #1 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询 q1=models.Book.objects.filter(Q(title__startswith='P')).all() print(q1)#[<Book: Python>, <Book: Perl>] # 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。 Q(title__startswith='P') | Q(title__startswith='J') # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 Q(title__startswith='P') | ~Q(pub_date__year=2005) # 4、应用范围: # Each lookup function that takes keyword-arguments (e.g. filter(), # exclude(), get()) can also be passed one or more Q objects as # positional (not-named) arguments. If you provide multiple Q object # arguments to a lookup function, the arguments will be “AND”ed # together. For example: Book.objects.get( Q(title__startswith='P'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) #sql: # SELECT * from polls WHERE title LIKE 'P%' # AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') # 5、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。 # 正确: Book.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith='P') # 错误: Book.objects.get( question__startswith='P', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
del_objects = Q() for room_id, time_list in del_list.items(): temp = Q() for time_id in time_list: temp.children.append(('room_id',room_id)) temp.children.append(('time',time_id)) temp.children.append(('user',request.user)) temp.children.append(('date',choose_date)) del_objects.add(temp,'OR') if del_objects: models.Order.objects.filter(del_objects).delete()
原生sql语句:Person.objects.raw('SELECT * FROM myapp_person'):
https://docs.djangoproject.com/en/2.1/topics/db/sql/
6.auth认证系统和User model
https://docs.djangoproject.com/en/1.11/topics/auth/
https://docs.djangoproject.com/zh-hans/2.1/topics/auth/customizing/
https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html
#继承AbstractUser from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): phone = models.CharField(max_length=11,null=True,unique=True) avatar = models.ImageField(upload_to='avatars/',default='avatars/default.png', verbose_name='头像')#储存在设置的MEDIA_ROOT下的avatars文件夹下 blog = models.OneToOneField(to='Blog', to_field='nid',null=True) def __str__(self): return self.username class Meta: verbose_name = '用户' verbose_name_plural = verbose_name #在settings.py文件中进行设置 AUTH_USER_MODEL = "blogHome.UserInfo" #用户注册时,创建用户 (注意是creat_user,不是creat) from django.contrib import auth def register(request): if request.method=='POST': username = request.POST.get('username') password = request.POST.get('password') user = models.UserInfo.objects.create_user(username=username,password=password) print user if user: return redirect('/login/') return render(request,'register.html') #用户登录时进行认证 def login(request): if request.method=='POST': username = request.POST.get('username') password = request.POST.get('password') print username,password user = auth.authenticate(username=username,password=password) print user if user: auth.login(request,user) #auth.logout(request)注销 return redirect('/order_list/') return render(request,'login.html')
7. django中间件
http://djangobook.py3k.cn/2.0/chapter17/
https://zhuanlan.zhihu.com/p/39275116
新建项目的settings.py中,django默认的中间件如下:

执行流程如下: 1. 有请求时,从上到下依次执行中间件的process_request方法,再从上到下依次执行process_view方法,
2. 执行视图函数view,
3. 从下到上依次执行中间件的process_template_response, 再从下到下依次执行process_response
4.上述执行过程中出现异常,会从下至上依次执行中间件的process_exception

自定义中间件:
1. 实现中间件六个方法中至少一个,分别是:(主要的是process_request和process_response)
- __init__(self): 仅在服务进程启动的时候调用,而在针对单个request处理时并不执行。
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
2. 在settings.py文件中的MIDDLEWARE中进行配置,注意顺序。
#coding:utf-8 #rbac:role-based access control 基于角色的权限控制 from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse,redirect import re #自定义中间件 class ValidPermission(MiddlewareMixin): def process_request(self,request): current_path = request.path # 拿到当前请求路径 '/user/' #设置白名单,允许任何人访问的url valid_urls = ['/login/','/admin/(.*)'] for url in valid_urls: path = '^%s$'%url ret = re.match(path, current_path) if ret: return None #对于没有登陆的用户重定向至登陆页面 user_id = request.session.get('user',[]) if not user_id: return redirect('/login/') #根据权限来匹配url,决定当前用户是否有访问权限 permission_list = request.session['permission_list'] # print permission_list,current_path for permission in permission_list.values(): urls = permission['permission__url'] for url in urls: path = '^%s$'%url ret = re.match(path,current_path) if ret: request.actions = permission['permission__action'] #[u'list', u'edit', u'add'] # print request.actions return None return HttpResponse('没有访问权限')
8.django测试
在单元测试方面,Django 继承python 的unittest.TestCase 实现了自己的django.test.TestCase,编写测试用例通常从这里开始。测试代码通常位于app 的tests.py 文件中(也可以在models.py 中编写,一般不建议)。在Django 生成的depotapp 中,已经包含了这个文件,并且其中包含了一个测试。(测试工具:unittest 或者pytest)
1. python manage.py test:执行所有的测试用例
2. python manage.py test app_name, 执行该app 的所有测试用例
3. python manage.py test app_name.case_name: 执行指定的测试用例
9. django的cookie和session
https://segmentfault.com/a/1190000016041458
Session 依赖于Cookie,如果浏览器不能保存cookie 那么session 就失效了。因为它需要浏览器的cookie 值去session 里做对比。session 就是用来在服务器端保存用户的会话状态。

设置cookie
rep = HttpResponse(...) 或 rep = render(request, ...) rep.set_cookie(key,value,...) def cookie_set(request): response = HttpResponse("<h1>设置Cookie,请查看响应报文头</h1>") response.set_cookie('h1', 'hello django') return response # 给cookie签名 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 True只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖),防止xss攻击
获取cookie
request.COOKIES[key] request.COOKIES.get(key) # 普通cookie是明文传输的,可以直接在客户端直接打开,所以需要加盐,解盐之后才能查看 request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 参数: default: 默认值 salt: 加密盐 max_age: 后台控制过期时间
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:数据库(默认,django_session表中),缓存,文件,缓存+数据库,加密cookie
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") #删除 request.session.clear() request.session.flush() del request.session['键']
session和cookie过期时间设置:
1.函数设置:request.session.set_expiry(value)
设置会话的超时时间,如果没有指定过期时间则两个星期后过期。
如果value 是一个整数,会话将在value 秒没有活动后过期。
如果value 为0,那么用户会话的Cookie 将在用户的浏览器关闭时过期。
如果value 为None,那么会话永不过期。
2. settings.py文件中配置
SESSION_COOKIE_AGE:设置cookie 在浏览器中存活的时间。
SESSION_EXPIRE_AT_BROWSER_CLOSE:默认情况下为False,会话cookie 可以在用户浏览器中保持有效达SESSION_COOKIE_AGE 秒(缺省设 置是两周,即1,209,600 秒)。如果SESSION_EXPIRE_AT_BROWSER_CLOSE设置为True,当浏览器关闭时,Django 会使cookie 失效。
10. django的缓存框架
https://zhuanlan.zhihu.com/p/40864335
https://www.jianshu.com/p/b4e243834715
11. django跨域问题解决
https://www.jianshu.com/p/63fb55bee142
https://www.cnblogs.com/silence-cho/p/10202854.html
- 使用
django-cors-headers全局控制 - 使用
JsonP,只能用于Get方法 - 在
views.py里设置响应头,只能控制单个接口 (CORS: access-control-allow-origin)
12.Django日志管理
http://www.liujiangblog.com/course/django/176
- Loggers: 记录器
- Handlers:处理器
- Filters: 过滤器
- Formatters: 格式化器
- logger.debug()
- logger.info()
- logger.warning()
- logger.error()
- logger.critical()
13.HttpRequest和HttpResponse对象
HttpRequest 是django 接受用户发送多来的请求报文后,将报文封装到HttpRequest 对象中去。
HttpResponse 返回的是一个应答的数据报文。render 内部已经封装好了HttpResponse 类。
request和response的属性及方法:
#request 属性 request.path 请求页面的路径,不包含域名 request.get_full_path 获取带参数的路径 request.method 页面的请求方式 request.GET GET 请求方式的数据 request.POST POST 请求方式的数据 request.COOKIES 获取cookie request.session 获取session request.FILES 上传图片(请求页面有enctype="multipart/form-data"属性时FILES 才有数据) request.GET.get()取值时如果一键多值情况,get 是覆盖的方式获取的。getlist()可以获取多个值。 #response属性 content: 表示返回的内容 charset: 表示response 采用的编码字符集,默认是utf-8 status_code:返回的HTTP 响应状态码,3XX 是对请求继续进一步处理,常见的是重定向。 init:创建httpResponse 对象完成返回内容的初始化 set_cookie:设置Cookie 信息(格式:set_cookies('key','value',max_age=None,expires=None) max_age 是一个整数,表示指定秒数后过期,expires 指定过期时间,默认两个星期后过期。) write 向响应体中写数据
14. form表单
django提供了两个django.forms.Form 和django.forms.ModelForm,可以方便根据model构造前端表单和后端数据验证和处理。其使用参考:https://www.cnblogs.com/silence-cho/p/10202634.html
https://docs.djangoproject.com/en/2.1/ref/forms/fields/#built-in-field-classes BooleanField CharField ChoiceField 单选框 TypedChoiceField DateField DateTimeField DecimalField DurationField EmailField FileField FilePathFiled FloatField ImageField IntegerField GenericIPAddressField MultipleChoiceField 多选框 TypedMultipleChoiceField NullBooleanField RegexField SlugField TimeField URLField UUIDField ComboField MultiValueField SplitDateTimeField #下面两个可以接受queryset对象,用于处理外键,多对多等关系 ModelChoiceField 下拉单选框 ModelMultipleChoiceField 下拉多选框 自定义field: If the built-in Field classes don’t meet your needs, you can easily create custom Field classes. To do this, just create a subclass of django.forms.Field. Its only requirements are that it implement a clean() method and that its __init__() method accept the core arguments mentioned above (required, label, initial, widget, help_text). You can also customize how a field will be accessed by overriding get_bound_field().
https://docs.djangoproject.com/en/2.1/ref/forms/fields/ 每个field有些自己特殊的参数,如max_length, min_length等,下面为一些通用的参数 required 默认为false label 前端显示标签 label_suffix 标签后缀 initial 初始值 widget 前端显示的html元素 # widget=forms.widgets.TextInput(attrs={'type':'number','class':'form-control'} help_text 提示文字,显示在field旁边 error_messages 字典,每个field自带的clean函数验证出错时,显示的错误信息 # error_messages={'required': 'Please enter your name'}, 每个field可设置的key不一样 validators 列表,加入一系列的函数来验证该字段,参考https://docs.djangoproject.com/en/2.1/ref/validators/ localize 参考https://docs.djangoproject.com/en/2.1/topics/i18n/formatting/ disabled 设置为True时,不可编辑
class BookForm(forms.Form): title=forms.CharField( label='书籍标题', max_length=32, widget=forms.widgets.TextInput(attrs={'class':'form-control'}) ) price = forms.DecimalField( label='价格', widget=forms.widgets.TextInput(attrs={'type':'number','class':'form-control'}) ) # 设置type来改变类型,显示数字输入框 publish = forms.ModelChoiceField( label='出版社', queryset=models.Publish.objects.all(), widget=forms.widgets.Select(attrs={'class': 'form-control'}) ) # gender=forms.ChoiceField(choices=((1,"男"),(2,"女"),(3,"其他"))) # publish=forms.ChoiceField(choices=Publish.objects.all().values_list("pk","title")) #ModelChoiceField继承了ChoiceField author = forms.ModelMultipleChoiceField( label='作者', queryset=models.Author.objects.all(), widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control'}) )
form表单提供了接口对数据进行验证,包括每个field自带的clean(), 为某个field添加的clean_field(),以及整个表单的clean(). 其执行顺序如下,参考:https://www.cnblogs.com/ccorz/p/5868380.html
验证流程: #每个field自带的clean 1. 函数full_clean()依次调用每个field的clean()函数,该函数针对field的max_length,unique等约束进行验证,如果验证成功则返回值,否则抛出ValidationError错误。如果有值返回,则放入form的cleaned_data字典中。 #为某个field添加的clean_field() 2. 如果每个field的内置clean()函数没有抛出ValidationError错误,则调用以clean_开头,以field名字结尾的自定义field验证函数。验证成功和失败的处理方式同步骤1。 #整个form表单的clean() 3. 最后,调用form的clean()函数——注意,这里是form的clean(),而不是field的clean()——如果clean没有错误,那么它将返回cleaned_data字典。 4. 如果到这一步没有ValidationError抛出,那么cleaned_data字典就填满了有效数据。否则cleaned_data不存在,form的另外一个字典errors填上验证错误。在template中,每个field获取自己错误的方式是:{{ form.username.errors }}。 5. 最后,如果有错误is_valid()返回False,否则返回True。 注意一点:自定义验证机制时:clean()和clean_&()的最后必须返回验证完毕或修改后的值.

from django import forms from django.core.exceptions import ValidationError import re 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('手机号码格式错误') class LoginForm(forms.Form): user = forms.CharField(required=True, error_messages={'required': '用户名不能为空.'}) pwd = forms.CharField(required=True, min_length=6, max_length=10, error_messages={'required': '密码不能为空.', 'min_length': "至少6位"}) pwd2 = forms.CharField(required=True, min_length=6, max_length=10, error_messages={'required': '密码不能为空.', 'min_length': "至少6位"}) num = forms.IntegerField(error_messages={'required': '数字不能空.', 'invalid': '必须输入数字'}) phone = forms.CharField(validators=[mobile_validate, ], ) def clean_user(self): user = self.cleaned_data.get('user') if user == 'cc': raise forms.ValidationError('用户名是我的!') return user def clean(self): cleaned_data = self.cleaned_data pwd = cleaned_data['pwd'] pwd2 = cleaned_data['pwd2'] print(pwd,pwd2) if pwd != pwd2: raise forms.ValidationError('二次输入密码不匹配') return cleaned_data #注意此处一定要return clean_data,否则会报错 def login(request): if request.POST: objPost = LoginForm(request.POST) ret = objPost.is_valid() if ret: print(objPost.clean()) else: from django.forms.utils import ErrorDict print(objPost.non_field_errors()) pass return render(request, 'login.html', {'obj1': objPost}) else: objGet = LoginForm() return render(request, 'login.html', {'obj1': objGet})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <style> .error_msg{ color: red; } </style> </head> <body> <form action="/login/" method="POST"> <div>用户名: {{ obj1.user }} {% if obj1.errors.user %} <span class="error_msg">{{ obj1.errors.user.0 }}</span> {% endif %} </div> <div>密码: {{ obj1.pwd }} {% if obj1.errors.pwd %} <span class="error_msg">{{ obj1.errors.pwd.0 }}</span> {% endif %} </div> <div>确认密码: {{ obj1.pwd2 }} {% if obj1.errors.pwd2 %} <span class="error_msg">{{ obj1.errors.pwd2.0 }}</span> {% endif %} </div> <div>数字: {{ obj1.num }} {% if obj1.errors.num %} <span class="error_msg">{{ obj1.errors.num.0 }}</span> {% endif %} </div> <div>电话: {{ obj1.phone }} {% if obj1.errors.phone %} <span class="error_msg">{{ obj1.errors.phone.0 }}</span> {% endif %} </div> <div> {% if obj1.non_field_errors %} {% for item in obj1.non_field_errors %} <span class="error_msg">{{ item }}</span> {% endfor %} {% endif %} </div> <input type="submit" value="提交"/> </form> </body> </html>
#coding:utf-8 from django import forms from django.core.exceptions import ValidationError from blogHome import models class RegisterForm(forms.Form): username = forms.CharField( max_length=16, label='用户名', error_messages={ 'max_length':'用户名最长16位', 'required':'用户名不能为空', }, widget=forms.widgets.TextInput( attrs={'class':'form-control'} ), ) password = forms.CharField( min_length=6, label='密码', error_messages={ 'min_length':'密码至少6位', 'required':'密码不能为空', }, widget=forms.widgets.PasswordInput( attrs={'class':'form-control'}, ), ) confirmPassword = forms.CharField( min_length=6, label='确认密码', error_messages={ 'min_length':'确认密码至少6位', 'required':'确认密码不能为空', }, widget=forms.widgets.PasswordInput( attrs={'class':'form-control'}, ), ) email = forms.EmailField( label='邮箱', widget=forms.widgets.EmailInput( attrs={'class':'form-control'}, ), error_messages={ 'invalid':'邮箱格式不正确', 'required':'邮箱不能为空', }, ) #重写用户名钩子函数,验证用户名是否已经存在 def clean_username(self): username = self.cleaned_data.get('username') is_exist = models.UserInfo.objects.filter(username=username) if is_exist: self.add_error('username',ValidationError('用户名已注册')) else: return username #重写邮箱钩子函数,验证邮箱是否已经存在 def clean_email(self): email = self.cleaned_data.get('email') is_exist = models.UserInfo.objects.filter(email=email) if is_exist: self.add_error('email',ValidationError('邮箱已被注册')) else: return email #重写form全局钩子函数,判断两次密码一致 def clean(self): password = self.cleaned_data.get('password') confirmPassword = self.cleaned_data.get('confirmPassword') if confirmPassword and password != confirmPassword: self.add_error('confirmPassword', ValidationError('两次密码不一致')) else: return self.cleaned_data #重写后必须返回cleaned_data数据
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>注册 </title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/myCSS.css"> </head> <body> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3 "> <form class="form-horizontal" action="/register/" method="post" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <label for="{{ form_obj.username.id_for_label }}" class="col-sm-2 control-label">{{ form_obj.username.label }}</label> <div class="col-sm-8"> {{ form_obj.username }} <div class="has-error"> <span class="help-block">{{ form_obj.username.errors.0 }}</span> </div> </div> </div> <div class="form-group"> <label for="{{ form_obj.password.id_for_label }}" class="col-sm-2 control-label">{{ form_obj.password.label }}</label> <div class="col-sm-8"> {{ form_obj.password }} <div class="has-error"> <span class="help-block">{{ form_obj.password.errors.0 }}</span> </div> </div> </div> <div class="form-group"> <label for="{{ form_obj.confirmPassword.id_for_label }}" class="col-sm-2 control-label">{{ form_obj.confirmPassword.label }}</label> <div class="col-sm-8"> {{ form_obj.confirmPassword }} <div class="has-error"> <span class="help-block">{{ form_obj.confirmPassword.errors.0 }}</span> </div> </div> </div> <div class="form-group"> <label for="{{ form_obj.email.id_for_label }}" class="col-sm-2 control-label">{{ form_obj.email.label }}</label> <div class="col-sm-8"> {{ form_obj.email }} <div class="has-error"> <span class="help-block">{{ form_obj.email.errors.0 }}</span> </div> </div> </div> <div class="form-group"> <label for="InputFile" class="col-sm-2 control-label">头像</label> <div class="col-sm-8"> <input type="file" id="InputFile" name="avatar"> </div> </div> <div class="form-group"> <div class="col-sm-offset-4 col-sm-8"> <button type="submit" class="btn btn-info">提交</button> </div> </div> </form> </div> </div> </div> <script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </body> </html>
15,后端生成简单图片验证码
(参考:https://www.cnblogs.com/alex3714/articles/6662365.html)
#生成验证码,保存到session中,并传给前端 def get_valid_img(request): from PIL import Image, ImageDraw, ImageFont import random #随机背景颜色 def get_random_bgcolor(): return random.randint(0,255), random.randint(0,255), random.randint(0,255) image_size = (220,35) # 图片大小 image = Image.new('RGB',image_size,get_random_bgcolor()) # 生成图片对象 draw = ImageDraw.Draw(image) # 生成画笔 font = ImageFont.truetype('static/font/kumo.ttf',35) # 字体类型和大小 temp_list = [] for i in range(5): u = chr(random.randint(65, 90)) # 生成大写字母 l = chr(random.randint(97, 122)) # 生成小写字母 n = str(random.randint(0, 9)) # 生成数字,注意要转换成字符串类型 temp = random.choice([u,l,n]) temp_list.append(temp) text = ''.join(temp_list) request.session['valid_code'] = text font_width, font_height = font.getsize(text) draw.text(((220-font_width)/4,(35-font_height)/4),text,font=font,fill=get_random_bgcolor()) #绘制干扰线 # for i in range(5): # x1 = random.randint(0, 220) # x2 = random.randint(0, 220) # y1 = random.randint(0, 35) # y2 = random.randint(0, 35) # draw.line((x1, y1, x2, y2), fill=get_random_bgcolor()) # # #加干扰点 # for i in range(40): # draw.point((random.randint(0, 220), random.randint(0, 35)), fill=get_random_bgcolor()) # x = random.randint(0, 220) # y = random.randint(0, 35) # draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_bgcolor()) # 不需要在硬盘上保存文件,直接在内存中加载就可以 from cStringIO import StringIO from io import BytesIO io_object = StringIO() image.save(io_object,'png') # 将生成的图片数据保存在io对象中 data = io_object.getvalue() # 从io对象里面取上一步保存的数据 return HttpResponse(data)
#参考:https://www.cnblogs.com/alex3714/articles/6662365.html 1 #_*_coding:utf-8_*_ 2 3 from PIL import Image,ImageDraw,ImageFont,ImageFilter 4 5 import random 6 import math, string 7 8 9 #字体的位置,不同版本的系统会有不同 10 font_path = '/Library/Fonts/Arial.ttf' 11 #font_path = '/Library/Fonts/Hanzipen.ttc' 12 #生成几位数的验证码 13 number = 4 14 #生成验证码图片的高度和宽度 15 size = (100,30) 16 #背景颜色,默认为白色 17 bgcolor = (255,255,255) 18 #字体颜色,默认为蓝色 19 fontcolor = (0,0,255) 20 #干扰线颜色。默认为红色 21 linecolor = (255,0,0) 22 #是否要加入干扰线 23 draw_line = True 24 #加入干扰线条数的上下限 25 line_number = (1,5) 26 27 28 29 30 def gen_text(): 31 source = list(string.ascii_letters) 32 for index in range(0,10): 33 source.append(str(index)) 34 return ''.join(random.sample(source,number))#number是生成验证码的位数 35 36 37 #用来绘制干扰线 38 def gene_line(draw,width,height): 39 begin = (random.randint(0, width), random.randint(0, height)) 40 end = (random.randint(0, width), random.randint(0, height)) 41 draw.line([begin, end], fill = linecolor) 42 43 def gene_code(save_path,filename): 44 width,height = size #宽和高 45 image = Image.new('RGBA',(width,height),bgcolor) #创建图片 46 47 font = ImageFont.truetype(font_path,25) #验证码的字体和字体大小 48 #font = ImageFont.truetype(25) #验证码的字体和字体大小 49 draw = ImageDraw.Draw(image) #创建画笔 50 #text = "我是中国人" #生成字符串 51 text = gen_text() #生成字符串 52 print(text) 53 font_width, font_height = font.getsize(text) 54 draw.text(((width - font_width) / number, (height - font_height) / number),text,\ 55 font= font,fill=fontcolor) #填充字符串 56 57 if draw_line: 58 gene_line(draw, width, height) 59 gene_line(draw, width, height) 60 gene_line(draw, width, height) 61 gene_line(draw, width, height) 62 63 image = image.transform((width + 20, height +10), Image.AFFINE, (1, -0.3, 0, -0.1, 1, 0), Image.BILINEAR) # 创建扭曲 64 image = image.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强 65 image.save('%s/%s.png' %(save_path,filename)) # 保存验证码图片 66 print("savepath:",save_path) 67 return text 68 69 if __name__ == "__main__": 70 gene_code('/tmp','test') #会把生成的图片存成/tmp/test.png
url配置:
url(r'^get_valid_img.png/', views.get_valid_img),
前端使用:验证码显示,并且点击验证码图片更新验证码(通过绑定一个click函数改变img的src属性,从而向后端重新发送请求)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>登陆</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/myCSS.css"> </head> <body> <form class="form-horizontal" action="/login/" method="post"> {% csrf_token %} <div class="form-group"> <label for="inputUsername3" class="col-sm-4 control-label">用户名</label> <div class="col-sm-4"> <input type="text" class="form-control" id="inputUsername3" placeholder="Username" name="username"> </div> </div> <div class="form-group"> <label for="inputPassword3" class="col-sm-4 control-label">密码</label> <div class="col-sm-4"> <input type="password" class="form-control" id="inputPassword3" placeholder="Password" name="password"> </div> </div> <div class="form-group"> <label for="validCode" class="col-sm-4 control-label">验证码</label> <div class="col-sm-4"> <input type="password" class="form-control" id="validCode" placeholder="验证码" name="validCode"> <img id="valid-img" class="valid-img" src="/get_valid_img.png/" alt=""> <span style="margin-left: 10px;color: green;">看不清,点击验证码刷新</span> </div> </div> <div class="form-group"> <div class="col-sm-offset-4 col-sm-8"> <div class="checkbox"> <label> <input type="checkbox"> 记住我 </label> </div> </div> </div> <div class="form-group has-error"> <div class="col-sm-offset-4 col-sm-8"> {% if result.status %} <span class="help-block">{{ result.msg }}</span> {% endif %} </div> </div> <div class="form-group"> <div class="col-sm-offset-4 col-sm-8"> <button type="submit" class="btn btn-info">登陆</button> </div> </div> </form> <script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> <script> $('#valid-img').click(function () { //console.log($(this)); //console.log($(this)[0]); $(this)[0].src+='?'; //变化src属性值不一样即可以重复提交url }); </script> </body> </html>
后端验证码验证:将前端传过来的验证码和session中保存的验证码进行对比
def login(request): ret = {'status': 0, 'msg': ''} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') valid_code = request.POST.get("validCode") if valid_code and valid_code.upper() == request.session['valid_code'].upper(): user = auth.authenticate(username=username,password=password) if user: auth.login(request,user) return redirect('/index') else: ret['msg'] = '用户名或密码错误!' ret['status'] = 1 return render(request, 'login.html',{'result':ret}) else: ret['msg'] = '验证码错误!' ret['status'] = 1 return render(request, 'login.html', {'result': ret}) else: return render(request, 'login.html',{'result': ret})
admin后台 unicodeerror报错时,参考在manage.py, admin.py中添加
import sys
reload(sys)
sys.setdefaultencoding("utf8")
参考博客:http://www.cnblogs.com/yuanchenqi/articles/6083427.html
https://simpleisbetterthancomplex.com/series/2017/09/11/a-complete-beginners-guide-to-django-part-2.html
3 django admin后台添加中文报错:
'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)
解决方案:
在manage.py 文件中加入 reload(sys)和sys.setdefaultencoding('utf8'),如下:
#coding:utf-8
#!/usr/bin/env python
import os
import sys
reload(sys)
sys.setdefaultencoding('utf8')
关于DateTimeInput(DateTimeField)
https://stackoverflow.com/questions/1450463/django-datetimewidget-not-showing-up
5 json.dumps只能序列化python的数据格式,无法序列化Django提供的特殊数据格式,如auto_now_and产生的时间
5.1 ValuesQuerySet序列化
models.Reply.objects.filter(new__id=nid).values('content','id')
values获得的数据,如果返回类型为ValuesQuerySet,先转化为list,再json.dumps()
5.2 QuerySet序列化
models.Reply.objects.filter(new__id=nid)
filter获得的数据,如果返回类型为QuerySet,得用serializers.serialize() 序列化(from django.core import serializers)
6. 扩展Django的User Model
https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html

浙公网安备 33010602011771号