基于django的auth 的 认证.

简介

我们需要一个用户表,用来实现登录注册功能,虽然django已经自带来用户登录注册功能,也有相应的表,但是不符合中国人习惯,需要我们对user模型进行自定义。实现自定义User模型最简单的方式就是继承AbstractBaseUser,AbstractBaseUser实现了User的核心功能,我们只需加一些额外的字段进行补充即可。

  1. 下列

    User模型原有的字段有:
    
    username
    password
    last_login
    is_superuser
    first_name
    last_name
    email
    is_staff
    is_active
    date_joined
    
    
    # 我们在自己的app/models.py下
    from django.contrib.auth.models import AbstractUser
    
    新建类 继承AbstractUser,依据业务需求,添加额外的字段
    
    # 在settings.py中指定自定义认证模型
    	AUTH_USER_MODEL = 'users.UserProfile'
    # AUTH_USER_MODEL = "app名.models里面对应的模型表名"
    # 用我们自己的表,实现所有的auth的功能
    

使用

1.在命令行创建超级用户,(不可手动插入,因为密码是加密的)
2.简单使用auth认证	
	from django.contrib import auth
        user = auth.authenticate(request,username=name,password=pwd)
        # 会自动到数据库
        # 类似 : user=models.User.objects.filter(username=name,password=pwd).first()
        if user:
            # 还需要给当前成功登陆的用户保存登陆状态,之前是通过cookie或者session
		 auth.login(request,user)  # 登陆,其实就把用户信息放到session中,跑一下验证session表
          # 等价于request.session['name']=name     
            return redirect('/home/')  # 登陆成功
3.注意,亮点在于 	:
    # 只要登陆成功执行了auth.login(request,user)
	# 之后在其他任意的视图函数中都通过request.user获取当前登陆用户对象

    # 当没有执行auth.login,request.user打印出来的是匿名用户。将session表数据删除即可演示改效果
   # 如何判断request.user用户是否通过auth.login登陆呢?request.user.is_auth
   # 为何执行auth.login之后,其他视图函数中就可以通过request.user拿到当前登陆对象呢?想想django的中间件中有没有一个叫Auth啥的中间件,它干了件什么事,能不能推导一下?取出session去表里面查相应的数据,然后放到request.user中,点进去看一下这个中间件确实如此
4.注销
	auth.logout(request)
	# 等价于删除session数据request.session.flush()
    

高级使用
5.装饰器校验是否登陆及跳转
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/',redirect_field_name='old')  # 没登陆会跳转到login页面,并且后面会拼接上你上一次想访问的页面路径/login/?next=/test/,可以通过参数修改next键名
def my_view(request):
    pass

6.如果我所有的视图函数都需要装饰并跳转到login页面,那么我需要写好多份
	# 可以在配置文件中指定auth校验登陆不合法统一跳转到某个路径
	LOGIN_URL = '/login/'  # 既可以局步配置,也可以全局配置
7.给user表插入数据
	from django.contrib.auth.models import User,我们自己的use表
        def register(request):
          User.objects.create()  # 不能用这个,因为密码是明文
          User.objects.createuser()  # 创建普通用户
          User.objects.createsuperuser()  # 创建超级用户
8.校验密码,修改密码
	request.user.check_password(pwd)  # 为什么不直接获取查,因为前端用户输入的是明文数据库密文
    request.user.set_password(pwd)
    request.user.save()  # 修改密码
9.auth.authenticate,自定义校验,不使用django帮助我们实现的校验
class UserBackend(ModelBackend):
    """
    自定义用户验证: setting中对应配置
    AUTHENTICATION_BACKENDS = (
        'users.views.UserBackend',
        )
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        print('走认证 了没 ,了没了没了没了没了没了没了没了没了没了没')
        try:
            user = User.objects.get(Q(username=username) | Q(mobile=username))
            if user.check_password(password):
                return user
        except Exception as e:
            return None
        
10.
"""
自定义认证系统默认使用的数据表之后,我们就可以像使用默认的auth_user表那样使用我们的UserInfo表了。
库里面也没有auth_user表了,原来auth表的操作方法,现在全步用自定义的表均可实现
"""

1.users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
    GENDER_CHOICES = (
        ('M', '男'),
        ('F', '女'),
    )
    nickname = models.CharField(blank=True, null=True, max_length=20)
    avatar = models.FileField(upload_to='avatar/')
    mobile = models.CharField(blank=True, null=True, max_length=13)
    gender = models.CharField(max_length=1, choices=GENDER_CHOICES,blank=True, null=True)
    subscribe = models.BooleanField(default=False)

    class Meta:
        db_table = "v_user"
        
 # 在settings.py下
AUTH_USER_MODEL = 'users.UserProfile'
# AUTH_USER_MODEL = "app名.models里面对应的模型表名"

2.users.forms.py

from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm
# 通过 auth.forms的form类继承
class UserLoginForm(AuthenticationForm):
    username = forms.CharField(min_length=4,max_length=30,
                               error_messages={
                                   'min_length': '用户名不少于4个字符',
                                   'max_length': '用户名不能多于30个字符',
                                   'required': '用户名不能为空',
                               },
                               widget=forms.TextInput(attrs={'placeholder': '请输入用户名'}))
    password = forms.CharField(min_length=8,max_length=30,
                               error_messages={
                                   'min_length': '密码不少于8个字符',
                                   'max_length': '密码不能多于30个字符',
                                   'required': '密码不能为空',
                               },
                               widget=forms.PasswordInput(attrs={'placeholder': '请输入密码'}))

    class Meta:
        model = User
        fields = ['username', 'password']

    error_messages = {'invalid_login': '用户名或密码错误', }
    
    
    
class SignUpForm(UserCreationForm):
    username = forms.CharField(min_length=4, max_length=30,
                               error_messages={
                                   'min_length': '用户名不少于4个字符',
                                   'max_length': '用户名不能多于30个字符',
                                   'required': '用户名不能为空',
                               },
                               widget=forms.TextInput(attrs={'placeholder': '请输入用户名'}))
    password1 = forms.CharField(min_length=8, max_length=30,
                                error_messages={
                                    'min_length': '密码不少于8个字符',
                                    'max_length': '密码不能多于30个字符',
                                    'required': '密码不能为空',
                                },
                                widget=forms.PasswordInput(attrs={'placeholder': '请输入密码'}))
    password2 = forms.CharField(min_length=8, max_length=30,
                                error_messages={
                                    'min_length': '密码不少于8个字符',
                                    'max_length': '密码不能多于30个字符',
                                    'required': '密码不能为空',
                                },
                                widget=forms.PasswordInput(attrs={'placeholder': '请确认密码'}))

    class Meta:
        model = User
        fields = ('username', 'password1', 'password2',)

    error_messages = {'password_mismatch': '两次密码不一致', }
    
    
    
class ChangePwdForm(PasswordChangeForm):
    old_password = forms.CharField(error_messages={'required': '不能为空',},
        widget=forms.PasswordInput(attrs={'placeholder': '请输入旧密码'})
    )
    new_password1 = forms.CharField(error_messages={'required': '不能为空',},
        widget=forms.PasswordInput(attrs={'placeholder': '请输入新密码'})
    )
    new_password2 = forms.CharField(error_messages={'required': '不能为空',},
        widget=forms.PasswordInput(attrs={'placeholder': '请输入确认密码'})
    )

3.users/views.py

from django.contrib import auth  
def login(request):
    if request.method == 'POST':
        form = UserLoginForm(request=request, data=request.POST)
		if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = auth.authenticate(username=username, password=password)
            if user is not None:
                auth.login(request, user)
                # 在session中存下了用户的信息.
                #  # 只要登陆成功执行了auth.login(request,user)
			   # 之后在其他任意的视图函数中都通过request.user获取当前登陆用户对象
            	return redirect('home')
            
            
def register(request);
	    if request.method == 'POST':
        	form = SignUpForm(data=request.POST)
            if form.is_valid():
                form.save() # 存入了数据库,注册成功了,
                username = form.cleaned_data.get('username')
                raw_password1 = form.cleaned_data.get('password1')
                user = authenticate(username=username, password=raw_password1)
                auth_login(request, user)
                return redirect('home')
            
def logout(request):
    auth.logout(request)
    return redirect('home')


def change_password(request):
	if request.method == 'POST':
        form = ChangePwdForm(request.user, request.POST)
        if form.is_valid():
            user = form.save(commit=False) # 先不提交到数据库
            if not user.is_staff and not user.is_superuser:
                user.save()
                update_session_auth_hash(request, user)  # 更新session 非常重要!
                messages.success(request, '修改成功')
                return redirect('users:change_password')
            else:
                messages.warning(request, '无权修改管理员密码')
                return redirect('users:change_password')
       	else:
            print(form.errors)

django通用视图使用

1.generic.ListView

帮助我们实现了get请求方式
class CollectListView(generic.ListView):
    template_name = 'users/collect_videos.html' # 选择返回的模板
    context_object_name = 'video_list' # 从数据库查到的数据的键,在模板中for循环使用
    paginate_by = 10 				  # 加上这个就会有分页,page_size
    page_kwarg = 'page'  # url携带的查询参数
    paginator_class  = dasdas 自定义自己的分页器,不然就会用自带的分页器
    需要实现self.get_queryset()方法,返回值为video_list对应的值
    需要实现self.self.get_context_data()方法,返回值为video_list对应的值

2.generic.UpdateView

实现get post 方法
    model = User
    form_class = SubscribeForm
    template_name = 'users/subscribe.html'
    当post成功后,调用
        def get_success_url(self):
        # messages.success(self.request, "保存成功")
        	return reverse('users:subscribe', kwargs={'pk': self.request.user.pk})

django model相关函数

from django.contrib.auth import get_user_model
# 返回当前用户的所在的model类
from django.shortcuts import get_object_or_404
user = get_object_or_404(User, pk=self.kwargs.get('pk')) # 获取不到就报错

django sorl-thumbnail 缩略图模块

1.pip install sorl-thumbnail
2.在工程同名文件的settings.py文件注册sorl-thumbnail应用
	"sorl.thumbnail"
3.在模板中load thumbnail
	{% load thumbnail %}
    ## model.字段名
    {% thumbnail user.avatar "200x200" crop="center" as im %}
                    <img class="ui avatar image" src="{{ im.url }}">
                    {% empty %}
                    <img class="ui avatar image" src="{%static'img/img_default_avatar.png' %}">
                    {% endthumbnail %}
4.注意要数据库迁移因为thumbnail需要在后台建表
5.if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
6.
    settings里面设置
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
7 在settings.py中设置THUMBNAIL_DEBUG = True,然后查看发生错误的原因.
8.models.py中使用sorl.thumbnail.ImageField.
9.forms.py中使用sorl.thumbnail.fields.ImageFormField

django提示消息messages

from django.contrib import messages
在代码中:
    在messages.api.py下找到接口
    messages.error(request, '亲,搜索内容不能为空哦。')
    messages.warning(request, '亲,搜索内容不能为空哦。')
    messages.success(request, '亲,搜索内容不能为空哦。')
    messages.info(request, '亲,搜索内容不能为空哦。')
    messages.debug(request, '亲,搜索内容不能为空哦。')
# 视图中
{% if messages %}
    <ul class="messages">
        {% for message in messages %}
            <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
                <div class="m_title">
                    {{ message.tags }}
                    <a href="" id="a_tuichu">
                        <i class="iconfont icon-fork"></i>
                    </a>
                </div>
                <div id="m_box">
                    {{ message }}
                </div>
                <a href="" id="a_sure">
                    <button id="button_sure">
                        确认
                    </button>
                </a>
            </li>
        {% endfor %}
    </ul>
{% endif %}

需求

在Web应用程序中,有时候需要在处理表单或其他类型的用户输入后向用户显示一次性通知消息(也称为“flash消息”),例如:用户注册成功、订单提交完成等信息。

为此,Django为匿名和经过身份验证的用户提供对基于cookie和session存储数据的消息传递框架。该消息框架允许将消息临时存储在一个请求中并检索它们以便在后续请求(通常是下一个请求)中显示。每个消息被标记以特定的level确定其优先级(例如,info, warning,或error)。

这个消息框架的数据传递方式基本就是我上一篇Django 2.1.7 redirect重定向数据传输的问题 中使用session的参数传递方式。

Django官网文档

https://docs.djangoproject.com/zh-hans/2.1/ref/contrib/messages/

在项目settings启用内置messages消息框架

  • django.contrib.messages需要安装在应用INSTALLED_APPS中,如下:
INSTALLED_APPS = (
    ...
    'django.contrib.messages', # django 内置的消息传递应用
)
  • 中间件MIDDLEWARE需要包含 'django.contrib.sessions.middleware.SessionMiddleware''django.contrib.messages.middleware.MessageMiddleware'
MIDDLEWARE = (
    ....
    'django.contrib.sessions.middleware.SessionMiddleware',
    ....
    'django.contrib.messages.middleware.MessageMiddleware',
    ...
)

默认存储后端依赖于session。这就是为什么SessionMiddleware 必须启用在MessageMiddleware的前面。

  • TEMPLATES设置包含'django.contrib.messages.context_processors.messages'
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 在django.contrib.messages 中提供三个内建的存储类:

class storage.session.SessionStorage
这个类存储所有的消息于请求的会话中。因此,它要求启用Django 的contrib.sessions 应用。

class storage.cookie.CookieStorage
这个类存储消息数据于与Cookie 中(已经用一个安全的哈希进行签名以防止篡改)以在请求之间传递消息。如果Cookie 数据的大小将超过2048 字节,将丢弃旧的消息。

class storage.fallback.FallbackStorage
这个类首先使用CookieStorage,如果消息塞不进一个Cookie 中则使用SessionStorage。 它同样要求启用Django 的contrib.sessions 应用。

这个行为避免每次都写会话。在通常情况下,它提供的性能应该是最好的。

FallbackStorage 是默认的存储类。如果它不适合你的需要,你可以通过设置 MESSAGE_STORAGE 为它的完整导入路径选择另外一个存储类,例如:

# settings.py下
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'

在视图和模板中使用消息

add_message(request,level,message,extra_tags ='',fail_silently = False)

添加消息

from django.contrib import messages
messages.add_message(request, messages.INFO, 'Hello world.')

还可以使用以下的快捷方法来添加具有常用标记的消息(通常表示为消息的HTML类):

messages.debug(request, '%s SQL statements were executed.' % count)
messages.info(request, 'Three credits remain in your account.')
messages.success(request, 'Profile details updated.')
messages.warning(request, 'Your account expires in three days.')
messages.error(request, 'Document deleted.')

显示消息

get_messages

在模板中,读取消息示例如下:

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

如果在视图中使用上下文处理器,则应使用 RequestContext。确保messages可用于模板上下文。

即使您知道只有一条消息,您仍应迭代messages序列,否则将不会为下一个请求清除消息存储。

上下文处理器还提供了一个DEFAULT_MESSAGE_LEVELS变量,它是消息级别名称到其数值的映射:

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
        {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %}
        {{ message }}
    </li>
    {% endfor %}
</ul>
{% endif %}

在模板之外,可以使用 get_messages()来获取消息:

from django.contrib.messages import get_messages

storage = get_messages(request)
for message in storage:
    do_something_with_the_message(message)

例如,您可以获取所有消息以在JSONResponseMixin中返回它们 而不是 TemplateResponseMixin

get_messages() 将返回已配置的存储后端的实例。

消息使用示例

在视图A发出一个messages消息记录,然后在视图B显示一次消息内容。

1)编写视图A,添加两个消息

from django.contrib import messages

def send_alert_msg(request):
    # 添加消息
    messages.add_message(request, messages.SUCCESS, 'successful msg!!')
    messages.add_message(request, messages.ERROR, 'error msg!!')
    return redirect('assetinfo:show_msg')

2)编写视图B,转发显示消息内容

def show_msg(request):
    return render(request,'alert_msg/show_msg.html')

模板内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% if messages %}
    <ul class="messages">
        {% for message in messages %}
        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
        {% endfor %}
    </ul>
    {% endif %}

    <h4>通过tags判断messages类型</h4>

    {% if messages %}
    <ul class="messages">
        {% for message in messages %}
            {% if message.tags == "success" %}
                <li class="{{ message.tags }}">{{ message }}</li>
            {% elif message.tags == "error" %}
                <li class="{{ message.tags }}">{{ message }}</li>
            {% endif %}
        {% endfor %}
    </ul>
    {% endif %}

</body>
</html>

3)在配置视图url

urlpatterns = [
    # ex:/assetinfo/send_alert_msg
    path('send_alert_msg', views.send_alert_msg, name='send_alert_msg'),
    # ex:/assetinfo/show_msg
    path('show_msg', views.show_msg, name='show_msg'),
]

4) 测试访问发送消息视图

在浏览器访问http://127.0.0.1:8000/assetinfo/send_alert_msg则自动重定向并显示消息如下:

img

5) 直接再次访问接收消息视图

img

因为没有发送消息,所以消息接收为空。说明消息是一次性的,但是如果多次只发送不显示处理消息数据,则会在cookie或者session存储后端中堆积起来,在最后一次处理消息数据的时候一次性显示出来。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

环境:

python 3.6.4

django2.0.6

使用重定向redirect(‘url name') 如果不需要传数据的话那这样就OK了 如果要传数据的话 我琢磨了半天 还是决定用session来传输

所以 就这么干:

request.session['key_name] = value

request.session['msg'] = u'用户未登录'

然后在模板中使用:

{{ request.session.username }}

{# 输出username保存的值 #} {# {{ request.session['username' }} 以及{{ request.session.get('username') }} 和{% request.session.get('username') %} 都是错误的写法 #}}

就可以了。嗯 搞定。

补充知识:****在django中,redirect如何传递message。

众所周知,在django中,默认的message,只能在同一个request中传递。

但如果在请求过程中,使用了redirect跳转,那么,这个一次性的message就会失败,

无法在前端给用户提示。

https://stackoverflow.com/questions/29673537/django-redirect-with-context/29673951#29673951

网上提供的思路,有如下两种:

一,使用message框架中的storeage存储实现。我觉得如果消息使用得频繁,且消息比较长时使用。

二,使用session来实现,这个实现更简单,但不可太频繁使用。

下面,就使用第二种来试试吧。

1,在有redirect的view中,加入session。

# 跨request传递message,使用session。
self.request.session['create_app'] = name
return redirect(reverse_lazy('app:list', args=()))

2,在需要获取message的view中,加入消息。

# 获取创建组件成功的session提示,同request传递message。
create_app = self.request.session.pop('create_app', False)
if create_app:
     messages.info(self.request, '{}创建成功,请编辑它的配置!'.format(create_app))

3,在前端网页中,显示此message。

{% for message in messages %}
<div class="alert alert-success alert-dismissible fade in" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">×</span>
    </button>
    <strong>组件创建提示!</strong> {{ message }}.
</div>
{% endfor %}

Django Admin组件

1.使用富文本编辑框kindeditor

富文本编辑器:kindeditor

第一步:从官网上下载点击打开链接

第二步:将解压过的文件夹放到js目录下面 ,删除多余文件asp,asp.net,jsp,php.在django中这些都没用

​ 并且在kindeditor目录下添加一个config.js文件

image-20201018115939070

在config.js 中填写配置信息(其他的设置请参考文档)

KindEditor.ready(function(K) {
        K.create('textarea[name=detail]',{  
            width:800,
            height:200,
            uploadJson: '/admin/upload/kindeditor', /*请求url*/
        });
});
//其中'textarea[name=text]'、'#id_text', 在admin后台通过F12,找到要添加富文本编辑器元素的name或id,可以用'textarea[name=text]'或'#id_text'方式
//注释: 这里的#id_content,或是name=content,是通过登录admin后,右击对应控件,选择审查元素获得的。

第三步:在admin.py中对应的modeladmin下面添加

class Media:
     js = (
            '/static/js/kindeditor-4.1.10/kindeditor.js',
            '/static/js/kindeditor-4.1.10/lang/zh_CN.js',
            '/static/js/kindeditor-4.1.10/config.js'
      	)

保存之后在对应的admin添加页面下就会出现富文本编辑器了保存之后在对应的admin添加页面下就会出现富文本编辑器了

但现在上传图片功能还不能使用,因为并没有处理文件上传按钮。

第四步:我们上次图片仍然会报错,因为我们并没有处理文件上传按钮。

1.在config.js加入:
    uploadJson: '/admin/upload/kindeditor',
这里/admin/upload/kindeditor是python的路由。

2.在url.py中有配置url(r'^admin/upload/(?P<dir_name>[^/]+)$', 
                 upload.upload_image, name='upload_image'upload_image,
                 name='upload_image'),
	dir_name是文件的存储路径。
3.upload_image是自定义的保存图片的函数
# upload.py

from django.http import HttpResponse
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
import os
import uuid
import json
import datetime as dt

@csrf_exempt
def upload_image(request, dir_name):
    ##################
    #  kindeditor图片上传返回数据格式说明:
    # {"error": 1, "message": "出错信息"}
    # {"error": 0, "url": "图片地址"}
    ##################
    result = {"error": 1, "message": "上传出错"}
    files = request.FILES.get("imgFile", None)
    if files:
        result = image_upload(files, dir_name)
    return HttpResponse(json.dumps(result), content_type="application/json")


# 目录创建
def upload_generation_dir(dir_name):
    today = dt.datetime.today()
    url_part = dir_name + '/%d/%d/' % (today.year, today.month)
    dir_name = os.path.join(dir_name, str(today.year), str(today.month))
    print("*********", os.path.join(settings.MEDIA_ROOT, dir_name))
    if not os.path.exists(os.path.join(settings.MEDIA_ROOT, dir_name)):
        os.makedirs(os.path.join(settings.MEDIA_ROOT, dir_name))
    return dir_name,url_part


# 图片上传
def image_upload(files, dir_name):
    # 允许上传文件类型
    allow_suffix = ['jpg', 'png', 'jpeg', 'gif', 'bmp']
    file_suffix = files.name.split(".")[-1]
    if file_suffix not in allow_suffix:
        return {"error": 1, "message": "图片格式不正确"}
    relative_path_file, url_part = upload_generation_dir(dir_name)
    path = os.path.join(settings.MEDIA_ROOT, relative_path_file)
    print("&&&&path", path)
    if not os.path.exists(path):  # 如果目录不存在创建目录
        os.makedirs(path)
    file_name = str(uuid.uuid1()) + "." + file_suffix
    path_file = os.path.join(path, file_name)
    file_url =settings.MEDIA_URL + url_part +file_name
    open(path_file, 'wb').write(files.file.read())
    return {"error": 0, "url": file_url}

文件保存后,路径为<img src="/upload/kindeditor/2018/5/dea7fffe-6251-11e8-946f-40b034409066.jpg" alt="" />

接着

4.使用django配置/upload来显示图片。
from django.views.static import serve
url(r'^upload/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT, }),

setting增加media的配置


STATIC_URL = '/static/'
 
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,  'static'),
)
 
MEDIA_URL = '/uploads/'
 
MEDIA_ROOT = os.path.join(BASE_DIR,  'uploads')

Django-chunked-upload

1.使用django-chunked-upload

pip install django-chunked-upload

INSTALLED_APPS=
    (# ...
    'chunked_upload',
    )
    
# settings.py下
CHUNKED_UPLOAD_EXPIRATION_DELTA
    创建后多久,上载将过期。
    默认值:datetime.timedelta(days=1)
CHUNKED_UPLOAD_PATH
    在完成之前将存储上载文件的路径。
    默认值:'chunked_uploads/%Y/%m/%d'
CHUNKED_UPLOAD_TO
    上载到要在模型的文件字段中使用的。
    默认值:CHUNKED_UPLOAD_PATH + '/{{ instance.upload_id }}.part'
CHUNKED_UPLOAD_STORAGE_CLASS
    存储系统(应为类)。
    默认值:None(使用默认存储系统)
CHUNKED_UPLOAD_ABSTRACT_MODEL
    定义ChunkedUpload模型是否抽象的布尔值(what does abstract model mean?)。
    默认值:True
CHUNKED_UPLOAD_ENCODER
    用于对响应数据进行编码的函数。接收dict并返回字符串。
    默认值:DjangoJSONEncoder().encode
CHUNKED_UPLOAD_CONTENT_TYPE
    响应数据的内容类型。
    默认值:'application/json'
CHUNKED_UPLOAD_MIMETYPE
	已弃用,请改用CHUNKED_UPLOAD_CONTENT_TYPE。
CHUNKED_UPLOAD_MAX_BYTES
    可上载的最大数据量(字节)。None表示没有限制。
    默认值:None

2.models.py

from django.db import models

from chunked_upload.models import ChunkedUpload

# 'ChunkedUpload' class provides almost everything for you.
# if you need to tweak it little further, create a model class
# by inheriting "chunked_upload.models.AbstractChunkedUpload" class

class MyChunkedUpload(ChunkedUpload):
    pass
MyChunkedUpload._meta.get_field('user').null = True # 设置对应的外键尾空

3.views.py

from .models import MyChunkedUpload
from chunked_upload.views import ChunkedUploadView, ChunkedUploadCompleteView

class MyChunkedUploadView(ChunkedUploadView):
    model = MyChunkedUpload
    field_name = 'the_file'

    def check_permissions(self, request):
        # Allow non authenticated users to make uploads
        pass
    
class MyChunkedUploadCompleteView(ChunkedUploadCompleteView):
    model = MyChunkedUpload

    def check_permissions(self, request):
        # Allow non authenticated users to make uploads
        pass

    def on_completion(self, uploaded_file, request):
        # Do something with the uploaded file. E.g.:
        # * Store the uploaded file on another model:
        # SomeModel.objects.create(user=request.user, file=uploaded_file)
        # * Pass it as an argument to a function:
        # function_that_process_file(uploaded_file)
        pass

    def get_response_data(self, chunked_upload, request):
        return {'message': ("You successfully uploaded '%s' (%s bytes)!" %
                            (chunked_upload.filename, chunked_upload.offset))}

4.urls.py

urlpatterns = [
    path('api/chunked_upload_complete/', views.MyChunkedUploadCompleteView.as_view(),
         name='api_chunked_upload_complete'),
    path('api/chunked_upload/', views.MyChunkedUploadView.as_view(), name='api_chunked_upload'),    
]

5.模板

<script src="/static/js/jquery.js"></script>
<script src="/static/js/jquery.ui.widget.js"></script>
<!-- The Iframe Transport is required for browsers without support for XHR file uploads -->
<script src="/static/js/jquery.iframe-transport.js"></script>
<!-- The basic File Upload plugin -->
<script src="/static/js/jquery.fileupload.js"></script>
<!-- Calculate md5 -->
<script src="/static/js/spark-md5.js"></script>


<h2 class="title">
    django-chunked-upload
</h2>
{% csrf_token %}
<label for="chunked_upload">
    <input type="file" name="the_file" id="chunked_upload">
</label>
<div id="messages"></div>
{#完成信号#}
<p id="progress"></p>
{#放置进度#}
<script type="text/javascript">
    var md5 = "",
        csrf = $("input[name='csrfmiddlewaretoken']")[0].value,
        form_data = [{"name": "csrfmiddlewaretoken", "value": csrf}];

    function calculate_md5(file, chunk_size) {
        var slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
            chunks = chunks = Math.ceil(file.size / chunk_size),
            current_chunk = 0,
            spark = new SparkMD5.ArrayBuffer();

        function onload(e) {
            spark.append(e.target.result);  // append chunk
            current_chunk++;
            if (current_chunk < chunks) {
                read_next_chunk();
            } else {
                md5 = spark.end();
            }
        };

        function read_next_chunk() {
            var reader = new FileReader();
            reader.onload = onload;
            var start = current_chunk * chunk_size,
                end = Math.min(start + chunk_size, file.size);
            reader.readAsArrayBuffer(slice.call(file, start, end));
        };
        read_next_chunk();
    }

    $("#chunked_upload").fileupload({
        url: "{% url 'api_chunked_upload' %}",
        dataType: "json",
        maxChunkSize: 100000, // Chunks of 100 kB
        formData: form_data,
        add: function (e, data) { // Called before starting upload
            $("#messages").empty();
            // If this is the second file you're uploading we need to remove the
            // old upload_id and just keep the csrftoken (which is always first).
            form_data.splice(1);
            calculate_md5(data.files[0], 100000);  // Again, chunks of 100 kB
            data.submit();
        },
        chunkdone: function (e, data) { // Called after uploading each chunk
            if (form_data.length < 2) {
                form_data.push(
                    {"name": "upload_id", "value": data.result.upload_id}
                );
            }
            {#$("#messages").append($('<p>').text(JSON.stringify(data.result)));#}
            var progress = parseInt(data.loaded / data.total * 100.0, 10);
            $("#progress").text(Array(progress).join("=") + "> " + progress + "%");
        },
        done: function (e, data) { // Called when the file has completely uploaded
            $.ajax({
                type: "POST",
                url: "{% url 'api_chunked_upload_complete' %}",
                data: {
                    csrfmiddlewaretoken: csrf,
                    upload_id: data.result.upload_id,
                    md5: md5
                },
                dataType: "json",
                success: function (data) {
                    $("#messages").append($('<p>').text(JSON.stringify(data)));
                }
            });
        },
    });
</script>

video在Chrome浏览器无法快进

由于工作原因,需要在Chrome浏览器播放视频,但使用的video标签在Chrome上无法快进,其他的浏览器上就可以,但老板要求必须是Chrome,后来发现,blob的路进下Chrome是可以快进的,我以为已经解决了这个问题,但当我需要播放大视频时,blob就不行了,它需要将视频全部缓存下来才开始播放,我选择一个3.4GB的视频,它需要加载3-4分钟才开始播放,极度影响用户体验,后来只能找个边加载边播放视频的功能,在网上找了很多,大部分都是ffmpeg将视频转了一下,但是在Chrome上又无法快进了,麻烦,后来无意间发现django的流式处理,但网上的代码都是python的代码,没有说到前端如何接收视频.
呃,咳咳,话不多说,直接上代码!

views.py

import re
import os
import mimetypes
from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse
from django.shortcuts import render


def file_iterator(file_name, chunk_size=8192, offset=0, length=None):
    with open(file_name, "rb") as f:
        f.seek(offset, os.SEEK_SET)
        remaining = length
        while True:
            bytes_length = chunk_size if remaining is None else min(remaining, chunk_size)
            data = f.read(bytes_length)
            if not data:
                break
            if remaining:
                remaining -= len(data)
            yield data


def stream_video(request):
    """将视频文件以流媒体的方式响应"""
    range_header = request.META.get('HTTP_RANGE', '').strip()
    range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)
    range_match = range_re.match(range_header)
    path = request.GET.get('path')
    size = os.path.getsize(path)
    content_type, encoding = mimetypes.guess_type(path)
    content_type = content_type or 'application/octet-stream'
    if range_match:
        first_byte, last_byte = range_match.groups()
        first_byte = int(first_byte) if first_byte else 0
        last_byte = first_byte + 1024 * 1024 * 10
        if last_byte >= size:
            last_byte = size - 1
        length = last_byte - first_byte + 1
        resp = StreamingHttpResponse(file_iterator(path, offset=first_byte, length=length), status=206, content_type=content_type)
        resp['Content-Length'] = str(length)
        resp['Content-Range'] = 'bytes %s-%s/%s' % (first_byte, last_byte, size)
    else:
        resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
        resp['Content-Length'] = str(size)
    resp['Accept-Ranges'] = 'bytes'
    return resp


def html(request):
    return render(request, 'test.html')

# 当然也可以写成def stream_video(request,path):再引入path
# 用以上方法即可解决进度条无法拖动的问题

urls.py

from django.urls import path, include
from django.conf.urls import url
from . import views

urlpatterns = [
    path('', views.html),
    url('test_resp/', views.stream_video),
]

test.html

<!DOCTYPE html>
<html lang="en">
<head>
    {% load staticfiles %}
    <meta charset="UTF-8">
    <title>test</title>
    <script></script>
    <script src="{% static 'jquery-3.4.1.js' %}"></script>

</head>
<body>
    <video id="media" src="" width="640" height="480" controls autoplay>您的浏览器不支持 video 标签 </video>
    <br />
    <button>333.mp4</button>
    <button>test1_2.mp4</button>
    <button>DestFile.mp4</button>
    <button>mkv_video.mkv</button>
    <button>test_yys.mp4</button>
    <button>sudu.mp4</button>
</body>
<script>
    $(function () {
        $('button').click(function () {
            let test = $(this).text();
            $("#media").attr('src', '/test_resp/?path=static/'+test); 
        });
     })
</script>
</html>

<!-->
<video id="example" class="vjs-default-skin vjs-big-play-centered"  poster="{% static '/imgs/v1.jpg'%}" controls preload="auto" >
     <source style=" background:#000;" src="/video/stream_video"  type="video/mp4">
     <p class="vjs-no-js"> To view this video please enable JavaScript, and consider upgrading to a web browser</p>
  </video>
<--!>


Django 分页器

1.基础

from django.core.paginator import Paginator
book_list = models.Book.objects.all()
paginator = Paginator(book_list, 10) # 

print("count", paginator.count)  # 数据的总数
print("num_pages", paginator.num_pages)  # 分页的总页数
print("page_range", paginator.page_range)  # 页数的范围列表
page1 = paginator.get_page(1)  # 获取第一页的所有数据
for i in page1:  # 遍历第一页所有数据对象
    print(i)
print(page1.object_list)  # 第一页的所用数据
print(page2.has_next())  # 是否有下一页
print(page2.next_page_number())  # 下一页的页码
print(page2.has_previous())  # 是否有上一页
print(page2.previous_page_number())  # 上一页的页码

# 抛错
# page=paginator.page(22)   # error:EmptyPage

# page=paginator.page("z")   # error:PageNotAnInteger

案例

1. view(视图层)


from django.shortcuts import render, redirect
from django.views import View
from app01 import models
from django.core.paginator import Paginator
import random
​
​
class Index(View):
    def get(self, request):
        """
        批量增加测试数据
        book_list = []
        for i in range(1, 101):
            # models.Book.objects.create(title="book_%s" % i, price=random.randint(20, 300))  # 执行效率低
            book_list.append(models.Book(title="book_%s" % i, price=random.randint(20, 300)))
        models.Book.objects.bulk_create(book_list)  # 批量新增数据
        """
        """
        # 分页器的基本语法
        book_list = models.Book.objects.all()
        # 实例化分页器对象
        paginator = Paginator(book_list, 10)
​
        print("count", paginator.count)  # 数据的总数
        print("num_pages", paginator.num_pages)  # 分页的总页数
        print("page_range", paginator.page_range)  # 页数的范围列表
​
        page1 = paginator.get_page(1)  # 获取第一页的所有数据
        for i in page1:  # 遍历第一页所有数据对象
            print(i)
​
        print(page1.object_list)  # 第一页的所用数据
​
        page2 = paginator.get_page(2)  # 获取第二页的所有数据
​
        print(page2.has_next())  # 是否有下一页
        print(page2.next_page_number())  # 下一页的页码
        print(page2.has_previous())  # 是否有上一页
        print(page2.previous_page_number())  # 上一页的页码
​
        # 抛错
        # page=paginator.page(22)   # error:EmptyPage
​
        # page=paginator.page("z")   # error:PageNotAnInteger
        """
​
        # ############# django分页器的基本使用 ##################
        book_list = models.Book.objects.all()
        paginator = Paginator(book_list, 10)
​
        current_page = int(request.GET.get("page") if request.GET.get("page") and request.GET.get("page").isdigit() else 1)  # 获取页码
        page = paginator.get_page(current_page)  # 获取当前页码的所有数据
​
        return render(request, "index.html", {"paginator": paginator, "page": page, "current_page": current_page})

templates

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
​
<h3>书籍列表</h3>
​
<ul>
    {% for book in page %}
        <li>{{ book.title }} --- {{ book.price }}</li>
    {% endfor %}
</ul>
<nav aria-label="Page navigation">
    <ul class="pagination">
        <li><a href="?page=1">首页</a></li>
        {% if page.has_previous %}
            <li><a href="?page={{ page.previous_page_number }}">上一页</a></li>
        {% else %}
            <li class="disabled"><a href="javascript:void(0);">上一页</a></li>
        {% endif %}
        {% for num in paginator.page_range %}
            {% if current_page == num %}
                <li class="active"><a href="?page={{ num }}">{{ num }}</a></li>
            {% else %}
                <li><a href="?page={{ num }}">{{ num }}</a></li>
            {% endif %}
        {% endfor %}
        {% if page.has_next %}
            <li><a href="?page={{ page.next_page_number }}">下一页</a></li>
        {% else %}
            <li class="disabled"><a href="javascript:void(0);">下一页</a></li>
        {% endif %}
        <li><a href="?page={{ paginator.num_pages }}">尾页</a></li>
    </ul>
</nav>
​
​
</body>
</html>

2.扩展

上面的示例,看似已经完成了分页的效果,但是,如果我们把每页显示的数量改小一点看一下效果。

image-20201018233224000

view(视图层)

        # ############# django分页器的基本使用 ##################
        book_list = models.Book.objects.all()
        paginator = Paginator(book_list, 2)

        current_page = int(request.GET.get("page") if request.GET.get("page") and request.GET.get("page").isdigit() else 1)  # 获取页码
        page = paginator.get_page(current_page)  # 获取当前页码的所有数据

        # 我们按照页面显示11个页码为例。
        # 如果总页码大于11
        if paginator.num_pages > 11:
            if current_page - 5 < 1:  # 当前页小于中间页码时
                page_range = range(1, 12)
            elif current_page + 5 > paginator.num_pages:  # 当前页大于中间页码时
                page_range = range(paginator.num_pages - 10, paginator.num_pages + 1)
            else:
                page_range = range(current_page - 5, current_page + 6)
        else:
            page_range = paginator.page_range

template(模板层)

{#        将之前的pagintor.page_range,改为page_range即可#}
        {% for num in page_range %}
            {% if current_page == num %}
                <li class="active"><a href="?page={{ num }}">{{ num }}</a></li>
            {% else %}
                <li><a href="?page={{ num }}">{{ num }}</a></li>
            {% endif %}
        {% endfor %}

3.自定制分页器

  1. 自定制分页器类
class Paginator:
    def __init__(self, current_page, all_count, per_page=10, max_page_num=11):
        """
        封装分页相关数据
        :param current_page:  当前页码
        :param all_count:  数据库中的数据总条数
        :param per_page:   每个页面显示的数据条数
        :param max_page_num:  最多显示的页码个数
        :param num_pages:  通过总条数/每个页面显示的条数,求出总页数
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1
        if current_page < 1:
            current_page = 1
        self.current_page = current_page
        self.all_count = all_count
        self.per_page = per_page

        # 计算总页数
        num_pages, temp = divmod(all_count, per_page)
        if temp:
            num_pages += 1
        self.num_pages = num_pages

        self.max_page_num = max_page_num  # 11
        self.page_count_half = int((self.max_page_num - 1) / 2)  # 5
        """
        self.num_pages=100
        per_page=8

        current_page =1     [0:8]
        current_page =2     [8:16]
        current_page =3     [16:24]
                            [(current_page-1)*per_page:current_page*per_page ]

        """

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page

    @property
    def end(self):
        return self.current_page * self.per_page

    def page_html(self):
        # 如果总页数小于self.max_page_num(最多显示的页码个数)
        if self.num_pages <= self.max_page_num:
            page_start = 1
            page_end = self.num_pages + 1
        else:
            # 如果当前页码<=页面上最多显示11/2个页码时
            if self.current_page <= self.page_count_half:
                page_start = 1
                page_end = self.max_page_num + 1
            # 如果当前页码+最多显示11/2 大于 总页数时
            elif self.current_page + self.page_count_half > self.num_pages:
                page_start = self.num_pages - self.max_page_num + 1
                page_end = self.num_pages + 1
            else:
                page_start = self.current_page - self.page_count_half
                page_end = self.current_page + self.page_count_half + 1

        page_html_list = []

        # 首页
        first_page = '<nav aria-label="Page navigation"><ul class="pagination"><li><a href="?page=1">首页</a></li>'
        page_html_list.append(first_page)

        # 上一页
        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="javascript:void(0);">上一页</a></li>'
        else:
            prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1, )
        page_html_list.append(prev_page)

        # 显示页码
        for i in range(page_start, page_end):
            if self.current_page == i:
                temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i)
            else:
                temp = '<li><a href="?page=%s">%s</a></li>' % (i, i)
            page_html_list.append(temp)

        # 下一页
        if self.current_page >= self.num_pages:
            next_page = '<li class="disabled"><a href="javascript:void(0);">下一页</a></li>'
        else:
            next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1)
        page_html_list.append(next_page)

        # 尾页
        last_page = '<li><a href="?page=%s">尾页</a></li></ul></nav>' % self.num_pages
        page_html_list.append(last_page)

        return "".join(page_html_list)
  1. 视图层
class Index(View):
    def get(self, request):
        book_list = models.Book.objects.all()
        current_page = request.GET.get('page')
        paginator = Paginator(current_page, book_list.count(), 6, 11)
        book_list = book_list[paginator.start: paginator.end]

        return render(request, "index2.html", {"book_list": book_list, "paginator": paginator, "current_page": current_page})
  1. 模板层
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

<h3>书籍列表!!!</h3>

<ul>
    {% for book in book_list %}
        <li>{{ book.title }} --- {{ book.price }}</li>
    {% endfor %}
</ul>

{{ paginator.page_html|safe }}

</body>
</html>

4. 自定制分页器(终极版本)

  跟上一版本的区别,主要是可以携带之前的参数了,这样我们在后面做项目时,如果有复杂的过滤条件时,就可以轻松应对了。请参考下面的代码:

  1. 分页器类
class Paginator:
    def __init__(self, request, current_page, all_count, per_page=10, max_page_num=13):
        """
        封装分页相关数据
        :param current_page:  当前页码
        :param all_count:  数据库中的数据总条数
        :param per_page:   每个页面显示的数据条数
        :param max_page_num:  最多显示的页码个数
        :param num_pages:  通过总条数/每个页面显示的条数,求出总页数
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1
        if current_page < 1:
            current_page = 1
        self.current_page = current_page
        self.all_count = all_count
        self.per_page = per_page

        # 计算总页数
        num_pages, temp = divmod(all_count, per_page)
        if temp:
            num_pages += 1
        self.num_pages = num_pages

        self.max_page_num = max_page_num  # 11
        self.page_count_half = int((self.max_page_num - 1) / 2)  # 5

        import copy
        self.url_args = copy.deepcopy(request.GET)
        print(self.url_args.urlencode())


        """
        self.num_pages=100
        per_page=8

        current_page =1     [0:8]
        current_page =2     [8:16]
        current_page =3     [16:24]
                            [(current_page-1)*per_page:current_page*per_page ]

        """

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page

    @property
    def end(self):
        return self.current_page * self.per_page

    def page_html(self):
        # 如果总页数小于self.max_page_num(最多显示的页码个数)
        if self.num_pages <= self.max_page_num:
            page_start = 1
            page_end = self.num_pages + 1
        else:
            # 如果当前页码<=页面上最多显示11/2个页码时
            if self.current_page <= self.page_count_half:
                page_start = 1
                page_end = self.max_page_num + 1
            # 如果当前页码+最多显示11/2 大于 总页数时
            elif self.current_page + self.page_count_half > self.num_pages:
                page_start = self.num_pages - self.max_page_num + 1
                page_end = self.num_pages + 1
            else:
                page_start = self.current_page - self.page_count_half
                page_end = self.current_page + self.page_count_half + 1

        page_html_list = []

        # 首页
        self.url_args['page'] = 1
        first_page = '<nav aria-label="Page navigation"><ul class="pagination"><li><a href="?%s">首页</a></li>' % (self.url_args.urlencode())
        page_html_list.append(first_page)

        # 上一页
        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="javascript:void(0);">上一页</a></li>'
        else:
            self.url_args['page'] = self.current_page - 1
            prev_page = '<li><a href="?%s">上一页</a></li>' % (self.url_args.urlencode(), )
        page_html_list.append(prev_page)

        # 显示页码
        for i in range(page_start, page_end):
            self.url_args['page'] = i
            if self.current_page == i:
                temp = '<li class="active"><a href="?%s">%s</a></li>' % (self.url_args.urlencode(), i)
            else:
                temp = '<li><a href="?%s">%s</a></li>' % (self.url_args.urlencode(), i)
            page_html_list.append(temp)

        # 下一页
        if self.current_page >= self.num_pages:
            next_page = '<li class="disabled"><a href="javascript:void(0);">下一页</a></li>'
        else:
            self.url_args['page'] = self.current_page + 1
            next_page = '<li><a href="?%s">下一页</a></li>' % (self.url_args.urlencode(), )
        page_html_list.append(next_page)

        # 尾页
        self.url_args['page'] = self.num_pages
        last_page = '<li><a href="?%s">尾页</a></li></ul></nav>' % self.url_args.urlencode()
        page_html_list.append(last_page)

        return "".join(page_html_list)
  1. 视图层
class Index(View):
    def get(self, request):        
        book_list = models.Book.objects.all()
        current_page = request.GET.get('page')
        paginator = Paginator(request, current_page, book_list.count(), 6, 11)
        book_list = book_list[paginator.start: paginator.end]

        return render(request, "index2.html", {"book_list": book_list, "paginator": paginator, "current_page": current_page})
  1. 模板层

  同上面的模板层代码。

Django自定义图片和文件上传路径

方法1:

在Django模型中定义upload_to选项。

Django模型中的ImageField和FileField的upload_to选项是必填项,其存储路径是相对于MEIDA_ROOT而来的。

这里有2个严重问题。

  • 所有用户都把头像上传到了同一个avatar文件夹了
  • 原文件名是什么,那么新文件名就是什么

用户很多,很可能发生文件重名问题


from django.db import models
from django.contrib.auth.models import User
import uuid
 
# Create your models here.
 

def user_directory_path(instance, filename):
    ext = filename.split('.')[-1]
    filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
    # return the whole path to the file
    return os.path.join(instance.user.id, "avatar", filename)

 
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    avatar = models.ImageField(upload_to=user_directory_path, verbose_name="头像")
# 用户上传文件可能是图片,也可能是pdf文件,我们如何把它们放在同一用户的不同文件夹下呢?实现这个很简单,如下图所示。

def user_directory_path(instance, filename):
    ext = filename.split('.')[-1]
    filename = '{}.{}'.format(uuid.uuid4().hex[:8], ext)
    sub_folder = 'file'
    if ext.lower() in ["jpg", "png", "gif"]:
        sub_folder = "avatar"
    if ext.lower() in ["pdf", "docx"]:
        sub_folder = "document"
    return os.path.join(instance.user.id, sub_folder, filename)

方法2

在视图中自定义上传图片或文件路径,最简单直白,但有一个较大缺陷,文件上传后未经处理就直接存储了

假如用户上传了图片,我们希望先对其压缩或裁剪,然后再存储,或者我们不希望上传图片或文件到默认的路径,这时我们就有必要在视图中自定义图片或文件路径了


@login_required
def ajax_avatar_upload(request):
    user = request.user
    user_profile = get_object_or_404(UserProfile, user=user)
 
    if request.method == "POST":
        form = AvatarUploadForm(request.POST, request.FILES)
        if form.is_valid():
            img = request.FILES['avatar_file']  # 获取上传图片
            cropped_avatar = crop_image(img, user.id)
            user_profile.avatar = cropped_avatar  # 将图片路径修改到当前会员数据库
         user_profile.save()
    return HttpResponseRedirect(reverse('myaccount:profile'))
 
 
def crop_image(file, uid):
    # 随机生成新的图片名,自定义路径。
   ext = file.name.split('.')[-1]
    file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
    cropped_avatar = os.path.join(uid, "avatar", file_name)
    # 相对根目录路径
   file_path = os.path.join("media", uid, "avatar", file_name)
 
    # 裁剪图片,压缩尺寸为200*200。
   img = Image.open(file)
    crop_im = img.crop((50,50,300, 300)).resize((200, 200), Image.ANTIALIAS)
    crop_im.save(file_path)
 
    return cropped_avatar

方法3

1.文件上传

fileField(upload_to='【路径】',max_length=100) 
# 实际保存的是一个字符串类型的路径,真实的文件保存在服务器文件系统
重要参数upload_to.是一个路径名,如果不存在则会自动创建
使用技巧:

1、可以自动生成日期格式的路径名譬如:upload=models.FileField(upload_to='upload/%Y/%m/%d')
2、接收一个回调函数
def user_directory_path(instance,filename):
    # 用户上传的是图片还是pdf等
    ext = filename.split('.')[-1]
    filename = '{}{}'.format(uuid.uuid4(),hex[:10],ext)
    return os.path.join(instance.user.id,'avatar',filename)

upload=models.FileField(upload_to=a)


2.图片上传

ImageFied(upload_to='[路径]',height_field=500,width_field=500,max_lenght=100)
# 用法与文件上传(FileField)类似,
但多出两个可选参数heigth_field和width_field,代表上传后图片的尺寸(自动缩放)
注意事项:在使用ImageField之前需要其他模块支持pillow模块

前期的设置工作:

1、需要先配置settings文件,设置文件系统的跟路径。MEDIA_ROOT='[路径]'。还需要配置使用时的URL路径MEDIA_URL='[路径]'

2、html使用:像正常调用字段名比如{{【类实例】.[文件字段].url}}

3、还可以通过【类实例】.【文件字段】.name/size 获取文件名或者文件大小

方法4

自己继承FileSystemStorage

from django.conf import global_settings, settings
from django.core.files.storage import FileSystemStorage

# 上传文件时重命名

class MyFileSystemStorage(FileSystemStorage):
    """
     def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL, ):
        super(MyFileSystemStorage, self).__init__(location, base_url)
    """

    def _save(self, name, content):
        ext = os.path.splitext(name)[1]  # 文件扩展名
        d = os.path.dirname(name)  # 文件目录
        fn = time.strftime('%Y%m%d%H%M%S')
        name = os.path.join(d, fn + ext)
        return super()._save(name, content)


from django.db import models

# 手动指定
cvoer = models.ImageField(upload_to='img/%Y/%m/%d', storage=MyFileSystemStorage)

# 自动指定
# settings.py下
DEFAULT_FILE_STORAGE = "***.***.***"即可

Django通用视图

Django提供了很多通用的基于类的视图(Class Based View),来帮我们简化视图的编写。这些View与上述操作的对应关系如下:

  • 展示对象列表(比如所有用户,所有文章)- ListView
  • 展示某个对象的详细信息(比如用户资料,比如文章详情) - DetailView
  • 通过表单创建某个对象(比如创建用户,新建文章)- CreateView
  • 通过表单更新某个对象信息(比如修改密码,修改文字内容)- UpdateView
  • 用户填写表单后转到某个完成页面 - FormView
  • 删除某个对象 - DeleteView
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

展示类视图(Display view)

1.generic.ListView

# Create your views here.
from django.views.generic import ListView
from .models import Article

class IndexView(ListView):

    model = Article

# 上述代码等同于:
# 展示所有文章
def index(request):
    queryset = Article.objects.all()
    return render(request, 'blog/article_list.html', {"object_list": queryset})

尽管我们只写了一行model = Article, ListView实际上在背后做了很多事情:

  • 提取了需要显示的对象列表或数据集queryset: Article.objects.all()
  • 指定了用来显示对象列表的模板名称(template name): 默认app_name/model_name_list.html, 即blog/article_list.html.
  • 指定了内容对象名称(context object name):默认值object_list 和 model_name_list

总结

# 只实现了get请求
class Publisher_list(generic.ListView):
    model = models.Publisher
    
# 最简单的
template_name_suffix = '_list'
template_name = "%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix)
# get_template_names
allow_empty = True # get_allow_empty # allow_empty_first_page 是否允许分页器第一页为空
queryset = None # get_queryset
model = None
paginate_by = None # 分页 --> 内部自动变为page_size,只有存在这个参数才会开始分页 # paginate_queryset
# get_paginate_by
paginate_orphans = 0 # get_paginate_orphans 
# Returns the maximum number of orphans extend the last page by when paginating.
# 使用分页器时,允许最后一页展示的数据的最大值,可以修改分页的页码总数,没啥用
context_object_name = None # object_list 和 `model_name`_list  # get_context_object_name
paginator_class = Paginator # get_paginator
page_kwarg = 'page' # 
ordering = None # ['',''] # get_ordering
方法;
	get_queryset
    get_context_data

2.generic.DetailView

# Create your views here.
from django.views.generic import DetailView
from .models import Article

class ArticleDetailView(DetailView):
    model = Article
  

DetailView默认的模板是app/model_name_detail.html,

默认的内容对象名字context_object_name是model_name。

本例中默认模板是blog/article_detail.html,

默认对象名字是article, 在模板里可通过 {{ article.title }}获取文章标题。

你同样可以通过重写queryset, template_name和context_object_name来完成DetailView的自定义。你还可以通过重写get_context_data方法传递额外的参数或内容。如果你指定了queryset, 那么返回的object是queryset.get(pk = id), 而不是model.objects.get(pk = id)。

# Create your views here.
from django.views.generic import ListView,DetailView
from .models import Article
from django.utils import timezone


# 只能url传参数,不能get的查询参数
# 可以自己设置传输 
class ArticleDetailView(DetailView):

    queryset = Article.objects.all().order_by("-pub_date") # 
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

在实际应用中,上述代码可能还不能满足你的要求。比如你希望一个用户只能看到自己发表的文章详情。当用户查看别人的文章详情时,返回http 404错误。这时候你可以通过更具体的get_object()方法来返回一个更具体的对象。代码如下:

# Create your views here.
from django.views.generic import DetailView
from django.http import Http404
from .models import Article
from django.utils import timezone

class ArticleDetailView(DetailView):

    queryset = Article.objects.all().order_by("-pub_date")
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

    def get_object(self, queryset=None):
        obj = super().get_object(queryset=queryset)
        if obj.author != self.request.user:
            raise Http404()
        return obj

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

总结

# 只实现了get请求
class Book_detail(generic.DetailView):
    model = models.Book
 
get_template_names() return[]
	template_name 优先级最高
    template_name_field = None # 不用这个
    template_name_suffix = '_detail'
    
model = None
queryset = None 	# self.get_queryset()
slug_field = 'slug' #数据库字段
context_object_name = None # model_name object
slug_url_kwarg = 'slug' # 查询字段
pk_url_kwarg = 'pk'
query_pk_and_slug = False

get_object()
get_queryset()

编辑类视图(Edit view)

重要:如果你要使用Edit view,请务必在模型models里定义get_absolute_url()方法,否则会出现错误。这是因为通用视图在对一个对象完成编辑后,需要一个返回链接。

1.CreateView

CreateView一般通过某个表单创建某个对象,通常完成后会转到对象列表。比如一个最简单的文章创建CreateView可以写成:

from django.views.generic.edit import CreateView
from .models import Article

class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'body', 'pub_date']

CreateView默认的模板是model_name_form.html,

即article_form.html。

默认的context_object_name是form。

模板代码如下图所示:

#blog/article_form.html
<form method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Save" />
</form>

如果你不想使用默认的模板和默认的表单,你可以通过重写template_name和form_class来完成CreateView的自定义。虽然form_valid方法不是必需,但很有用。当用户提交的数据是有效的时候,你可以通过定义此方法做些别的事情,比如发送邮件,存取额外的数据。

from django.views.generic.edit import CreateView
from .models import Article
from .forms import ArticleCreateForm

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'blog/article_create_form.html'
    form_class = ArticleCreateForm

    def form_valid(self, form):
       form.do_sth()
       return super().form_valid(form)

form_valid方法一个常见用途就是就是将创建对象的用户与model里的user结合。见下面例子。

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'blog/article_create_form.html'
    form_class = ArticleCreateForm

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

总结

# 实现了 get post方法
template_name_suffix = '_form'
get_template_names()
names.append("%s/%s%s.html" % (
    object_meta.app_label,
    object_meta.model_name,
    self.template_name_suffix
))

model 
fields = None  -->通过modelform传输过来的

form_class = None # 优先级最高,找不到, 和 fields 互斥
success_url = None  # get_success_url(self):
# 或者url = self.success_url.format(**self.object.__dict__)
initial = {} # Returns the initial data to use for forms on this view.设置forms的初始值
prefix = None # 无用,不要要添加,源码在modelform内面,乐意看到用法.
get_context_data() 
    # 内有form  # get_form()
    # view类的对象
get_form()
	form_class
    None  get_form_class()
    
# get请求object为None
# post请求

2.UpdateView

UpdateView一般通过某个表单更新现有对象的信息,更新完成后会转到对象详细信息页面。它需要URL提供访问某个对象的具体参数(如pk, slug值)。比如一个最简单的文章更新的UpdateView如下所示。

from django.views.generic.edit import UpdateView
from .models import Article

class ArticleUpdateView(UpdateView):
    model = Article
    fields = ['title', 'body', 'pub_date']

UpdateView和CreateView很类似,比如默认模板都是model_name_form.html。但是区别有两点:

  • CreateView显示的表单是空表单,UpdateView中的表单会显示现有对象的数据。

  • 用户提交表单后,CreateView转向对象列表,UpdateView转向对象详细信息页面。

你可以通过重写template_name和form_class来完成UpdateView的自定义。

  • 本例中默认的form是article_form.html, 你可以改为article_update_form.html。

  • 虽然form_valid方法不是必需,但很有用。当用户提交的数据是有效的时候,你可以通过定义此方法做些别的事情,比如发送邮件,存取额外的数据。

from django.views.generic.edit import UpdateView
from .models import Article
from .forms import ArticleUpdateForm

class ArticleUpdateView(UpdateView):
    model = Article
    template_name = 'blog/article_update_form.html'
    form_class = ArticleUpdateForm

    def form_valid(self, form):
       form.do_sth()
       return super().form_valid(form)

总结

template_name_suffix = '_form'
get_template_names()
names.append("%s/%s%s.html" % (
    object_meta.app_label,
    object_meta.model_name,
    self.template_name_suffix
))

# 实现了 get post方法
model = None
queryset = None 	# self.get_queryset()
slug_field = 'slug' #数据库字段
context_object_name = None # model_name object
slug_url_kwarg = 'slug' # 查询字段
pk_url_kwarg = 'pk'
query_pk_and_slug = False

对应的form

3.FormView

FormView一般用来展示某个表单,而不是某个模型对象。当用户输入信息未通过表单验证,显示错误信息。当用户输入信息通过表单验证提交成功后,转到其它页面。使用FormView一般需要定义template_name, form_class和跳转的success_url.

见下面代码。

# views.py - Use FormView
from myapp.forms import ContactForm
from django.views.generic.edit import FormView

class ContactView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)

总结

template_name 类变量必须,否则报错
实现get 和 post请求
form_class
success_url 类似

4.DeleteView

DeleteView一般用来删除某个具体对象。它要求用户点击确认后再删除一个对象。使用这个通用视图,你需要定义模型的名称model和成功删除对象后的返回的URL。默认模板是myapp/model_confirm_delete.html。默认内容对象名字是model_name,本例中为article。

本例使用了默认的模板blog/article_confirm_delete.html,删除文章后通过reverse_lazy方法返回到index页面。

from django.urls import reverse_lazy
from django.views.generic.edit import DeleteView
from .models import Article

class ArticleDeleteView(DeleteView):
    model = Article
    success_url = reverse_lazy('index')

模板内容如下:

#blog/article_confirm_delete.html
<form method="post">{% csrf_token %}
    <p>Are you sure you want to delete "{{ article }}"?</p>
    <input type="submit" value="Confirm" />
</form>

但这段代码还有个问题,你注意到没? 用户可以删除任意文章,包括别人发表的文章。如果我们想用户只能删除自己的文章,上述代码怎么改? 我们通过get_queryset方法筛选出作者自己的文章即可。views.py可改成下文:

from django.urls import reverse_lazy
from django.views.generic.edit import DeleteView
from .models import Article

class ArticleDeleteView(DeleteView):
    model = Article
    success_url = reverse_lazy('index')

    def get_queryset(self):
        return self.model.objects.filter(author=self.request.user)

比较简单的视图

1.TemplateView

实现get方法,提供一个template_name,或者get_template_names()方法

get_context_data()方法,最好super一下,基本没啥东西

2.RedirectView

permanent = False # 临时还是永久
url = None #  url = self.url % kwargs
            
pattern_name = None # 和url是互斥关系 # url = reverse(self.pattern_name, args=args, kwargs=kwargs)
query_string = False
	get_redirect_url()方法
# A view that provides a redirect on any GET request.
# 其他请求全部到get请求上了

django3新特性ASGI初体验

django3.0的三个重要特性是ASGI、支持MariaDB10.1+和自定义枚举类型(TextChoices,IntegerChoices)。相关信息大家可以查阅官方资料。https://www.djangoproject.com/weblog/2019/dec/02/django-3-released/

其它新特性我们暂且不管,我们主要的是要了解一下ASGI,大家都知道,在django3.0之前django的Web服务器网关接口一直用的是WSGI,ASGI的A就是Async,也就是异步的意思,ASGI简单的来说就是异步的WSGI。由于Django基于WSGI表现起来比较低效,Django团队首创了ASGI的概念,ASGI模式将Django作为原生异步应用程序运行,原有的WSGI模式将围绕每个Django调用运行单个事件循环,以使异步处理层与同步服务器兼容。

简单来说,Django3.0之后就开始支持异步了。废话不多说,下面我们开始来简单体验一下ASGI。

大家看一下,我们的目录里比之前的django2多出了一个asgi.py文件,这个就是之前我们所说的ASGI组件。下面我们将用这个服务器组件来启动我们的项目。

ASGI服务器组件,我们有两种应用服务器可以来启动它,一种是用Uvicorn,Uvicorn是基于uvloop和httptools的ASGI服务器,它理论上是Python中最高性能的框架了。另一种是Daphne,Daphne是Django软件基金会开发的一个基于ASGI (HTTP/WebSocket)的服务器。

1、用Uvicorn来启动ASGI组件

首先先安装Uvicorn。

pip install uvicorn

安装好之后我们用下面的命令来启动我们的项目:

uvicorn django_cn.asgi:application

2、用Daphne来启动ASGI组件

使用方法和Uvicorn一样。

#安装Daphne
pip install daphne
#然后使用下面命令启动项目
daphne djang_cn.asgi:application
posted on 2020-11-02 22:48  啊,那逝去的青春  阅读(366)  评论(0编辑  收藏  举报