Python / 34 Django 常用插件实战

Django 常用插件实战

前面我们已经简单介绍了 django-restframework 插件的基本用法,本节的插件实战主要以 django-celery 和 django-guardian 这两个插件进行演示。使用这两个插件完成一些简单的需求实验,体验这两个插件给我们带来的好处。

1. django-celery 框架实战

我们使用 django-celery 框架完成2个小实验,简单体验下 celery 框架的功能。首先准备好实验环境,包括安装 django-celery 等依赖的模块以及完成初始配置代码。按照以下步骤进行:

安装依赖模块:包括 django-celery 等;

(django-manual) [root@server first_django_app]# pip install django-celery

注意: 如果想要使用 Django 的 ORM 来存储结果,想要安装 django-celery-results 插件;

安装好 redis 服务,使用源码或者 yum 安装即可;

(django-manual) [root@server first_django_app]# yum install redis

推荐使用源码安装方式,可以安装高版本的 redis。最后我在本机上安装了 redis 5.0 版本的服务,如下:

(django-manual) [root@server first_django_app]# redis-server --version
Redis server v=5.0.7 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=5cb8e5612a04130d
(django-manual) [root@server first_django_app]# systemctl status redis
● redis.service - Redis
   Loaded: loaded (/etc/systemd/system/redis.service; disabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/redis.service.d
           └─limit.conf
   Active: active (running) since 一 2020-01-13 22:41:09 CST; 3 months 28 days ago
 Main PID: 1707 (redis-server)
   CGroup: /system.slice/redis.service
           └─1707 /usr/local/bin/redis-server 127.0.0.1:6379

新增一个处理异步任务的应用:async_tasks,另外设置相应的 settings.py 中的配置:

(django-manual) [root@server first_django_app]# django-admin startapp async_tasks

async_tasks 目录下准备一个 tasks.py 文件在,这个代码里面会放一些需要异步处理的任务:

# 代码位置:async_tasks/tasks.py
# Create your tasks here
from __future__ import absolute_import, unicode_literals
import time
from celery import shared_task

@shared_task
def add(x, y):
    time.sleep(10)
    return x + y

最后,为了能启动 celery 服务,我们需要在 Django 项目下 settings.py 的同级目录下添加一个 celery.py 文件 (必须这样命名) :

# 代码位置:first_django_app/celery.py
from __future__ import absolute_import, unicode_literals 
import os
from celery import Celery
from django.conf import settings

# 设置环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "first_django_app.settings")

#创建celery应用
app = Celery('first_django_app')
app.config_from_object('django.conf:settings', namespace='CELERY')

#如果在工程的应用中创建了 tasks.py 模块,那么Celery应用就会自动去检索创建的任务。
app.autodiscover_tasks(lambda:settings.INSTALLED_APPS)
# 代码位置:first_django_app/__init__.py
from __future__ import absolute_import

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

接下里我们在 settings.py 中增加 celery 的相关配置,包括设置 broker 等

# 代码位置:first_django_app/settings.py
INSTALLED_APPS = [
    # ...
    'djcelery',
    # 注册应用
    'hello_app',
    'async_tasks'
]  

import djcelery
djcelery.setup_loader()
CELERY_TIMEZONE='Asia/Shanghai'
CELERY_BROKER_URL='redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0' # BACKEND配置,这里使用redis
CELERY_RESULT_SERIALIZER = 'json' 
CELERY_IMPORTS = ('async_tasks.tasks')
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'  ###

测试环境。下面第一个以交互式方式启动 celery,后续则是以守护进程方式启动。

# 交互式方式启动
(django-manual) [root@server first_django_app]# celery -A first_django_app worker -l info
...
# 以守护进程方式启动
(django-manual) [root@server first_django_app]# celery multi start w1 -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid
celery multi v4.4.2 (cliffs)
> Starting nodes...
> w1@server: OK
(django-manual) [root@server first_django_app]# celery multi stop w1 -A first_django_app -l info
celery multi v4.4.2 (cliffs)
> w1@server: DOWN

启动服务之后,我们在另一个连接窗口进行测试,使用 Django 的 shell 交互模式调用 celery 的 task。

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from async_tasks.tasks import add
>>> add.delay(8, 10)
<AsyncResult: c41bee82-f17d-4556-8c48-b246b36822b8>

# 当然在等待10s之后,我们也能通过如下方式获取结果
>>> from celery.result import AsyncResult
>>> res = AsyncResult("c41bee82-f17d-4556-8c48-b246b36822b8")
>>> res.result
18

执行该异步任务后,我们在 redis 中查看执行的结果。刚开始是空的,等到10s 之后,结果就会写入 redis 中,默认的 key 是 celery-task-meta-[任务id]:

[root@server ~]# redis-cli
127.0.0.1:6379> get celery-task-meta-c41bee82-f17d-4556-8c48-b246b36822b8
(nil)
# 10s过后,结果才会出来
127.0.0.1:6379> get celery-task-meta-c41bee82-f17d-4556-8c48-b246b36822b8
"{\"status\": \"SUCCESS\", \"result\": 18, \"traceback\": null, \"children\": [], \"date_done\": \"2020-05-12T09:51:53.716003\", \"task_id\": \"c41bee82-f17d-4556-8c48-b246b36822b8\"}"

有了上面的基础后,我们就可以实现两个简单的工作了。

实验1:异步发送邮件。

准备好邮件发送代码,放到 async_tasks/utils.py 中。

# 代码位置:async_tasks/utils.py
import smtplib
from email.mime.text import MIMEText
from email.header import Header

# 发送方邮箱
mail_from = '2894577759@qq.com'  
# 大家可以使用自己的邮箱作为发送邮箱
passwd = 'xxxxx' 

def send_email(subject_text, content='', receivers=['2894577759@qq.com']):
    send_html = "<html><head></head><body><h3>{}</h3></body></html>".format(content)
    msg = MIMEText(send_html, 'html', 'utf-8')

    # 放入邮件主题
    msg['Subject'] = subject_text
    msg['From'] = mail_from

    try:
        s = smtplib.SMTP_SSL("smtp.qq.com", 465)
        # 登录到邮箱
        s.login(mail_from, passwd)
        # 发送邮件:发送方,收件方,要发送的消息
        s.sendmail(mail_from, receivers, msg.as_string())
        print("发送邮件成功")
    except s.SMTPException as e:
        print("发送邮件失败:{},请手工查看生成的巡检报告".format(str(e)))
        return False
    finally:
        s.quit()
    return True

if __name__ == "__main__":
    send_email('Django-Celery测试', "慕课网的朋友们,大家好!")

可以直接使用 python 测试,结果如下:

图片描述

准备异步发送邮件的 task:

# 代码位置: async_tasks/tasks.py

from celery import shared_task
from async_tasks.utils import send_email
from first_django_app import celery_app

@celery_app.task
def send_email_task(title, content="慕课网的兄弟们,大家好!"):
    time.sleep(10)
    return send_email(title, content)

准备好视图函数,比较简单,直接使用异步处理即可:

# 代码位置:async_tasks/views.py
from django.http.response import HttpResponse
from async_tasks.tasks import send_email_task

# Create your views here.
def send_email(request, *args, **kwargs):
    try: # res = send_email_task.apply_async((), countdown=10)        
        res = send_email_task.delay('这是来自django-celery的测试', '慕课网的朋友们,大家好,我是xxx,来自xxx!')  
    except Exception as e:
        print('异常:{}'.format(str(e)))
    return HttpResponse('发送邮件成功,请耐心等待')

准备好 URLConf 配置,注意我们这里新建了一个 app,所以需要在主入口也要加上 URLConf 配置,此外还需要在该 app 下新建对应的 urls.py 文件并在其中添加相应的映射关系:

# 代码位置:first_django_app/urls.py

# ...

# 所有url入口
urlpatterns = [
    url('admin/', admin.site.urls),
    url('hello/', include('hello_app.urls')),
    # 添加async_tasks应用的映射入口
    url('celery/', include('async_tasks.urls')),
    url('hello_world/', hello_world),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
# 代码位置:async_tasks/urls.py
from django.urls import path
from async_tasks import views

urlpatterns = [
    path('send_email/', views.send_email),
]

在项目目录下,启动 celery 服务(最好以守护进程方式),这一步非常重要,千万不能漏掉!!

(django-manual) [root@server first_django_app]# celery multi start w1 -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid
(django-manual) [root@server first_django_app]# ps -ef | grep celery
root      7592 23339  0 23:35 pts/0    00:00:00 grep --color=auto celery
root     18503     1  0 22:49 ?        00:00:06 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@server
root     18851 18503  0 22:49 ?        00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@server
root     18875 18503  0 22:49 ?        00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@server
root     18890 18503  0 22:49 ?        00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@server
root     18903 18503  0 22:49 ?        00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@server

最后,我们启动服务,然后请求这个异步的接口,效果如下视频所示。可以看到,请求会立马返回,而任务会交给 celery 去执行,并最后将结果放到 redis 服务中。

效果演示:

实验2:设置定时任务。

我们来在 Django 工程中使用 celery 设置两个定时任务,一个任务用于在某个目录下每隔3秒生成一个日志文件,另一个定时任务则每分钟清除该目录下的所有日志文件。如果想要在 Django 中使用定时任务功能需要靠 beat 完成任务发送功能,当在 Django 中使用定时任务时,需要安装 django-celery-beat 插件。然后在 settings.py 的 INSTALLED_APPS 值中添加 'django_celery_beat' 应用。最后该应用还提供了一些表,我们使用 migrate 命令在我们数据库中迁移这些表,操作如下:

(django-manual) [root@server first_django_app]# pip install django-celery-beat
...

(django-manual) [root@server first_django_app]# python manage.py migrate django_celery_beat
Operations to perform:
  Apply all migrations: django_celery_beat
Running migrations:
  Applying django_celery_beat.0001_initial... OK
  Applying django_celery_beat.0002_auto_20161118_0346... OK
  Applying django_celery_beat.0003_auto_20161209_0049... OK
  Applying django_celery_beat.0004_auto_20170221_0000... OK
  Applying django_celery_beat.0005_add_solarschedule_events_choices... OK
  Applying django_celery_beat.0006_auto_20180322_0932... OK
  Applying django_celery_beat.0007_auto_20180521_0826... OK
  Applying django_celery_beat.0008_auto_20180914_1922... OK
  Applying django_celery_beat.0006_auto_20180210_1226... OK
  Applying django_celery_beat.0006_periodictask_priority... OK
  Applying django_celery_beat.0009_periodictask_headers... OK
  Applying django_celery_beat.0010_auto_20190429_0326... OK
  Applying django_celery_beat.0011_auto_20190508_0153... OK
  Applying django_celery_beat.0012_periodictask_expire_seconds... OK

来添加两个定时任务,在 settings.py 文件中:

# 代码位置:first_django_app/settings.py
# ...
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
    # 定时任务1:生成日志文件
    'task1': {
        'task': 'async_tasks.tasks.echo_hello',
        'schedule': 3, # 每3秒执行一次
    },
    # 定时任务2:删除日志文件
    'task2': {
        'task': 'async_tasks.tasks.clear_logs',
        'schedule': crontab(minute='*/1'),
        'args': ('/root/test/logs', )
    }
}

首先启动 celery 服务,然后启动celery-beat 服务,这样定时任务就可以看到效果了。

(django-manual) [root@server first_django_app]# celery -A first_django_app worker -l info
(django-manual) [root@server first_django_app]# celery -A first_django_app beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler

就简单这样子,我们就相应于设置了定时任务。我们简单看看演示效果:

由于篇幅限制,就不再看如何在 Django 内置的 admin 管理系统中演示如何添加定时任务了,这个工作就当作课后练习。

2. django-guardian 框架实战

我们前面介绍过 django-guardian 框架时,知道它的主要特点是实现了基于对象的权限控制。首先我们准备相应的开发环境,包括安装 django-guardian 模块、注册 guardian 应用以及迁移 guardian 应用的数据表。我们会在一个全新的项目中来完成 django-guardian 框架的实验:

(django-manual) [root@server django-manual]# django-admin startproject test_guardian
(django-manual) [root@server django-manual]# cd test_guardian
(django-manual) [root@server test_guardian]# django-admin startapp articles

安装 django-guardian 模块并修改 settings.py 文件,操作如下:

(django-manual) [root@server django-manual]# pip install django-guardian
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting django-guardian
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/56/ed/b0e24c766a498da38e2d10942be313613e12943cfdfe147dcdcdf1516c1d/django_guardian-2.2.0-py3-none-any.whl (104kB)
     |████████████████████████████████| 112kB 37.8MB/s 
Requirement already satisfied: Django>=2.1 in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from django-guardian) (2.2.12)
Requirement already satisfied: pytz in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from Django>=2.1->django-guardian) (2019.3)
Requirement already satisfied: sqlparse in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from Django>=2.1->django-guardian) (0.3.1)
Installing collected packages: django-guardian
Successfully installed django-guardian-2.2.0

# 添加guardian应用到INSTALLED_APPS中
(django-manual) [root@server test_guardian]# vim test_guardian/settings.py
...
# 调试阶段打开
DEBUG = True

# 主机白名单
ALLOWED_HOSTS = ['*', ]

INSTALLED_APPS = [
    ...
    'guardian',
    'articles'
]

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', # 这是Django默认的
    'guardian.backends.ObjectPermissionBackend', # 这是guardian的
)

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',
            ],
        },
    },
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': ['/root/test/html/jinja2'],
    }
]

# 必须先新建立好数据库
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test_guardian',
        'USER': 'store',
        'PASSWORD': 'xxxxx',
        'HOST': '180.76.152.113',
        'PORT': '9002',
    }
}

...

首先我们建立一个文章表(article),每个记录关联着一个作者。这里的文章类会有2个权限:一个是编辑权限(edit),另一个为删除权限(delete)。

# 代码位置:test_guardian/models.py
from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class Article(models.Model):
    title = models.CharField('文章标题', max_length=30)
    synopsis = models.TextField('文章简介', max_length=300, default='暂无简介')
    content = models.TextField('文章内容', default='暂无内容')
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

    class Meta:
        db_table = 'article'
        permissions = (
            ('edit', 'edit_article'),
            ('delete', 'delete_article'),
        )

有了模型之后,我们就可以使用 makemigrations 和 migrate 指令来生成我们的数据表了。

(django-manual) [root@server test_guardian]# python manage.py makemigrations
(django-manual) [root@server test_guardian]# python manage.py migrate

图片描述

首先新建 super user,然后我们进入 django 的 shell 模式,新建两个普通用户以及三篇文章记录:

(django-manual) [root@server test_guardian]# python manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address: admin@163.com
Password: 
Password (again): 
Superuser created successfully.
(django-manual) [root@server test_guardian]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from articles.models import Article
>>> from django.contrib.auth.models import User
>>> author1=User(username="author1", email="11@qq.com")
>>> author1.set_password('author1.123456')
>>> author1.save()
>>> author2=User(username="author2", email="22@qq.com")
>>> author2.set_password('author2.123456')
>>> author2.save()
>>> a1 = Article(title='测试岗位', synopsis='介绍测试岗位职责', content='这是关于测试岗位的介绍...')
>>> a2 = Article(title='开发岗位', synopsis='介绍开发岗位职责', content='这是关于开发岗位的介绍...')
>>> a3 = Article(title='瞎写', synopsis='简单介绍', content='胡言乱语,一派胡言...')
>>> a1.author=author1
>>> a2.author=author1
>>> a3.author=author2
>>> a1.save()
>>> a2.save()
>>> a3.save()

接下来,我们要使用 django-guardian 框架中给我们提供的 assign_perm() 来将对象的权限赋给相应的 user。通过 assign_perm() 可以给用户赋权,assign_perm() 接收三个参数,分别为permuser_or_group以及object。这里我们将文章1、2的编辑和删除权限赋给 author1,文章3的编辑和删除权限赋给 author2:

# 用户是否拥有对象a1的‘edit’权限
>>> author1.has_perm('edit', a1)
False
>>> author1.has_perm('edit', a2)
False
>>> author1.has_perm('delete', a1)
False
>>> author1.has_perm('delete', a2)
False
>>> from guardian.shortcuts import assign_perm
>>> assign_perm('edit', author1, a1)
<UserObjectPermission: 测试岗位 | author1 | edit>
>>> assign_perm('delete', author1, a1)
<UserObjectPermission: 测试岗位 | author1 | delete>
>>> assign_perm('edit', author1, a2)
<UserObjectPermission: 开发岗位 | author1 | edit>
>>> assign_perm('delete', author1, a2)
<UserObjectPermission: 开发岗位 | author1 | delete>

>>> assign_perm('edit', author2, a3)
<UserObjectPermission: 瞎写 | author2 | edit>
>>> assign_perm('delete', author2, a3)
<UserObjectPermission: 瞎写 | author2 | delete>

django-guardian 中的 get_perms() 方法可以获取用户对 obj 的权限:

# 获取用户对obj的权限    
>>> from guardian.shortcuts import get_perms    
>>> get_perms(author1, a1)
['edit', 'delete']
>>> get_perms(author2, a1)
[]
>>> get_perms(author2, a2)
[]
>>> get_perms(author2, a3)
['edit', 'delete']

get_objects_for_user() 方法用来显示用户具有某个权限的所有对象:

# 显示用户所具有某个权限的所有对象
>>> from guardian.shortcuts import get_objects_for_user
>>> get_objects_for_user(author1,'articles.edit')
<QuerySet [<Article: 测试岗位>, <Article: 开发岗位>]>
>>> get_objects_for_user(author2,'articles.edit')
<QuerySet [<Article: 瞎写>]>
>>> get_objects_for_user(author2,'articles.delete')
<QuerySet [<Article: 瞎写>]>

ObjectPermissionChecker() 方法判断用户对某个对象是否具有相应的权限:

>>> from guardian.core import ObjectPermissionChecker
>>> checker = ObjectPermissionChecker(author1)
>>> checker.has_perm('edit', a1)
True
>>> checker.has_perm('edit', a2)
True
>>> checker.has_perm('edit', a3)
False

当我们需要去除权限时,可以使用 remove_perm() 方法,remove_perm()方法与 assign_perm()方法类似,同样接收三个参数,参数类型也类似,唯一不同的是 assign_perm() 的第二个参数可以是 QuerySet,而 remove_perm() 的第二个参数必须是 instance。

以上是在操作对象是可以判断某个对象是否拥有某个权限并对其进行处理。我们上面已经为用户和文章分配好了相应的权限,接下来我们使用 django-guardian 来完成一个简单的页面:不同的用户只对有权限的文章进行操作。为了简单方便,我只用操作按钮代表相应的操作。我们按照如下的步骤完成代码,来实现这样一个具有权限控制的页面。

准备模板文件,包括登录的页面 login.html 以及文章列表页面 articles.html:

{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" />
<form action="/articles/login/" method="POST">
{% csrf_token %}
<div><span>{{ form.name.label }}</span>{{ form.name }}
<div><span>{{ form.password.label }}</span>{{ form.password }}
<div><input class="input-text input-red" type="submit" value="登录" style="width: 214px"/></div>
{% if err_msg %}
<div><label class="color-red">{{ err_msg }}</label</div>
{% endif %}
</form>
{% load staticfiles %}
{% load guardian_tags %}

<p>登录用户:{{ username }}
<a href="/articles/logout/" style="margin-left:30px">登出</a>
</p>
<div>
<table border="1" class="member-table">
  <thead>
  <tr>
    <th></th>
    <th>文章编号</th>
    <th>文章标题</th>
    <th>文章简介</th>
    <th>文章内容</th>
    <th>发布时间</th>
    <th>操作</th>
  </tr>
  </thead>
  <tbody>
  {% for article in articles %}
  <tr>
    <td></td>
    <td>{{ article.id }}</td>
    <td>{{ article.title }}</td>
    <td>{{ article.synopsis }}</td>
    <td>{{ article.content }}</td>
    <td>{{ article.created_at | date:"Y-m-d H:m:s"}}</td>
    {% get_obj_perms request.user for article as "article_perms" %}
    <td>
    {% if "edit" in article_perms %}
        <a href="/articles/edit/{{ article.id }}/">编辑</a>
    {% endif %}
    {% if "delete" in article_perms %}
        <a href="/articles/delete/{{ article.id }}/">删除</a>
    {% endif %}
    </td>
  </tr>
  {% endfor %}
   </tbody>
</table>
<div >
<div class="page">
</div>
</div>
</div>

注意:首先要加载 guardian_tags 标签库,然后就可以用 get_obj_perms 标签获取当前用户对 obj 的权限。

编写视图,只写登录相关的逻辑,利用了前面的 session 保存用户信息:

# 代码位置:articles/views.py

from django.shortcuts import render, redirect
from django.views.generic import View
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth import authenticate

from articles.models import Article

class LoginForm(forms.Form):
    name = forms.CharField(
        label="账号",
        min_length=4,
        required=True,
        error_messages={'required': '账号不能为空', "min_length": "账号名最短4位"},
        widget=forms.TextInput(attrs={'class': "input-text",
                                      'placeholder': '请输入登录账号'})
    )
    password = forms.CharField(
        label="密码",
        min_length=6,
        max_length=20,
        required=True, 
        error_messages={'required': '密码不能为空', "min_length": "密码最短8位", "max_length": "密码最长20位"},
        widget=forms.TextInput(attrs={'class': "input-text",'placeholder': '请输入密码', 'type': 'password'}),
    )

class LoginView(View):
    def get(self, request, *args, **kwargs):
        success = False
        form = LoginForm()
        if request.session.get('has_login', False):
            logined_user = User.objects.all().get(id=int(request.session['user_id']))
            request.user = logined_user
            articles = Article.objects.all()
            return render(request, "articles.html", {"username":logined_user.username, "articles": articles})
        return render(request, "login.html", {'form': form})

    def post(self, request, *args, **kwargs):
        form = LoginForm(request.POST)
        err_msg = ""
        if form.is_valid():
            login_data = form.clean()
            name = login_data['name']
            password = login_data['password']
            user = authenticate(username=name, password=password)
            if not user:
                success = False
                err_msg = "用户名密码不正确"
            else:
                request.user = user
                request.session['has_login'] = True
                request.session['user_id'] = user.id
                # 设置1000s后过期
                request.session.set_expiry(100)
                articles = Article.objects.all()
                return render(request, "articles.html", {"username": user.username, "articles": articles})
        else: 
            err_msg = "提交表单不正确"    
        return render(request, 'login.html', {'err_msg': err_msg, 'form': form})    

def logout(request, *args, **kwargs):
    if 'has_login' in request.session:
        del request.session["has_login"]
    if 'user_id' in request.session:
        del request.session["user_id"]
    request.session.flush()
    return redirect('/articles/login/')

这里用到了在上一个项目中用来做登录的表单,也使用了相关代码(session 操作)完成登录操作。不过登录的认证使用 Django 内置的 authenticate() 方法来实现的。另外,我们还实现了一个登出的相关操作。

有了这些视图出来,就可以编写 URLConf 配置了。首针对 article 应用,添加登录和登出的 URLConf 配置:

# 代码位置:articles/urls.py

from django.urls import path
from articles import views

urlpatterns = [
    # 登录
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.logout, name='logout'),
]

最后要在 URLConf 配置的总入口添加该应用的 URL 前缀,代码如下:

# test_guardian/urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import include, url

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

准备好上面这些之后,我就可以启动看页面效果了,具体的演示如下视频所示。

可以看到,针对超级用户是有所有文章的修改和删除权限的,而 author1 则能控制文章1和文章2的修改和删除,author2只能对文章3进行编辑和删除,这样就通过 django-guardian 框架实现了在 django 中无法做到的对象权限控制。更多的使用操作以及相关的 API 说明,请参考官方文档

3. 小结

本小节中我们主要实战了 django-celery 和 django-guardian 这两个非常有用的第三方插件。基于这两个插件,我们得以快速完成了一些需求开发。到此为止 ,Django 的第四部分综合介绍篇也就结束了,接下来我会准备一个简单的实战案例,用到我们学到的大部分知识以及相应的第三方框架,来检验我们的学习成果,也希望大家能一步一步动手将这个案例完成并顺利测试。