django知识点七

Django扩展

一、验证码

1、简介

在常规的Form表单使用中,验证码是常用的组件,用于更好的保障请求的合法性,防止无效访问,恶意访问,暴力破解等攻击

在服务器端,生成一个随机的code:“aecd1” ,将code画到一张图片中,最终将图片写出给client。

注意:依赖第三方包:pillow

pip install pillow

2、验证码使用过程

2.1 导入第三方库

​ [图片上传失败...(image-4edab9-1556782941244)]

将文件拷贝到自己的项目app中,2个py文件,1个data文件夹,保证三个处于项目中的统一个目录中。

2.2 生成验证码
import random,string
from captcha.image import ImageCaptcha   #从image.py中导入ImageCaptchar类

def getcaptcha(request):   # 127.0.0.1:8000/getcaptcha   
    #为验证码设置字体 获取当前目录下的xxx目录下的segoesc.ttf文件
    image = ImageCaptcha(fonts=[os.path.abspath("xxx/segoesc.ttf")])
    #随机码
    #大小写英文字母+数字,随机抽取5位作为验证码 ['x','x','x','x','x']
    code = random.sample(string.ascii_lowercase+string.ascii_uppercase+string.digits,5) 
    #将验证码存入session,以备后续验证
    random_code =  "".join(code)
    request.session['code']=random_code 
    #将生成的随机字符拼接成字符串,作为验证码图片中的文本
    
    data = image.generate(random_code )
    #写出验证图片 给客户端
    return HttpResponse(data,"image/png")
2.3 在html中使用验证码
<input type="text" name="identifycode">
<img src="{% url 'identify_demo:getcaptcha' %}" id="image_code" width="80px" height="30px" align="center">   
<a href="javascript:void(0)" onclick="change()">换一张</a>

<script>
    function change() {
        var url = "{% url 'identify_demo:getcaptcha' %}?"+new Date().getTime()
        $('#image_`    code').attr('src',url)    //刷新验证码
    }
</script>
2.4 验证是否正确
def registlogic(request):
    code = request.session.get('code')
    if code.lower() == request.POST.get('identifycode').lower():
        return HttpResponse("成功")
    else:
        return HttpResponse("失败")

二、文件上传

1、简介

Django的模型类(django.db.models.Model)提供了两个字段FileField和ImageField用于上传文件和图片。而ImageField继承之FileField,class ImageField(FileField):

使用Django的ImageField需要提前安装pillow模块,pip install pillow即可。

2、使用步骤

使用FileField或者ImageField字段的步骤:

  1. 在settings文件中,配置MEDIA_ROOT,作为你上传文件在服务器中的基本路径。
  2. 添加FileField或者ImageField字段到模型中,定义好upload_to参数,文件最终会放在MEDIA_ROOT目录的“upload_to”子目录中。
  3. 所有真正被保存在数据库中的,只是指向你上传文件路径的字符串而已。可以通过url属性,在Django的模板中方便的访问这些文件。例如,假设有一个ImageField字段,名叫mug_shot,那么在Django模板的HTML文件中,可以使用{{ user.mug_shot.url }}来获取该文件。
  4. 可以通过name和size属性,获取文件的名称和大小信息。

3、实例:为用户上传头像

3.1 设置文件保存目录
# settings.py中:

MEDIA_ROOT = os.path.join(BASE_DIR,"media")   # 项目目录下的media目录 需要在项目目录下创建media目录
3.2 定义Model
class User(models.Model):
    name = models.CharField(max_length=20)
    #文件将存于 MEDIA_ROOT目录下的pics目录下
    pic = models.ImageField(upload_to="pics")

注意:定义好Model,记得生成移植文件并执行

3.3 定义form表单
<form action="{% url 'uploadfile_demo:uplogic' %}" method="post"  enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" name="name"><br>
    头像:<input type="file" name="source">
    <input type="submit" value="提交">
</form>
3.4 定义view函数
def uplogic(request):
    try:
        name = request.POST.get('name')
        file = request.FILES.get('source')
        file.name = generateUUID(file.name)    # 调用自定义的generateUUID生成唯一文件名

        user = User.objects.create(name=name, pic=file)
        return HttpResponse("上传成功")
    except:
        return HttpResponse("上传失败")
import uuid,os
def generateUUID(filename):    # 创建唯一的文件名
    id = str(uuid.uuid4())
    extend = os.path.splitext(filename)[1]
    return id+extend

注意,此时数据库中存储的路径是相对于MEDIA_ROOT的路径

所以可以将MEDIA_ROOT设置为静态资源根目录,可便于后续的头像回显

3.5 回显图片
  1. 设置静态资源根目录

    MEDIA_ROOT = os.path.join(BASE_DIR,'media')
    
    STATIC_URL = '/static/'
    STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), MEDIA_ROOT ]
    
  2. view函数

    def query(request):
        users = User.objects.all()
        return render(request,'uploadfile_demo/detail.html',{'users':users})
    
  3. 模板中使用

    <img src="{% static user.pic.url %}" width="50px">
    

补充:Python原生文件操作

def test_upload2(r):
    import os,uuid
    b = r.FILES.get("source")  # 获取上传的文件
    unique_name = str(uuid.uuid4())  # 唯一文件名
    ext = os.path.splitext(b.name)[1]  # 文件后缀
    name = unique_name + ext  # 拼接文件名
    with open(file=os.path.join(os.path.abspath("media/just_test/"),name),mode="wb") as output:
        for chunk in b.chunks(): #如果超过2.5M则分块,分为64Kb的块依次读取
                                #chunks是django.core.files.base.File中的方法
            output.write(chunk)
    return HttpResponse("ok")

三、分页显示

1、简介

Django自身提供了一些类来实现管理分页,数据被分在不同的页面中,并带有“上一页/下一页”标签。这个类叫做Pagination,其定义位于 django/core/paginator.py 中。

2、Paginator分页器

2.1 初始化方法

pagtor = Paginator(User.objects.all(),per_page=3) # 构造分页器对象

# 源码
class Paginator(object):

    def __init__(self, object_list, per_page, orphans=0,
                 allow_empty_first_page=True):
        self.object_list = object_list
        self.per_page = int(per_page)
        self.orphans = int(orphans)
        self.allow_empty_first_page = allow_empty_first_page
        self._num_pages = self._count = None
2.2 属性
  • Paginator.count:所有页面对象总数,即统计object_list中item数目。
  • Paginator.num_pages:页面总数。
  • paginator.page_range:页面范围,从1开始,例如[1,2,3,4]。
pagtor = Paginator(User.objects.all(),per_page=3)   # 构造分页器对象
print(pagtor.count)                                 # 获取item总数
print(pagtor.num_pages)                             # 页面数
print(pagtor.page_range)                            # 页面范围
2.3 page方法(重点)

Paginator.page(number):根据参数number返回一个Page对象,表示第number页。

page = Paginator(User.objects.all(),per_page=3).page(1)  # 获取第一页

3、Page对象

class Page(collections.Sequence):

    def __init__(self, object_list, number, paginator):
        self.object_list = object_list
        self.number = number                
        self.paginator = paginator    # 分页器对象
3.1 方法
  • Page.has_next() 如果有下一页,则返回True
  • Page.has_previous() 如果有上一页,返回 True
  • Page.has_other_pages() 如果有上一页或下一页,返回True
  • [图片上传失败...(image-e6d3a6-1556782941244)] 返回下一页的页码。如果下一页不存在,抛出InvlidPage`异常。
  • Page.previous_page_number() 返回上一页的页码。如果上一页不存在,抛出InvalidPage异常。
  • Page.start_index() 返回当前页上的第一个对象,相对于分页列表的所有对象的序号,从1开始。比如,将五个对象的列表分为每页两个对象,第二页的start_index()会返回3
  • Page.end_index() 返回当前页上的最后一个对象,相对于分页列表的所有对象的序号,从1开始。 比如,将五个对象的列表分为每页两个对象,第二页的end_index() 会返回 4
3.2 属性
  • Page.object_list当前页上所有对象的列表。
  • Page.number当前页的序号,从1开始。
  • Page.paginator相关的Paginator对象。

4、实例

4.1 简单分页
# http://localhost:8000/page/index/?num=2
def index(request):
    number = request.GET.get('num')
    pagtor = Paginator(User.objects.all(),per_page=4)
    page = pagtor.page(number)  # 某一页的page对象
    return render(request,'page_demo/index.html',{'page':page})  
{# 显示某一页所有数据#}

{% for user in page.object_list %}
  {{ user.id }} --  {{ user.name }} -- {{ user.password }} <br>
{% endfor %}
4.2 输出序号
{# 显示某一页所有数据#}

{% for user in page.object_list %}
  {{ user.id }} --  {{ user.name }} -- {{ user.password }} <br>
{% endfor %}

{# 输出所有页号 #}
{% for page_num in page.paginator.page_range %}
    <a href="/page/index/?num={{ page_num }}">{{ page_num }}</a>
{% endfor %}
4.3 显示上一页/下一页
{% for user in page.object_list %}
  {{ user.id }} --  {{ user.name }} -- {{ user.password }} <br>
{% endfor %}

{% if page.has_previous %}             {# 是否有上一页 #}
    <a href="/page/index/?num={{ page.previous_page_number }}">上一页</a>
{% endif %}

{% for num in page.paginator.page_range %}     {# 是否有下一页 #}
    <a href="/page/index/?num={{ num }}">{{ num }}</a>
{% endfor %}

{% if page.has_next %}
    <a href="/page/index/?num={{ page.next_page_number }}">下一页</a>
{% endif %}
4.4 页码样式
<style>
    .a{
        width:20px;
        height: 20px;
        border:1px solid #e1e2e3;
        cursor:pointer;
        display: inline-block;
        text-align: center;
        line-height: 20px;
    }
    .b{
        border:0;
        width:20px;
        height: 20px;
        cursor:pointer;
        display: inline-block;
        text-align: center;
        line-height: 20px;
    }
    a{
        text-decoration:none;
    }
</style>
#显示某一页面数据
{% for user in page.object_list %}
  {{ user.id }} --  {{ user.name }} -- {{ user.password }} <br>
{% endfor %}

#显示上一页
{% if page.has_previous %}
    <a href="/page/index/?num={{ page.previous_page_number }}">上一页</a>
{% endif %}

{% for num in page.paginator.page_range %}
    <a href="/page/index/?num={{ num }}">
        {% if num == page.number %}            {# 当前选中页的样式 #}
            <span class="a">{{ num }}</span>
        {% else %}
            <span class="b">{{ num }}</span>
        {% endif %}
    </a>
{% endfor %}

 #显示下一页
{% if page.has_next %}
    <a href="/page/index/?num={{ page.next_page_number }}">下一页</a>
{% endif %}

四、中间件

1、简介

中间件(Middleware)用于在http请求到达视图函数之前视图函数return之后,django会根据自己的规则在合适的时机执行中间件中相应的方法。

常用作view中冗余功能的抽取,如每个页面(或某些页面)在访问前强制登录

2、定义中间件

class MyMiddleware(MiddlewareMixin):          # 自定义的中间件
    def __init__(self,get_response):#初始化
        super().__init__(get_response)
        print("init1")
    #view处理请求前执行
    def process_request(self,request):
        
        print("request:",request)
    #在process_request之后View之前执行
    def process_view(self,request, view_func, view_args, view_kwargs):
        print("view:",request,view_func,view_args,view_kwargs)
    #view执行之后,响应之前执行
    def process_response(self,request,response):
        print("response:",request,response)
        return response #必须返回response
    #如果View中抛出了异常
    def process_exception(self,request,ex):#View中出现异常时执行
        print("exception:",request,ex)

中间件中常用的两个过程:process_request , process_response

3、激活中间件

每当有请求发生时,所有中间件都会执行自己的生命周期。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    ....
    'middleware115.middlewares.MyMiddleware',# 注册自定义中间件,尽量放在最后注册
]

4、强制登录实例

class MyMiddleAware2(MiddlewareMixin):
    #如果验证成功,则什么一个不用做,否则返回HttpResponse即可响应请求(中断)
    def process_request(self,request):#强制登录判断
        if "login" not in request.path:#路径中如果没有"login"
            print("登录验证")
            session = request.session #获取session
            if session.get("login"): #判断是否有登录的标记
                print("已登录")
            else:
                print("未登录")
                return render(request,"login.html") #未登录则,跳转登录页面
        else:
            print("正在登录") #如果路径中"login"则是登录动作本身
    def process_response(self,request,response):
        print("response:",request,response)
        return response #持续返回响应

五、CSRF

1、简介

CSRF(Cross-site request forgery)跨站请求伪造,是一种常见的网络攻击手段。

2、基本使用

Django为我们提供了防范CSRF攻击的机制。

默认情况下,使用django-admin startproject xxx命令创建工程时,CSRF防御机制就已经开启了。如果没有开启,请在MIDDLEWARE设置中添加'django.middleware.csrf.CsrfViewMiddleware'。

对于GET请求,一般来说没有这个问题,CSRF通常是针对POST方法的!

在含有POST表单的模板中,需要在其<form>表单元素内部添加csrf_token标签,如下所示:

<form action="" method="post">
    {% csrf_token %}
    ....
</form>

3、实现原理

  1. 在发送请求时,通过CsrfViewMiddleware 在服务器端生成一个随机的token(令牌),在render渲染模板时存放于form单的隐藏域和cookie中
  2. 当再次form发送请求时,会携带隐藏域令牌和cookie中的令牌,此时CsrfViewMiddleware 会先于View执行,去判断两块令牌是否一致,验明正身。

六、Admin管理后台

1、简介

Admin站点是Django有别于其它Web框架最重要的一点

admin通过读取你的模型数据,快速构造出一个可以对实际数据进行管理的Web站点

常用于开发测试,简单管理等场合,适用于部门内部为工作方便的场合,但不建议在生产环境中使用。

2、admin使用步骤

2.1 安装django.contrib.admin

确保,在settins.py中的INSTALLED_APPS 中安装了admin,它会扫描每个app的admin.py,进而支持后台管理。

INSTALLED_APPS = [
    'django.contrib.admin',            # 安装admin
    'django.contrib.auth',             # 下面四个为admin的依赖模块
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    ...
 ]
2.2 注册Model

在App的admin.py中注册需要管理的Model类

admin.site.register(User)
2.3 创建管理员账户
 python manage.py createsuperuser
2.4 启动服务
python manage.py runserver
2.5 访问后台管理页面
http://localhost:8000/admin

在URLconf中默认已经配置好admin的访问路径

urlpatterns = [
    path('admin/', admin.site.urls),
    ...
]

3、定制admin

3.1 User(Models)管理页面

[图片上传失败...(image-4c824b-1556782941244)]

修改后台列表显示User Object,改为更为直观的信息:

class User(models.Model):
    name = models.CharField(max_length=30)
    imagepath = models.ImageField(upload_to='Mr_lee')

    def __str__(self):          # 重写__str__方法
        return self.name

[图片上传失败...(image-cb5636-1556782941244)]

3.2 操作选项位置
  • 定制前:

[图片上传失败...(image-b25ac2-1556782941244)]

补充:注册admin的另一种方式 装饰器

@admin.register(User)                # 等价于 admin.site.register(User)
class UserAdmin(admin.ModelAdmin):
    #定制操作选项位置
    actions_on_top = True
    actions_on_bottom = True
  • 定制后:

[图片上传失败...(image-32b0e2-1556782941244)]

3.3 定制列名
  • 定制前:

[图片上传失败...(image-4270cb-1556782941244)]

  • 定制后:

[图片上传失败...(image-e8e5d1-1556782941244)]

  • 代码:
class UserAdmin(admin.ModelAdmin):
    list_display = ['name','imagepath'] # 定制显示的列名
  • 定制列名的别名(可以是中文)
class User(models.Model):
    name = models.CharField(max_length=30)
    imagepath = models.ImageField(upload_to='Mr_lee')

    def other_name(self):
        return self.name
    other_name.short_description = '姓名'  # 改列名
    other_name.admin_order_field = 'name'  # 保证改列名后依然可以排序

    def other_path(self):
        return self.imagepath
    other_path.short_description = '图片路径'
    other_name.admin_order_field = 'imagepath'
# -- admin.py -- #
class UserAdmin(admin.ModelAdmin):

    list_display = ['other_name','other_path'] # 定制显示的列名的别名 注意使用的是other_name

[图片上传失败...(image-4e46af-1556782941244)]

3.4 定制Model名
  • 定制前:

[图片上传失败...(image-d5c0f6-1556782941244)]

  • 定制后:

[图片上传失败...(image-129607-1556782941244)]

  • 代码:
class User(models.Model):
    ...

    class Meta:
        verbose_name = "用户"          # 修改Model名
        verbose_name_plural = "用户"   # 复数Model名 默认为users
3.5 定制过滤查询栏
  • 定制前:

[图片上传失败...(image-b19dcc-1556782941244)]

  • 定制后:

[图片上传失败...(image-3b973b-1556782941244)]

  • 代码:
class UserAdmin(admin.ModelAdmin):

    list_display = ['other_name','other_path']  
    list_filter = ['name','imagepath']         # 定制过滤查询栏
3.6 定制分页
  • 定制前:

[图片上传失败...(image-5827df-1556782941244)]

  • 定制后:

[图片上传失败...(image-5e5b74-1556782941244)]

3.7 定制搜索框
  • 定制前:

[图片上传失败...(image-daceb7-1556782941244)]

  • 定制后:

[图片上传失败...(image-cf9c46-1556782941244)]

  • 代码:
class UserAdmin(admin.ModelAdmin):
    search_fields = ['name'] #可以以哪列作为条件搜索,会增加搜索框
3.8 定制可操作的列
  • 定制前:

[图片上传失败...(image-289646-1556782941244)]

  • 定制后:

[图片上传失败...(image-1270d9-1556782941244)]

  • 代码:
class UserAdmin(admin.ModelAdmin):
    fields = [('name', 'imagepath')] #增加和修改页显示的列,且name和imagepath列在一行显示
3.9 定制分组显示
  • 定制前:

[图片上传失败...(image-a03ff7-1556782941244)]

  • 定制后:

[图片上传失败...(image-9aa8e2-1556782941244)]

  • 代码:
class UserAdmin(admin.ModelAdmin):

    ...

    fieldsets = (
        ('基本信息',{'fields':['name']}),
        ('头像',{'fields':['imagepath']})
    )
3.10 关联显示

[图片上传失败...(image-184a36-1556782941244)]

class Order(models.Model):
    title = models.CharField(max_length=30)
    price = models.FloatField()
    user = models.ForeignKey(to=User,on_delete=models.CASCADE)
class OrderInline(StackedInline):
    model = Order

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    inlines = [
        OrderInline
    ]

七、Form(了解)

可以快速定制前端的form表单,并提供参数接收,数据验证。功能看似很全面

但实则使用价值不大,因为有众多前端框架,可以更专业的定制前端逻辑,而django如此定制有些越俎代庖,而且会影响前端框架的建设,无法和前端人员或前端技术对接(如:easyUI,bootstrap等)

所以,此章节请自学,了解即可!

class UserForm(forms.ModelForm):
    class Meta:
        model = User2
        #fields=["name","age","gender"]
        exclude=["salary2"]
def aa(request):
    print("goto a template")
    render(request,"xx.html",{"form":UserForm()})
xx.html
<form action = "xxx" method="post">
    {% csrf_token %}
    <table>
        {{ form }}
        <tr>
        <td colspan="2" align="center"><input type="submit" value="Submit" /></td>
        </tr>
    </table>
</form>
def test3(request):
    form = UserForm(request.POST)
    if form.is_valid():
        print("表单数据合法")
        #{'name': 'zzz', 'age': 18, 'gender': True, 'birth': datetime.date(2018, 11, 12),...}
        data = form.cleaned_data
        print(data)
    return render(...)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容