当时年轻的我在做项目时遇到的坑
models中choices属性的使用及扩展限制探讨
我们都知道,在使用Django的models建表的时候可以为诸如性别这样的在特定的使用场景只会二选一或者多选一的属性字段用choices指定:
class Student(models.Model): SEX_CHOICE = ((1,'男'),(2,'女'),) name = models.CharField(max_length=12,null=True) sex = models.IntegerField(choices=SEX_CHOICE,)
上面定义的Student类的sex属性映射到数据库后,在数据库中以数字1或2进行存储,而我们需要显示在外面的是元组后面的值男或女。
在视图函数中拿一个学生对象直接打印sex得到的是1或2,而想要拿到具体的值需要用model对象的get_sex_display方法(这里的sex是具体的属性值,前面的get与display是固定写法~)
def test(request): obj = models.Student.objects.get(name='whw') #注意需要用model对象而不是queryset对象 print(obj.sex) # 1 print(obj.get_sex_display()) # 男 注意这是个方法需要加括号 print(obj.name) #whw return render(request,'testchoice.html',{'obj':obj})
在前端模板中想要获取sex的具体的值的话也需要用到get_sex_display方法(不用加括号):
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet"> </head> <body> 姓名:{{ obj.name }} 性别:{{ obj.get_sex_display }} <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> </body> </html>
但是models在实际中使用choices的时候需要特别注意其“扩展”问题!
比如说项目之前定义了一个“设备状态”的字段使用了choices属性,选项只有“停止”与“运行”两个;但是由于项目拓展我们需要为这个字段新增一个“就绪”的状态~~想要扩展choices属性内部的字段还是十分棘手的!
我暂时的解决方案是创建一张单独的设备状态表来存放设备的不同状态,然后主表与这张表做外键关联,这样实际中再想添加设备状态字段的话,我只需要动一张表就OK了!
另外,choices的使用其实是解决数据库存储空间的问题,做外键关联的话主表也只存了外键的id值,设备状态表的字段与记录也不重复,同样可以解决空间问题,而且其扩展性更好。只不过实际中外键关联也会受到很多的限制~现实中大项目也比较少用到外键关联~
所以实际中如何选择表结构还是要根据实际情况来的!
路由分发下反向解析遇到不同路由相同name属性值的问题处理
准备工作
首先,我们新建一个名为NameSpace的Django项目,然后在里面创建两个应用,分别叫customer与payment,记得在settings中的INSTALLED_APPS中注册这两个应用~
接着,在template目录中分别创建两个项目的模板文件夹~而且让其中的两个网页名字一样,项目的具体结构如下:
路由分发
全局的urls文件中(跟项目名同名的目录里的文件我习惯叫全局文件)做路由分发:
urlpatterns = [ path('admin/', admin.site.urls), re_path('^customer/',include('customer.urls')), re_path('^payment/',include('payment.urls')), ]
customer目录中新建的urls文件
from django.urls import path,re_path from customer import views urlpatterns = [ re_path(r'^index/$',views.index,name='index'), ]
payment目录中新建的urls文件
from django.urls import path,re_path from payment import views urlpatterns = [ re_path(r'^index/$',views.index,name='index'), ]
视图函数
customer目录中的视图函数
from django.shortcuts import render,HttpResponse from django.urls import reverse def index(request): if request.method == 'GET': url = reverse('index') print('customer_url:',url) return HttpResponse(url)
payment目录中的视图函数
from django.shortcuts import render,HttpResponse from django.urls import reverse def index(request): if request.method == 'GET': url = reverse('index') print('payment:',url) return HttpResponse(url)
存在问题
大家可以看到:我在两个应用的分发路由中给两个不同的index路径(事实上,我们访问customer的indexs的路径其实是/customer/index/,同理payment的index的路径是/payment/index/)取了相同的别名index;而且在不同的视图函数中通过反向解析拿到了别名index对应的路径打印了出来并返回给浏览器。
现在我们试试实际的运行效果:
当我访问payment的index的时候确实打印出了正确的路径:
但是,当我访问customer的路径的时候出错了:这个url竟然被反向解析成了payment的index路径!
让我们再来看看后台打印的效果:从第二条数据可以看出来~浏览器明明收到的是customer的get请求,可是反向解析出来的index的路径却是另外一条路由!
实际中这样出现这样的问题是十分危险的!为了避免这种情况发生,可以利用名称空间解决~
名称空间解决
利用名称空间可以解决上面的问题~
首先我们在路由分发的时候为每条分发的路由加上名称空间:
urlpatterns = [ path('admin/', admin.site.urls), re_path('^customer/',include(('customer.urls','customer'))), re_path('^payment/',include(('payment.urls','payment'))), ]
两个应用的分发的路由中的写法还跟上面的一样~
在视图函数中进行反向解析的时候为每个别名加上自己的名称空间~
customer的视图函数:
from django.shortcuts import render,HttpResponse from django.urls import reverse def index(request): if request.method == 'GET': url = reverse('customer:index') print('customer_url:',url) return HttpResponse(url)
payment的视图函数:
from django.shortcuts import render,HttpResponse from django.urls import reverse def index(request): if request.method == 'GET': url = reverse('payment:index') print('payment:',url) return HttpResponse(url)
最后让我们看看结果:
(1)访问payment的结果:
(2)访问customer的结果:
(3)最后看看打印结果:
所以~实际开发中名称空间不能重名!
ajax的url利用带参数的路由反向解析
问题概述
实际中,我们在利用ajax给后台传入一个url时会带着参数。有些小伙伴喜欢用路由解析去给后台传url,所以在a标签中利用反向解析时会这样做:
<a href="{% url "del_book 1" %}">删除</a>
这样做完全没问题。
但是,实际项目中常会用到ajax异步操作,如果你在ajax中这样写url的话:
var del_id = $('#del_id').text();
。。。
。。。 url:'{% url "del_book" del_id %}',
浏览器会给你返回一个这样的错误:路由中拿不到你传的这个参数~
这就说明:在ajax的反向解析中,不可以传一个“变量”!
解决方案1
可以另辟蹊径,我们可以把需要的路由放在一个标签中,这个标签用模版的反向解析的语法获取到这个路由,然后再传给ajax的url:
//构建一个获取url的标签 <span id="del_url" class='hidden'>{% url 'del_book' 1 %}}</span> 。。。 //用一个变量接收这个路由:/del_book/1/ var u1 = $('#del_url').text(); $.ajax({ //url用这个变量u1 url:u1, type:'post', 。。。 })
但是呢~聪明的你可能一眼就看出问题来了:你这个 1 是一个常量不是变量呀!我想加一个变量怎么办呢?~~请看方法2~
解决方案2
实际上,我们会通过模板语法去渲染页面~方法1中提到的“常量”其实可以在模板渲染的时候拿到,
既然我们能通过{{ obj.pk }}值可以获取到这个条目的id值~那可以在这个位置构建一个隐藏的标签:
<span class='hidden'>{% url "index" {{ obj.pk }} %}</span>
大家可以看到~这个span标签里存放着不就是我们需要的那个带动态参数的路径吗?接着利用jQuery的选择器拿到这个路径参数。
//假设这个span标签在我们点击的按钮的后面 var id_url = $(this).next().text();
然后,在url的data中就可以用这个路径了:
...
$.ajax({
url:id_url,
...
})
...
解决方案3
第三种解决方案是“路径拼接”~特别注意:这里的路径必须是一个全路径!
{# 获取要被删除的书籍的id #} var del_id = $('#del_id').text(); $.ajax({ {# 拼接路径 #} url:'/book/del_book/' + del_id +'/', {#url:'{% url "del_book" del_id %}', //不可以这样写#} type:'post', 。。。 })
数据库严格模式的引入及设置
现实中的一个问题
关于数据库的严格模式的概念这里不详述,我来演示一个实际中会遇到的很深的坑(DBA大佬请绕行0.0)~
先说明一下,我们新安装的数据库默认是没有设置严格模式的。
1、我这里在测试的数据库中新创建一张表users:大家可以看到我的name字段规定的最大长度是5个字节~
2、然后我往这个表中插入两条数据(第二条数据超出了name字段的最大长度):
大家注意这里有一条Warring~其实这条Warring就是提示我插入的数据不符合字段定义规范~但是这些信息需要在SQL的后台查看,而且数据插入依然成功了!
3、然后我们看一下表中的内容:
糟糕的事情来了:我刚刚插入的第二条数据被无情的截取了!
但是实际中~存在数据库中的数据是必须跟录入的一致的!
那是否有一种方式可以在插入数据时遇到不符合条件的数据直接阻止数据的录入呢?~~设置数据库的严格模式!
MySQL中严格模式的设置
1、首先查看一下数据库的sql_mode:
2、设置数据库的严格模式
3、然后我们先清空一下表中的内容重新插入数据试试:
大家可以看到~设置完数据库的严格模式后再试图往里面添加不符合规范的数据会报错~而且前面符合规范的数据也插不进去~~很显然这里做了事物~~
Django中对数据库严格模式的设置
在Django中,可以在settings中的DATABASES列表中设置严格模式:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql',#引擎,选mysql 'NAME':'books',#要连接的数据库,连接前需要创建好 'USER':'root',#连接数据库的用户名 'PASSWORD':'123',#连接数据库的密码 'HOST':'127.0.0.1',#连接主机,默认本本机 'PORT':3306,#端口 默认3306 #Django中设置数据库的严格模式 'OPTIONS':{ 'init_command':"set sql_mode='STRICT_TRANS_TABLES' ", } } }
Django防改动措施
项目中引入静态文件
曾几何时,我们在项目中引入静态文件时这样做:先在settings中配置静态文件“别名”及路径,然后在html文件中引入
这里的staticfiles是存放静态文件的目录;static是引入文件时用的“别名”。
html文件中引入的时候是利用别名引入的。
特别注意!存放静态文件的目录名一定不能跟“别名”一样了!
如果实际中遇到需要“修改别名”的情况~你想一下,所有引入这个别名的html文件都必须同时修改了!
可以利用{% load static %}解决:
这里的static你可以将它看成是一个“关键字”~以后修改别名的话html文件中也不需要改动了!
路由的反向解析
模板文件中的反向解析
说实话实际中遇到修改路径的情况还是十分少见的,我自己也就遇到过一次这样的情况,总结进来把~
一般情况下我们的url是这样写的:
url(r'^login/',views.login),
模板中如果用到form表单提交数据(好吧~其实就是举个例子说明问题而已0.0)路径可以这样写:
<form action="/login/" method="post"> xxx </form>
如果遇到修改访问路径的情况这里也需要修改~
为了以后少出点故障点,我们可以利用反向解析来避免些麻烦:
路由中这样写:
url(r'^login/',views.login,name='book_login'),
模板中如果想把这次post请求提交给/login/对应页面的话这样写:
<form action="{% url 'book_login' %}" method="post">
以后即使/login/这个路径变成了/book_login/,只要后面name的属性不变,你在模板中一直用这个别名就不用有太多改动。
视图函数中的反向解析
这种情况多用在重定向的时候,实际中遇见过几次。
当你登陆成功后会跳转到另外一个页面。假如这个页面的路径是/index/,路由是这么写的:
# index主页 url(r'^index/',views.index,)
视图中登陆成功后可以重定向到index页面:
但是,如果重定向的这个页面路径发生变化的话不仅要在urls中修改也得在视图函数中改。
用反向解析可以解决这个问题~不过首先我们也需要在urls中先为这个路径“取个别名”:
# index主页 url(r'^index/',views.index,name='book_index'),
视图函数中需要先引入reverse,然后通过reverse('book_index')得到index的路径,只要这个name与url的对应关系不变~就不用担心以后修改路径再回过头来修改视图中的内容了~
另外再提一下~上面的例子其实是为了说明一下视图函数中反向解析怎么用。如果真用在重定向里的话,可以直接将路由的“别名”写在redirect中:
原因是redirect函数其实内置了reverse方法~大家有兴趣的话可以研究下它的源码。
页面跳转时路径的写法遇坑
假设你的主页中有个按钮(这里用a标签),点击它给浏览器发送一个get请求的路径,然后根据路由系统去视图函数中处理返回到对应的页面。
这里a标签是这样写的:
<a href="/add/" class="btn btn-success pull-right">添加</a>
浏览器在收到后,会在自己的地址栏拼接成这样的路径:
这样,urls中如果定义一个这样的路由,就可以顺利的找到视图函数去处理了~
# 添加书籍 url(r'^add/$',views.add),
但是~如果我们把a标签的href的路径前面的那个斜杠去掉的话~像下面这样写:
<a href="add/" class="btn btn-success pull-right">添加</a>
此时浏览器得到的路径却是这样的~同时还会上报一个404错误~
而如果把后面的那个斜杠去掉的话就不影响结果:
# 这样写不影响结果
<a href="/add" class="btn btn-success pull-right">添加</a>
当然~我们也可以利用“反向解析”去避免这个问题:
路由中:
# 添加书籍 url(r'^add/$',views.add,name='add_book'),
模板中:
<a href="{% url 'add_book' %}" class="btn btn-success pull-right">添加</a>
删除数据库时primary key起始值的问题
当然~是在自己测试数据的时候删除的0.0
另外一般情况下都会设置它为auto_increment
原有数据如下:
利用delete from删除
执行命令
delete from book_book;
但是,我再下一次往表中插入数据的时候id的值不是从1开始而是从上一次最后一个值+1开始:
利用truncate语句删除
执行命令
truncate table book_book;
这样的话,再下一次插入数据的时候主键id就会从1开始取值了。
设置:APPEND_SLASH=False
(1)settings配置文件里面加上上面这句话,意思是说,告诉django,如果别人请求我的路径的时候,你不要自己处理别人输入的路径最后面的/了。如果这个值为True,而我们假如写了一个url为url('^index/',views.test),如果用户输入的时127.0.0.1:8000/index的话,
django会让浏览器重新再发一次请求,并且在这个路径后面加上/,也就成了127.0.0.1:8000/index/,此时和我们的url就能匹配上了,因为我们的url正则写的就加了/。
但如果你将下面这个值设置成false,那么django就不会自动帮你做这个事情了!那么用户在输入127.0.0.1:8000/index,没有最后那个斜杠的路径时,就无法和我们的url正则匹配上了,所以就找不到url了,就会报错。
(2)但是注意,django只能帮你重定向让浏览器再发一个get请求,如果你是post请求(非get请求),django就没有办法了,他还是帮你重新定向发送get请求,
不能满足你的需求,所以如果你用post方法提交数据的时候,就像上面这个ajax里面的那个url写的必须和你后端配置的那个url对应好,
所以别忘了如果你后端url上url('^index/',views.test)这个index后面加了/,那么你写ajax往这个路径下提交数据的时候,ajax里面的url参数后面别忘了写上/,让这个url和后端url正则规则对应好。
路由匹配有结尾符$时的坑
re_path('^cookie/$',views.cookie),
如果你的settings中没有配置APPEND_SLASH=False
而路由正则匹配要用结尾符$的话,必须像上面这样写路由~将斜杠(/)写在$符的里面!
Django与MySQL时区问题
在进行ORM查询的时候,比如查询2012年2月的订单:
all_payments = PayMent.objects.filter(time__year=2012,time__month=2)
如果明明有结果,你却查不出结果,是因为mysql数据库的时区和咱们django的时区不同导致的!
你需要做的就是将django中的settings配置文件里面的USE_TZ = True改为False,就可以查到结果了,以后这个值就改为False!
而且就是因为咱们用的mysql数据库才会有这个问题,其他数据库没有这个问题。
如果时间差8个小时的话,这样改:
django的配置文件settings中,之前有两个全局变量:TIME_ZONE 与USE_TZ 把这两个值改一下: TIME_ZONE = 'Asia/Shanghai' USE_TZ = False -------------这句话,如果已经存在了,就需要把USE_TZ改成USE_TZ=False
distinct的说明与values/values_list的机制
注意:
必须先用values/values_list筛选出数据后再进行去重~如果不先筛选,将全部数据进行distinct()的操作,由于id肯定不一样,所以这样做没有意义!
values的用法和返回结果举例
all_books = models.Book.objects.all().values('id','title') print(all_books) #<QuerySet [{'title': 'linux', 'id': 6}, {'title': '你好', 'id': 7}, {'title': 'linux', 'id': 8}, {'title': 'xxx', 'id': 9}, {'title': 'gogogo', 'id': 10}]> ''' values做的事情: ret = [] #queryset类型 for obj in Book.objects.all(): temp = { #元素是字典类型 'id':obj.id, 'title':obj.title } ret.append(temp) '''
values_list的用法和返回结果说明
all_books = models.Book.objects.all().values_list('id','title') print(all_books) #<QuerySet [(6, 'linux'), (7, '你好'), (8, 'linux'), (9, 'xxx'), (10, 'gogogo')]> ''' values做的事情: ret = [] #queryset类型 for obj in Book.objects.all(): temp = ( #元素是元祖类型 obj.id,obj.title ) ret.append(temp) '''
distinct的用法和返回结果说明
''' all_books = models.Book.objects.all().distinct() #这样写是表示记录中所有的字段重复才叫重复,但是我们知道有主键的存在,所以不可能所有字段数据都重复 all_books = models.Book.objects.all().distinct('price') #报错,不能在distinct里面加字段名称 all_books = models.Book.objects.all().values('price').distinct()#<QuerySet [(Decimal('11.00'),), (Decimal('111.00'),), (Decimal('120.00'),), (Decimal('11111.00'),)]> ''' all_books = models.Book.objects.all().values_list('price').distinct() #<QuerySet [{'price': Decimal('11.00')}, {'price': Decimal('111.00')}, {'price': Decimal('120.00')}, {'price': Decimal('11111.00')}]> 只能用于valuse和values_list进行去重 #title和price两个同时重复才算一条重复的记录 all_books = models.Book.objects.all().values_list('title','price').distinct()
~~