对user模块进行开发
设计数据表
Django默认就提供了和用户相关的功能,但是这个Django默认提供的功能有个不好的点: 不太适合我们的项目,例如里面的字段不够等,所以我们要对它进行改造一下,方便项目开发。
拓展用户模型
进入虚拟环境安装pillow第三方库,为了方便保存和使用图片
pip install pillow
在blog/blog/下创建一个名为media的文件夹,用来存放图片
在blog/blog/apps/user/models.py文件中添加如下代码:
from django.db import models
from django.contrib.auth.models import AbstractUser
# 模型层的代码 类的三大特性: 封装 继承 多态
# django 的 orm
# 类 对应 数据表 必须继承django提供的 moedls.Model
# 类中的属性 对应 字段
# 类实例化的对象 对应 一条记录
# 这应该是基于django提供的用户类进行扩展的
# 基于:以前django提供的很多功能有一些是可以直接使用的
# 扩展:因为字段不够 所以要扩展
class User(AbstractUser):
# 增加一个字段 存放手机号码,unique=True:唯一索引,verbose_name:在admin管理后台中展示的名字
phone = models.CharField(max_length=20,unique=True,verbose_name='手机号码',null=True)
# 增加一个字段 存放用户头像
# 注意事项: 要想使用ImageField 必须先安装一个模块:pillow
avater = models.ImageField(upload_to='avatar/%Y%m%d/',verbose_name='头像',null=True)
# 增加一个字段 存放个人简介
bio = models.TextField(max_length=500,verbose_name='个人简介',null=True)
# 这个类 生成的数据表 叫什么名字 user_user 如果我不想叫这个名字
class Meta: # 在类中定义一个类 类名必须叫Meta
db_table = 'blog_users' # db_table 这个的值 就是你要创建的数据表的表名
还要在配置文件中 指定上传的文件的目录在哪里,就是保存用户头像的文件夹,并且我们上传的文件肯定还需要访问看到,所以还要配置请求的URL前缀,在setting/dev.py中增加如下代码:
# 配置上传的文件的目录
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
# 配置上传的文件的请求的 url
MEDIA_URL = '/media/'
在setting/dev.py文件中添加如下代码,告诉Django要使用我们自己写的新的user模型类
# 格式:AUTH_USER_MODEL = '应用名.模型类型'
AUTH_USER_MODEL = 'user.User'
注意:
扩展了Django的用户模型类,等于破坏了Django的数据库,所以扩展用户模型类应该在第一次执行迁移文件之前就做好这个工作!
软删除
软删除的本质:就是多加一个标记字段,所谓的删除数据,无非就是将这个字段的值改变,以后查看的时候,就多个查询条件而已。
并没有真正的删除,当要删除一条记录时,就把标记字段改为1,展示时只展示标记字段为0的记录,这样在用户角度来看,看不到这些为1 的记录,就相当于删除。
对模型层进行改动,user/models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models import QuerySet
class MySoftDeletableQuerySet(QuerySet):
"""
当使用filter与all时所执行的操作类
"""
def delete(self):
# 重写删除方法
self.update(is_delete=True)
class BaseManager(models.Manager):
_queryset_class = MySoftDeletableQuerySet
def get_queryset(self):
return super().get_queryset().filter(is_delete=False).order_by('-create_time')
class BaseModel(models.Model):
# auto_now_add 每次新增数据的时候 自动加入时间
create_time = models.DateTimeField(auto_now_add=True,verbose_name='创建时间')
# auto_now 每次修改数据的时候 自动加入时间
update_time = models.DateTimeField(auto_now=True,verbose_name='修改时间')
# 增加字段 用来实现软删除的 默认是False 如果要删除数据,就将这个字段的值改成True
is_delete = models.BooleanField(default=False, verbose_name='是否删除')
objects = BaseManager()
def delete(self, using=None, keep_parents=False):
self.is_delete = True # 将这个字段的值改成True
self.save() # 保存
# 一旦执行迁移 user_basemodel 继承
class Meta:
abstract = True
# 这是一个抽象的基类 不会为这个类创建数据表 只是作为父类给别人继承而已
ordering = ('-create_time') # 根据创建时间进行排序
class User(AbstractUser,BaseModel):
phone = models.CharField(max_length=20,unique=True,verbose_name='手机号码',null=True)
avater = models.ImageField(upload_to='avatar/%Y%m%d/',verbose_name='头像',null=True)
bio = models.TextField(max_length=500,verbose_name='个人简介',null=True)
# 这个类 生成的数据表 叫什么名字 user_user 如果我不想叫这个名字
class Meta: # 在类中定义一个类 类名必须叫Meta
db_table = 'blog_users' # db_table 这个的值 就是你要创建的数据表的表名
2. 执行数据库迁移,生成对应的数据表(在虚拟环境中,manage.py文件所在路径下执行以下代码)
python manage.py makemigrations
python manage.py migrate
实现注册功能
在视图层(blog/apps/user/views.py)添加如下代码(这里仅做测试,具体实现逻辑见下一步):
from django.shortcuts import render,HttpResponse
from django.views import View
# Create your views here.
# 注册 get 展示注册页面 post 实现注册功能
class Register(View):
# 展示用户注册的页面
def get(self,request):
return render(request,'user/register.html')
# 接受用户的数据,实现注册功能
def post(self,request):
return HttpResponse('这是实现注册功能的')
写路由:blog/apps/user/urls.py
from django.urls import path
from . import views
app_name = 'user'
urlpatterns = [
path('register',views.Register.as_view(),name='register')
])
写前端文件
首先在templates文件夹下创建base.html,用来展示博客的导航栏,就是头部和尾部,这些是保持不变的
{% load staticfiles %}
<!-- 设置引入静态文件 -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<!-- 网站采用的字符编码 -->
<meta charset="utf-8">
<!-- 预留网站标题的位置 -->
<title>{% block title %}{% endblock %}</title>
<!-- 引入bootstrap的css文件 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- bootstrap.js 依赖 jquery.js 和popper.js,因此在这里引入 -->
<script src="{% static 'jquery-3.6.1.min.js' %}"></script>
<!-- 引入layer.js -->
<script src="{% static 'layer/layer.js' %}"></script>
<!-- 引入popper.js -->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1-lts/dist/umd/popper.min.js"></script>
<!-- 引入bootstrap的js文件 -->
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
<script>
$('div#article_body table').addClass('table table-bordered');
$('div#article_body thead').addClass('thead-light');
</script>
</head>
<body>
<!-- 定义导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<!-- 导航栏商标 -->
<a class="navbar-brand" href="#">我的博客</a>
<!-- 导航入口 -->
<div>
<ul class="navbar-nav">
<!-- 条目 -->
<li class="nav-item">
<a class="nav-link" href="#">文章</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">写文章</a>
</li>
<!-- start 如果用户已经登录则显示用户名和头像 -->
<img src="" style="width: 38px;height: 38px; border-radius: 15%;">
<a class="nav-link" href="#">添加头像</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">退出登录</a>
<a class="dropdown-item" href="#">个人中心</a>
</div>
</li>
<!-- 如果用户未登录,则显示 “登录” -->
<li class="nav-item">
<a class="nav-link" href="#">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">注册</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- 预留具体页面的位置 -->
{% block content %}{% endblock content %}
<div>
<br><br><br>
</div>
<footer class="py-3 bg-dark fixed-bottom">
<div class="container">
<p class="m-0 text-center text-white">Copyright © xxx</p>
</div>
</footer>
</body>
</html>
在templates/user里面创建register.html文件
{% extends 'base.html' %}
{% block title %}注册页面{% endblock title %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<br>
<form method="post" action="">
<!-- 账号 -->
<div class="form-group col-md-4">
<label for="username">昵称</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<!-- 邮箱 -->
<div class="form-group col-md-4">
<label for="email">邮箱</label>
<input type="text" class="form-control" id="email" name="email">
</div>
<!-- 密码 -->
<div class="form-group col-md-4">
<label for="password">设置密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<!-- 确认密码 -->
<div class="form-group col-md-4">
<label for="password2">确认密码</label>
<input type="password" class="form-control" id="password2" name="password2" required>
</div>
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>错误!</strong>
</div>
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
{% endblock content %}
实现注册逻辑
修改blog/apps/user/view.py文件中用户注册的代码如下:
import re
from django.shortcuts import render,HttpResponse
from django.views import View
from user.models import User
class Register(View):
# 展示用户注册的页面
def get(self,request):
return render(request,'user/register.html')
# 接受用户的数据,实现注册功能
def post(self,request):
# 编写注册功能的逻辑 基本上所有的逻辑都差不多 1
# 1. 接受用户数据
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
password2 = request.POST.get('password2')
# 2. 做基本的验证
# 2.1 验证参数是不是都有
if not all([username,email,password,password2]):
return render(request,'user/register.html',{'register_errmsg':'参数不全'})
# 2.2 判断用户名是否是5~20个字符
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
return render(request, 'user/register.html', {'register_errmsg': '请输入5-20个字符的用户名'})
# 2.3 判断邮箱是否合格
if not re.match(r'^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(com|cn|net)', email):
return render(request, 'user/register.html', {'register_errmsg': '邮箱格式不正确'})
# 判断密码是否是8-20个字符
if not re.match(r'^[0-9A-Za-z]{6,20}$', password):
return render(request, 'user/register.html', {'register_errmsg': '请输入6-20个字符的密码'})
# 判断两次密码是否一致
if password != password2:
return render(request, 'user/register.html', {'register_errmsg': '两次输入的密码不一致'})
# 3. 数据入库
try:
# 创建用户
User.objects.create_user(username=username,password=password,email=email)
except Exception as err:
return render(request, 'user/register.html', {'register_errmsg': '用户名重复'})
# 4. 返回结果 自动跳转到登录页
return redirect(reverse('user:login'))
对register.html文件进行优化
{% extends 'base.html' %}
{% block title %}注册页面{% endblock title %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<br>
<form method="post" action="">
{% csrf_token %}
<!-- 账号 -->
<div class="form-group col-md-4">
<label for="username">昵称</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<!-- 邮箱 -->
<div class="form-group col-md-4">
<label for="email">邮箱</label>
<input type="text" class="form-control" id="email" name="email">
</div>
<!-- 密码 -->
<div class="form-group col-md-4">
<label for="password">设置密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<!-- 确认密码 -->
<div class="form-group col-md-4">
<label for="password2">确认密码</label>
<input type="password" class="form-control" id="password2" name="password2" required>
</div>
<!-- 显示错误信息-->
{% if register_errmsg %}
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>错误!</strong>{{ register_errmsg }}
</div>
{% endif %}
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
{% endblock content %}
实现登录功能
写视图: blog/blog/apps/user/views.py
from django.contrib.auth import authenticate,login
# 登录功能
class Login(View):
# 展示用户登录的页面
def get(self,request):
return render(request,'user/login.html')
# 实现用户登录的功能逻辑
def post(self,request):
# 1. 接收用户的参数
username = request.POST.get('username')
password = request.POST.get('password')
# 2. 检验参数
if not all([username, password]):
return render(request, 'user/login.html', {'login_errmsg': '参数不全'})
# 3. 认证登录用户
user = authenticate(username=username,password=password)
if user is None:
return render(request, 'user/login.html', {'login_errmsg': '用户名或密码错误'})
# 保持登录状态
login(request,user)
# 4. 返回结果
return HttpResponse('登录成功')
写路由:blog/blog/apps/user/urls.py
from django.urls import path
from . import views
app_name = 'user'
urlpatterns = [
path('register',views.Register.as_view(),name='register'),
path('login',views.Login.as_view(),name='login'),
]
创建模板文件:templates/user/login.html
{% extends 'base.html' %}
{% block title %}登录页面{% endblock title %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<br>
<form method="post" action="">
{% csrf_token %}
<!-- 账号 -->
<div class="form-group col-md-4">
<label for="username">昵称或者邮箱</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<!-- 密码 -->
<div class="form-group col-md-4">
<label for="password">输入密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<!-- 显示错误信息 -->
{% if login_errmsg %}
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>错误!</strong>{{login_errmsg}}
</div>
{% endif %}
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary">提交</button>
</form>
<h5>忘记密码了?</h5>
<h5>点击<a href='#'>这里</a>重置密码</h5>
</div>
</div>
</div>
{% endblock content %}
接下来就可以启动项目,实现注册和登录功能,项目结构如下:
需要静态资源文件可私信!!!