目录
Django个人博客搭建1-创建Django项目和第一个App
Django个人博客搭建2-编写文章Model模型,View视图
Django个人博客搭建3-创建superuser并向数据库中添加数据并改写视图
Django个人博客搭建4-配置使用 Bootstrap 4 改写模板文件
Django个人博客搭建5-编写文章详情页面并支持markdown语法
Django个人博客搭建6-对文章进行增删查改
Django个人博客搭建7-对用户登陆注册等需求的实现
Django个人博客搭建8-优化文章模块
Django个人博客搭建9-增加文章评论模块
1. 用户的登陆和退出
首先命令行cd进manage.py同级目录下创建用户app
python manage.py startapp userprofile
Microsoft Windows [版本 10.0.17763.253]
(c) 2018 Microsoft Corporation。保留所有权利。
F:\Desktop\myblog>python manage.py startapp userprofile
F:\Desktop\myblog>
没有反馈,我们看一下目录
多了一个文件夹,这就是我们创建的用户app
用户登陆时需要填写用户名和密码等,因此需要用到Form表单类
在userprofile中新建表单类文件froms.py:
from django import forms
from django.contrib.auth.models import User
class UserLoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField()
接着编写视图,在userprofile/views.py中写视图函数:
from django.contrib.auth import authenticate, login
from django.http import HttpResponse
from django.shortcuts import render
# Create your views here.
from userprofile.froms import UserLoginForm
def user_login(request):
if request.method == "POST":
user_login_form = UserLoginForm(data=request.POST)
if user_login_form.is_valid():
# .cleaned_data洗出合法数据
data = user_login_form.cleaned_data
# 检验账号、密码是否正确匹配数据库中的某个用户
# 如果均匹配则返回这个user对象
user = authenticate(username=data['username'], password=data['password'])
# authenticate()方法验证用户名称和密码是否匹配,如果是,则将这个用户数据返回。
if user:
# 将用户数据保存在session中,实现的登陆动作
login(request, user)
return redirect("article:article_list")
else:
return HttpResponse("账号或密码输入有误,请重新输入")
else:
return HttpResponse("账号或密码输入不合法")
elif request.method =="GET":
user_login_form = UserLoginForm()
context = {'from': user_login_form}
return render(request, 'userprofile/losin.html', context)
else:
return HttpResponse("请使用GET或POST请求数据")
接着来编写登陆模板:/userprofile/login.html
{% extends "base.html" %}
{% load staticfiles %}
{% 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">
<label for="username">账号label>
<input type="text" class="form-control" id="username" name="username">
div>
<div class="form-group">
<label for="password">账号label>
<input type="password" class="form-control" id="password" name="password">
div>
<button type="submit" class="btn btn-primary">登陆button>
form>
div>
div>
div>
{% endblock content %}
接下来在header.html中增加登陆按钮
<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="{% url 'article:article_list' %}">文章a>
li>
<li class="nav-item">
<a class="nav-link" href="{% url 'article:article_create' %}">写文章a>
li>
{% if user.is_authenticated %}
<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">
{{ user.username }}
a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">退出登录a>
div>
li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'userprofile:login' %}">登录a>
li>
{% endif %}
ul>
div>
div>
nav>
接着创建配置userprofile/urls.py
from django.urls import path
from . import views
app_name = 'userprofile'
urlpatterns=[
path('login/', views.user_login, name='login'),
]
配置my_blog/urls.py:
path('userprofile/', include('userprofile.urls', namespace='userprofile')),
接着配置my_blog/settings.py中注册app
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'article',
'userprofile',
]
再接着运行服务器
可以看见登陆界面正常显示了,我们点击登陆:
可以看见文章列表右上角显示了用户名,
2. 用户的退出
接下来继续实现退出登陆逻辑, 在userprofile/views.py中新增user_logout函数:
from django.contrib.auth import authenticate, login, logout
# 用户退出
def user_logout(request):
logout(request)
return redirect("article:article_list")
配置userprofile/urls.py
path('logout/', views.user_logout, name='logout'),
接下来改动header.html中退出登陆的href链接指向
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="#">我的博客a>
...
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
退出登录a>
div>
li>
...
nav>
3. 用户的注册
首先在userprofile/views.py中增加用户注册函数
def user_register(request):
if request.method == 'POST':
user_register_form = UserRegisterForm(data=request.POST)
if user_register_form.is_valid():
new_user = user_register_form.save(commit=False)
# 设置密码
new_user.set_password(user_register_form.cleaned_data['password'])
new_user.save()
# 保存好数据后立即登录并返回博客列表页面
login(request, new_user)
return redirect("article:article_list")
else:
return HttpResponse("注册表单输入有误。请重新输入~")
elif request.method == 'GET':
user_register_form = UserRegisterForm()
context = { 'form': user_register_form }
return render(request, 'userprofile/register.html', context)
else:
return HttpResponse("请使用GET或POST请求数据")
接下来编写模板和 urls.py
userprofile/register.html
{% extends "base.html" %}
{% load staticfiles %}
{% 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">Emaillabel>
<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>
<button type="submit" class="btn btn-primary">提交button>
form>
{# #}
div>
div>
div>
{% endblock content %}
接下来在登陆界面中增加注册入口:
login.html:
{% extends "base.html" %}
{% load staticfiles %}
{% 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">
<label for="username">账号label>
<input type="text" class="form-control" id="username" name="username">
div>
<div class="form-group">
<label for="password">账号label>
<input type="password" class="form-control" id="password" name="password">
div>
<button type="submit" class="btn btn-primary">登陆button>
form>
<br>
<h5>还没有账号?h5>
<h5>点击<a href='{% url "userprofile:register" %}'>注册账号a>加入我们吧!h5>
<br>
div>
div>
div>
{% endblock content %}
最后在userprofile/urls.py中新增注册路由:
path('register/', views.user_register, name="register"),
然后我们重启服务器打开登陆界面:
点击注册账号
接着填写注册信息
点击提交
可以看见成功注册并且自动登陆了
4. 用户的删除
编写userprofile/views.py
from django.contrib.auth.models import User
# 引入验证登录的装饰器
from django.contrib.auth.decorators import login_required
...
@login_required(login_url='/userprofile/login/')
def user_delete(request, id):
user = User.objects.get(id=id)
# 验证登录用户、待删除用户是否相同
if request.user == user:
#退出登录,删除数据并返回博客列表
logout(request)
user.delete()
return redirect("article:article_list")
else:
return HttpResponse("你没有删除操作的权限。")
@login_required是一个装饰器,就是 @login_required要求调用user_delete()函数时,用户必须登陆如果不登陆则不执行函数并且将重定向到**/userprofile/login/**地址去。
关于@login_required的详细用法。
然后改写templates/header.html,新增删除用户入口,并且添加弹窗组件的代码
...
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
退出登录a>
<a class="dropdown-item" href="#" onclick="user_delete()">删除用户a>
div>
...
nav>
{% if user.is_authenticated %}
<script>
function user_delete() {
layer.open({
title:"确认删除",
content:"确认删除用户资料吗?",
yes: function (index, layero) {
location.href='{% url "userprofile:delete" user.id %}'
}
})
}
script>
{% endif %}
注意到**user_delete()**函数是用if模板语句包裹起来的。因为用户未登录时页面对象中是没有user.id属性的,但是函数中却又包含了 user.id,Django在解析模板时就会报错。if语句确保了只有在用户登录时才对这段JavaScript代码进行解析,回避了这个问题。
最后修改userprofile/urls.py:
path('delete//' , views.user_delete, name='delete'),
运行服务器看一下效果:
点击删除用户:
点击确定:
用户已经退出登陆了
检查数据库的用户表,发现admin用户信息已经没有了
就证明用户信息已经删除了
5. 重置用户密码
业务流程分析如下:
向用户邮箱发送包含重置密码地址的邮件。邮件的地址需要动态生成,防止不怀好意的用户从中捣乱;
向网站用户展示一条发送邮件成功的信息;
用户点击邮箱中的地址后,转入重置密码的页面;
向用户展示一条重置成功的信息。
Django已经为我们写好了重置密码功能了:Django验证系统
首先我们需要下载一个第三方库:Django-password-reset:
打开cmd输入:
pip install -U django-password-reset
-U参数的作用
第三方库也是一个app,因此应该在settings.py中注册app,
INSTALLED_APPS = [
...
'userprofile',
# ------
'password_reset',
# ------
]
在my_glog/urls.py中添加app的地址:
path('password-reset/', include('password_reset.urls')),
修改 userporfile/login.html,提供重置密码的入口:
<button type="submit" class="btn btn-primary">登陆</button>
</form>
<!-- 新增这里 -->
<br>
<h5>
忘记密码了?
</h5>
<h5>
点击<a href="{% url "password_reset_recover" %}">这里</a>重置密码
</h5>
<!-- 新增这里结束 -->
<br>
<h5>还没有账号?</h5>
<h5>点击<a href='{% url "userprofile:register" %}'>注册账号</a>加入我们吧!</h5>
<br>
</div>
</div>
</div>
{% endblock content %}
接下来配置发件邮箱的账号密码以及发送邮件的端口,发件人等信息
配置my_blog/settings.py
# SMTP服务器
EMAIL_HOST = 'smtp.qq.com'
# 邮箱名
EMAIL_HOST_USER = '[email protected]'
# 授权码
EMAIL_HOST_PASSWORD = '*********'
# 发送邮件的端口
EMAIL_PORT = 25
# 是否使用 TLS
EMAIL_USE_TLS = True
# 默认的发件人
DEFAULT_FROM_EMAIL = 'wei.guo <[email protected]>'
**EMAIL_HOST_PASSWORD **应该输入的的邮箱的授权码
点击重置密码输入邮箱后:
打开邮箱可以看见邮件
我们点击链接:
输入新密码提交后重新登陆
可以看见重新登陆了
6. 扩展用户信息
编写userprofile/models.py
from django.contrib.auth.models import User
from django.db import models
# Create your models here.
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
# 与User模型构成一对一的关系
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
# 电话号码字段:
phone = models.CharField(max_length=20, blank=True)
# 头像
avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)
# 个人简介
bio = models.TextField(max_length=500, blank=True)
def __str__(self):
return 'user{}'.format(self.user.username)
# 信号接受函数,每当新建User实例时自动调用
@receiver(post_save, sender=User)
def create_user_profile(sender,instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
# 信号更新函数,每当更新User实例时自动调用
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
每个Profile模型对应唯一的一个User模型,形成了对User的外接扩展,因此你可以在Profile添加任何想要的字段。这种方法的好处是不需要对User进行任何改动,从而拥有完全自定义的数据表。模型本身没有什么新的知识,比较神奇的是用到的信号机制。
Django包含一个“信号调度程序”,它可以在框架中的某些位置发生操作时,通知其他应用程序。简而言之,信号允许某些发送者通知一组接收器已经发生了某个动作。当许多代码可能对同一事件感兴趣时,信号就特别有用。
这里引入的post_save就是一个内置信号,它可以在模型调用save()方法后发出信号。
有了信号之后还需要定义接收器,告诉Django应该把信号发给谁。装饰器receiver就起到接收器的作用。每当User有更新时,就发送一个信号启动post_save相关的函数。
通过信号的传递,实现了每当User创建/更新时,Profile也会自动的创建/更新。
接下来重建数据库, 由于avatar是图像字段,需要安装pillow来支持
在命令行输入:
pip install Pillow
然后迁移数据
F:\Desktop\myblog>python manage.py makemigrations
System check identified some issues:
WARNINGS:
article.ArticlePost.created: (fields.W161) Fixed default value provided.
HINT: It seems you set a fixed date / time / datetime value as default for this field. This may not be what you want. If you want to have the current date as default, use `django.utils.timezone.now`
Migrations for 'article':
article\migrations\0002_auto_20190209_1224.py
- Alter field created on articlepost
Migrations for 'userprofile':
userprofile\migrations\0001_initial.py
- Create model Profile
F:\Desktop\myblog>
F:\Desktop\myblog>python manage.py migrate
System check identified some issues:
WARNINGS:
article.ArticlePost.created: (fields.W161) Fixed default value provided.
HINT: It seems you set a fixed date / time / datetime value as default for this field. This may not be what you want. If you want to have the current date as default, use `django.utils.timezone.now`
Operations to perform:
Apply all migrations: admin, article, auth, contenttypes, sessions, userprofile
Running migrations:
Applying article.0002_auto_20190209_1224... OK
Applying userprofile.0001_initial... OK
F:\Desktop\myblog>
成功迁移数据库了.
如果你试图登陆用户,会得到报错,这是因为之前创建的User数据都没有对应的Profile模型,违背了现有的模型。一种解决办法就是干脆删除旧的数据,因此就需要用到Django的shell命令。
在虚拟环境中输入python manage.py shell:
F:\Desktop\myblog>python manage.py shell
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
接下来输入:
In [1]: from django.contrib.auth.models import User
In [2]: User.objects.all().delete()
Out[2]:
(13,
{'admin.LogEntry': 8,
'auth.User_groups': 0,
'auth.User_user_permissions': 0,
'article.ArticlePost': 3,
'userprofile.Profile': 0,
'auth.User': 2})
In [3]:
注意因为前面写的article模型中,与User的外键也采用了models.CASCADE级联删除模式,因此随着User的删除,相关的文章也一并删除了。
输入exit()退出shell,输入指令python manage.py createsuperuser,重新创建管理员账户。
接下来为Profile模型新建一个表单类userprofile/forms.py去编辑它的内容:
from .models import Profile
...
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('phone', 'avatar', 'bio')
接下来在userprofile/views.py中填写处理用户信息的函数profile_edit
from .forms import ProfileForm
from .models import Profile
...
# 编辑用户信息
@login_required(login_url='/userprofile/login/')
def profile_edit(request, id):
user = User.objects.get(id=id)
# user_id是OneToOneField自动生成的字段
profile = Profile.objects.get(user_id=id)
if request.method == 'POST':
# 验证修改数据者是否为本人
if request.user != user:
return HttpResponse("你没有权限修改次用户信息")
profile_form = ProfileForm(data=request.POST)
if profile_form.is_valid():
# 取得清洗后的合法数据
profile_cleaned_data = profile_form.cleaned_data
profile.phone = profile_cleaned_data['phone']
profile.bio = profile_cleaned_data['bio']
profile.save()
return redirect("userprofile:edit", id=id)
else:
return HttpResponse("注册表单输入有误,请重新输入")
elif request.method == "GET":
profile_form = ProfileForm()
context = {'profile_form': profile_form, 'profile': profile, 'user': user}
return render(request, 'userprofile/edit.html', context)
else:
return HttpResponse("请使用GET或POST请求数据")
接下来在templates/userprofile中新建模板文件edit.html
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
用户信息
{% endblock title %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<br>
<div class="col-md-4">用户名{{ user.username }}div>\
<br>
<form method="post" action=".">
{% csrf_token %}
<div class="form-group col-md-4">
<label for="phone">电话label>
<input type="text" class="form-control" id="phone" name="phone" value="{{ profile.phone }}">
div>
<div class="form-group col-md-4">
<label for="bio">简介label>
<textarea type="text" class="form-control" id="bio" name="bio" rows="12">{{ profile.bio }}textarea>
div>
<button type="submit" class="btn btn-primary">提交button>
form>
div>
div>
div>
{% endblock content %}
最后一步配置userprofile/urls.py
path('edit//' , views.profile_edit, name='edit'),
在浏览器地址栏输入http://127.0.0.1:8000/userprofile/edit/4/(找不到用户id可以新建一个用户)
接下来给人个信息添加一个入口
修改templates/header.html
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
个人信息a>
<a class="dropdown-item" href="#" onclick="user_delete()">删除用户a>
退出登录a>
div>
接下来修改article/views.py视图, 前面为了简单就把所有文章的作者全部绑定为用户id为1的用户,还没有对用户登陆状态进行检查:
...
from django.contrib.auth.decorators import login_required
...
# 检查登陆
@login_required(login_url='/userprofile/login')
def article_create(request):
...
new_article = article_post_form.save(commit=False)
new_article.author = User.objects.get(id=request.user.id)
new_article.save()
...
重启服务器:
点击用户信息:
修改用户信息就完成了
我们应该也在用户删除更新文章时同样进行登陆状态检查,在views.py中每个函数前面加上:**#
@login_required(login_url=’/userprofile/login’)**即可
7. 配置Admin
在admin中将User profile合并为一张完整的表格:修改userprofile/admin.py
from django.contrib import admin
# Register your models here.
from userprofile.models import Profile
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'UserProfile'
# 将Profile关联到User中
class UserAdmin(BaseUserAdmin):
inlines = (ProfileInline, )
# 重新注册User
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
打开admin中User表,可以看见Profile的数据已经堆叠在底部了
8. 上传用户头像
图片属于一种媒体文件,需要设置一个统一的目录,便于集中存储和访问
修改/my_blog/settings.py,在最后加上:
# MEDIA_ROOT和MEDIA_URL是用户上传文件保存、访问的位置
MEDIAL_URL = '/media/'
MEDIAL_ROOT = os.path.join(BASE_DIR, 'media/')
在my_blog/urls.py添加下面的语句:
...
from django.conf.urls.static import static
from django.conf import settings
...
urlpatterns += static(settings.MEDIAL_URL, document_root = settings.MEDIAL_ROOT)
为以后上传的图片配置好了URL路径
编写MTV修改视图使之能够对图片进行处理,修改userprofile/views.py:
# 编辑用户信息
@login_required(login_url='/userprofile/login/')
def profile_edit(request, id):
...
profile_form = ProfileForm(request.POST, request.FILES)
if profile_form.is_valid():
# 取得清洗后的合法数据
...
profile.bio = profile_cleaned_data['bio']
if 'avatar' in request.FILES:
profile.avatar = profile_cleaned_data['avatar']
profile.save()
...
修改模板文件,添加代码显示,处理用户的头像:
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
用户信息
{% endblock title %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<br>
<div class="col-md-4">用户名{{ user.username }}div>
<br>
{% if profile.avatar %}
<div class="col-md-4">头像div>
<img src="{{ profile.avatar.url }}" style="max-width: 20%; border-radius:15%" class="col-md-4">
{% else %}
<h5 class="col-md-4">暂无头像h5>
{% endif %}
<br>
<br>
<form method="post" action="." enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="avatar">上传头像label>
<input type="file" class="form-control-file" name="avatar" id="avatar">
div>
...
{% endblock content %}
模板语法{% if … %}判断用户是否上传头像。
标签用于显示图片内容;在style属性中规定了图片的最大宽度并带有一点的圆角。
注意,表单必须设置enctype="multipart/form-data"属性,才能够正确上传图片等文件。
添加标签用于上传图片。
重启服务器点击选择文件,然后点击提交:
头像已经成功上传了,查看目录发现图片已经在media/avatar中了
目录
Django个人博客搭建1-创建Django项目和第一个App
Django个人博客搭建2-编写文章Model模型,View视图
Django个人博客搭建3-创建superuser并向数据库中添加数据并改写视图
Django个人博客搭建4-配置使用 Bootstrap 4 改写模板文件
Django个人博客搭建5-编写文章详情页面并支持markdown语法
Django个人博客搭建6-对文章进行增删查改
Django个人博客搭建7-对用户登陆注册等需求的实现
Django个人博客搭建8-优化文章模块
Django个人博客搭建9-增加文章评论模块