以最快的速度掌握Django的第二部分
进行中
2017年11月3日 现在
第一章 已完成
第二章 已完成
第三章 已完成
这次要做的事情
我們將擴展上一篇文章中建立的Django應用程序。
未来的扩展具体内容包括
我打算以这种方式继续进行。我将努力在前半和后半中解释到细微之处。
与第一篇文章不同的是,我打算以随时更新的形式进行发布。
所以,我接受解释请求,如果您有任何要求,请在评论中告诉我。我将尽力根据时间和能力予以配合。
此外,根据章节的内容,可能会因为与该网站的数据不符而需要详细说明文档。
引入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的设置已完成并且是否成功地显示出来。
我已经成功地显示出了这种感觉。
我正在查看/worker_list/页面,但发现加载时间相当长。
这个网站太糟糕了!执行了1001次SQL查询,花费了整整6.48秒来显示结果。
点击SQL部分,看看实际投入了什么样的查询。(点击可能频繁投入的部分的+号,可以看到其内容)
由於後半部分將在之後進行解釋,因此本次將省略詳細內容。由於速度太慢,所以只提供解決方案。
请将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文件。
只需要这个就可以了!
我们马上开始实施登录功能!
概览图
请参考上方图表获取概要信息!
实现登录功能
首先,我们将修改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进行了少许更改。)
我想创建一个用于注销的终点,并结束本章。 (由于注册页面涉及表单,我打算稍后创建它。)
退出登录
<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的配置页面上,有各种设置选项。
is_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/
当您发送请求时,您可以通过劫持来执行以下操作。
点击“释放gragragrao”即可解除劫持。
就是这样。