django-allauth 是非常受欢迎的管理用户登录与注册的第三方 Django 安装包,django-allauth 集成了 local 用户系统 和 social 用户系统,其 social 用户系统 可以挂载多个账户。 django-allauth 能实现以下核心功能:
安装 django-allauth
allenlideMacBook-Pro:~ allen$ mkvirtualenv oauth
(oauth) allenlideMacBook-Pro:~ allen$ pip install django
(oauth) allenlideMacBook-Pro:~ allen$ pip install django-allauth
创建 Django 项目
项目基础配置
安装好后设置 oauth/settings.py,将allauth相关APP加入到INSTALLED_APP里去。对于第三方的providers,你希望用谁就把它加进去。值得注意的是allauth对于站点设置django.contrib.sites有依赖,你必需也把它加入进去,同时设置SITE_ID。
INSTALLED_APPS = [
...,
# django-allauth 需要注册的 app
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.weibo',
'allauth.socialaccount.providers.github',
]
# 当出现 "SocialApp matching query does not exist" 这种报错的时候就需要更换这个ID
SITE_ID = 1
设置 BACKENDS 并提供用户登录验证的方法和用户登录后跳转的链接
# allauth 设置 BACKENDS
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
# 设置登录和注册成功后重定向的页面,默认是 "/accounts/profile/"
LOGIN_REDIRECT_URL = "/accounts/profile/"
配置 django-allauth 其它选项
ACCOUNT_EMAIL_VERIFICATION = 'mandatory' # 强制注册邮箱验证(注册成功后,会发送一封验证邮件,用户必须验证邮箱后,才能登陆)
ACCOUNT_AUTHENTICATION_METHOD = "username_email" # 登录方式(选择用户名或者邮箱都能登录)
ACCOUNT_EMAIL_REQUIRED = True # 设置用户注册的时候必须填写邮箱地址
ACCOUNT_LOGOUT_ON_GET = False # 用户登出(需要确认)
配置邮箱
EMAIL_HOST = "smtp.sina.com"
EMAIL_PORT = 25
EMAIL_HOST_USER = "opcoder@sina.com"
EMAIL_HOST_PASSWORD = "password" # 这个不是邮箱密码,而是授权码
EMAIL_USE_TLS = True # 这里必须是 True,否则发送不成功
EMAIL_FROM = "opcoder@sina.com" # 发件人
DEFAULT_FROM_EMAIL = "OPCoder 博客 <opcoder@sina.com>" # 默认发件人(如果不添加DEFAULT_FROM_EMAIL字段可能会导致如下错误: 451, b'Sender address format error.', 'webmaster@localhost')
修改时区
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
将 allauth 添加加到项目的 urls.py 中
from django.conf.urls import url, include
urlpatterns = [
...,
url(r'^accounts/', include('allauth.urls')),
]
django-allauth 常见设置选项
生成数据库
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
页面访问
用户注册 当注册成功后,用户会收到一封邮件来验证邮箱(使用邮箱强制验证),在你提交表单后,django-allauth会自动检测用户名和email是否已经存在。
邮箱验证 点击邮件中的链接,点击"确认"就可以验证邮箱了。 如果你不需要邮箱验证,只需要设置 ACCOUNT_EMAIL_VERIFICATION = 'none' 就可以了。
如果需要去掉邮件中的 "example.com",只需要在 admin后台 中改下 "显示名称" 就可以了。
# 创建超级用户,用于登陆后台页面
python manage.py createsuperuser
重新注册, 检查邮件内容是否已经变更
用户登录
用户登出
修改密码
重置密码
修改邮箱
django-allauth 内置的 URLs
django-allauth 并没有提供展示和修改用户资料的功能,也没有对用户资料进行扩展,所以我们需要自定义用户模型来进行扩展。
创建 app 及配置
由于 django-allauth 已经占用了 account 这个 app,所以我们需要创建一个名叫 users 的 app,并将其加入 settings.py 配置文件的 INSTALL_APPS 中,同时把url也加入到项目的 ROOT URLs 中。
python manage.py startapp users
# settings.py
INSTALLED_APPS = [
...,
'users',
# django-allauth 需要注册的 app
'django.contrib.sites',
'allauth',
...,
]
# urls.py
from django.conf.urls import url, include
urlpatterns = [
...,
url(r'^accounts/', include('allauth.urls')),
url(r'^accounts/', include('users.urls')),
]
因为我们希望用户在登录或注册成功后,自动跳转到 "/accounts/profile/",我们可以加入(修改)如下代码
# settings.py
LOGIN_REDIRECT_URL = "/accounts/profile/"
创建用户模型及表单
# users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
GENDER_CHOICE = (
('male', '男'),
('female', '女')
)
nick_name = models.CharField(max_length=20, verbose_name='昵称', null=True, blank=True)
mobile = models.CharField(max_length=11, verbose_name='手机', null=True, blank=True)
address = models.CharField(max_length=200, verbose_name='地址', null=True, blank=True)
class Meta:
verbose_name = '用户信息'
verbose_name_plural = verbose_name
ordering = ['-id']
def __str__(self):
return self.username
# users/forms.py
from django import forms
from .models import UserProfile
class ProfileForm(forms.ModelForm):
'''从模型继承表单'''
class Meta:
model = UserProfile
fields = ['nick_name', 'mobile', 'address']
创建自定义用户模型后, 需更改settings.py文件,指明使用的是自定义用户模型
AUTH_USER_MODEL = 'users.UserProfile'
创建视图并配置URLs
我们需要创建2个URLs和对应的视图来实现用户资料展示和用户资料编辑页面。
# users/urls.py
from django.conf.urls import url
from . import views
app_name = 'users'
urlpatterns = [
url(r'^profile/$', views.profile, name='profile'),
url(r'^profile/change/$', views.change_profile, name='change_profile'),
]
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import UserProfile
from .forms import ProfileForm
@login_required
def profile(request):
'''展示个人资料'''
user = request.user
return render(request, 'users/profile.html', {'user':user})
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import UserProfile
from .forms import ProfileForm
@login_required
def change_profile(request):
'''更新个人资料'''
if request.method == 'POST':
# instance参数表示用model实例来初始化表单,这样就可以达到通过表单来更新数据
form = ProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
# 添加一条信息,表单验证成功就重定向到个人信息页面
messages.add_message(request, messages.SUCCESS, '个人信息更新成功!')
return redirect('users:profile')
else:
# 不是POST请求就返回空表单
form = ProfileForm(instance=request.user)
return render(request, 'users/change_profile.html', context={'form': form})
创建模板文件
# users/templates/users/profile.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>个人资料</title>
</head>
<body>
<!--消息块-->
{% if messages %}
<div class="container">
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% else %}alert-secondary{% endif %} alert-dismissible rounded-0 fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
{% if user.is_authenticated %}
<a href="{% url 'users:change_profile' %}">修改资料</a>
<a href="{% url 'account_logout' %}">注销</a>
{% endif %}
<p>Welcome, {{ user.username }}</p>
<ul>
<li>nick_name: {{ user.nick_name }}</li>
<li>mobile: {{ user.mobile }}</li>
<li>address: {{ user.address }}</li>
</ul>
</body>
</html>
由于修改个人资料需要处理表单, 我们可以安装 django-crispy-forms 插件来处理(美化)表单
# 安装
pip install django-crispy-forms
# 加入 INSTALLED_APPS
INSTALLED_APPS = [
...,
'allauth.socialaccount.providers.weibo',
'allauth.socialaccount.providers.github',
'crispy_forms', # bootstrap 表单样式
]
# 配置表单插件使用的样式
CRISPY_TEMPLATE_PACK = 'bootstrap4'
# users/templates/users/change_profile.html
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改资料</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
</head>
<body>
<!--消息块-->
{% if messages %}
<div class="container">
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% else %}alert-secondary{% endif %} alert-dismissible rounded-0 fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
{% if user.is_authenticated %}
<a href="{% url 'users:change_profile' %}">修改资料</a>
<a href="{% url 'account_logout' %}">注销</a>
{% endif %}
<div class="container">
<form method="post" enctype="multipart/form-data" action="{% url 'users:change_profile'%}">>
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-info btn-sm rounded-0" type="submit">更新资料</button>
</form>
</div>
</body>
</html>
生成数据库
由于数据库已存在默认的用户表,使用自定义用户表进行migrate时, 应将数据库重置为初始状态,初始化成功后, 自定义用户表将会覆盖默认的用户表。
python manage.py makemigrations
python manage.py migrate
页面访问
python manage.py runserver
代码优化
# users/models.py 定义 "邮箱验证" 方法
from django.db import models
from django.contrib.auth.models import AbstractUser
from allauth.account.models import EmailAddress
class UserProfile(AbstractUser):
...,
def email_verified(self):
if self.is_authenticated:
result = EmailAddress.objects.filter(email=self.email)
if len(result):
return result[0].verified
else:
return False
# 直接在数据中修改 "account_emailaddress.verified=0",表示邮箱未验证
SQL> update account_emailaddress t set t.verified = 0;
# 修改 settings.py 文件 ACCOUNT_EMAIL_VERIFICATION = 'none',表示邮箱未验证,也可以登录
ACCOUNT_EMAIL_VERIFICATION = 'none'
# users/templates/users/profile.html 添加消息(验证邮箱)
<!--消息块-->
{% if messages %}
<div class="container">
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% else %}alert-secondary{% endif %} alert-dismissible rounded-0 fade show" role="alert">
{{ message }}
{% if not user.email_verified %}
<a href="{% url 'account_email' %}" style="text-decoration:none">验证邮箱.</a>
{% endif %}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
用户未验证邮箱(显示"验证邮箱")
用户已验证邮箱(不会显示"验证邮箱")
注: 邮箱验证消息提示,可以只显示在用户登录成功后页面。
github 账号
INSTALLED_APPS = [
...,
'allauth.socialaccount.providers.github',
]
baidu 账号
INSTALLED_APPS = [
...,
'allauth.socialaccount.providers.github',
]
注: 在开发环境中请确保 "sites" 的 "domain.name" 已经设置为 "127.0.0.1",而生产环境中, 可以设置为自己的域名。
django-allauth 自带的模板是没有经过美化的,另外涉及到邮箱验证和各种消息也是固定的,所以我们就需要进行模板的美化以及邮箱验证和消息文本的修改。
# users/static/users/css/account.css
.secondaryAction {
color: #868e96;
}
.secondaryAction:hover {
text-decoration: none;
color: #007bff;
}
.asteriskField {
margin-left: .25rem;
color: #dc3545;
}
#social-login .login-title {
position: relative;
display: block;
margin-bottom:10px;
}
#social-login span {
color:#999;
}
#social-login span:before,
#social-login span:after {
position: absolute;
top: 50%;
background: #eee;
width: 38%;
height: 1px;
content: '';
}
#social-login span:before {
left:0;
}
#social-login span:after {
right:0;
}
.fa-weibo {
color: #e12f11;
opacity: .8;
}
.fa-github {
color: #333;
opacity: .8;
}
.fa-weibo:hover,
.fa-github:hover {
opacity: 1;
}
.btn-sm {
padding:.2rem .7rem;
}
.change_profile .form-control,
.card-login .form-control {
border-radius: 0;
}
.change_profile .alert,
.card-login .alert {
border-radius: 0;
}
.change_profile .alert li,
.card-login .alert li {
margin-bottom: .5rem;
}
.change_profile .alert ul,
.card-login .alert ul {
padding-left:.5rem;
margin-bottom: 0;
}
#profile-avatar .avatar {
width:80px;
padding: .25rem;
background-color: #fff;
border: 1px solid #dee2e6;
border-radius: .25rem;
}
TEMPLATES = [
{
...,
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 模板文件
'APP_DIRS': True,
...,
},
]
# templates/account/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="description" content="用户账号管理,使用django-allauth社交用户系统,支持Baidu、Github等社交账号登录。">
<meta name="keywords" content="django-allauth,社交用户系统,OAuth 2.0">
<title>{% block head_title %}{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="../../users/static/users/css/account.css" rel="stylesheet">
</head>
<body>
{% block message %}
{% if messages %}
<div class="container">
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% else %}alert-secondary{% endif %} alert-dismissible rounded-0 fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% block base_content %}
<div class="container">
<div class="row">
<div class="col-12 col-sm-8 col-md-6 offset-sm-2 offset-md-3 px-xl-5">
<div class="card rounded-0 px-3 px-lg-4">
<div class="card-header text-center bg-white py-2">
<h3 class="my-1 text-info">{% block user_title %}账号管理{% endblock %}</h3>
</div>
<div class="card-body card-login">{% block content %}{% endblock %}</div>
<div class="text-center mb-5" id="social-login">
<div class="login-title">
<span>快速登录</span>
</div>
<div class="login-link">
<a class="mx-4" href="/accounts/weibo/login/?next={{ next_url }}" title="社交账号登录有点慢,请耐心等候!"><i class="fa fa-weibo fa-2x"></i></a>
<a class="mx-4" href="/accounts/github/login/?next={{ next_url }}" title="社交账号登录有点慢,请耐心等候!"><i class="fa fa-github fa-2x"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_body %}
{% endblock %}
</body>
</html>
# templates/account/login.html
{% extends "account/base.html" %}
{% load i18n %}
{% load account socialaccount %}
{% load crispy_forms_tags %}
{% block head_title %}{% trans "Sign In" %}{% endblock %}
{% block content %}
<form class="login" method="POST" action="{% url 'account_login' %}">
{% csrf_token %}
{{ form|crispy }}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
{% endif %}
<a class="secondaryAction" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
<button class="pull-right btn btn-info btn-sm rounded-0" type="submit">{% trans "Sign In" %}</button>
</form>
{% endblock %}