Django 2 0 项目实战 扩展Django自带User模型,实现用户注册与登录

首先给大家推荐一下我老师大神的人工智能教学网站。教学不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵黄段子!点这里可以跳转到网站

用户的注册与登陆是一个网站应该具有的基本功能。网上很多Django关于实现用户注册与登录的教程都是用Django 1.X写的,比较老了,所以小编我觉得有必要亲自动手用Django 2.0重写用户注册与登陆教程。另外网上很多教程忽略了Django Auth模块自带的User模型而重新建立了自己用户的模型,小编我一看到这种教程就会投去一脸鄙视的目光。一个网站会什么要有两个User模型? Why? Why? 本文会教你在不自建User模型的情况下实现用户的注册与登陆。另外,我们会对Django Auth自带的User模型进行扩展,允许用户添加更多的个人信息。由于全文非常的长,我们会分3部分推送,欢迎订阅我的微信公众号【Python与Django大咖之路】获取最新文章。

总体开发思路

我们要利用Django 2.0开发一个叫users的app,来实现以下6项功能。我们一共将分3篇文章来介绍。本文只介绍用户的注册和登录部分。

  • 用户注册: 注册完成后转到登录页面 
  • 用户登录: 登录完成后转到用户资料页面
  • 用户资料页面: 查看用户注册信息,并提供编辑资料按钮
  • 用户资料编辑:编辑完成后转到用户资料查看页面
  • 用户密码重置
  • 用户退出登陆

由于Django Auth自带的User模型字段有限,我们还需要自定义模型UserProfile对其扩展。

Django Auth模块自带User模型所包含字段username:用户名email: 电子邮件
password:密码first_name:名last_name:姓is_active: 是否为活跃用户。默认是True
is_staff: 是否为员工。默认是Falseis_superuser: 是否为管理员。默认是Falsedate_joined: 加入日期。系统自动生成。
自定义的UserProfile模型user: 与User是1对1关系org:用户名telephone: 电话mod_date: 最后修改日期。系统自动生成

第一步: 创建名叫users的app并修改设置setting.py

我们假设你已经利用Django创建了一个叫mysite的项目,你可以在终端cmd窗口cd进入这个目录,并输入以下命令创建一个叫users的app。

python manage.py startapp users 

然后找到mysite/settings.py里将’users’ 加到INSTALLED_APPS里,如下图所示。

INSTALLED_APPS = [
   'reg.apps.RegConfig',
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'users',
]

第二步: 建立名叫UserProfile的模型(Model)

我们并没有改变Django Auth自带的User模型,也没有建立新的User模型。UserProfile只是对User模型的扩展。找到users/models.py, 并创建如下UserProfile模型。由于我们引用了Django Auth自带的User模型,所以我们必需开始先把它import进来。


from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):

   user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')

   org = models.CharField(
       'Organization', max_length=128, blank=True)

   telephone = models.CharField(
       'Telephone', max_length=50, blank=True)

   mod_date = models.DateTimeField('Last modified', auto_now=True)

   class Meta:
       verbose_name = 'User Profile'

   def __str__(self):return self.user.__str__()

然后你可以在终端输入以下命令,就可以创建UserProfile的数据表。

python manage.py makemigrations 
python manage.py migrate 

第三步:配置URL

小编我是自上而下思考的,所以我习惯上先编写URL,再写视图view。从URL配置上你应该可以直接理解我们想实现的6个功能。下面是users/urls.py里的全部代码。你应该注意到,我们给动态链接/register/取了个名字’register’, 这样我们就可以在html模板里可以通过{% url ‘users:register’ %}调用这个链接了。

from django.urls import re_path
from . import views

app_name = 'users'
urlpatterns = [
   re_path(r'^register/$', views.register, name='register'),
   re_path(r'^login/$', views.login, name='login'),
   re_path(r'^user/(?P<pk>\d+)/profile/$', views.profile, name='profile'),
   re_path(r'^user/(?P<pk>\d+)/profile/update/$', views.profile_update, name='profile_update'),
   re_path(r'^user/(?P<pk>\d+)/pwdchange/$', views.pwd_change, name='pwd_change'),
   re_path(r'^logout/$', views.logout, name='logout'),
]

另外找到mysite/urls.py, 把我们这个app的URLs也加进去,如下图所示。这样当用户访问/accounts/register/时,浏览器会调用views.py里的register函数。

urlpatterns = [
   url(r'^admin/', admin.site.urls),
   url(r'^accounts/', include('users.urls')),

]

第四步: 编写试图(view)

我们需要编写register和login两个视图, 让用户通过表单向我们提交数据,并处理这些数据。因为这两个视图都需要用表单,所以我们先在users目录下新建forms.py, 然后创建两个form,一个RegistrationForm,一个LoginForm。代码如下:

from django import forms
from django.contrib.auth.models import User
import re


def email_check(email):
   pattern = re.compile(r"\"?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)\"?")
   return re.match(pattern, email)


class RegistrationForm(forms.Form):

   username = forms.CharField(label='Username', max_length=50)
   email = forms.EmailField(label='Email',)
   password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
   password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

   # Use clean methods to define custom validation rules

   def clean_username(self):
       username = self.cleaned_data.get('username')

       if len(username) < 6:
           raise forms.ValidationError("Your username must be at least 6 characters long.")
       elif len(username) > 50:
           raise forms.ValidationError("Your username is too long.")
       else:
           filter_result = User.objects.filter(username__exact=username)
           if len(filter_result) > 0:
               raise forms.ValidationError("Your username already exists.")

       return username

   def clean_email(self):
       email = self.cleaned_data.get('email')

       if email_check(email):
           filter_result = User.objects.filter(email__exact=email)
           if len(filter_result) > 0:
               raise forms.ValidationError("Your email already exists.")
       else:
           raise forms.ValidationError("Please enter a valid email.")

       return email

   def clean_password1(self):
       password1 = self.cleaned_data.get('password1')

if len(password1) < 6:
           raise forms.ValidationError("Your password is too short.")
       elif len(password1) > 20:
           raise forms.ValidationError("Your password is too long.")

       return password1

   def clean_password2(self):
       password1 = self.cleaned_data.get('password1')
       password2 = self.cleaned_data.get('password2')

       if password1 and password2 and password1 != password2:
           raise forms.ValidationError("Password mismatch. Please enter again.")

       return password2


class LoginForm(forms.Form):

   username = forms.CharField(label='Username', max_length=50)
   password = forms.CharField(label='Password', widget=forms.PasswordInput)

   # Use clean methods to define custom validation rules

   def clean_username(self):
       username = self.cleaned_data.get('username')

       if email_check(username):
           filter_result = User.objects.filter(email__exact=username)
           if not filter_result:
               raise forms.ValidationError("This email does not exist.")
       else:
           filter_result = User.objects.filter(username__exact=username)
           if not filter_result:
raise forms.ValidationError("This username does not exist. Please register first.")

       return username

千万不要上面的代码吓到。之所以代码这么长是因为我们用clean方法加入了很多表单验证项,比如检查用户名是否过短,用户名是否已经存在。如果你把表单验证拿掉,其实代码非常少。我之所以加上这些验证规则,是让你了解最真实的网站开发。

当然你也可以不用新建forms.py而直接在html模板里写表单,但我并不建议这么做。用forms.py的好处显而易见: 

  • 所有的表单在一个文件里,非常便于后期维护,比如增添或修订字段。
  • forms.py可通过clean方法自定义表单验证,非常便捷。不用在views.py里再进行表单验证(比如检查用户是否已存在),逻辑上更清晰。

我们的视图users/views.py是这样子的。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from .models import UserProfile
from django.contrib import auth
from .forms import RegistrationForm, LoginForm, ProfileForm, PwdChangeForm
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required


def register(request):
   if request.method == 'POST':

       form = RegistrationForm(request.POST)
       if form.is_valid():
           username = form.cleaned_data['username']
           email = form.cleaned_data['email']
           password = form.cleaned_data['password2']

           # 使用内置User自带create_user方法创建用户,不需要使用save()
        user = User.objects.create_user(username=username, password=password, email=email)

           # 如果直接使用objects.create()方法后不需要使用save()
        user_profile = UserProfile(user=user)
           user_profile.save()

           return HttpResponseRedirect("/accounts/login/")

   else:
       form = RegistrationForm()

   return render(request, 'users/registration.html', {'form': form})


def login(request):
   if request.method == 'POST':
       form = LoginForm(request.POST)
       if form.is_valid():
          username = form.cleaned_data['username']
          password = form.cleaned_data['password']

          user = auth.authenticate(username=username, password=password)

          if user is not None and user.is_active:
              auth.login(request, user)
              return HttpResponseRedirect(reverse('users:profile', args=[user.id]))

           else:
               # 登陆失败
           return render(request, 'users/login.html', {'form': form,
                              'message': 'Wrong password. Please try again.'})
   else:
       form = LoginForm()

   return render(request, 'users/login.html', {'form': form})

我们先看下views.register函数是怎么工作的:

  • 当用户通过POST方法提交表单,我们先验证表单RegistrationForm的数据是否有效。如果有效,我们先用Django User模型自带的create_user方法创建user对象,再创建user_profile。用户通过一张表单提交数据,我们实际上分别存储在两张表里。
  • 如果用户注册成功,我们通过HttpResponseRedirect方法转到登陆页面
  • 如果用户没有提交表单或不是通过POST方法提交表单,我们转到注册页面,生成一张空的RegistrationForm

我们再看下views.login函数是怎么工作的:

  • 当用户通过POST方法提交表单,我们先验证表单LoginForm的数据是否有效。如果有效,我们调用Django自带的auth.authenticate() 来验证用户名和密码是否正确。如果正确且用户是活跃的,我们调用auth.login()来进行登录。
  • 如果用户登录失败,会重新转到登录页面,并返回错误信息。
  • 如果用户登录成功,我们通过HttpResponseRedirect方法转到用户个人信息页面
  • 如果用户没有提交表单或不是通过POST方法提交表单,我们转到登录页面,生成一张空的LoginForm

第五步: 编写HTML模板(Template)

在users目录下创建/templates/users/文件夹,编写html模板registration.html和login.html。其目录结构应该如下图所示:

下面是模板registration.html的代码:

{% block content %}
<div class="form-wrapper">
  <form method="post" action="" enctype="multipart/form-data">
     {% csrf_token %}
     {% for field in form %}
          <div class="fieldWrapper">
       {{ field.errors }}
       {{ field.label_tag }} {{ field }}
       {% if field.help_text %}
            <p class="help">{{ field.help_text|safe }}</p>
       {% endif %}
          </div>
       {% endfor %}
     <div class="button-wrapper submit">
        <input type="submit" value="Submit" />
     </div>
  </form>
</div>
{% endblock %}

下面是模板login.html的代码:

{% block content %}
<h2>Login</h2>
{% if message %}
{{ message }}
{% endif %}
<div class="form-wrapper">
  <form method="post" action="" enctype="multipart/form-data">
     {% csrf_token %}
     {% for field in form %}
          <div class="fieldWrapper">
       {{ field.errors }}
       {{ field.label_tag }} {{ field }}
       {% if field.help_text %}
            <p class="help">{{ field.help_text|safe }}</p>
       {% endif %}
          </div>
       {% endfor %}
     <div class="button-wrapper submit">
        <input type="submit" value="Login" />
     </div>
  </form>
   <a href="/accounts/register">Register</a>
</div>
{% endblock %}

第六步:实战效果

现在你可以在终端输入以下命令,看实战效果了。

python manage.py runserver

打开浏览器,访问http://127.0.0.1:8000/accounts/register/和http://127.0.0.1:8000/accounts/login/,你就应该看到如下效果了。

当你注册时两次密码不一致,或用户名太短,或用户名已存在,表单会告诉你错误在哪里,如下图所示:

当你成功登录时,你会被转到个人资料页面。你将被允许修改自己的信息,如下图所示:

结语

本文利用Django 2.0实现了用户注册与登录的两个功能,扩展了Django自带的User模型,并分享了代码。接下来我会分享如何实现查看用户资料,允许用户修改自己资料,修改密码和退出登录的其它4个功能。另外,我会专门介绍如何调用静态文件css和js来美化本项目中的表单。

如果你喜欢我的文章,欢迎关注我的微信号【Python与Django大咖之路】

点这里可以跳转到人工智能网站

发表评论