Django 开发相关知识 整理

 

前言

旨在记录Django web经常使用的开发技术,帮助开发者更加快速开发javascript

前端ajax

详细
注意:post提交时,url对后缀必定要补齐'/'
$.ajax({
  url: "/cookie_ajax/",
  type: "POST",
  data: {
    "username": "Q1mi",
    "password": 123456,
    "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
  },
  success: function (data) {
    console.log(data);
  }
})
// 原生ajax,XMLHttpRequest     
    如下两函数功能同样。
         function Ajax1()
            {
                $.ajax({
                    url:'index.html',
                    type:'GET',
                    data:{'p':123},
                    success:function(arg){
                        console.log(arg);
                    }
                })
            }    
    
           
        function Ajax2()
            {
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange=function(){
                    if(xhr.readyState==4){
                        //状态‘4’表示将服务器的返回的数据所有接收完毕
                        console.log(xhr.responseText);  //xhr.responseText表示服务器端返回的数据
                    }
                }
                xhr.open('GET','/index.html?p=123').  //GET请求只能将data数据写在url中
                xhr.send(null)
            }
        
        而POST请求而只要改动如下
        xhr.open('POST','/index.html') 
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');  
     xhr.send('p=123')  
//send中字符串参数是post请求,并且必须带请求头,不然django不会解析,即request.POST会无数据,
//可是request.body中能够查看到数据确实发过去了。jquery的ajax已经帮咱们封装好请求头了。而且可能有其余框架会指定要特有的请求头。
//注意,若前端使用Formdata封装数据(由于不须要头,jquery的ajax中也要作特殊处理),则不用请求头也可

 

HTTP请求头

首先理解一下HTTP协议
1.创建在tcp协议之上,属于应用层的协议
2.我把它理解为一个规范的传输数据格式,即请求和响应(请求头\r\n\r\n请求体、响应头\r\n\r\n响应体)
3.无状态,短连接(一次请求一次响应,而后断开连接)

经常使用请求头php

    content-type :服务器端给客户端发送的数据类型
    accept :服务器端告诉客户端要接收数据类型
    User-agent
    COOKIE

django的request.POST里面有值要求有
1.请求头要求:
若是请求头中的 Content-Type: application/x-www-form-urlencoded,request.POST中才有值,django会去request.body中解析数据
2.数据格式要求:
即便请求头中的 Content-Type为 application/x-www-form-urlencoded。request.POST也不必定有值,还要request.body要知足name=alex&age=18&gender=男 这种格式
form表单提交默认的 Content-Type为 application/x-www-form-urlencoded

ajax能够序列化和定制请求头css

        // 默认:(和form表单同样的头)
        $.ajax({
            url:...
            type:POST,
            data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender=男
        })
        
         // 状况二:(定制了请求头,Content-Type 再也不是 application/x-www-form-urlencoded)
        $.ajax({
                url:...
                type:POST,
                headers:{'Content-Type':"application/json"}
                data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender=男
            })
            # body有值;POST无
        //  状况三:(序列化,数据格式不知足,并且头也不知足)
            $.ajax({
                url:...
                type:POST,
                headers:{'Content-Type':"application/json"}
                data:JSON.stringfy({name:alex,age=18}) # {name:alex,age:18...}
            })
            // body有值;POST无
            // json.loads(request.body)

ajax上传文件

使用ajax文件上传,ajax配置必须带如下参数
1.processData:false
2.contentType:false
3.data必须是formData()html

        // #avatar是文件上传按钮
          $('#avatar').click(function () {
                    if (!$('#avatar-img').val()) {
                        // $('#avatar-img').val() 是文件路径名
                        // $('#avatar-img')[0].files[0] 是文件对象,后台从request.FILES里取出就是它
                    }
                    var formData = new FormData();
                    formData.append('csrfmiddlewaretoken', $('[name = "csrfmiddlewaretoken"]').val());
                    formData.append('img', $('#avatar-img')[0].files[0]);
                    $.ajax({
                        url: '/account/upload_avatar/',
                        type: 'post',
                        processData: false,
                        contentType: false,
                        data: formData,
                        dataType: 'json',
                        success: function (data) {
                            console.log(typeof(data), data);
                        }
                    })
                });

jsonp跨域

同源策略是检测网页js脚本是否是向同一个域的url发送请求,ajax恰好受到同源策略限制,而script标签则没有。
而script标签的工做原理是向src指向的url发送GET请求,而后将请求到的数据包裹在标签里面。
因此咱们能够建立一个标签<script src=‘http://xxx.com/fuck'> <script>
例如:
<script> egon </script>
#return HttpResponse('egon')
可是,因为数据是直接被包裹在标签里面,至关于声明一个变量名,而js不支持因此报错
因此常见的作法是,本地声明一个回调函数,jsonp返回该函数包裹的跨域数据字符串。
例如:
HttpResponse("func({'a':1,'b':2})")
更深一层,咱们能够经过传递GET参数告诉服务器毁掉函数,实现动态的返回指定包裹函数
例如:127.0.0.1:9999?callback=myfunc前端

jquery版小技巧:不写JsonpCallback,这样作jquery会随机生成一串字符串做为callback函数名,这样jquery会直接将返回的数据取出做为success函数的参数
视图:
HttpResponse("%s('%s')"%(callback,data))
java

 

URL

设计

多个url对应一个视图
利用是‘子类’在前,‘父类’在后,多余的参数使用**kwargs接收node

url(r'^(?P<type>type_[1-3])', views.index),
url(r'^$', views.index),

在URL传递搜索条件
通常是以GET参数传递,这个了解一下也好python

# 该正则表示按标签或者日期或者种类过滤
url(r'^(?P<site>\w+)/(?P<condition>((tag)|(date)|(category))/(?P<val>\w+).html$',home.filter)

分发

url匹配有两种,一种是路径对视图,另外一种则是分发
- url分发的格式mysql

url('^yuan',([],None,None))

正则对应再也不是一个视图函数,而是一个元组。
元组的第一个元素是列表,里面有多个url,通常url对象里面是路径对视图,可是你若喜欢能够再嵌套分发
第二元素为App名,第三元素为名字空间
jquery

url参数编码

将空格等特殊字符编码,经常使用于处理GET传参的特别参数
from django.utils.http import urlquote
def parse_filter_kwargs(kwargs):
    # kwargs: {'title':1,'price':12000}
    # return: title=1&price=12000
    l = []

    for k, v in kwargs.items():
        l.append('%s=%s' % (k, urlquote(v)))
    return '&'.join(l)

反向生成url

视图
from django.core.urlresolvers import reverse
#kwargs参数接收 一个字典,表明命名正则的名 和 对应的值
    if kwargs:
            base_url = reverse('index', kwargs=kwargs)
        else:
            base_url = '/'
html
# 默认版
url('all/(\d+).html$',home.index,name='index')
{%url "index" 1%} # 在html中
reverse('index',kwargs=(1,)) # 在view中

# 命名正则版
url('all/(?<my_id>\d+).html$',home.index,name='index')
{%url "index" my_id=1%} # 在html中:
reverse('index',kwargs={"my_id":1}) #在view中

# 命名空间的url反转
{% url 'org:org_list'%}

视图

request对象

POST

1. input 类型像checkbox这种多值的,要用values = request.POST.getlist('hobby',None)
2. requst.body #请求体,若要去这里拿值时,必定是post方式进来的

url信息

视图返回值

HttpResponse

1. input 类型像checkbox这种多值的,要用values = request.POST.getlist('hobby',None)
2. requst.body #请求体,若要去这里拿值时,必定是post方式进来的

JsonRespone 

from django.http import JsonRespone
#注意:默认JsonRespone只能返回字典,若必定要返回列表,则须要加参数
    return JsonRespone([11,22,33],safe=False)

shortcuts

- render

1.render的context参数能够传递locals()函数,将当前做用域全部的变量传递到模板里面去

- redirect

详细
1.redirect函数对于ajax提交没用
2.redirect('/index') 和 redirect('index')的区别:前者跳转到127.0.0.1:8000/index,后者在现有url的网址后面追加index
 例如:现访问的是127.0.0.1:8000/backend,追加为:127.0.0.1:8000/backend/index

返回值响应头和状态设置

# HttpResponse能够带几个参数:

"""

content: 返回的内容
content_type: 返回内容的类型
status

"""

def index(request):
    return HttpResponse('<p>this is a test</p>', content_type="text/html", status="201")

- 响应头设置

 res = HttpRespone('xxxxx')
 res['Access-Control-Allow-Origin']='*'
 return res

CBV

- 更好对代码复用

例如:自定义cbv的login验证mixin

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views import View


class LoginRequiredMixin(object):
    @method_decorator(login_required(login_url='/url/'))
    def dispatch(self, request, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)


# 注意继承顺序
class CommentView(LoginRequiredMixin, View):
    pass
 
- CBV装饰器使用

要在CBV视图中使用咱们上面的check_login装饰器,有如下三种方式:

1. 加在CBV视图的get或post方法上

from django.utils.decorators import method_decorator

class HomeView(View):

    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")
    
    @method_decorator(check_login)
    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

2. 加在dispatch方法上,由于CBV中首先执行的就是dispatch方法,因此这么写至关于给get和post方法都加上了登陆校验。

from django.utils.decorators import method_decorator

class HomeView(View):

    @method_decorator(check_login)
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

3. 直接加在视图类上,但method_decorator必须传 name 关键字参数,若是get方法和post方法都须要登陆校验的话就写两个装饰器。

from django.utils.decorators import method_decorator

@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):

    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

cookie与session

详细
# cookie
request.COOKIES.get('username111')
res = render(request,'index.html')
res.set_cookie('key',"value",max_age=10)  # 设置cookie, N秒后失效

#session
x = request.session['xx'] 
request.session['xx'] = 1
request.session.set_expiry(value)
        # * 若是value是个整数,session会在些秒数后失效。
        # * 若是value是个datatime或timedelta,session就会在这个时间后失效。
        # * 若是value是0,用户关闭浏览器session就会失效。
        # * 若是value是None,session会依赖全局session失效策略。

自定义404页面

1.全局urls.py上

# 指明函数路径
handler404 = 'users.views.page_not_found'

2.编写函数

def page_not_found(request):
    from django.shortcuts import render_to_response
    response = render_to_response('404.html',{})
    response.status_code = 404
    return response

3.设置settings.py的DEBUG=false,ALLOW_HOSTS=['*']
4.注意:DEBUG=false以后,django的static静态文件处理会实现。因此,要跟处理media同样,本身编写url路由

url(r'^static/(?P<path>.*)$,serve,{"document_root":STATIC_ROOT})

ORM

详细

 

    # django setting里面的database的初始化命令
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'imoocc',
            'USER': 'imoocc',
            'PASSWORD': 'imoocccom',
            'HOST': '127.0.0.1',
            'PORT': '',
            'OPTIONS': {},
            'init_command': 'SET storage_engine=INNODB,'
                            'SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED, autocommit=1, names "utf8";',
        }
}

 

字段

注意:定义数据库字段时,当心那些冲突的关键字,例如date, datetime这些就不要取了

- obj.get_xxx_display能够显示将原本数据库存储的数字按照choice的定义转换为相应字符串

status = models.IntegerField(choices=[(-1, '未处理'), (0, '待处理'), (1, '已处理')])
  # obj.get_status_choice

- default参数能够传入一个无参函数,当对象建立时便会调用

send_time = models.DateTimeFIeld(default=datetime.now)

-一对一

和ForeignKey相似,可是多了一个不能重复的约束
OnetoOne经常使用于分表
即当一张表的某一些字段查询的比较频繁,另一些字段查询的不是特别频繁,把不怎么经常使用的字段 单独拿出来作成一张表 而后用过一对一关联起来,
好处是既保证数据都完整的保存下来,又能保证大部分的检索更快

- 多对多
注意:ManytoMany 一般设置在正向查询多的那边
两种方式混合定义多对多

# 若是有额外的字段的时候
tags=models.ManyToManyField( to='Tag', through = 'Article2Tag', through_fields = ('article','tag'), ) 

注意:使用此种方式建立多对多表的时候,没有 add() remove() 等方法
即建立时候就须要直接使用第三张表

models.Author2Book.objects.get(author_id=1,book_id=1).delete()

- 文件字段
 文件字段都设置了upload_to参数,upload_to指向的字符串,若存在'/',则表示文件夹,若文件夹不存在则会自动建立
例如:upload_to = 'head'表示放在settings配置uploads文件下的head文件夹里面
也能够指向一个函数,该函数返回一个动态的文件夹路径

# 例如:为每一个用户建立一个指定的文件夹
def upload_to(instance, filename):
    import uuid
    filename = '%s-%s' % (str(uuid.uuid4())[:8], filename)
    return '/'.join(['head', instance.username, filename])

这个参数可让django内部帮助咱们处理文件上传,只要将数据库的文件字段指向文件对象便可

    UserInfo.objects.create(img=request.FILES)
    # 或者:
    username = form.cleaned_data['username']
            headImg = form.cleaned_data['headImg']
            #写入数据库
            user = User()
            user.username = username
            user.headImg = headImg
            user.save()
    
upload_to能够支持%Y%m表示年月,换句话说,他会自动将上传时间传入
image = models.ImageField(upload_to="courses/%Y/%m")

 

 
- content-type
通常而言,表数量能够动态变化,表结构不能变化。注意表结构设计时,表结构不随着其有关系的表的数量增长而改变结构。有时候须要一个定义一个外键关联不一样的表,就显得有点麻烦。
为此,Django提供了ContentType,ContenType django给咱们写好的处理一张表关联混合类型外键的组件。所谓混合关联,指的是这个外键字段可能关联到不一样的表
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType


class Course(models.Model):
    """
    普通课程
    """
    title = models.CharField(max_length=32)
    # 不生成表列,仅用于反向查找
    price_policy_list = GenericRelation("PricePolicy")


# 用于反向查找的特殊字段(一个课程能够对应多个价格策略),因为不能用xxx_set。
# 注意:GenericRelation("PricePolicy")要定义在一对多的‘一’处
# course = models.Course.objects.filter(id=1).first()
# price_policys = course.price_policy_list.all() # 反向查找出课程全部的价格策略


class DegreeCourse(models.Model):
    """
    学位课程
    """
    title = models.CharField(max_length=32)

    price_policy_list = GenericRelation("PricePolicy")


class PricePolicy(models.Model):
    """
    价格策略
    """
    price = models.IntegerField()
    period = models.IntegerField()

    # ContentType是django生成一张管理全部表的表,会自动将全部表的名称存进去进行管理
    content_type = models.ForeignKey(ContentType, verbose_name='关联的表')  #
    object_id = models.IntegerField(verbose_name='关联的表中的数据行的ID')

    # 帮助你快速实现带contenttype关联字段的表对象建立工做,不会数据库中生成列
    content_object = GenericForeignKey('content_type', 'object_id')


# 1. 为学位课“Python全栈”添加一个价格策略:一个月 9.9
"""
obj = DegreeCourse.objects.filter(title='Python全栈').first()
# obj.id
cobj = ContentType.objects.filter(model='course').first()
# cobj.id
PricePolicy.objects.create(price='9.9',period='30',content_type_id=cobj.id,object_id=obj.id)
"""

# 增长GenericForeignKey("conten_type","object_id")以后,会自动识别obj的表的名称,并将表在contenType中的id赋予GenericForeignKey第二个参数
# obj = DegreeCourse.objects.filter(title='Python全栈').first()
# PricePolicy.objects.create(price='9.9',period='30',content_object=obj)

查询

QuerySet的查询时惰性的,即当咱们去执行all,filter,get等时是不会去执行sql的,只有当咱们调用查询结果集的时候才会真正执行sql 

管理器自带api

- get()
返回model对象,若没有符合条件的对象,触发DoesNotExist异常
try:
  aricle = Article.objects.get(pk=id)
except Article.DoesNotExist:
  return render(request,'failure.html',{'reason':'没有找到对应的文章'})

- 跨表
‘__’能够在filter和values或者相关函数中使用,而且是双向的,只要存在关系便可

# 注:两种用法,能够作条件,也能够取值
 BookInfo.object.filter(heroinfo__hgender__exact = False)
#取值
v2 = models.Host.objects.filter(nid__gt=0).values('nid','hostname','b_id','b__caption')
v3 = models.Host.objects.filter(nid__gt=0).values_list('nid','hostname','b_id','b__caption')

- values与values_list

values 能够提取反向的关联对象
注意:反向查询的参数是小写的orm类名,能够看做是关联表id另外一种表示法

a = CategoryInfo.objects.values('articleinfo')
# a = CategoryInfo.objects.filter(articleinfo=1) #能够看做articleinfo__id

values 能够提取外键字段
注意:经过双下划线连接

ArticleInfo.objects.values('category__title') #即便是manytomany也行

- 复杂查询extra()函数

 extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
 select和select_params是一组,用于子查询
 where和params是一组,用于附加过滤条件
tables表示要引入哪些新表(会有笛卡尔积),order_by表示数据条目按本表的哪一个字段排序

# 附加where条件
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
# 先filter筛选出指定博客掉文章,再按指定年月过滤
article_list = modles.Article.objects.filter(blog=blog).extra(where=['strftime("%%Y-%%m",create_time)=%s'],params=[val,])


# 自定义一个“子查询”字段
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) 
# 查询语句对比:
    models.UserInfo.objects.extra(
                        select={'newid':'select count(1) from app01_usertype where id>%s'},
                        select_params=[1,],
                        where = ['age>%s'],
                        params=[18,],
                        order_by=['-age'],
                        tables=['app01_usertype']
                    )
                    """
                    select 
                        app01_userinfo.id,
                        (select count(1) from app01_usertype where id>1) as newid
                    from app01_userinfo,app01_usertype
                    where 
                        app01_userinfo.age > 18
                    order by 
                        app01_userinfo.age desc
                    """
    

- 统计函数 aggregate

没有groupby效果,而且查出来以后就不死queryset对象了,是一个字典。适用于没有groupby的统计

TableInfo.objects.aggregate(c=Count('category_id')) 

- 统计聚合函数annotate
 注意:annotate前面必须有个的value来指定group by后面的字段,默认是以本身的id分组               

from django.db.models import Sum, Count
Comment.objects.values(‘article’).annotate(comment_count=Count(‘article’)).order_by(‘-comment_count)。
# select count(article) as c from Evainfo group by aricle ordey by c
# annotate前面的value指定group by后面的字段,默认是以本身的id分组
# 注:comment_count是自定义查询字典的key

annotate统计操做默认以发起表的id进行group by,配合反向查询很是好用

# 查询不一样种类对应的书籍数目
CategoryInfo.objects.filter(blog_id=1).values(c=Count('articleinfo')).values('title', 'c')
 # 按日期归档
 archive_list = models.Article.objects.filter(user=user).extra(
  select={"archive_ym": "date_format(create_time,'%%Y-%%m')"}).values("archive_ym").annotate(c=Count("nid")).values("archive_ym", "c")
    
# 标签,种类归档
category_list = models.Category.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")
 tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")


- 其余不经常使用

# like查询
filter(sname__contains='') #也适用于整型等其余类型,而且要配合比较运算符使用 等同于select * from table where sname like "黄%" 

#字段排序 
order_by('-price')   #支持多字段排序 order_by('id','-price','title') 

#判断查询集中是否有数据,返回bool值
exists() 

#返回查询集的总条数
count() 

# 去重
distinct()

# 排除指定条件,经常使用做不等于
exclude()
obj = Permission2Action.objects.filter().exclude(p__menu_isnull =True)

#根据字段排序,支持多字段排序 
order_by('id','-price','title') 

# 附:查看执行的sql原生语句
#obj.query 

django提供api(get_object_or_404 与 get_list_or_404)

 通常都会有这么一个需求,若查找不出指定对象,返回404页面
from django.http import Http404

try:
    product = Product.objects.get(pk=1)
except MyModel.DoesNotExist:
    raise Http404

get_object_or_404 会简化这个操做

from django.shortcuts import get_object_or_404 
product = get_object_or_404(Product, pk=1) 

原生查询

- Manger.extra()
经常使用于附加复杂条件,子查询

# 查询语句对比:
    models.UserInfo.objects.extra(
                        select={'newid':'select count(1) from app01_usertype where id>%s'},
                        select_params=[1,],
                        where = ['age>%s'],
                        params=[18,],
                        order_by=['-age'],
                        tables=['app01_usertype']
                    )
                    """
                    select 
                        app01_userinfo.id,
                        (select count(1) from app01_usertype where id>1) as newid
                    from app01_userinfo,app01_usertype
                    where 
                        app01_userinfo.age > 18
                    order by 
                        app01_userinfo.age desc
                    """
     

Manager.raw()
注意,raw查询,查询结果必定要包含主键

Topic.objects.raw('select * form forum_topic')
 
# 异常,由于返回的结果没有包含主键
blog_article.objects.raw(‘select distinct date_format(r‘blog_article’.’date_publist’,’%Y-%m’) from blog_article’)
- DB-API
直接使用了MySQLbd的api,使用原生语句查询 )
from django.db import connection, transaction
        cursor = connection.cursor() 
      # cursor = connections['default'].cursor()
     
        # 数据修改操做——提交要求
        cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
        transaction.commit_unless_managed()
     
        # 数据检索操做,不须要提交
        cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
        row = cursor.fetchone()

连表查询

注意的是:selected_related不支持ManytoMany,而prefetch_related支持全部关系

用于查询的对象

Q对象

 

from django.db.models import Q

Q(Q(account='fuck_man') & Q(status=0) | Q(birthday_lte=ctime))

# 多个Q对象组装复杂条件
q = Q()

q1 = Q()
q1.connector = 'AND'
q1.children.append('account', 'fuck_man')
q1.children.append('status', 0)
q1.children.append('birthday_lte', ctime)

q2 = Q()
q2.connector = 'AND'
q2.children.append('title', 'fuck')

q.add(q1, 'OR')
q.add(q2, 'OR')


- filter 的 != 条件应该使用Q查询

from django.db.models.query import Q
queryset = UserInfo.objects.filter(~Q(name=""))

- 条件的“或”操做- 动态实现Q条件 
好比简单的搜索功能(搜索一个文章的标题或内容或做者名称包含某个关键字):

import operator
from django.db.models.query import Q

# 用户要查询的值
q = request.GET.get('q', '').strip()
# 可能的查询键值列表
key_list = ['title__contains', 'content__contains','author__name__contains']
# 实现Q(title__contains=q)|Q(content__contains=q)|Q(author__name__contains=q)
l = [Q(**{key:q}) for key in key_list]
#reduce至关于c++的accumulate,累加函数, operator.or_(x,y)是二进制的'或'操做, 从列表里将全部Q对象一块儿'或'起来,获得最终的结果
queryset = Entry.objects.filter(reduce(operator.or_, l)) 

F对象

- 字段间比较
例如:查询书id小于价格的书籍

models.Book.objects.filter(id__lt=F("price"))

-  django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操做

models.Book.objects.filter(id__lt=F("price")/2)

- 将对象的一个字段通过修改(加减乘除),赋值给另外一个字段,以此进行更新

models.Book.objects.all().update(price=F("price")+30) 

序列化

一、serializers

from django.core import serializers

ret = models.BookType.objects.all()

data = serializers.serialize("json", ret)

二、json.dumps

    import json

    #ret = models.BookType.objects.all().values('caption')
    ret = models.BookType.objects.all().values_list('caption')

    ret=list(ret)

    result = json.dumps(ret)

因为json.dumps时没法处理datetime日期,因此能够经过自定义处理器来作扩展,如:

import json  
from datetime import date  
from datetime import datetime  
  
class JsonCustomEncoder(json.JSONEncoder):  
   
    def default(self, field):  
    
        if isinstance(field, datetime):  
            return o.strftime('%Y-%m-%d %H:%M:%S')  
        elif isinstance(field, date):  
            return o.strftime('%Y-%m-%d')  
        else:  
            return json.JSONEncoder.default(self, field)  
  
  
# ds = json.dumps(d, cls=JsonCustomEncoder)  

自定义Manager

class MyCustomManager(models.Manager):
    # 链接查询时也生效
    use_for_related_fields = True 

    def get_queryset(self):
        return super(MyCustomManager, self).get_queryset().filter(isDelete=False)


# 配置 use_for_related_fields = True,必定要写在外面。不知道是否是bug,必定要再类的外面的实例化
custom_manager = MyCustomManager()


class BaseModel(models.Model):
    create_time = models.DateTimeField(verbose_name='建立时间', auto_now=True)
    isDelete = models.BooleanField(verbose_name='是否已经删除', default=False)

    default_objects = models.Manager()  # objects = BaseManger会被覆盖
    objects = custom_manager

    class Meta:
        abstract = True

    def delete(self, using=None, keep_parents=False):
        """重写数据库删除方法实现逻辑删除"""
        self.isDelete = True
        self.save()
        print('isDelete = false')

 

模型继承

详细

数据库事务

详细

资源共享问题

数据资源共享问题,须要借助返回值(受影响的行数)

row = Trouble.objects.filter(id=nid,status=1).update(**form.cleaned_data)
if not row:
    return HttpResponse('来晚一步,被人抢走了')
  # 须要考虑多个用户抢同一个资源的问题,update返回的是数字,若返回是0,表示单子被抢走了。
else:
    return redirect('/backend/trouble-list.html')

分库

分库的方法

- 手动路由
# 查询,使用using函数,参数就是要查询的数据库
User.objects.using('user1').all()
# 保存或者更新,使用save的using参数,值就是要使用的数据库
my_object.save(using='user1')
#删除,使用delete的using参数
user_obj.delete(using='user1')

- 自动路由
详细

分库的思路

- 垂直分库
即一个app对应一个数据库,上面自动路由的例子就是一个垂直分库的例子,auth1使用user1数据库,auth2使用user2数据库。固然也可使用手动路由。

- 水平分库
水平分库建议使用手动路由,由于每一个model的分库机制可能都不同,自动路由实现起来有些麻烦会形成性能不高,而手动路由,每一个model根据本身的规则来得到不一样的数据库。


模板

- 注意点
1.  函数在模版里面不能加括号
2. 模版里面统计反向集合数据条数 要用{{ article.commentinfo_set.count }} #{{article.commentinfo_set|length}}不行
3.  默认request对象已经传入模版里面,例如:
  {% if request.session.user|default:'' != '' %}
4.  在模版语言中,变量类型不会由于输出语句,例如{{name}},而改变类型,好比说,int类型仍是int类型,不回变为字符串

- 模板html文件加载顺序
Django会依次到如下目录查找模板文件,若是都找不到,则报错:

1.项目配置的模板目录
2.admin应用的templates模板目录
3.auth应用的templates模板目录
4. 应用自己的templates模板目录

内置过滤器

 

# 注意的是,模版的传入的参数都是为字符串,而且传参时都要带‘:’
    {{courses | length}} #集合的长度
    {{ time_obj|date:"Y-m-d H:i:s"}}  # 对传入转为日期时间格式
    {{ str_obj|truncatewords:"30" }}  # 截取前三十个字符,没法截取中文
    {{str_obj| slice:"30"}} #截取中文
    {{ my_list|first|upper }}  # 把第一个字符大写
    {{ article.summary |default:'' }}   #这个能够保障模版不输出None,由于从数据库取出的值若没有则为None
    {{ name|lower }}  # 把传入值小写
   {{ article.date_publish | date:"Y-m-d" }} 
  {{html_str|safe}} #让html_str给浏览器解析,默认出于安全考虑不解析

  {% autoescape off %} #批量转义   {{ course.detail }}   {% endautoescape %} 注:还有另外一种方式可让浏览器解析, from django.utils.safestring import mark_safe   html_str=mark_safe(html_str)

 

- 循环计数

        {% for row in v1 %}           # 假设3条数据
        {{ forloop.counter}}      # 依次为:一、二、3
        {{ forloop.counter0}}     # 依次为:0、一、2
        {{ forloop.revcounter}}   # 依次为:三、二、1
        {{ forloop.revcounter0}}  # 依次为:二、一、0
        {{ forloop.last }}        # 是否最后一个,是为True,其余False
        {{ forloop.first }}       # 是否第一个
        {{ forloop.parentloop }}  # 嵌套循环,表示上层循环的上面的六个值别是多少。
{% endfor %}    

自定义过滤器

from django import template
register = template.Library()

# 定义一个将日期中月份转换为大写的过滤器,如8转换为八

@register.filter
def month_to_upper(key):
    return ['','','','','','','','','','','十一','十二'][key]


# 使用,模版文件要load一下才能使用,filter是filter.py文件的文件名
{%load filter%}
<div class='month'>{{article.date_publish | month_to_upper}}</div> # article.date_publish会被当作month_to_upper的参数

 

{{ cls_verbose_name |test:app_name }} # 若是过滤器有参数时,必定要肯定没有空格,即过滤器test后面必定要紧贴':'

 

# 配置    
注意的是:必定要在指定App目录下创建templatetags目录,而且要在setting.py设置 
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')]
            ,
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
    
                'libraries': {
                    'my_tags': 'web.templatetags.filter',  #my_tags为自定义键值, web.templatyetags.filter为路径 }
            },
        },

自定义simple_tag

过滤器最多只能带1个额外参数只能带1个额外参数,而自定义simple_tag能够带无数个

# 能够接收多个参数
@register.simple_tag
def create_filter_str(order, q, filter_dict, field, value=''):
    d = copy.copy(filter_dict)
    remove_previous_field(d, field)
    d[field] = value

    order_str = 'order=%s' % order
    q_str = 'q=%s' % q
    filter_str = parse_filter_kwargs(d)
    return '?' + '&'.join([order_str, q_str, filter_str])

#调用
 <a href="{% create_filter_str args.order args.q args.filter data_dict.field %}">所有</a>

#或者{% create_filter_str args.order args.q args.filter data_dict.field as filter_str%}
{{filter_str}}

页面分离与导入

传入参数通过计算渲染装饰器所指定的页面,这个页面能够被其余模板经过调用引入

@register.inclusion_tag('web/sub/common_bar.html')
def get_common_bar(blog):
    tag_statistics = TagInfo.objects.filter(blog=blog). \
        annotate(article_count=Count('articleinfo')).values('id', 'article_count', 'title')

    category_statistics = CategoryInfo.objects.filter(blog=blog). \
        annotate(article_count=Count('articleinfo')).values('id', 'article_count', 'title')

    date_statistics = ArticleInfo.objects.filter(blog_id=1). \
        extra(select={'create_date': 'date_format(time,"%%Y-%%m")'}). \
        values('create_date').annotate(article_count=Count('id')).values('article_count', 'create_date')

    # 粉丝数
    fans_count = Star2Fans.objects.filter(star_user=blog.user).count()
    # 关注数
    star_count = Star2Fans.objects.filter(fans_user=blog.user).count()

    # 模版只会根据这个函数返回的上下文进行渲染
    return locals()

- 使用

{% get_common_bar blog %}

settings.py配置

多个setting文件

详细

静态文件与上传文件配置

# 原理就是url拼配成功后,截取剩下的路径去指定目录下查找
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'static'),
)
MEDIA_URL = '/uploads/'
MEDIA_ROOT = os.path.join(BASE_DIR,'uploads')
# 注意:upload_to 设置的路径是相对于MEDIA_ROOT下建立

# 模板中引用静态文件
<link href ="{% static 'css/base.css' %}" rel="stylesheet"}

#配置能访问upload的文件

from django.views.static import serve
from django.conf import settings

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^uploads/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
]

#配置在模版中使用{{MEDIA_URL}} 表明 ‘/uploads/'
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.template.context_processors.media',
            ],

日志配置 

BASE_LOG_DIR = os.path.join(BASE_DIR, "log")
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'SF': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,根据文件大小自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 3,  # 备份数为3  xx.log --> xx.log.1 --> xx.log.2 --> xx.log.3
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'TF': {
            'level': 'INFO',
            'class': 'logging.handlers.TimedRotatingFileHandler',  # 保存到文件,根据时间自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'backupCount': 3,  # 备份数为3  xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ...
            'when': 'D',  # 天天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/若是没指定时间就默认在午夜
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {
        '': {  # 默认的logger应用以下配置
            'handlers': ['SF', 'console', 'error'],  # 上线以后能够把'console'移除
            'level': 'DEBUG',
            'propagate': True,
        },
        'collect': {  # 名为 'collect'的logger还单独处理
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}
# 用户使用logging.getLogger([name])获取logger实例,若是没有名字,返回logger层级中的根logger(root logger)
# django名字的logger多是默认终端的输出,未验证
logger = logging.getLogger(‘django’)
logger.info(“This is an error msg”)

sql查询语句显示配置

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

django内置组件

Form组件

详细

字段

# 经常使用字段
# CharField
# IntegerField
# DeimalField
# DateField
# DateTimeField
# EmailField
# ChoiceField
# FileField
# RegexField
# GenericIPAddressField
from django import forms
from django.forms import fields 
#forms的单选框
     auto = forms.CharField(
            label='一个月内自动登陆',
            widget=widgets.CheckboxInput(attrs={'id': "auto", 'name': 'auto'}),
            required=False,
        )
例如:手机号码定义
    phone = fields.CharField(
        min_length=11,
        max_length=12,
        error_messages={'required': 'server:手机号码不能为空',
                        'min_length': 'server:号码必须为11位或者12位',
                        'max_length': 'server:号码必须为11位或者12位',
                        },
        validators=[RegexValidator(r'^1[0-9]{10,11}$', '手机号码格式错误'), ],
        label='电话号码',
        widget=widgets.TextInput(attrs={'name': 'phone', 'placeholder': "请输入手机号码", \
                                        'class': 'form-control', 'id': 'phone'}),
    )
例如:根据model的choices字段生产选项组框
    city = forms.ChoiceField(
        choices =models.CityInfo.objects.all().values_list('id','name'),
        # choices = [(1,"东莞"),(2,"广州"),(3,"深圳")],
        label = "城市",
        initial = 1,
        widget = forms.widgets.Select
        )
        
#注意:以上的作法,当数据库更新时,不会实时更新。这时候须要自定义form的构造方法 class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].choices = ((1, '上海'), (2, '北京'),) self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')

验证顺序

先验证了min_length那些参数字段以后再执行clean_xx钩子函数,最后执行总体验证clean()

  • 不管以前定义的min_length那些验证成功与否,都会执行clean_xx函数,因此在里面操做时应当使用request.POST,而不是self.clean_data。(由于当验证失败时会取不到值)
  • 若是验证成功须要return self.clean_data['xx']回去,否则clean_data里面那操做过的那个字段会是None
#单一字段的验证,clean_xxx()  
def clean_verify(self):
        user_verify_code = self.request.POST.get('verify', '').upper()
        verify_code = self.request.session.get('verify_code').upper()
        if user_verify_code != verify_code:
            raise ValidationError(message='验证码错误', code='invalid')


# 总体验证例子
def clean(self):
    value_dict = self.clean_data
    v1 = value_dict.get('username')
    v2 = value_dict.get('user_id')
    if v1!='root' and v2 !=1:
    raise ValidationError('总体错误信息')
return self.cleaned_data

后续增长错误信息

# form.errors['username'] = ['用户名不存在或者密码错误', ]
form.add_error(field, error)

只读字段处理

#form.fields['title'].widget.attr['readonly']=True
#form.fields['title'].widget.attr.update(['disabled':'true'})
form.fields['title'].disabled = True

使用接口

form = UserForm(request.POST,request.FILE)
    if form.is_valid():
        form.cleaned_data #验证过的数据,字典形式,键是name属性
    else:
    # 错误数据,字典形式,键是name属性,不过它的__str__默认返回errors.as_ul(),也就是字符串。html的ul代码
        form.errors 

注意:使用字典参数做为自定义form的data参数,传入的的值会被本身定义的规则所验证。若不想验证,则传入参数为initial

form = TroubleMaker(initial={'title':'标题1','detail':'XXXX'}) 

模板中使用

errors内部格式

{'username:['错误信息1','错误信息2'],'password':['错误信息1,]}

取错误列表里面的第一个

{{ form.errors.username.0}}

在模板中显示Django’__all__’形式的错误,即总体错误信息

{{ form.non_field_errors }}

验证器

Django将验证器定义为一个可调用的对象,它接受一个值,并在不符合一些规则时抛出ValidationError异常。 
换句话说,知足以上定义的都是验证器,那咱们就能够欢快地来自定义验证器吧!

例如,这个验证器只容许偶数:

from django.core.exceptions import ValidationError

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError('%s is not an even number' % value)

你能够经过字段的validators参数将它添加到模型字段中:

from django.db import models

class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

因为值在验证器运行以前会转化为Python,你能够在表单上使用相同的验证器:

from django import forms

class MyForm(forms.Form):
    even_field = forms.IntegerField(validators=[validate_even])

你也可使用带有 __call__()方法的类,来实现更复杂或可配置的验证器。例如,RegexValidator就用了这种技巧。

RegexValidator
class RegexValidator([regex=None, message=None, code=None, _inversematch=None, flags=0])[source]

regex
用于搜索提供的value的正则表达式,或者是预编译的正则表达式对象。一般在找不到匹配时抛出带有 message 和code的 ValidationError异常。这一标准行为能够经过设置inverse_match 为True来反转,这种状况下,若是找到匹配则抛出 ValidationError异常。一般它会匹配任何字符串(包括空字符串)。
message
验证失败时ValidationError所使用的错误信息。默认为"Enter a valid value"code
验证失败时ValidationError所使用的错误代码。默认为"invalid"inverse_match
New in Django 1.7.regex的匹配模式。默认为False。
flags
New in Django 1.7.编译正则表达式字符串regex时所用的标识。若是regex是预编译的正则表达式,而且覆写了flags,会产生TypeError异常。默认为 0

 


modelform

class UserCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='密码', widget=forms.PasswordInput(attrs={'class': 'form-control'}), \
                                max_length=32, min_length=8)
    password2 = forms.CharField(label='重复密码', widget=forms.PasswordInput(attrs={'class': 'form-control'}))

    class Meta:
        model = UserInfo
        fields = ('email', 'username')
        labels = {
            'username': '用户名',
            'email': '邮箱',
        }
        error_messages = {
            '__all__': {},

            'email': {
                'required': '邮箱不能为空',
                'invalid': '邮箱格式错误',
            },
            'username': {
                'required': '用户名不能为空',
                'invalid': '用户名格式错误..',
            }
        }

        widgets = {
            'username': widgets.TextInput(attrs={'class': 'form-control'}),
            'email': widgets.TextInput(attrs={'class': 'form-control'})
        }

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("两次密码输入不一致")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

视图中使用

 # instance参数表示和data参数共同融合
form = form_cls(instance=obj, data=request.POST)
if form.is_valid():
   obj = form.save() #存储对象,返回model对象

模板中使用

{%for field in form %}
{{ field.label }} # verbose_name 
{{ field.name}} # modelinfo原始定义的字段名,例如price
{{ field }} # input组件
{{ field.errors.0 }} #错误信息
{%endfor%}

动态modelform

class FormClassFactory(object):
    @classmethod
    def from_admin(cls, admin):
        class Meta:
            model = admin.cls_info
            fields = '__all__'
            exclude = admin.readonly_fields

        def __new__(cls, *args, **kwargs):
            for field_name in cls.base_fields:
                field_obj = cls.base_fields[field_name]
                field_obj.widget.attrs.update({'class': 'form-control'})   # 传入组件属性
            return ModelForm.__new__(cls)

        form_cls = type('DynamicModelForm', (ModelForm,), {'Meta': Meta, '__new__': __new__})
        return form_cls

缓存

from django.core.cache import cache
is_had_read = cache.get(article_id)
if not is_had_read:
     article.read_num += 1
     article.save()
     cache.set(article_id, 1, 60 * 60 * 24 * 30) #一个月过时

中间件

详细
 -中间件执行流程。

a. - 请求到来走wsgi,wsgi负责按照HTTP协议的规则解析请求成请求头
b. 先执行全部安装中间件的process_request
c. - 绕回去匹配url
d. 执行全部中间件的process_view #这里和process_request的区别是,这里已经找到了要执行的视图函数
e. -执行视图函数返回
f. 执行全部中间件的process_response
g. 若报错,执行全部的process_excepiton
h. 若用render返回,则执行全部的process_render方法

异常

 

 

脚本运行环境配置

import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_name.settings")  # project_name 改为你本身的项目名称
django.setup()

APP

自定义AppConfig

方法1
# 第一步在app的apps.py中
class AccountConfig(AppConfig):
    name = 'account'
    verbose_name = u'用户信息'  # 更改app的在admin中的显示 

# 第二步,在该app的__init__.py中
default_app_config = 'account.apps.AccountConfig'
方法2

直接在settings的INSTALLED_APPS中后面加上.apps.UsersConfig
例如:'users.apps.AppConfig'

- ready方法

app一被加载就自动调用的方法
 from django.apps import AppConfig
    class App01Config(AppConfig):
        name = 'app01'
    
        def ready(self):
            from django.utils.module_loading import autodiscover_modules
            # 在项目全部的目录下查找并调用aaaa.py文件
            autodiscover_modules("aaaa")

app设计

通常咱们经过“名词+动词分析”进行数据库设计,但进行app设计时,models可能会有循环导入问题。
通常解决的方法是,定义一个动做appoperation,operation能够根据角色的动做去设计
),导入其余名词appuserorganizationcourse

- 整理app文件夹
即将全部app存放于一个文件夹中

1.新建包apps
2.移动app至apps,注意不要勾选search on xx这个选项,要的不是from apps.course.models import * 这种效果
3.改掉编辑器的错误提示,右键-->Mark Directory as--->Sources Root,这样会使得编辑器查找模块时,也会将apps做为跟文件夹开始搜索
4.settings.py文件增长
sys.path.insert(0,os.path.join(BASE_DIR,'apps')
5.注意:当要重新migration时会报错,由于django默认会将外键的路径加上apps这个文件夹,致使错误。因此搜索出来删掉(ctrl+H全局搜索)


信号

Django中提供了“信号调度”,用于在框架执行操做时解耦。通俗来说,就是一些动做发生的时候,信号容许特定的发送者去提醒一些接受者。

一、Django内置信号

    Model signals
        pre_init                    # django的modal执行其构造方法前,自动触发
        post_init                   # django的modal执行其构造方法后,自动触发
        pre_save                    # django的modal对象保存前,自动触发
        post_save                   # django的modal对象保存后,自动触发
        pre_delete                  # django的modal对象删除前,自动触发
        post_delete                 # django的modal对象删除后,自动触发
        m2m_changed                 # django的modal中使用m2m字段操做第三张表(add,remove,clear)先后,自动触发
        class_prepared              # 程序启动时,检测已注册的app中modal类,对于每个类,自动触发
    Management signals
        pre_migrate                 # 执行migrate命令前,自动触发
        post_migrate                # 执行migrate命令后,自动触发
    Request/response signals
        request_started             # 请求到来前,自动触发
        request_finished            # 请求结束后,自动触发
        got_request_exception       # 请求异常后,自动触发
    Test signals
        setting_changed             # 使用test测试修改配置文件时,自动触发
        template_rendered           # 使用test测试渲染模板时,自动触发
    Database Wrappers
        connection_created          # 建立数据库链接时,自动触发

对于Django内置的信号,仅需注册指定信号,当程序执行相应操做时,自动触发注册函数:

    from django.core.signals import request_finished
    from django.core.signals import request_started
    from django.core.signals import got_request_exception

    from django.db.models.signals import class_prepared
    from django.db.models.signals import pre_init, post_init
    from django.db.models.signals import pre_save, post_save
    from django.db.models.signals import pre_delete, post_delete
    from django.db.models.signals import m2m_changed
    from django.db.models.signals import pre_migrate, post_migrate

    from django.test.signals import setting_changed
    from django.test.signals import template_rendered

    from django.db.backends.signals import connection_created


    def callback(sender, **kwargs):
        print("xxoo_callback")
        print(sender,kwargs)

    xxoo.connect(callback)
    # xxoo指上述导入的内容
View Code
from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")
View Code

二、自定义信号

a. 定义信号

            import django.dispatch
            pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

b. 注册信号

        def callback(sender, **kwargs):
            print("callback")
            print(sender,kwargs)

        pizza_done.connect(callback)

c. 触发信号

        from 路径 import pizza_done

        pizza_done.send(sender='seven',toppings=123, size=456)

因为内置信号的触发者已经集成到Django中,因此其会自动调用,而对于自定义信号则须要开发者在任意位置触发。


时区

 

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai' 
USE_TZ = False # 防止django存储数据库时采用utc时间,而use_tz为false则表示使用本地时间

- 注意
1. 若是直接用标准库的datetime,可能会出现如下警告,由于你使用的是一个不带时区的时间

 这个问题的隐患是,假如你的django若是用了时区,而标准库是当前系统时区,那么就极可能出现时间查
  解决办法:安装django的指定的时区来
  from django.utils.timezone import datetime

闪现

详细

 csrf

跨站请求伪造,重点在跨站二字
钓鱼网站的页面和正经网站的页面对浏览器来讲有什么区别?
- 钓鱼网站的页面是由 钓鱼网站的服务端给你返回的
- 正经网站的网页是由 正经网站的服务端给你返回的
钓鱼网站的目的是:真正的目的是你要交互的合法网站所存储的cookie,这是你的身份凭证。
手段是:引导你生成不合法的数据,或者直接借由本身双手提交一个已经设计好的非法表单数据。

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

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

auth组件

详细
 
自定义authenticate,实现用户能够经过邮箱或者用户名登陆
    # settings.py文件中配置,指明路径
        AUTHENTICATION_BACKENDS = (
            'users.views.CustomBackend',
        )
        
    from django.contrib.auth.backends import ModelBackend
    from django.db.models import Q
    class CustomBackend(ModelBackend):
        def authenticate(self, username=None, password=None, **kwargs):
            try:
                user = UserProfile.objects.get(Q(username=username)|Q(email=username))
                if user.check_password(password):
                    return user
            except Exception as e:
                return None

admin

详细

邮件

# Email settings smtp/pop3
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = "smtp.163.com"  # smtp加上指定的域名
EMAIL_PORT = 25
EMAIL_HOST_USER = "jdecree@163.com"
EMAIL_HOST_PASSWORD = "xxxxx"  # 受权码
# EMAIL_USE_TLS = True
EMAIL_FROM = "jdecree@163.com"
class EmailHelper(object):
    def __init__(self):
        self.email_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        self.email_from = settings.EMAIL_FROM  # 发送人

    def send_captcha(self, receive_email):
        """
        发送验证码
        """
        captcha_helper = CaptchaHelper()
        captcha = captcha_helper.create_and_save_captcha(email=receive_email)
        # 邮件发送参数
        email_title = '修仔科技'
        email_body = '%s' % (captcha,)
        status = send_mail(email_title, email_body, self.email_from, [self.email_from, receive_email])
        return status

    def set_user_activate_link(self):
        """
        发送注册后的激活邮件
        :return: 
        """
        pass

    def send_password_reset_link(self):
        """
        发送密码重置连接
        :return: 
        """

    def send_email_reset_link(self):
        """
        发送邮箱重置连接
        :return: 
        """
# 附上邮箱model


class EmailVerifyRecord(models.Model):
    code = models.CharField(max_length=20, verbose_name=u"验证码")
    email = models.EmailField(max_length=50, verbose_name=u"邮箱")
    send_type = models.CharField(verbose_name=u"验证码类型",
                                 choices=(("register", u"注册"), ("forget", u"找回密码"), ("update_email", u"修改邮箱")),
                                 max_length=30)
    send_time = models.DateTimeField(verbose_name=u"发送时间", default=datetime.now)

    class Meta:
        verbose_name = u"邮箱验证码"
        verbose_name_plural = verbose_name

# 点击邮箱连接激活
# 经过随机字符串找出邮箱,再经过邮箱找出用户

class AciveUserView(View):
    def get(self, request, active_code):
        all_records = EmailVerifyRecord.objects.filter(code=active_code)
        if all_records:
            for record in all_records:
                email = record.email
                user = UserProfile.objects.get(email=email)
                user.is_active = True
                user.save()
        else:
            return render(request, "active_fail.html")
        return render(request, "login.html")

第三方库

二级域名组件(django-hosts)

详细

cors跨域处理包(django-cors-headers)

安装
pip install django-cors-headers
添加应用
在settings里面配置

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)
中间层设置
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]
添加白名单
# CORS
CORS_ORIGIN_WHITELIST = (
    '127.0.0.1:8080',
    'localhost:8080', #凡是出如今白名单中的域名,均可以访问后端接口
)
CORS_ALLOW_CREDENTIALS = True  # 指明在跨域访问中,后端是否支持对cookie的操做 

调试工具(django-pdb)

1. pip install django-pdb
2. INSTALLED_APPS = (
  # 放在第一位置
  'django_pdb',)
3.MIDDLEWARE = [
  # 放在第一位置
  'django_pdb.middleware.PdbMiddleware',]
4.settings.DEBUG = True
5. 再pdb中输入c,到视图中执行

- pdb命令


命令 解释
break 或 b 设置断点
continue 或 c 继续执行程序
list 或 l 查看当前行的代码段
step 或 s 进入函数
return 或 r 执行代码直到从当前函数返回
exit 或 q 停止并退出
next 或 n 执行下一行
pp 打印变量的值
help 帮助

redis(django-redis)

g. Redis缓存(依赖:pip3 install django-redis)

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100,'decode_responses': True}
            # "PASSWORD": "密码",
        }
    }
}
View Code
from django_redis import get_redis_connection
conn = get_redis_connection("default")
视图中连接并操做

二、应用

a. 全站使用

   使用中间件,通过一系列的认证等操做,若是内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户以前,判断缓存中是否已经存在,若是不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

    MIDDLEWARE = [
        'django.middleware.cache.UpdateCacheMiddleware',
        # 其余中间件...
        'django.middleware.cache.FetchFromCacheMiddleware',
    ]

    CACHE_MIDDLEWARE_ALIAS = ""
    CACHE_MIDDLEWARE_SECONDS = ""
    CACHE_MIDDLEWARE_KEY_PREFIX = ""
View Code

b. 单独视图缓存

    方式一:
        from django.views.decorators.cache import cache_page

        @cache_page(60 * 15)
        def my_view(request):
            ...

    方式二:
        from django.views.decorators.cache import cache_page

        urlpatterns = [
            url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
        ]
View Code

c、局部视图使用

    a. 引入TemplateTag

        {% load cache %}

    b. 使用缓存

        {% cache 5000 缓存key %}
            缓存内容
        {% endcache %}
View Code

- redis API
详细

celery(django-celery)

详细
# celery
# pip install redis==2.10.6
djcelery.setup_loader()
BROKER_BACKEND = 'redis'
BROKER_URL = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'
# 设置路径让celery能找到tasks文件
CELERY_IMPORTS = ['web_chat.tasks', ]
# 设置时区
CELERY_TIMEZONE = TIME_ZONE
# 防止死锁
CELERYD_FORCE_EXECV = True
# 并发数
CELERYD_CONCURRENCY = 4
# 容许重试
CELERYD_ACKS_LATE = True
# 一个worker最多执行任务数
CELERYD_MAX_TASKS_PER_CHILD = 100
# 单个任务最大运行时间
CELERY_TASK_TIME_LIMIT = 60

 

验证码(django-simple-captcha)

详细

第三方分页组件(django-pure-pagination)

详细

富文本编辑器(DjangoUeditor)

详细

xadmin

详细1详细2
后台管理三大要素(权限管理,少前端样式,快速开发)
1. pip install xadmin
2. pip uninstall xadmin
3. gitclone https://github.com/J-Decree/mxonline_resources
4. 新建一个extra_apps存放,跟整理app的步骤同样
5. INSTALL_APP加上xadmin
6. urls.py中(和django-admin统一文件)
  urlpatterns=[url(r'^xadmin/',xadmin.site.urls),]

注意:
- model显示

class Meta:
    verbose_name = u"邮箱验证码" 
    verbose_name_plural = verbose_name  #这个是复数形式的显示,默认会加个s

 

- 将UserProfile这个表移动到本身定义应用account下管理

1.UserAdmin在xadmin的auth.py下,里面有个UserAdmin
2.仍是再plugin/auth.py下替换User,否则一些修改密码的连接会报错

3.仍是auth.py的最下面,修改url,默认是users/user,改密码时会找不到

 

- 权限说明

针对于表,django为每一个表都生成增删改查4个权限,拥有的权限差异会致使操做的不一样甚至可能连表都看不到

- modeladmin方法:保存对象时,能够所作的操做

 

 - 怎么写插件
需求1:以xadmin遇到UeditorField字段能够显示富文本

1.在xadmin/plugin文件夹中新增ueditor.py(点我)
2.修改xadmin/plugin/__init__.py中的PLUGIN列表增长ueditor,注意要跟文件名同样
3.adminx的modeladmin添加style_fields = {'detail':'ueditor'} 指明哪一个字段生效,detail是字段名

需求2: excel导入插件

1.在xadmin/plugin文件夹中新增excel.py(点我)
2.在xadmin/templates增长上面的蓝色标记的excel.html(点我)
3.在须要按钮的modeladmin下定义import_excel = True,并增长post方法,导入按钮的方法的逻辑就是在这里编写

def post(self, request, *args, **kwargs):
    if 'excel' in request.FILES:
        pass
    return super(CourseAdmin, self).post(request, args, kwargs)

4.xadmin/__init__.py的PLUGIN列表中增长excel

支付宝

详细

易错点:
1. 运行报错,注意看有没有安装pycryptodope:pip install pycryptodope
2. 支付使用的是商户的私钥+支付宝的公钥,而不是你我的的公钥(注意:支付宝公钥是根据公钥自动生成的)
3. 支付宝支付成功后,首先页面作了跳转,支付宝会往你指定的url里发了一个post请求
4. 支付宝要求处理post请求的视图函数返回HttpResponse('success') #若是没有返回success,一天之内一直给你发相同post请求

  

开发环境

pip

# 批量安装
pip install -r requirements.txt  #注,若是有报错,先注释掉报错模块,而后单独安装
# 生成requirement.txt文件
pip freeze > requirements.txt
# 查找指定模块
pip frzeeze | grep 'pexpect'

虚拟环境

详细

pycharm断点调试

详细  

Django推荐项目布局

详细

生产环境

uWSGI

#启动
uwsgi --ini uwsgi.ini
#查看
ps ajx|grep uwsgi
#中止
uwsgi --stop uwsgi.pid 

nginx

详细

- nginx经常使用命令

启动
# 启动nginx,能够浏览器访问一下本地地址 127.0.0.1:8080
nginx -c /path/to/nginx.conf
 # 测试nginx配置文件是否正确
nginx -t -c /path/to/nginx.conf

 查看
 lsof -i:80
 ps aux | grep nginx


更新
# 平滑重启nginx kill -HUP 主进程号 #修改配置后从新加载生效
nginx -s reload 
#从新打开日志文件
nginx -s reopen  

关闭 # 快速中止nginx
nginx -s stop  
# 完整有序的中止nginx
nginx -s quit

kill关闭
ps -ef | grep nginx
# 从容中止Nginx
kill -QUIT 主进程号     
# 快速中止Nginx
kill -TERM 主进程号     
# 强制中止Nginx
pkill -9   主进程号             

- 配置

首先,简单地复习一下配置语法
若配置语法忘了,点击这里,简洁明了
基础的场景配置,点击这里

一些指令(坑)
index指令
alias和root指令对比
error_page指令(例子说明,自定义404页面)
location指令(顺序)

部署流程

一.代码准备

第一步,setting.py更改变量

DEBUG = False 
ALLOWED_HOSTS = ['*']

第二步,指明静态文件路径
注:  1.DEBUG为False时,django的开发环境的自动静态文件代理失效,要使用STATIC_ROOT来指明你的静态文件在哪里,就像MEDIA_ROOT同样。
  2.STATIC_ROOT还跟django的python manage.py collectstatic命令联系,指定的文件夹会指定为用于静态文件收集的文件夹
  3.若使用uwsgi和nginx均可以代理静态文件,其实指不指明没什么影响

#setting.py:
STATIC_ROOT = os.path.join(BASE_DIR, "static")
#ErrorReport/urls.py:
url(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}),

第三步,收集静态文件
1.STATIC_ROOT = os.path.join(BASE_DIR, "static1")
2.python manage.py collectstatic
3.删除原有的static,将static1重命名为static

二.环境准备

第一步,pip freeze > plist.txt
第二步,将项目代码和plist.txt上传
第三步,虚拟环境准备

pip install virtualenv
pip install virtualenvwrapper
mkvirtualenv -p python3.6 env36

三.配置uWSGI

第一步,安装uwsgi

pip install uwsgi

第二步,命令行启动测试

uwsgi --http 127.0.0.1:8080 --file ErrorReport/wsgi.py --static-map=/static=static

注意:若出现异常,cant not import django 解决办法
  第一步:先重装
  pip install django==1.11.12
  第二步:若django已经存在,则在wsgi.py中
  sys.path.append('/Users/yi/.virtualenvs/yi3/lib/python3.6/site-packages')  

第三步:新建配置文件wsgi.ini进行配置(若浏览器访问127.0.0.1:8080 正常,则进行第三步)
 项目目录下建立script文件夹,里面新建wsgi.ini

[uwsgi]
# 项目目录
chdir = /Users/yi/PycharmProjects/djangos/ErrorReport/
# 指定虚拟环境。防止虚拟virtualenv环境的包,而在真实环境没有安装相关包的致使错误
home = /Users/yi/.virtualenvs/yi3
# 指定项目的application
module = ErrorReport.wsgi:application
# 指定sock的文件路径
# socket = /Users/yi/PycharmProjects/djangos/ErrorReport/script/uwsgi.sock
# 启用主进程
master = true
# 进程个数
process=4
pidfile = /Users/yi/PycharmProjects/djangos/ErrorReport/script/uwsgi.pid
# 指定HTTP IP端口,如果有上游服务器,则这里为socket
 http = 127.0.0.1:9000
# socket = 127.0.0.1:9000

# 使用静态文件挂载点
#“映射”指定请求前缀到你的文件系统上的物理目录,--static-map mountpoint=path
# 例如:--static-map /images=/var/www/img
 static-map = /static=static
# 启动uwsgi的用户名和用户组
uid = root
gid = root
# 自动移除unix Socket和pid文件当服务中止的时候
vacuum = true
# 序列化接受的内容,若是可能的话
thunder-lock = true
# 启用线程
enable-threads = true
# 设置自中断时间
harakiri = 30
# 设置缓冲
post-buffering = 4096
# 设置日志目录
daemonize = /Users/yi/PycharmProjects/djangos/ErrorReport/script/uwsgi.log
#每一个进程能够接受的最大请求
max-requests = 5000
# buffer-size,代表收到的最大请求size,默认是4096,能够将其改为65535
buffer-size = 65535

第四步:更改uwsgi为上游服务器(若浏览器测试127.0.0.1:9000访问成功)
注: 当单独测试uwsgi,http = 127.0.0.1:9000。当nginx作前端服务器时,要改为socket = 127.0.0.1:9000
uwsgi.ini中:
http = 127.0.0.1:9000 => socket = 127.0.0.1:9000

四.配置nginx

第一步,安装
这里我用的是macOS电脑,主要用于开发测试,因此使用brew install nginx安装便可。
若到真正的生产环境上,对nginx的模块定制有更加严格的要求,则须要编译安装

macOS中使用brew安装后,nginx的配置文件所在目录为

/usr/local/etc/nginx

第二步,更改目录
macOS本来的nginx.conf很是不科学,咱们按照ubuntu的目录结构来改造
在 /usr/local/etc/nginx中建立如下文件夹

conf.d # 通用通常配置
sites-available # 存放当前的server配置, 在这里修改
sites-enabled # 激活并使用的server配置(从sites_available的文件建立快捷方式到sites-enabled)

第三步,更改nginx.conf文件
macOS的nginx.conf配置成ubuntu的默认nginx.conf。

worker_processes auto;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;
    gzip_disable "msie6";

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include conf.d/*.conf;
    include sites-enabled/*.conf;
}

#mail {
#    # See sample authentication script at:
#    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#    # auth_http localhost/auth.php;
#    # pop3_capabilities "TOP" "USER";
#    # imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#    server {
#        listen     localhost:110;
#        protocol   pop3;
#        proxy      on;
#    }
# 
#    server {
#        listen     localhost:143;
#        protocol   imap;
#        proxy      on;
#    }
#}

第四步,在sites-available下新增要部署项目的server配置
  例如:新建ErrorReport.conf

server {
        listen       80;
        server_name  myblog;
        client_max_body_size 75m;
        charset utf-8;
        
        # 静态文件代理
        location /static{
            alias /Users/yi/PycharmProjects/djangos/ErrorReport/static;
        }
        
        # 上传的静态文件代理
        location /uploads{
            alias /Users/yi/PycharmProjects/djangos/ErrorReport/uploads;
        }
       
        # uwsgi转发配置
        location /{
            include  /usr/local/etc/nginx/uwsgi_params;
            uwsgi_pass  127.0.0.1:9000;         
        }

        #location / {
         #   root /usr/local/etc/nginx/html;
          #  index index.html;
        #}
    }

第五步,在sites-enabled新建一个软链接指向上一步新建的配置文件

从/usr/local/etc/nginx能够看到,http块下用include导入conf.d,sites-enabled的配置文件

 ln -s sites-available/ErrorReport.conf sites-enabled/ErrorReport.conf 

第六步,启动nginx测试

sudo nginx -t 
sudo nginx
sudo nginx -s stop
sudo nginx -s reload

若访问127.0.0.1成功,则部署成功
注:也能够按照你所配置第server_name来测试访问,可是要更改本地第host文件

经过supervisor管理进程

上面咱们已经用到了uwsgi,后面可能还会用到redis、celery,都须要开启守护进程,其中celery自身还不支持守护进程。那么如何管理这么多进程呢,这时候能够考虑下supervisor

pip install supervisor

配置

咱们能够配置redis,celery,uwsgi进去,好比向下面同样

[program:redis]
;指定运行目录
directory=%(here)s/
;执行命令(redis-server redis配置文件路径)
command=redis-server /etc/redis.conf

;启动设置
numprocs=1          ;进程数
autostart=true      ;当supervisor启动时,程序将会自动启动
autorestart=true    ;自动重启

;中止信号
stopsignal=INT


[program:celery.worker.default]
;指定运行目录
directory=%(here)s/
;运行目录下执行命令
command=celery -A DjangoProject worker --loglevel info --logfile log/celery_worker.log -Q default -n %%h-%(program_name)s-%(process_num)02d
process_name=%(process_num)02d

;启动设置 
numprocs=2          ;进程数
autostart=true      ;当supervisor启动时,程序将会自动启动 
autorestart=true    ;自动重启
 
;中止信号,默认TERM 
;中断:INT (相似于Ctrl+C)(kill -INT pid),退出后会将写文件或日志(推荐) 
;终止:TERM (kill -TERM pid) 
;挂起:HUP (kill -HUP pid),注意与Ctrl+Z/kill -stop pid不一样 
;从容中止:QUIT (kill -QUIT pid) 
stopsignal=INT

[program:uwsgi]
;指定运行目录
directory=%(here)s/
;运行目录下执行命令
command=uwsgi -i conf/uwsgi/uwsgi9090.ini

;启动设置
numprocs=1          ;进程数
autostart=true      ;当supervisor启动时,程序将会自动启动
autorestart=true    ;自动重启

;中止信号,默认TERM
;中断:INT (相似于Ctrl+C)(kill -INT pid),退出后会将写文件或日志(推荐)
;终止:TERM (kill -TERM pid)
;挂起:HUP (kill -HUP pid),注意与Ctrl+Z/kill -stop pid不一样
;从容中止:QUIT (kill -QUIT pid)
stopsignal=INT

使用

启动supervisor输入以下命令,使用具体的配置文件执行:

supervisord -c supervisord.conf

关闭supervisord须要经过supervisor的控制器:

supervisorctl -c supervisord.conf shutdown

重启supervisord也是经过supervisor的控制器:

supervisorctl -c supervisord.conf reload

一些特殊的变量

%(here)s   配置文件所在路径
(program_name)s   program的名字
%(process_num)02d  多进程时的进程号

注意:1.command中若是含有%,须要进行转义%%
   2.多进程时若是不指定process_name会遇到以下错误

Error: Format string 'celery -A INTProject worker --loglevel info --logfile log/celery_worker.log -Q diff_task,caller_task -n %h' for 'program:celery.worker.mac.command' is badly formatted: incomplete format in section 'program:celery.worker.mac' (file: 'supervisord.conf')
相关文章
相关标签/搜索