以最快的速度掌握Django的第二部分

进行中

2017年11月3日 现在
第一章 已完成
第二章 已完成
第三章 已完成

这次要做的事情

我們將擴展上一篇文章中建立的Django應用程序。

未来的扩展具体内容包括

       (前半) Django自体の説明(後半) Django自体の説明+DockerやMySQLなどとの連携1章Django Debug Toolbarの導入DjangoアプリをDocker上で動かしてみる2章ログイン機能の実装MySQLを使用する3章Hijack機能の実装SQL文の効率化と高速化(Django組み込み関数を使う)4章カスタムtag・カスタムfilterSQL文の効率化と高速化(自作関数を使う)5章FormAjaxを使った高度な通信6章自動テストを書こう(追加予定あり)

我打算以这种方式继续进行。我将努力在前半和后半中解释到细微之处。

与第一篇文章不同的是,我打算以随时更新的形式进行发布。

所以,我接受解释请求,如果您有任何要求,请在评论中告诉我。我将尽力根据时间和能力予以配合。

此外,根据章节的内容,可能会因为与该网站的数据不符而需要详细说明文档。

引入Django Debug Toolbar的第一章

尽管与Django本身的功能无关,但由于有与没有会对开发效率产生很大的影响,因此建议引入此功能。

*官方文件/公式文件

我們將根據上面的官方文件進行導入。

我会先安装它。

$ pip install django-debug-toolbar

请在urls.py文件中添加以下内容(请参考GitHub仓库中的合并结果和上一次提交的内容!)

from django.conf import settings
from django.conf.urls import include, url

if settings.DEBUG:
    import debug_toolbar
    urlpatterns += [
        url(r'^__debug__/', include(debug_toolbar.urls)),
    ]

只有在settings.py中DEBUG=True时,才会出现Debug Toolbar。

让我告诉Django在settings.py中安装的应用程序。 wǒ Django settings.py de .)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',  # これがあることを確認(なければ追加)
    'debug_toolbar',  # 追加部分
    'manager',
]

将调试工具栏置于中间件中。

大概只需要更改MIDDLEWARE就可以了,但是我不明白为什么不添加到MIDDLEWARECLASS中,Debug Toolbar就无法显示出来。我想写出解释(但不知道原因,希望知道的人能指点一下)。

MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'debug_toolbar.middleware.DebugToolbarMiddleware',  # 追加
]

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'debug_toolbar.middleware.DebugToolbarMiddleware',  # 追加
]

接下来,在settings.py文件中添加以下内容,以便出现Debug Toolbar。

# Debug Toolbar

DEBUG = True

if DEBUG:
    INTERNAL_IPS = ['127.0.0.1', 'localhost']

    def custom_show_toolbar(request):
        return True

    DEBUG_TOOLBAR_PANELS = [
        'debug_toolbar.panels.timer.TimerPanel',
        'debug_toolbar.panels.request.RequestPanel',
        'debug_toolbar.panels.sql.SQLPanel',
        'debug_toolbar.panels.templates.TemplatesPanel',
        'debug_toolbar.panels.cache.CachePanel',
        'debug_toolbar.panels.logging.LoggingPanel',
    ]

    DEBUG_TOOLBAR_CONFIG = {
        'INTERCEPT_REDIRECTS': False,
        'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
        'HIDE_DJANGO_SQL': False,
        'TAG': 'div',
        'ENABLE_STACKTRACES': True,
    }

设置已完成。

然后,我会下载 Debug Toolbar 的静态文件。

请在主目录(我自己随意地称之为”master directory”,以下同样)中运行以下命令,以下载静态文件。

$ python manage.py collectstatic

这样一来,就会创建一个名为assets的目录(根据我看到的说明,它应该是在static目录中创建的,但是因为设置了相应的配置,所以大概默认情况下会在assets目录中创建),所以我们将debug_toolbar文件夹完整地复制到其中的static目录下。

不需要assets目录,所以在复制后应该将其删除。

让我们看一下以上的内容,确认Django Debug Toolbar的设置已完成并且是否成功地显示出来。

スクリーンショット 2017-08-05 18.41.30.png

我已经成功地显示出了这种感觉。

我正在查看/worker_list/页面,但发现加载时间相当长。

这个网站太糟糕了!执行了1001次SQL查询,花费了整整6.48秒来显示结果。

点击SQL部分,看看实际投入了什么样的查询。(点击可能频繁投入的部分的+号,可以看到其内容)

スクリーンショット 2017-08-05 18.44.24.png

由於後半部分將在之後進行解釋,因此本次將省略詳細內容。由於速度太慢,所以只提供解決方案。

请将views.py文件中创建查询的部分更改为以下内容。

class WorkerListView(TemplateView):
    template_name = "worker_list.html"

    def get(self, request, *args, **kwargs):
        context = super(WorkerListView, self).get_context_data(**kwargs)

        workers = Worker.objects.all().select_related('person')  # 変更部分
        context['workers'] = workers

        return render(self.request, self.template_name, context)

让我们重新加载一下,看看效果。

这次查询的数量变为了1,并且显示时间缩短到了0.25秒!

这感觉就是典型的N+1问题吧!

实现登录功能的第二章

我们将实施不可或缺的登录功能于网络应用中。

* 官方文件

由于对models.py进行了根本性的修改,为了防止出现错误,我们应删除上次创建的迁移文件和数据库。

(这次我们要使Person能够登录,并且进行了各种更改,所以很特殊。但是,偶尔可能会遇到需要清空数据库的情况,所以我们会介绍一下方法。)

虽然如此,由于Django的标准设置是使用sqlite3,所以只需删除文件即可!

请删除manager/migrations目录下除了__init__.py以外的所有文件,以及db.sqlite3文件。

只需要这个就可以了!

我们马上开始实施登录功能!

概览图

スクリーンショット 2017-08-12 14.15.31.png

请参考上方图表获取概要信息!

实现登录功能

首先,我们将修改Person模型。

由于代码只针对重要的部分进行了编写,请想要查看全部内容的人请访问我的GitHub代码库!

from django.contrib.auth.models import AbstractBaseUser
from manager.managers import PersonManager

class Person(AbstractBaseUser):  #1
    objects = PersonManager()  # 2

    identifier = models.CharField(max_length=64, unique=True, blank=False)  # 3
    name = models.CharField(max_length=128)
    email = models.EmailField()

    is_active = models.BooleanField(default=True)  # 必要です!

    USERNAME_FIELD = 'identifier'  # 4

1) 为了登录功能,继承AbstractBaseUser。
2) 定义使得在Person.objects.create()时也能正常创建(PersonManager将在下文中写出)。
3) 添加账号名的列。
4) 由于没有username这一列,请告诉Django使用identifier作为替代。


请创建一个名为 manager/managers.py 的文件,并在其中编写 PersonManager。

from django.contrib.auth.models import BaseUserManager
from django.utils import timezone


class PersonManager(BaseUserManager):

    def create_user(self, identifier, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Users must have an email address')

        email = PersonManager.normalize_email(email)
        person = self.model(
            identifier=identifier,
            email=email,
            **extra_fields
        )
        person.set_password(password)
        person.save(using=self._db)

        return person

接下来,在views.py中编写处理登录页面的代码。

from django.contrib.auth.views import login
from django.contrib.auth import authenticate

class CustomLoginView(TemplateView):
    template_name = "login.html"

    def get(self, _, *args, **kwargs):
        if self.request.user.is_authenticated():
            return redirect(self.get_next_redirect_url())
        else:
            kwargs = {'template_name': 'login.html'}
            return login(self.request, *args, **kwargs)

    def post(self, _, *args, **kwargs):
        username = self.request.POST['username']
        password = self.request.POST['password']
        user = authenticate(username=username, password=password)  # 1
        if user is not None:
            login(self.request, user)
            return redirect(self.get_next_redirect_url())
        else:
            kwargs = {'template_name': 'login.html'}
            return login(self.request, *args, **kwargs)

    def get_next_redirect_url(self):
        redirect_url = self.request.GET.get('next')
        if not redirect_url or redirect_url == '/':
            redirect_url = '/worker_list/'
        return redirect_url

这里是关键的一点!正在对用户进行认证。
我会稍微解释一下这个authenticate函数在做什么。

让我们来看一下Django的代码。
在django/contrib/auth/backends.py文件中定义了authenticate函数。

def authenticate(self, request, username=None, password=None, **kwargs):
    if username is None:
        username = kwargs.get(UserModel.USERNAME_FIELD)  # 2
    try:
        user = UserModel._default_manager.get_by_natural_key(username)  # 3
    except UserModel.DoesNotExist:
        # Run the default password hasher once to reduce the timing
        # difference between an existing and a nonexistent user (#20760).
        UserModel().set_password(password)
    else:
        if user.check_password(password) and self.user_can_authenticate(user):  # 4
            return user

这段代码很厉害吧!哈哈
虽然在if和else之间插入了try-except,但是很难理解它会如何处理。
简单地说,这里的技巧是即使if条件满足,else的代码仍会执行。请您自行创建简单的函数来验证!

如果没有username列,则会获取在USERNAME_FIELD中定义的列。

3) 通过使用用户名作为键获取对象。

4) 正在检查密码是否正确


为了启用这些功能,需要在setting.py中添加以下内容!

可能是因为我没有仔细阅读,但是我记得有些东西不是在文档或stack overflow上写的,让我感到困惑。
这种时候,你可以查看Django的源代码,确认运行的是什么处理,这样就能解决问题了!

# user authentication

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
)

AUTH_USER_MODEL = 'manager.Person'

剩下的工作是在urls.py中定义了登录用的url,并添加了login.html。
由于内容较长,请查看GitHub仓库以获取文件!

由于清空了迁移文件和数据文件,我们需要重新创建。
在主目录下执行以下操作。

$ python manage.py makemigrations
$ python manage.py migrate

让我们来确认一下,你能否成功登录!我将使用一个人的账号进行登录试试看。

$ python manage.py shell

# from manager.models import *
# import datetime
# person = Person(identifier="gragragrao", name="gragragrao", email="example@gmail.com", sex=0, birthday=datetime.date.today(), address_from=21, current_address=21)
# person.set_password("grao_pass")
# person.save()

现在可以登录了!用户名是gragragrao,密码是grao_pass。

让我们尝试登录一下,看看能否成功!(请注意也对WorkerListView进行了少许更改。)

https://gyazo.com/f34a3c30c2f92a36d33ad87cdc9b5336

我想创建一个用于注销的终点,并结束本章。 (由于注册页面涉及表单,我打算稍后创建它。)

退出登录

<ul class="nav" id="side-menu">
  <li><a href="/worker_list/"><i class="fa fa-bar-chart" aria-hidden="true"></i>  Worker一覧</a></li>
  <li><a href="/logout/"><i class="fa fa-bar-chart" aria-hidden="true"></i>  ログアウト</a></li>
</ul>
from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    return redirect('/login/')
from django.contrib.auth.decorators import login_required  # そのページに飛ぶ時、ログインを要求される

urlpatterns = [
    url(r'^logout/', manager_view.logout_view),
    url(r'^worker_list/', login_required(manager_view.WorkerListView.as_view())),
]

这下子第二章终于结束了。

如果有任何问题,无法顺利进行,请在评论中告知,因为这太复杂了。

第三章:实施劫持功能

在这一章中,我们将实现Hijack功能。

我认为,如果我是管理员,可能会有一些场合想以注册用户的身份登录。

在这种情况下,即使不知道注册用户的密码,也可以通过劫持功能登录。

因为它很简单,所以让我们迅速地实现它!

我会按照文件所示的内容进行操作。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'debug_toolbar',
    'manager',
    'hijack',
    'compat',
]

# hijack
HIJACK_LOGIN_REDIRECT_URL = '/worker_list/'
HIJACK_LOGOUT_REDIRECT_URL = '/worker_list/'
HIJACK_ALLOW_GET_REQUESTS = True

HIJACK_USE_BOOTSTRAP = True

添加“hijack”和依赖项“compat”到“INSTALLED_APPS”中。

在django-admin的配置页面上,有各种设置选项。

設定説明DefaultHIJACK_DISPLAY_WARNINGhijackしていることを示す黄色いバーを表示するかどうかTrueHIJACK_USE_BOOTSTRAPBootstrapに最適化するかどうかFalseHIJACK_URL_ALLOWED_ATTRIBUTESuserがどの属性を通してHijackされることができるか下記参照HIJACK_AUTHORIZE_STAFFis_staff=True のuserが他の is_staff=False のuserに対してhijackできるかFalseHIJACK_AUTHORIZE_STAFF_TO_HIJACK_STAFFis_staff=True のuserが他の is_staff=True のuserに対してhijackできるかFalseHIJACK_LOGIN_REDIRECT_URLhijackした時にどのURLにリダイレクトされるかsettings.LOGIN_REDIRECT_URLHIJACK_LOGOUT_REDIRECT_URLhijackをreleaseした時にどのURLにリダイレクトされるかsettings.LOGIN_REDIRECT_URLHIJACK_AUTHORIZATION_CHECKhijackできる権限を決める関数’hijack.helpers.is_authorized_default’ (下記参照)HIJACK_ALLOW_GET_REQUESTShijackがGETできるかどうかFalse

关于 HIJACK_URL_ALLOWED_ATTRIBUTES

默认:(’用户ID’,’电子邮件’,’用户名’)

Django可以处理的URL如下所示。

^hijack/ ^email/(?P<email>[^@]+@[^@]+\.[^@]+)/$ [name='login_with_email']
^hijack/ ^username/(?P<username>.*)/$ [name='login_with_username']
^hijack/ ^(?P<user_id>[\w-]+)/$ [name='login_with_id']

换句话说,如果email字段是example@example.com的人,可以通过/hijack/email/example.com/进行劫持对其进行操作。
我查看了Django-hijack中的代码,除了默认的列之外,其他的列是无法使用的。如果只想通过email进行劫持操作,可以将其设置为(’email’)(感觉无法增加列,只能减少列的数量)。

关于HIJACK_AUTHORIZATION_CHECK的检查

默认情况下:’hijack.helpers.is_authorized_default’

以下是默认函数的样式↓

def is_authorized_default(hijacker, hijacked):
    if hijacker.is_superuser:
        return True

    if hijacked.is_superuser:
        return False

    if hijacker.is_staff and hijack_settings.HIJACK_AUTHORIZE_STAFF:
        if hijacked.is_staff and not hijack_settings.HIJACK_AUTHORIZE_STAFF_TO_HIJACK_STAFF:
            return False
        return True

    return False

本人将为以下被用于劫持的属性进行添加。

class Person(AbstractBaseUser):
    # hijack機能の実装に必要
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=True)
    is_superuser = models.BooleanField(default=False)

我将在URL中添加以下内容。

url(r'^hijack/', include('hijack.urls')),

在 base.html 的 header 中加入 {% load hijack_tags %} 和 ,并在 body 的直接下方添加 {% hijack_notification %}。

如果对这一部分的实现不太理解的话,请参考我的GitHub代码。

我们的实施到此为止。
让我们确保它正常运行。

$ python manage.py makemigrations
$ python manage.py migrate

在数据库中应用模型更改,并按以下方式创建具有 is_superuser=True 的 Person(假设这是管理员)。

$ python manage.py shell

# from manager.models import *
# import datetime
# person = Person(identifier='grao_super', name='grao_super', email='grao@example.com', birthday=datetime.datetime(1990, 11, 3), sex=1, address_from=21, current_address=21, is_superuser=True)
# person.set_password('grao_pass')
# person.save()

这样就完成了。

如果没有is_superuser为False的Person存在,请适当创建。

接下来要劫持的人是 id=1,email=’example@example.com’(唯一值)。

在这种情况下,如果使用is_superuser=True登录到Person(在上面创建的grao_super)之后,打开hijack的URL,就可以劫持。

# python manage.py runserver 8080

登录后,

http://localhost:8080/hijack/1/
http://localhost:8080/hijack/email/example@example.com/

当您发送请求时,您可以通过劫持来执行以下操作。

image.png

点击“释放gragragrao”即可解除劫持。

就是这样。

广告
将在 10 秒后关闭
bannerAds