一、创建项目
1.1.创建项目和app
django-admin startproject mysite-login
python manage.py startapp login
1.2.设置时区和语言
Django默认使用美国时间和英语,在项目的settings文件中,如下所示:
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
我们把它改为亚洲/上海
时间和中文
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
1.3.启动
运行测试一下工程,在本机的浏览器中访问http://127.0.0.1:8000/
二、设计数据模型
2.1.数据库模型设计
作为一个用户登录和注册项目,需要保存的都是各种用户的相关信息。很显然,我们至少需要一张用户表User,在用户表里需要保存下面的信息:
- 用户名
- 密码
- 邮箱地址
- 性别
- 创建时间
进入login/models.py,代码如下
# login/models.py
from django.db import models
class User(models.Model):
'''用户表'''
gender = (
('male','男'),
('female','女'),
)
name = models.CharField(max_length=128,unique=True)
password = models.CharField(max_length=256)
email = models.EmailField(unique=True)
sex = models.CharField(max_length=32,choices=gender,default='男')
c_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
ordering = ['c_time']
verbose_name = '用户'
verbose_name_plural = '用户'
各字段含义:
- name必填,最长不超过128个字符,并且唯一,也就是不能有相同姓名;
- password必填,最长不超过256个字符(实际可能不需要这么长);
- email使用Django内置的邮箱类型,并且唯一;
- 性别使用了一个choice,只能选择男或者女,默认为男;
- 使用
__str__
帮助人性化显示对象信息; - 元数据里定义用户按创建时间的反序排列,也就是最近的最先显示;
注意:这里的用户名指的是网络上注册的用户名,不要等同于现实中的真实姓名,所以采用了唯一机制。如果是现实中可以重复的人名,那肯定是不能设置unique的。
2.2.设置数据库为Mysql
#settings.py
DATABASES = { 'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'database', #数据库名字
'USER': 'root', #账号
'PASSWORD': '123456', #密码
'HOST': '127.0.0.1', #IP
'PORT': '3306', #端口
}
}
init.py里面导入pymysql模块
# login/init.py
import pymysql #导入
pymysql.install_as_MySQLdb()
2.3.数据库迁移
注册app
#seting.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'login',
]
迁移到数据库
python manage.py makemigrations
python manage.py migrate
三、admin后台
3.1.在admin中注册模型
# login/admin.py
from django.contrib import admin
from . import models
admin.site.register(models.User)
3.2.创建超级管理员
python manage.py createsuperuser
然后再增加几个测试用户
四、url路由和视图
前面我们已经创建好数据模型了,并且在admin后台中添加了一些测试用户。下面我们就要设计好站点的url路由、对应的处理视图函数以及使用的前端模板了。
4.1.路由设计
初步设想需要下面的四个URL:
考虑到登录系统属于站点的一级功能,为了直观和更易于接受,这里没有采用二级路由的方式,而是在根路由下直接编写路由条目,同样也没有使用反向解析名(name参数)。
mysite_login/urls.py
from django.urls import path
from django.contrib import admin
from login import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
path('login/', views.login),
path('register/', views.register),
path('logout/', views.logout),
]
4.2.架构初步视图
路由写好了,就进入login/views.py
文件编写视图的框架,代码如下:
login/views.py
from django.shortcuts import render,redirect
def index(request):
pass
return render(request,'login/index.html')
def login(request):
pass
return render(request,'login/login.html')
def register(request):
pass
return render(request,'login/register.html')
def logout(request):
pass
return redirect('/index/')
我们先不着急完成视图内部的具体细节,而是把框架先搭建起来。
4.3.创建HTML页面文件
在项目根路径的login目录中创建一个templates目录,再在templates目录里创建一个login目录
在login/templates/login
目录中创建三个文件index.html
、login.html
以及register.html
,并写入如下的代码:
index.html
首页
首页
login.html
登录
登录页面
register.html
注册
注册页面
五、前端页面设计
5.1.原生HTML页面
login.html
文件中的内容,写入下面的代码:
登录
欢迎登录!
可以看到如下图的页面:
5.2.引入Bootstrap
Bootstrap3.3.7下载地址
根目录下新建一个static目录,并将解压后的bootstrap-3.3.7-dist
目录,整体拷贝到static目录中,如下图所示:
由于Bootstrap依赖JQuery,所以我们需要提前下载并引入JQuery:[下载地址](http://www.jq22.com/jquery-info122)
在static目录下,新建一个css和js目录,作为以后的样式文件和js文件的存放地,将我们的jquery文件拷贝到static/js
目录下。
然后打开项目的settings文件,在最下面添加配置,用于指定静态文件的搜索目录:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
5.3.创建base.html模板
既然要将前端页面做得像个样子,那么就不能和前面一样,每个页面都各写各的,单打独斗。一个网站有自己的统一风格和公用部分,可以把这部分内容集中到一个基础模板base.html
中。现在,在根目录下的templates中新建一个base.html
文件用作站点的基础模板。
在Bootstrap文档中,为我们提供了一个非常简单而又实用的基本模板,代码如下:
Bootstrap 101 Template
你好,世界!
将它整体拷贝到base.html
文件中。
5.4.创建页面导航条
Bootstrap提供了现成的导航条组件
其中有一些部分,比如搜索框是我们目前还不需要的,需要将多余的内容裁剪掉。同时,有一些名称和url地址等需要按我们的实际内容修改。最终导航条的代码如下:
5.5.使用Bootstrap静态文件
{% static '相对路径' %}
这个Django为我们提供的静态文件加载方法,可以将页面与静态文件链接起来
最后,base.html
内容如下:
{% load staticfiles %}
{% block title %}base{% endblock %}
{% block css %}{% endblock %}
{% block content %}{% endblock %}
简要说明:
- 通过页面顶端的
{% load staticfiles %}
加载后,才可以使用static方法; - 通过
{% block title %}base{% endblock %}
,设置了一个动态的页面title块; - 通过
{% block css %}{% endblock %}
,设置了一个动态的css加载块; - 通过
{% block content %}{% endblock %}
,为具体页面的主体内容留下接口; - 通过
{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}
将样式文件指向了我们的实际静态文件,下面的js脚本也是同样的道理。
看下效果
5.6.设计登录页面
Bootstarp提供了一个基本的表单样式,代码如下:
如下:
我们结合Bootstrap和前面自己写的form表单,修改login/templates/login/login.html
成符合项目要求的样子:
{% extends 'login/base.html' %}
{% load staticfiles %}
{% block title %}登录{% endblock %}
{% block css %} {% endblock %}
{% block content %}
{% endblock %}
说明:
- 通过
{% extends 'base.html' %}
继承了‘base.html’模板的内容; - 通过
{% block title %}登录{% endblock %}
设置了专门的title; - 通过
block css
引入了针对性的login.css
样式文件; - 主体内容定义在
block content
内部 - 添加了一个重置按钮。
在static/css
目录中新建一个login.css
样式文件,这里简单地写了点样式,
body {
background-color: #eee;
}
.form-login {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-login .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px; font-size: 16px;
}
.form-login .form-control:focus {
z-index: 2;
}
.form-login input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-login input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
最后效果
六、登录视图
6.1.登录视图
根据我们在路由中的设计,用户通过login.html
中的表单填写用户名和密码,并以POST的方式发送到服务器的/login/
地址。服务器通过login/views.py
中的login()
视图函数,接收并处理这一请求。
我们可以通过下面的方法接收和处理请求:
def login(request):
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
return redirect('/index/')
return render(request, 'login/login.html')
还需要在前端页面的form表单内添加一个{% csrf_token %}
标签:
通过if判断,当登录时,显示当前用户名和登出按钮。未登录时,显示登录和注册按钮。
注意其中的模板语言,{{ request }}
这个变量会被默认传入模板中,可以通过圆点的调用方式,获取它内部的{{ request.session }}
,再进一步的获取session中的内容。其实{{ request }}
中的数据远不止此,例如{{ request.path }}
就可以获取先前的url地址。
再修改一下index.html
页面,根据登录与否的不同,显示不同的内容:
{% extends 'base.html' %}
{% block title %}主页{% endblock %}
{% block content %}
{% if request.session.is_login %}
你好,{{ request.session.user_name }}!欢迎回来!
{% else %}
你尚未登录,只能访问公开内容!
{% endif %}
{% endblock %}
看下效果:
十、注册视图
10.1.创建forms
在/login/forms.py
中添加一个新的表单类:
class RegisterForm(forms.Form):
gender = (
('male', "男"),
('female', "女"),
)
username = forms.CharField(label="用户名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))
password1 = forms.CharField(label="密码", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password2 = forms.CharField(label="确认密码", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
email = forms.EmailField(label="邮箱地址", widget=forms.EmailInput(attrs={'class': 'form-control'}))
sex = forms.ChoiceField(label='性别', choices=gender)
captcha = CaptchaField(label='验证码')
说明:
- gender和User模型中的一样,其实可以拉出来作为常量共用,为了直观,特意重写一遍;
- password1和password2,用于输入两遍密码,并进行比较,防止误输密码;
- email是一个邮箱输入框;
- sex是一个select下拉框;
10.2.完善register.html
同样地,类似login.html文件,我们在register.html中编写forms相关条目:
{% extends 'login/base.html' %}
{% block title %}注册{% endblock %}
{% block content %}
{% if message %}
{{ message }}
{% endif %}
{% csrf_token %}
欢迎注册
{{ register_form.username.label_tag }}
{{ register_form.username}}
{{ register_form.password1.label_tag }}
{{ register_form.password1 }}
{{ register_form.password2.label_tag }}
{{ register_form.password2 }}
{{ register_form.email.label_tag }}
{{ register_form.email }}
{{ register_form.sex.label_tag }}
{{ register_form.sex }}
{{ register_form.captcha.errors }}
{{ register_form.captcha.label_tag }}
{{ register_form.captcha }}
{% endblock %}
10.3.注册视图
进入/login/views.py
文件,现在来完善我们的register()
视图:
def register(request):
if request.session.get('is_login', None): # 登录状态不允许注册。你可以修改这条原则!
return redirect("/index/")
if request.method == "POST":
register_form = RegisterForm(request.POST)
message = "请检查填写的内容!"
if register_form.is_valid(): # 获取数据
username = register_form.cleaned_data['username']
password1 = register_form.cleaned_data['password1']
password2 = register_form.cleaned_data['password2']
email = register_form.cleaned_data['email']
sex = register_form.cleaned_data['sex']
if password1 != password2: # 判断两次密码是否相同
message = "两次输入的密码不同!"
return render(request, 'login/register.html', locals())
else:
same_name_user = models.User.objects.filter(name=username)
if same_name_user: # 用户名唯一
message = '用户已经存在,请重新选择用户名!'
return render(request, 'login/register.html', locals())
same_email_user = models.User.objects.filter(email=email)
if same_email_user: # 邮箱地址唯一
message = '该邮箱地址已被注册,请使用别的邮箱!'
return render(request, 'login/register.html', locals()) # 当一切都OK的情况下,创建新用户
new_user = models.User.objects.create()
new_user.name = username
new_user.password = password1
new_user.email = email
new_user.sex = sex
new_user.save()
return redirect('/login/') # 自动跳转到登录页面
register_form = RegisterForm()
return render(request, 'login/register.html', locals())
从大体逻辑上,也是先实例化一个RegisterForm的对象,然后使用is_valide()
验证数据,再从cleaned_data
中获取数据。
重点在于注册逻辑,首先两次输入的密码必须相同,其次不能存在相同用户名和邮箱,最后如果条件都满足,利用ORM的API,创建一个用户实例,然后保存到数据库内。
看一下注册的页面:
注册成功在admin后台可以看到注册的用户
10.4.密码加密
用户注册的密码应该加密才对
对于如何加密密码,有很多不同的途径,其安全程度也高低不等。这里我们使用Python内置的hashlib库,使用哈希值的方式加密密码,可能安全等级不够高,但足够简单,方便使用,不是么?
首先在login/views.py
中编写一个hash函数:
import hashlib
def hash_code(s, salt='mysite'):# 加点盐
h = hashlib.sha256()
s += salt
h.update(s.encode()) # update方法只接收bytes类型
return h.hexdigest()
然后,我们还要对login()和register()视图进行一下修改:
#login.html
if user.password == hash_code(password): # 哈希值和数据库内的值进行比对
#register.html
new_user.password = hash_code(password1) # 使用加密密码
views.py全部代码
重启服务器,进入注册页面,新建一个用户,然后进入admin后台,查看用户的密码情况:
再使用该用户登录一下,大功告成!
可以看到密码长度根据你哈希算法的不同,已经变得很长了,所以前面model中设置password字段时,不要想当然的将max_length
设置为16这么小的数字。