Python之路第十七篇Django进阶篇

Posted 北方客888

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python之路第十七篇Django进阶篇相关的知识,希望对你有一定的参考价值。

规范

确立规范的好处

  1. 代码可读性高
  2. 方便代码的定位极其查找
  3. 为以后代码扩容带来便利

场景:

在多个APP的场景下,单个app的URL函数功能较多的时候,我们可以通过以下方法来解决。

把Views写成模块的方式并且为不同的功能进行不同的划分,并且在Templates中使用同样规则,如下图:

我根据不同的html然后创建不同的函数,命名和templates模板目录一样这样非常方便找到,这个页面中的函数在哪里。

设置路由的时候就得导入相应的函数(下面的函数是在app01中的url,通过object的url转发过来):

复制代码
from cmdb.views import account
from cmdb.views import home
from cmdb.views import asset

urlpatterns = [

    #账户操作登录登出
    url(r\'^login/$\', account.login),
    url(r\'^logout/$\', account.logout),

    #home操作
    url(r\'^index/$\', home.index),

    #资产信息操作
    url(r\'^lists/$\', asset.lists),
    url(r\'^save_hostinfo/$\', asset.save_hostinfo),
    url(r\'^del_hostinfo/$\', asset.del_hostinfo),
    url(r\'^add/$\', asset.add),

    #default url
    url(r\'\', account.login),

]
复制代码

还有就是Form也是有很多文件的所以我们也要单独创建下!

2、静态文件导入优化

看下面的一种情况首先咱们在导入静态的css和js的时候是怎么导入的?

复制代码
{#CSS#}
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet"  href="/static/plugins/bootstrap/css/bootstrap.min.css"/>
    <link rel="stylesheet"  href="/static/plugins/font-awesome/css/font-awesome.min.css"/>
    <link rel="stylesheet"  href="/static/css/commons.css"/>
    <link rel="stylesheet"  href="/static/css/account.css"/>
</head>


{#JS#}
<script src=\'/static/js/jquery-1.8.2.min.js\'></script>
<script src=\'/static/js/valid.js\'></script>
复制代码

这个static在哪里设置的呢?在object的settings里设置的:

复制代码
########### 静态文件路径 ##########
STATIC_URL = \'/static/\'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, \'static\'),
)

########### 增加\'django.core.context_processors.static\', 在OPTIONS里

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\',
                \'django.core.context_processors.static\',
            ],
        },
    },
]


‘’‘
##查看原理##
\'django.core.context_processors.static\',

def static(request):
    """
    Adds static-related context variables to the context.
    """
    return {\'STATIC_URL\': settings.STATIC_URL} #这里和模板语言一样返回一个字典


’‘’
复制代码

那如果以后想把static目录改成content怎么改呢?难道要在每个html中去修改路径吗?这样如果html文件多的话会疯的,所以我们可以这么修改:

如果想修改路径的话直接修改:settings里的   STATIC_URL = \'/static/\' 指定新的目录即可

复制代码
{#CSS#}
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet"  href="{{ STATIC_URL }}plugins/bootstrap/css/bootstrap.min.css"/>
    <link rel="stylesheet"  href="{{ STATIC_URL }}plugins/font-awesome/css/font-awesome.min.css"/>
    <link rel="stylesheet"  href="{{ STATIC_URL }}css/commons.css"/>
    <link rel="stylesheet"  href="{{ STATIC_URL }}css/account.css"/>
    <link rel="stylesheet"  href="{{ STATIC_URL }}css/login.css"/>
</head>

{#JS#}
<script src=\'{{ STATIC_URL }}js/jquery-2.1.4.min.js\'></script>
<script src=\'{{ STATIC_URL }}js/valid.js\'></script>
复制代码

上面的原理就类似模板语言的渲染一样!

效果如下:

2、还有一种方式

通过simple_tag 的方式进行导入,他不需要注册option即可。注意里面的路径

{#在顶部load 一个staticfiles#}
{% load staticfiles %}


{#在底部使用#}
<script src=\'{% static "js/jquery-2.1.4.min.js" %}\'></script>

Form

首先咱们看下,咱们在Form里和Views里都使用了account很容易造成混乱所以我们可以使用别名来区分:

from cmdb.forms import account as AccountForm

1、Form保存用户输入内容

场景:比如有一个html页面有很多的input标签需要你输入,但是如果你输入错误了,数据已经提交过去了,所有数据需要你在写一遍是不是非常不人性化,看下面的例子:(我故意输入错误了密码之后信息还需要我在重新写一遍)

代码如下:

复制代码
def login(request):
    obj = AccountForm.LoginForm()
    if request.method == \'POST\':
        #获取用户输入
        login_form = AccountForm.LoginForm(request.POST)
        #判断用户输入是否合法
        if login_form.is_valid():#如果用户输入是合法的
            username = request.POST.get(\'username\')
            password = request.POST.get(\'password\')
            user_info_list = models.UserInfo.objects.all()
            for i in user_info_list:
                if username == i.email and password == i.passwords:
                    request.session[\'auth_user\'] = username
                    return redirect(\'/index/\')
            else:
                return render(request,\'account/login.html\',{\'model\': obj,\'backend_autherror\':\'用户名或密码错误\'})
        else:
            error_msg = login_form.errors.as_data()
            return render(request,\'account/login.html\',{\'model\': obj,\'errors\':error_msg})

    # 如果登录成功,写入session,跳转index
    return render(request, \'account/login.html\', {\'model\': obj})
复制代码

优化的方法就是:

复制代码
def login(request):
    #获取用户输入
    login_form = AccountForm.LoginForm(request.POST)
    if request.method == \'POST\':
        #判断用户输入是否合法
        if login_form.is_valid():#如果用户输入是合法的
            username = request.POST.get(\'username\')
            password = request.POST.get(\'password\')
            user_info_list = models.UserInfo.objects.all()
            for i in user_info_list:
                if username == i.email and password == i.passwords:
                    request.session[\'auth_user\'] = username
                    return redirect(\'/index/\')
            else:
                return render(request,\'account/login.html\',{\'model\': login_form,\'backend_autherror\':\'用户名或密码错误\'})
        else:
            error_msg = login_form.errors.as_data()
            return render(request,\'account/login.html\',{\'model\': login_form,\'errors\':error_msg})

    # 如果登录成功,写入session,跳转index
    return render(request, \'account/login.html\', {\'model\': login_form})
复制代码

这里,我们把用户输入的请求封装到Form表单给用户返回回去了!这样用户提交的数据就可以在同样返回给用户!

那有人会问如果不是POST的时候,你也这样返回没有问题吗?没有问题的,没有POST请求的话request.POST是空所以不影响。

效果如下:

2、Form错误信息

Form的错误信息有3种,咱们常用的就是:

error_msg = errors

error_msg = errors.as_json()

error_msg.as_ul很少用

咱们在返回Ajax的请求的时候能返回对象吗?是不可以的!(咱们一般返回字符串,或字符串类型的字典),但是咱们可以通过error_msg = errors.as_json()来返回!

print type(login_form.errors)
print type(login_form.errors.as_json())

结果:

<class \'django.forms.utils.ErrorDict\'>
<type \'str\'>

第一个是ErrorDict是Django定义的一个类,第二个是字符串。

看下第一个错误信息,他是一个字典我们可以通过字典来取他的值看下(Views):

error_msg = login_form.errors.as_data()
print error_msg[\'username\'][0]
print error_msg[\'password\'][0]

然后当我们发送数据的时候通下面的方式发送在模板中我们不能通过字典的方式取值只能通过“.”的方式取字典的值怎么办?所以就需要simple_tag来做了:

return render(request,\'account/login.html\',{\'model\': login_form,\'errors\':error_msg})
复制代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe
from django.template.base import resolve_variable, Node, TemplateSyntaxError

register = template.Library()

@register.simple_tag
def error_message(arg):
    if arg: 
        return arg[0][0]
    else:
        return \'\'
复制代码

2、对于Ajax来说

error_msg = login_form.errors.as_json()
print error_msg,type(error_msg)
{
"username": [{"message": "\\u90ae\\u7bb1\\u8d26\\u6237\\u4e0d\\u80fd\\u4e3a\\u7a7a", "code": "required"}], 
"password": [{"message": "\\u5bc6\\u7801\\u4e0d\\u80fd\\u4e3a\\u7a7a", "code": "required"}]} 
<type \'str\'>

之前说过的在error的时候有多个错误,我们默认取第0个就可以了,我们在通过Ajax发送登录认证的时候可以通过返回来的错误信息通过js来进行显示即可!

3、FormSelect更新

看下面的这种情况基本的通过form生成select标签

html

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>可更新下拉菜单</h1>
    <p>{{ obj.host }}</p>
</body>
</html>
复制代码

form

复制代码
class ImportForm(forms.Form):
    HOST_TYPE_LIST = (
        (1,\'物理机\'),
        (2,\'虚拟机\'),
    )

    host = forms.IntegerField(
        widget=forms.Select(choices=HOST_TYPE_LIST)
    )
复制代码

views

def index(request):
    obj = HomeForm.ImportForm(request.POST)
    return render(request,\'home/index.html\',{\'obj\':obj})

这个select里面的选项是写死的就两个选项他是静态的,如果这个是业务线的话,是动态的话,我有新增了一个业务线怎么办?先拿文件举例,后面会跟上从数据库取数据的例子!

有一个文件db_admin通过json.dumps生成的文本

[[1, "物理机"], [2, "虚拟机"]]

然后在form里调用,html&views不变,修改下form

复制代码
class ImportForm(forms.Form):

    f = open(\'db_admin\')
    data = f.read()
    data_tuple = json.loads(data)
    f.close()
    host = forms.IntegerField(
        widget=forms.Select(choices=data_tuple)
    )
复制代码

现在是动态生成了,我现在在db_admin里在增加一个字段

[[1, "物理机"], [2, "虚拟机"],[3, "云主机"]]

当我在刷新页面的时候发现:为什么我添加的‘[3,"云主机"]’,为什么没有出现呢?我已经添加了!

说道这个问题就得回顾下之前的面相对象的东西了,静态再断当类创建之后就保存在内存中了,当你在去调用ImportForm的时候他不会再去执行了,所以当调用这个ImportForm类实例化的时候,静态字段是不变的他没有重新读!

意思就是,当他调用的时候还是之前的内容怎么办呢?“重启整个程序让他重新加载类”,太low了吧!

当类在被调用的时候首先执行的是类的构造方法:__init__方法,每当调用类的实例化的时候他都执行!所以我们可以给form定义一个form方法。

复制代码
class ImportForm(forms.Form):
    f = open(\'db_admin\')
    data = f.read()
    data_tuple = json.loads(data)
    f.close()
    host = forms.IntegerField(
        widget=forms.Select(choices=data_tuple) 
    )

    def __init__(self, *args, **kwargs):
        super(ImportForm, self).__init__(*args, **kwargs)
        f = open(\'db_admin\')
        data = f.read()
        data_tuple = json.loads(data)
        f.close()

        #通过self.fields找到host这个静态字段的.wideget.choices属性并重新给他赋值!
        self.fields[\'host\'].widget.choices = data_tuple
复制代码

优化:

复制代码
class ImportForm(forms.Form):
    host = forms.IntegerField(
        widget=forms.Select()  #这里默认是空值,每一次我们使用的时候通过__init__进行赋值
    )

    def __init__(self, *args, **kwargs):
        super(ImportForm, self).__init__(*args, **kwargs) #执行当前类的父类的构造方法(__init__)
        f = open(\'db_admin\')
        data = f.read()
        data_tuple = json.loads(data)
        f.close()

        #通过self.fields找到host这个静态字段的.wideget.choices属性并重新给他赋值!
        self.fields[\'host\'].widget.choices = data_tuple
复制代码

Model

到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞:

  • 创建数据库,设计表结构和字段
  • 使用 MySQLdb 来连接数据库,并编写数据访问层代码
  • 业务逻辑层去调用数据访问层执行数据库操作
复制代码
import MySQLdb
 
def GetList(sql):
    db = MySQLdb.connect(user=\'root\', db=\'luotianshuai\', passwd=\'1234\', host=\'localhost\')
    cursor = db.cursor()
    cursor.execute(sql)
    data = cursor.fetchall()
    db.close()
    return data
 
def GetSingle(sql):
    db = MySQLdb.connect(user=\'root\', db=\'luotianshuai\', passwd=\'1234\', host=\'localhost\')
    cursor = db.cursor()
    cursor.execute(sql)
    data = cursor.fetchone()
    db.close()
    return data
复制代码

首先Django是通过Model操作数据库,不管你数据库的类型是MySql或者Sqlite,Django它自动帮你生成相应数据库类型的SQL语句,所以不需要关注SQL语句和类型,对数据的操作Django帮我们自动完成。只要回写Model就可以了!

1、创建数据库表

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

  •   PHP:activerecord
  •   Java:Hibernate 
  •    C#:Entity Framework

django中遵循 Code Frist 的原则,即:根据代码中定义的类来自动生成数据库表。

对于ORM框架里:

我们写的表示数据库的表

如果根据这个类创建的对象是数据库表里的一行数据

对象.id 对象.value 是每一行里的数据 

class UserInfo(models.Model):
    username = models.CharField(max_length=64)
    email = models.EmailField(max_length=64,null=True)
    email2 = models.EmailField(max_length=64,default="luotianshuai@qq.com")

2、更多字段

首先要了解,如果Django如果不是有一个后台admin可以操作数据库的话,Model没有必要设置很多的数据类型,那么这么多的数据类型,他的存在的意义就是为了限制从admin时的操作。 

 还有就是不要把Form和Model弄混了!他们两个是完全独立的!Model的数据类型和Form没有关系,数据类型存在的意义就是限制admin的操作。(原因:如果不对admin进行限制的话容易造成脏数据)但是form提交的数据也必须是符合Model的存储要求的,这个可以在form哪里进行判断,然后存储即可。

在admin添加数据的时候,如果在Model设置了规则如下:

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

这里单独说下

models.DateTimeField

复制代码
class UserInfo(models.Model):
    username = models.CharField(max_length=64)
    email = models.EmailField(max_length=64,null=True)
    email2 = models.EmailField(max_length=64,default="luotianshuai@qq.com")
    ctime = models.DateTimeField(auto_now=True) #默认这样写上就不用管了,每当你创建一行数据的时候就会在那一行数据中增加一个ctime字段
    uptime = models.DateTimeField(auto_now_add=True)#默认写成这样也不同管了,当前表任何一行有修改的时候他就会自动更新.
复制代码

他在数据库的存储方式如下:

models.GenericIPAddressField  

他和email类似也是字符串类型然后进行了正则表达式的判断他支持IPV4,IPV6,旧的models.IPAddressField 已经很少用了

models.ImageField 图片
models.FilePathField 文件

复制代码
class UserInfo(models.Model):
    username = models.CharField(max_length=64)
    email = models.EmailField(max_length=64,null=True)
    email2 = models.EmailField(max_length=64,default="luotianshuai@qq.com")
    img = models.ImageField(null=True,blank=True,upload_to=\'user_upload\')
    #Python之路第十七篇:Django进阶篇

Python之路第十七篇:Django进阶篇

Python之路第十七篇:Django进阶篇

Python之路第十七篇:Django之进阶篇

Python之路(第十七篇)logging模块

Python开发第二十二篇:Web框架之Django进阶