在本教程中,我们将向您展示如何允许用户使用自己的帐户登录到您的网站,以及如何根据用户是否已登录及其权限来控制他们可以执行和查看的内容。作为演示的一部分,我们将扩展LocalLibrary网站,添加登录页面和注销页面,以及用户和员工特定的页面以查看已借阅的图书。

先决条件: 完成之前的所有教程主题,包括Django 教程 7:Sessions 框架。
目的: 了解如何设置和使用用户身份验证和权限。

概观

Django 提供了一个身份验证和授权(“权限”)系统,该系统构建在上一个教程中讨论的会话框架之上,允许您验证用户凭据,并定义每个用户可允许执行的操作。该框架包括用户Users和分组Groups的内置模型(一次向多个用户应用权限的通用方法),用于登录用户的权限/标志,以指定用户是否可以执行任务,表单和视图,以及查看限制内容的工具。

注意: Django身份验证系统的目标非常通用,因此不提供其他Web身份验证系统中,所提供的某些功能。某些常见问题的解决方案,可作为第三方软件包提供。例如,限制登录尝试,和针对第三方的身份验证(例如 OAuth)。

在本教程中,我们将向您展示,如何在LocalLibrary网站中,启用用户身份验证,创建您自己的登录和注销页面,为模型添加权限,以及控制对页面的访问。我们将使用身份验证/权限,来显示用户和图书馆员借用图书的列表。

身份验证系统非常灵活,您可以根据需要,从头开始构建 URLs,表单,视图和模板,只需调用提供的API,即可登录用户。但是,在本文中,我们将在登录和注销页面,使用 Django 的“库存” 身份验证视图和表单。我们仍然需要创建一些模板,但这很简单。

我们还将向您展示如何创建权限,以及检查视图和模板中的登录状态和权限。

启用身份验证

我们在创建框架网站时(在教程2中),自动启用了身份验证,因此您此时,无需再执行任何操作。

注意: 当我们使用 django-admin startproject 命令,以创建应用程序时,所有必要的配置都已完成。当我们第一次调用 python manage.py migrate 时,创建了用户和模型权限的数据库表。

配置在项目文件(locallibrary/locallibrary/settings.py)的INSTALLED_APPSMIDDLEWARE部分中设置,如下所示:

<span style="color:#333333"><code class="language-python">INSTALLED_APPS = [...
<strong>    'django.contrib.auth',  </strong>#Core authentication framework and its default models.
<strong>    'django.contrib.contenttypes',  #</strong>Django content type system (allows permissions to be associated with models).....MIDDLEWARE = [...
<strong>    'django.contrib.sessions.middleware.SessionMiddleware',</strong>  #Manages sessions across requests...
<strong>    'django.contrib.auth.middleware.AuthenticationMiddleware',</strong>  #Associates users with requests using sessions.....</code></span>

创建用户和分组

在教程 4 中,当我们查看 Django 管理站点时,您已经创建了第一个用户(这是一个超级用户,使用命令 python manage.py createsuperuser 创建)。我们的超级用户已经过身份验证,并拥有所有权限,因此我们需要创建一个测试用户,来代表普通网站用户。我们将使用管理站点,来创建我们的 locallibrary 组別和网站登录,因为这是最快的方法之一。

注意: 您还可以用编程方式创建用户,如下所示。您会必须这样做,例如,如果要开发一个界面,能允许用户创建自己的登录(您不应该授予用户访问管理站点的权限)。

<span style="color:#333333"><span style="color:#333333"><code class="language-python">from django.contrib.auth.models import User# Create user and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')# Update fields and then save again
user.first_name = 'John'
user.last_name = 'Citizen'
user.save()</code></span></span>

下面,我们首先创建一个分组,然后创建一个用户。即使我们还没有为我们的图书馆成员添加任何权限,如果我们以后需要,将它们添加到分组中,要比单独添加到每个成员要容易得多。

启动开发服务器,并到本地 Web 浏览器中的管理站点(http://127.0.0.1:8000/admin/)。使用超级用户帐户的凭据,登录该站点。 Admin 站点的最上级显示所有模型,按 “django application” 排序。在 “身份验证和授权” Authentication and Authorisation 部分 ,您可以单击用户 Users ,或分组 Groups 链接,以查看其现有记录。

首先,我们为图书馆成员,创建一个新的分组。

  1. 单击“添加” Add 按钮(“分组” Group 旁边)以创建新的分组;在分组的名称 Name ,输入“Library Members”。
  2. 我们不需要该组的任何权限,因此只需按SAVE(您将进入分组列表)。

现在让我们创建一个用户:

  1. 回到管理站点的主页
  2. 单击“用户”旁边的“添加”按钮 Add,以打开“添加用户”对话框。
  3. 为测试用户输入适当的用户名(Username)和密码/密码确认(Password/Password confirmation )
  4. 按 SAVE 创建用户。

    管理站点将创建新用户,并立即转到 “更改用户” 屏幕,您可以在其中更改用户名(username),并添加用户模型的可选字段的信息。这些字段包括名字,姓氏,电子邮件地址,用户状态和权限(仅应设置活动标志 Active)。再往下,您可以指定用户的分组和权限,并查看与用户相关的重要日期(例如,他们的加入日期和上次登录日期)。

  5. 在“分组”(Groups)部分中,从“可用分组”(Available groups)列表中,选择“图书馆成员”分组 Library Member,然后点击这些框之间的右箭头,将其移动到“选择的分组”(Chosen groups)框中。
  6. 我们不需要在此处执行任何其他操作,因此只需再次选择 SAVE ,即可转到用户列表。

就是这样!现在您有一个 “普通的图书馆成员” 帐户,您可以使用该帐户进行测试(一旦我们实现了页面,使他们能够登录)。

注意: 您应该尝试创建另一个图书馆用户。此外,为图书馆管理员创建一个分组,并添加一个用户!

设置身份验证视图

Django 提供了创建身份验证页面所需的几乎所有功能,让处理登录,注销和密码管理等工作,都能 “开箱即用”。这些相关功能包括了 url 映射器,视图和表单,但它不包括模板 - 我们必须创建自己的模板!

在本节中,我们将展示如何将默认系统,集成到 LocalLibrary 网站并创建模板。我们将它们放在主项目的 URL 当中。

注意: 您不必一定要使用这些代码,但您可能希望这样做,因为它使事情变得更容易。如果更改用户模型(高级主题!),您几乎肯定需要更改表单处理代码。但即便如此,您仍然可以使用先前已经有的视图功能。

注意: 在这种情况下,我们可以合理地将认证页面(包括URL和模板)放在我们的目录应用程序中。但是,如果我们有多个应用程序,最好将这个共享登录行为分开,并让它在整个站点上可用,这就是我们在这里展示的内容!

项目网址

将以下内容,添加到项目 urls.py(locallibrary/locallibrary/urls.py)文件的底部:

<span style="color:#333333"><code class="language-python">#Add Django site authentication urls (for login, logout, password management)
urlpatterns += [path('accounts/', include('django.contrib.auth.urls')),
]</code></span>

打开 URL http://127.0.0.1:8000/accounts/ (注意前面的斜杠!),Django将显示一个错误,它无法找到此URL,并列出它尝试过的所有URL。从中您可以看到可以使用的URL,例如:

注意: 使用上面的方法,添加以下带有方括号中的名称的 URL,可用于反转 URL 映射。您不必实现任何其他内容 - 上面的 url 映射,会自动映射下面提到的URL。

<span style="color:#333333"><span style="color:#333333"><code class="language-python">accounts/ login/ [name='login']
accounts/ logout/ [name='logout']
accounts/ password_change/ [name='password_change']
accounts/ password_change/done/ [name='password_change_done']
accounts/ password_reset/ [name='password_reset']
accounts/ password_reset/done/ [name='password_reset_done']
accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/ reset/done/ [name='password_reset_complete']</code></span></span>

现在尝试打开登录 URL(http://127.0.0.1:8000/accounts/login/)。这将再次失败,但有一个错误告诉您,我们在模板搜索路径上缺少必需的模板(registration/login.html)。您将在顶部的黄色部分中,看到以下文字:

<span style="color:#333333"><code class="language-python">Exception Type:    TemplateDoesNotExist
Exception Value:    <strong>registration/login.html</strong></code></span>

下一步是在搜索路径上创建注册目录,然后添加 login.html 文件。

模板目录

我们希望在模板搜索路径中的目录 /registration/ 某处,找到刚刚添加的 url(以及隐式视图)的关联模板。

对于此站点,我们将 HTML 页面,放在 templates/registration/ 目录中。此目录应该位于项目的根目录中,即与 catalog 和 locallibrary 文件夹相同的目录)。请立即创建这些文件夹。

注意: 您的文件夹结构,现在应如下所示:
locallibrary (django project folder)
   |_catalog
   |_locallibrary
   |_templates (new)
                |_registration

要使这些目录对模板加载器可见(即将此目录放在模板搜索路径中),请打开项目设置(/locallibrary/locallibrary/settings.py),并更新TEMPLATES 部分的 “DIRS” 那一行,如下所示。

<span style="color:#333333"><code class="language-python">TEMPLATES = [{...
<strong>        'DIRS': ['./templates',],</strong>'APP_DIRS': True,...</code></span>

登录模板

重要说明: 本文提供的身份验证模板,是 Django 演示登录模板的基本/略微修改版本。您可能需要自定义它们,以供自己使用!

创建一个名为 /locallibrary/templates/registration/login.html 的新HTML文件。为它加入以下内容:

<span style="color:#333333"><code class="language-html">{% extends "base_generic.html" %}{% block content %}{% if form.errors %}
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>Your username and password didn't match. Please try again.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>
{% endif %}{% if next %}{% if user.is_authenticated %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>Your account doesn't have access to this page. To proceed,please login with an account that has access.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>{% else %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>Please login to see this page.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>{% endif %}
{% endif %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>form</span> <span style="color:#669900">method</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>post<span style="color:#999999">"</span></span> <span style="color:#669900">action</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{% url <span style="color:#999999">'</span>login<span style="color:#999999">'</span> %}<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>
{% csrf_token %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>div</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.username.label_tag }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.username }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>div</span><span style="color:#999999">></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>div</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.password.label_tag }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.password }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>div</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>div</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>input</span> <span style="color:#669900">type</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>submit<span style="color:#999999">"</span></span> <span style="color:#669900">value</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>login<span style="color:#999999">"</span></span> <span style="color:#999999">/></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>input</span> <span style="color:#669900">type</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>hidden<span style="color:#999999">"</span></span> <span style="color:#669900">name</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>next<span style="color:#999999">"</span></span> <span style="color:#669900">value</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{{ next }}<span style="color:#999999">"</span></span> <span style="color:#999999">/></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>div</span><span style="color:#999999">></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>form</span><span style="color:#999999">></span></span>{# Assumes you setup the password_reset view in your URLconf #}
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>a</span> <span style="color:#669900">href</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{% url <span style="color:#999999">'</span>password_reset<span style="color:#999999">'</span> %}<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>Lost password?<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>a</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>{% endblock %}</code></span>

此模板与我们之前看到的模板,有一些相似之处 - 它扩展了我们的基本模板,并覆盖了内容区块 content。其余代码,是相当标准的表单处理代码,我们将在后面的教程中讨论。您现在需要知道的是,这将显示一个表单,您可以在其中输入您的用户名和密码,如果您输入的值无效,则会在页面刷新时,提示您输入正确的值。

保存模板后,回到登录页面(http://127.0.0.1:8000/accounts/login/),您应该看到如下内容:

如果您尝试登录,将会成功,并且您将被重定向到另一个页面(默认情况下,这将是 http://127.0.0.1:8000/accounts/profile/)。这里的问题是,默认情况下,Django希望在登录后,你可能会被带到个人资料页面,这可能是,也可能不是。由于您还没有定义此页面,您将收到另一个错误!

打开项目设置(/locallibrary/locallibrary/settings.py),并将下面的文本添加到底部。现在登录时,您应该默认重定向到站点主页。

<span style="color:#333333"><code class="language-python"># Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = '/'</code></span>

登出模板

如果您打开登出网址(http://127.0.0.1:8000/accounts/logout/),那么您会看到一些奇怪的行为 - 您所属的用户肯定会被登出,但您将被带到管理员登出页面。这不是您想要的,只是因为该页面上的登录链接,将您带到管理员登录屏幕(并且仅对具有is_staff权限的用户可用)。

创建并打开 /locallibrary/templates/registration/logged_out.html。将下面的文字,复制到文档中:

<span style="color:#333333"><code class="language-html">{% extends "base_generic.html" %}{% block content %}
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>Logged out!<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>  <span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>a</span> <span style="color:#669900">href</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{% url <span style="color:#999999">'</span>login<span style="color:#999999">'</span>%}<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>Click here to login again.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>a</span><span style="color:#999999">></span></span>
{% endblock %}</code></span>

这个模板非常简单。它只显示一条消息,通知您已登出,并提供一个链接,您可以点击此按钮,返回登录屏幕。如果再次回到登出 URL,您应该看到此页面:

密码重置模板

默认密码重置系统,使用电子邮件向用户发送重置链接。您需要创建表单,以获取用户的电子邮件地址,发送电子邮件,允许他们输入新密码,以及记录整个过程的完成时间。

以下模板可作为起点。

密码重置表单

这是用于获取用户电子邮件地址的表单(用于发送密码重置电子邮件)。创建 /locallibrary/templates/registration/password_reset_form.html,并为其提供以下内容:

<span style="color:#333333"><code class="language-html">{% extends "base_generic.html" %}
{% block content %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>form</span> <span style="color:#669900">action</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span><span style="color:#999999">"</span></span> <span style="color:#669900">method</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>post<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>{% csrf_token %}{% if form.email.errors %} {{ form.email.errors }} {% endif %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>{{ form.email }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span> <span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>input</span> <span style="color:#669900">type</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>submit<span style="color:#999999">"</span></span> <span style="color:#669900">class</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>btn btn-default btn-lg<span style="color:#999999">"</span></span> <span style="color:#669900">value</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>Reset password<span style="color:#999999">"</span></span> <span style="color:#999999">/></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>form</span><span style="color:#999999">></span></span>{% endblock %}</code></span>

密码重置完成

收集您的电子邮件地址后,会显示此表单。创建 /locallibrary/templates/registration/password_reset_done.html,并为其提供以下内容:

<span style="color:#333333"><code class="language-html">{% extends "base_generic.html" %}
{% block content %}
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>
{% endblock %}</code></span>

密码重置电子邮件

此模板提供 HTML 电子邮件的文本,其中包含我们将发送给用户的重置链接。创建 /locallibrary/templates/registration/password_reset_email.html,并为其提供以下内容:

<span style="color:#333333"><code class="language-html">Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}</code></span>

密码重置确认

点击密码重置电子邮件中的链接后,您可以在此页面输入新密码。创建 /locallibrary/templates/registration/password_reset_confirm.html,并为其提供以下内容:

<span style="color:#333333"><code class="language-html">{% extends "base_generic.html" %}{% block content %}{% if validlink %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>Please enter (and confirm) your new password.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>form</span> <span style="color:#669900">action</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span><span style="color:#999999">"</span></span> <span style="color:#669900">method</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>post<span style="color:#999999">"</span></span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>div</span><span style="color:#669900"> <span style="color:#669900">style</span></span><span style="color:#999999">="</span><span style="color:#0077aa"><span style="color:#990055">display</span><span style="color:#999999">:</span>none</span><span style="color:#999999">"</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>input</span> <span style="color:#669900">type</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>hidden<span style="color:#999999">"</span></span> <span style="color:#669900">value</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{{ csrf_token }}<span style="color:#999999">"</span></span> <span style="color:#669900">name</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>csrfmiddlewaretoken<span style="color:#999999">"</span></span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>div</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>table</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>tr</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.new_password1.errors }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>label</span> <span style="color:#669900">for</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>id_new_password1<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>New password:<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>label</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.new_password1 }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>tr</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>tr</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.new_password2.errors }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>label</span> <span style="color:#669900">for</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>id_new_password2<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>Confirm password:<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>label</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span>{{ form.new_password2 }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>tr</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>tr</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>input</span> <span style="color:#669900">type</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>submit<span style="color:#999999">"</span></span> <span style="color:#669900">value</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>Change my password<span style="color:#999999">"</span></span> <span style="color:#999999">/></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>td</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>tr</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>table</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>form</span><span style="color:#999999">></span></span>{% else %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>h1</span><span style="color:#999999">></span></span>Password reset failed<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>h1</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>{% endif %}{% endblock %}</code></span>

密码重置完成

这是最后一个密码重置模板,显示该模板,以在密码重置成功时通知您。创建 /locallibrary/templates/registration/password_reset_complete.html,并为其提供以下内容:

<span style="color:#333333"><code class="language-html">{% extends "base_generic.html" %}
{% block content %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>h1</span><span style="color:#999999">></span></span>The password has been changed!<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>h1</span><span style="color:#999999">></span></span>
<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>p</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>a</span> <span style="color:#669900">href</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{% url <span style="color:#999999">'</span>login<span style="color:#999999">'</span> %}<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>log in again?<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>a</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>p</span><span style="color:#999999">></span></span>{% endblock %}</code></span>

测试新的身份验证页面

现在您已经添加了 URL 配置,并创建了所有模板,现在认证页面应该可以正常工作了!

您可以尝试登录,然后使用以下 URL 登出超级用户帐户,来测试新的身份验证页面:

  • http://127.0.0.1:8000/accounts/login/
  • http://127.0.0.1:8000/accounts/logout/

您将能够从登录页面中的链接,测试密码重置功能。请注意,Django只会向已存储在其数据库中的地址(用户)发送重置电子邮件!

注意: 密码重置系统,要求您的网站支持电子邮件,这超出了本文的范围,因此该部分将无法使用。要测试此功能,请将以下一行放在 settings.py 文件的末尾。这会记录发送到命令行控制台的所有电子邮件(因此您可以从命令行控制台,复制密码重置链接)。

<span style="color:#333333"><span style="color:#333333"><code class="language-python">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'</code></span></span>

有关更多信息,请参阅发送电子邮件(Django文档)。

测试已验证身份的用户

本节介绍如何根据用户是否登录,来有选择地控制用户看到的内容。

在模板中测试

您可以使用{{ user }}模板变量,以获取有关模板中,当前登录用户的信息(默认情况下,在我们在骨架中设置项目时,会将其添加到模板上下文中)。

通常,您将首先针对 {{ user.is_authenticated }} 模板变量进行测试,以确定用户是否有资格查看特定内容。为了展示这一点,接下来我们将更新侧边栏,以在用户登出时,显示“登录”链接,如果他们已登录,则显示“登出”链接。

打开基本模板(/locallibrary/catalog/templates/base_generic.html),并将以下文本,复制到侧边栏区块sidebar中,紧接在endblock模板标记之前。

<span style="color:#333333"><code class="language-html"><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>ul</span> <span style="color:#669900">class</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>sidebar-nav<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>...{% if user.is_authenticated %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>li</span><span style="color:#999999">></span></span>User: {{ user.get_username }}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>li</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>li</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>a</span> <span style="color:#669900">href</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{% url <span style="color:#999999">'</span>logout<span style="color:#999999">'</span>%}?next<span style="color:#999999">=</span>{{request.path}}<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>Logout<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>a</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>li</span><span style="color:#999999">></span></span>   {% else %}<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>li</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"><</span>a</span> <span style="color:#669900">href</span><span style="color:#0077aa"><span style="color:#999999">=</span><span style="color:#999999">"</span>{% url <span style="color:#999999">'</span>login<span style="color:#999999">'</span>%}?next<span style="color:#999999">=</span>{{request.path}}<span style="color:#999999">"</span></span><span style="color:#999999">></span></span>Login<span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>a</span><span style="color:#999999">></span></span><span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>li</span><span style="color:#999999">></span></span>   {% endif %} <span style="color:#990055"><span style="color:#990055"><span style="color:#999999"></</span>ul</span><span style="color:#999999">></span></span></code></span>

如您所见,我们使用 if-else-endif模板标签,根据 {{ user.is_authenticated }}是否为 true ,来有条件地显示文本。如果用户已通过身份验证,那么我们知道,我们拥有有效用户,因此我们会调用 {{ user.get_username }} ,来显示其名称。

我们使用 url模板标记,和相应 URL 配置的名称,创建登录和登出链接 URL。另外请注意,我们如何将 “?next={{request.path}}附加到URL的末尾。这样做,是将包含当前页面地址(URL)的URL参数,添加到链接URL的末尾。用户成功登录/登出后,视图将使用此“下一个”值,将用户重定向,回到他们首次单击登录/登出链接的页面。

注意: 试试吧!如果您在主页上,并单击侧栏中的“登录/登出”,在操作完成后,您应该返回到同一页面。

在视图中测试

如果您正在使用基于函数的视图,则限制访问函数的最简单方法,是将login_required装饰器,应用于您的视图函数,如下所示。如果用户已登录,则您的视图代码将正常执行。

如果用户未登录,则会重定向到项目设置 (settings.LOGIN_URL)中定义的登录URL,并将当前绝对路径,作为URL参数("下一个"next)来传递。如果用户成功登录,则会返回到此页面,但这次会进行身份验证。

<span style="color:#333333"><code class="language-python">from django.contrib.auth.decorators import login_required@login_required
def my_view(request):...</code></span>

注意: 您可以通过request.user.is_authenticated,测试手动执行类似的操作,但装饰器更方便!

同样,在基于类别的视图中,限制对登录用户的访问的最简单方法,是从LoginRequiredMixin派生。您需要在主视图类之前的超类列表中,首先声明此 mixin。

<span style="color:#333333"><code class="language-python">from django.contrib.auth.mixins import LoginRequiredMixinclass MyView(LoginRequiredMixin, View):...</code></span>

这与login_required装饰器,具有完全相同的重定向行为。如果用户未经过身份验证(login_url),还可以指定一个替代位置,以将用户重定向到该位置,并使用URL参数名称,而不是“next”,来插入当前绝对路径(redirect_field_name)。

<span style="color:#333333"><code class="language-python">class MyView(LoginRequiredMixin, View):login_url = '/login/'redirect_field_name = 'redirect_to'</code></span>

有关其他详细信息,请查看Django文档。

示例 - 列出当前用户的书本

现在我们知道,如何将页面限制为特定用户,让我们为当前用户借阅的书本,创建一个视图。

不幸的是,我们还没有办法让用户借书!因此,在我们创建图书清单之前,我们首先会扩展BookInstance模型,以支持借阅的概念,并使用Django Admin应用程序,借给测试用户一些书。

模型

首先,我们必须让用户可以借用书本实例BookInstance(我们已经拥有状态status和还书日期due_back,但这个模型和用户之间,没有任何关联。我们将使用ForeignKey(一对多)字段,来创建一个。我们还需要一个简单的机制,来测试借出的书是否过期。

打开 catalog/models.py,然后从 django.contrib.auth.models  导入 User模型(在文件顶部的上一个导入行的正下方添加它,好让后续代码可以使用 User):

<span style="color:#333333"><code class="language-python">from django.contrib.auth.models import User</code></span>

接下来将借用者字段borrower,添加到BookInstance模型:

<span style="color:#333333"><code class="language-python">borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)</code></span>

当我们在这里,让我们添加一个属性,我们可以从模板中调用它,来判断特定的书本实例是否过期。虽然我们可以在模板本身中计算这一点,但使用如下所示的属性会更有效率。将其添加到文件的底部:

<span style="color:#333333"><code class="language-python">from datetime import date@property
def is_overdue(self):if self.due_back and date.today() > self.due_back:return Truereturn False</code></span>

注意: 在进行比较之前,我们首先验证due_back是否为空。空的due_back字段,会导致Django抛出错误,而不是显示页面:空值不具有可比性。这不是我们希望用户体验到的东西!

现在我们已经更新了模型,我们需要对项目进行新的迁移,然后应用这些迁移:

<span style="color:#333333"><code class="language-bash">python3 manage.py makemigrations
python3 manage.py migrate</code></span>

管理员

现在打开 catalog/admin.py,并将borrower字段,添加到BookInstanceAdmin类别中的list_displayfieldsets,如下所示。这将使该字段在Admin部分中可见,以便我们可以在需要时将User分配给BookInstance

<span style="color:#333333"><code class="language-python">@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):list_display = ('book', 'status'<strong>, 'borrower'</strong>, 'due_back', 'id')list_filter = ('status', 'due_back')fieldsets = ((None, {'fields': ('book','imprint', 'id')}),('Availability', {'fields': ('status', 'due_back'<strong>,'borrower'</strong>)}),)</code></span>

借几本书

现在可以将书本借给特定用户,然后借出一些BookInstance记录。将他们的借用字段borrowed,设置为您的测试用户,将状态status设置为 “On loan”,并在将来和过去设置截止日期。

注意: 我们不会一步一步说明这个流程,因为您已经知道如何使用管理站点!

在借书视图

现在我们将添加一个视图,以获取已经借给当前用户的所有书本列表。我们将使用我们熟悉的、基于类的通用类列表视图,但这次我们还将导入并派生自LoginRequiredMixin,以便只有登录用户才能调用此视图。我们还将选择声明template_name,而不是使用默认值,因为我们最终可能会有几个不同的 BookInstance 记录列表,其中包含不同的视图和模板。

将以下内容添加到 catalog/views.py:

<span style="color:#333333"><code class="language-python">from django.contrib.auth.mixins import LoginRequiredMixinclass LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):"""Generic class-based view listing books on loan to current user. """model = BookInstancetemplate_name ='catalog/bookinstance_list_borrowed_user.html'paginate_by = 10def get_queryset(self):return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</code></span>

为了将查询,限制为当前用户的BookInstance对象,我们重新实现了get_queryset(),如上所示。请注意,“o”是表示借出当中“on loan”的存储代码,我们按due_back日期排序,以便首先显示最旧的项目。

借书的 URL 设置

现在打开/catalog/urls.py,并添加指向上面视图的path()(您只需将下面的文本复制到文件末尾)。

<span style="color:#333333"><code class="language-python">urlpatterns += [   path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]</code></span>

借书的模板

现在,我们需要为此页面添加一个模板。首先,创建模板文件/catalog/templates/catalog/bookinstance_list_borrowed_user.html,并为其提供以下内容:

<span style="color:#333333"><code class="language-python">{% extends "base_generic.html" %}{% block content %}<h1>Borrowed books</h1>{% if bookinstance_list %}<ul>{% for bookinst in bookinstance_list %} <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"><a href="{% url 'book-detail' bookinst.book.pk %}">{{bookinst.book.title}}</a> ({{ bookinst.due_back }})        </li>{% endfor %}</ul>{% else %}<p>There are no books borrowed.</p>{% endif %}
{% endblock %}</code></span>

此模板与我们之前为 Book 和 Author对象创建的模板非常相似。这里唯一新的东西,是我们检查在模型中添加的方法(bookinst.is_overdue),并使用它,来更改过期项目的颜色。

当开发服务器运行时,您现在应该能够在浏览器中,查看登录用户的列表,网址为http://127.0.0.1:8000/catalog/mybooks/。在您的用户登录并登出后,尝试此操作(在第二种情况下,您应该被重定向到登录页面)。

将列表添加到侧栏

最后一步,是将这个新页面的链接,添加到侧边栏中。我们将把它放在我们为登录用户显示其他信息的同一部分。

打开基本模板(/locallibrary/catalog/templates/base_generic.html),并将粗体标识的那一行,添加到侧边栏区块,如下所示。

<span style="color:#333333"><code class="language-python"><ul class="sidebar-nav">{% if user.is_authenticated %}<li>User: {{ user.get_username }}</li>
<strong>   <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong><li><a href="{% url 'logout'%}?next={{request.path}}">Logout</a></li>   {% else %}<li><a href="{% url 'login'%}?next={{request.path}}">Login</a></li>   {% endif %} </ul></code></span>

它看起来是什么样子的?

当任何用户登录时,他们会在侧栏中看到 My Borrowed 链接,并显示如下所示的书本列表(第一本书没有截止日期,这是我们希望在以后的教程中修复的错误!) 。

权限

权限与模型相关联,并定义可以由具有权限的用户,在模型实例上执行的操作。默认情况下,Django会自动为所有模型提供添加,更改和删除权限,这允许具有权限的用户,通过管理站点执行相关操作。您可以为模型定义自己的权限,并将其授予特定用户。您还可以更改与同一模型的不同实例关联的权限。

对于视图和模板中的权限测试,非常类似于对身份验证状态的测试(实际上,测试权限也会测试身份验证)。

模型

在模型“class Meta”部分上,使用 permissions字段,完成权限定义。您可以在元组中指定所需的权限,每个权限本身,都在包含权限名称和权限显示值的嵌套元组中被定义。例如,我们可能会定义一个权限,允许用户标记已归还的图书,如下所示:

<span style="color:#333333"><code class="language-python">class BookInstance(models.Model):...class Meta:...
<strong>        permissions = (("can_mark_returned", "Set book as returned"),)  </strong></code></span>

然后,我们可以将权限分配给管理站点中的图书管理员“Librarian”分组。打开 catalog/models.py,然后添加权限,如上所示。您需要重新运行迁移(调用 python3 manage.py makemigrations 和 python3 manage.py migrate),以适当地更新数据库。

模板

当前用户的权限,存在名为 {{ perms }}的模板变量中。您可以使用关联的Django “app” 中的特定变量名,来检查当前用户是否具有特定权限 - 例如,如果用户具有此权限,则 {{ perms.catalog.can_mark_returned }}将为True,否则为False。我们通常使用模板标记 {% if %} 测试权限,如下所示:

<span style="color:#333333">{% if perms.catalog.<code>can_mark_returned</code> %}<!-- We can mark a BookInstance as returned. --><!-- Perhaps add code to link to a "book return" view here. -->
{% endif %}
</span>

视图

在功能视图中,可以使用 permission_required装饰器,或在基于类别的视图中,使用 PermissionRequiredMixin测试权限。模式和行为与登录身份验证相同,但当然您可能需要添加多个权限。

功能视图装饰器:

<span style="color:#333333">from django.contrib.auth.decorators import permission_required@permission_required('catalog.<code>can_mark_returned</code>')
@permission_required('catalog.<code>can_edit</code>')
def my_view(request):...</span>

基于类别视图的权限要求 mixin。

<span style="color:#333333">from django.contrib.auth.mixins import PermissionRequiredMixinclass MyView(PermissionRequiredMixin, View):permission_required = 'catalog.<code>can_mark_returned</code>'# Or multiple permissionspermission_required = ('catalog.<code>can_mark_returned</code>', 'catalog.can_edit')# Note that 'catalog.can_edit' is just an example# the catalog application doesn't have such permission!</span>

示例

我们不会在这里更新 LocalLibrary;也许在下一个教程中!

挑战自己

在本文前面,我们向您展示了,如何为当前用户创建一个页面,列出他们借用的书本。现在的挑战,是创建一个只对图书馆员可见的类似页面,它显示所有借用的书本,其中包括每个借用人的名字。

您应该能够遵循与其他视图相同的模式。主要区别在于,您需要将视图限制为仅限图书馆员。您可以根据用户是否是工作人员(函数装饰器:staff_member_required,模板变量:user.is_staff)来执行此操作,但我们建议您改为使用can_mark_returned权限,和 PermissionRequiredMixin,如上一节所述。

重要: 请记住,不要使用超级用户进行基于权限的测试(即使尚未定义权限,权限检查也会对超级用户返回 true)。而是要创建一个图书管理员用户,并添加所需的功能。

完成后,您的页面应该类似于下面的屏幕截图。

总结

做的太好了 — 你已经创造了一个网站,图书馆用户可以登入并检视他们拥有的内容,图书管理员(有正确的授权)可以检视所有借出的书本以及借阅者。目前,我们仍然只是查看内容,但是当您想要开始修改和添加数据时,会使用相同的原则和技术。

在我们的下一篇文章,我们将介绍如何使用Django 表单,收集使用者输入,然后开始修改我们储存的一些资料。

原文地址:https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Authentication

django 用户授权与许可相关推荐

  1. 转:权限管理——用户认证和用户授权

    转自: https://blog.csdn.net/xdd19910505/article/details/51926540 因为做了权限的项目经理,so,恶补一下一个权限框架:shiro.其实作为框 ...

  2. 权限管理——用户认证和用户授权

    因为做了权限的项目经理,so,恶补一下一个权限框架:shiro.其实作为框架首要目标是易于使用和理解.安全有时候是很复杂的,甚至是痛苦的,但框架没有必要这样.框架应该尽可能掩盖复杂的地方,露出一个干净 ...

  3. OAuth2.0协议(一) - 授权码许可流程

    OAuth2.0是什么可以拿来做什么,它只认真的做了一件事授权(Authorization).OAuth2.0是 Open Authorization 2.0的简称,既然是2.0那前面肯定有个1.0. ...

  4. 微信小程序弹出用户授权弹窗,微信小程序引导用户授权,获取位置经纬度

    我们在开发小程序时,有些操作必须让用户授权.比如我们获取用户位置,需要用户授权位置信息.授权操作我们需要给用户弹窗提示,在用户禁用某些权限时,又要引导用户去设置页开启相应权限.我们这里就以获取经纬度为 ...

  5. mysql grant 主机名_MySQL GRANT:用户授权

    授权就是为某个用户赋予某些权限.例如,可以为新建的用户赋予查询所有数据库和表的权限.MySQL 提供了 GRANT 语句来为用户设置权限. 在 MySQL 中,拥有 GRANT 权限的用户才可以执行 ...

  6. 04: 用户授权及撤销 、 数据备份与恢复 、 MySQL管理工具

    day04 一 .管理root用户密码 1.1 修改密码 1.2 恢复密码 二.用户授权与权限撤销 2.1 授权 2.2 撤销权限 三.安装图形管理工具 四 数据备份与恢复 ----- 完全备份与恢复 ...

  7. 小程序button引导用户授权

    wx.getUserInfo(OBJECT) 注意:此接口有调整,使用该接口将不再出现授权弹窗,请使用 <button open-type="getUserInfo"> ...

  8. MySql中添加用户,新建数据库,用户授权,删除用户,修改密码

    MySql中添加用户,新建数据库,用户授权,删除用户,修改密码(注意每行后边都跟个;表示一个命令语句结束): 1.新建用户 登录MYSQL: @>mysql -u root -p @>密码 ...

  9. linux中如何授权限,Linux系统下,为普通用户授权。

    Linux系统下,如何为普通用户授权 编辑sudoers vi /etc/sudoers(visudo) 添加如下内容 fly ALL=(ALL) ALL  (为普通用户fly赋予root权限) 第一 ...

最新文章

  1. 64位 windows python3.4及numpy matplot等的安装
  2. 【Linux笔记】CentOS下找不到eth0设备的解决方法
  3. python实现rsa加密源代码_python实现RSA加密(解密)算法
  4. 记一次vue项目yarn打包环境配置失效的解决方案
  5. ogre研究之第一个程序(一)
  6. inode索引节点的概念
  7. java编译sql存过_SQL SERVER 临时表导致存储过程重编译(recompile)的一些探讨
  8. Android事件总线(四)源码解析otto
  9. java多线程编程(三)- 线程的创建
  10. [胡言乱语] 20170622
  11. 手把手Java爬虫教学 - 1. 了解爬虫
  12. win10+ubuntu16.04双系统双硬盘(SSD+机械硬盘)安装
  13. 新版升级 DAEMON Tools v4.10 X86 32 Bits (with SPTD 1.50)
  14. 光驱启动以及联想笔记本电脑如何设置从光驱启动
  15. Matlab-初级教程-系列1:matlab之入门教学视频-3 数组和矩阵分析3
  16. jenkins自动化_通过Jenkins自动化PSR合规性
  17. 传Snapchat母公司Snap拟于3月在纽交所IPO上市
  18. mysql中有没有FM_关于FM数据库,简单介绍一下
  19. 【javaScript】获取某年某月的的最后一天(即当月天数) 妙用
  20. 准备加入“暑假遛娃”大军,13个出游体验看看有想去的吗?

热门文章

  1. 华为设备配置基于IP地址策略路由
  2. 计算机保研边缘er如何华丽逆袭?
  3. android 连续播放动画,华为充电动画循环播放
  4. 使用torchsummary时出现AttributeError: ‘list‘ object has no attribute ‘size‘解决方案
  5. 嵌入式项目实战——基于QT的视频监控系统设计(三)
  6. luogu P2862 [USACO06JAN]Corral the Cows G
  7. 关于自然数集N到素数集P的一个单射的三种构造
  8. matlab中的节点注入功率是如何定义的,节点注入功率
  9. word无法创建工作文件,请检查临时环境变量 Mark一下
  10. [模型]多目标规划模型