BBS项目-首页搭建
1 首页导航条搭建
参照博客园的首页布局
首先使用 bootstrap 模板搭建导航条,导航条最右边用户功能,实现未登录用户,展示“注册/登录”;已登录用户展示用户名以及更多用户操作,因此需要在前端用模板语法获取用户,判断用户是否登录。
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出登录</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登录</a></li>
{% endif %}
</ul>
后端返回request对象,利用auth组件 is_authenticated 方法判断用户是否登录,因为登录接口在校验通过后保存了用户登录状态。
未登录用户,展示注册/登录,同样用a标签,利用模板语法反向解析,动态获取到注册与登录接口的路由。
2 用户功能之修改密码
首先,只有登录用户才有更多操作。
其次,点击修改密码,前端用模态框展示修改密码页面。
最后,前端用ajax提交post请求到后端修改密码接口
2.1 模态框
在 bootstrap 找到 js插件,从模态框模板中拷贝其中一个。
点击button按钮就弹出模态框,button标签里的核心代码是
data-toggle="modal" data-target=".bs-example-modal-lg"
我们要实现的是点击“修改密码”的a标签触发模态框,因此把上面的代码拷贝到 a 标签中,然后删除掉button标签。
注意:删除button标签后,下面对应的div标签,需要加上 bs-example-modal-lg 属性,和 a 标签对应。
模态框页面,需要用户输入用户名(默认)、旧密码、新密码、确认密码等数据,然后用ajax给后端发送post请求。
我们自己写模态框修改密码页面,页面需要两个button按钮(取消/提交),我们拷贝的 bootstrap 的样式。模态框整体代码如下:
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h1 class="text-center">修改密码</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" disabled class="form-control" value="{{ request.user.username }}">
</div>
<div class="form-group">
<label for="">旧密码</label>
<input type="password" id="id_old_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="id_new_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="id_confirm_pwd" class="form-control">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary">修改</button>
</div>
</div>
</div>
</div>
</div>
</div>
页面效果如下:
2.2 ajax提交数据
<script>
$('#id_edit').click(function () {
$.ajax({
url: '/edit_pwd/',
type: 'post',
data: {
'old_password': $('#id_old_pwd').val(),
'new_password': $('#id_new_pwd').val(),
'confirm_password': $('#id_confirm_pwd').val()
},
success: function (args) {
alert(args)
}
})
})
</script>
2.3 后端修改密码接口
后端修改密码视图函数,前提是登录用户,因此需要利用auth组件的登录认证装饰器。
@login_required
def edit_pwd(request):
return HttpResponse('ok')
在setting.py中全局配置,自定义登录的URL,如果未登录用户访问,都让它跳转到login页面。
LOGIN_URL = '/login/'
修改密码接口我们没有用forms组件,因此对密码的校验逻辑需要自己写,并返回给前端错误提示。因为是/home/页面直接发送ajax请求到edit_pwd接口,后端直接获取数据进行校验即可,不需要判断是get请求还是post请求。
views.py
@login_required
def edit_pwd(request):
back_dic = {'code': 0, 'msg': ''}
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
# 校验旧密码
if request.user.check_password(old_password):
if not new_password:
back_dic['code'] = 1
back_dic['msg'] = '新密码不能为空'
elif len(new_password) < 4 or len(new_password) > 8:
back_dic['code'] = 1
back_dic['msg'] = '密码最短4位最长8位'
elif new_password != confirm_password:
back_dic['code'] = 1
back_dic['msg'] = '两次密码不一致'
else:
# 修改密码
request.user.set_password(new_password)
request.user.save()
back_dic['msg'] = '修改成功'
else:
back_dic['code'] = 1
back_dic['msg'] = '旧密码错误'
return JsonResponse(back_dic)
前端对返回的信息进行处理。
<script>
$('#id_edit').click(function () {
$.ajax({
url: '/edit_pwd/',
type: 'post',
data: {
'old_password': $('#id_old_pwd').val(),
'new_password': $('#id_new_pwd').val(),
'confirm_password': $('#id_confirm_pwd').val()
},
success: function (args) {
if (args.code === 0) {
window.location.href = '/login/'
}else{
$('#id_pwd_error').text(args.msg)
}
}
})
})
$('input').focus(function () {
$('#id_pwd_error').text('')
})
</script>
3 用户功能之退出登录
登录用户点击 “退出登录”a 标签,跳转到 logout 路由,后端接口用auth组件清空掉request.user里的登录用户对象,并让页面重定向到首页。
home.html
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退出登录</a></li>
</ul>
views.py
@login_required
def logout(request):
auth.logout(request)
return redirect('/home/')
4 首页搭建
参考博客园网站,首页 2-8-2 布局
<div class="container-fluid">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8"></div>
<div class="col-md-2"></div>
</div>
</div>
左右两侧可以用来展示广告、网站信息、外站链接、新闻等
4.1 admin后台管理
中间用来做文章展示,我们需要录入一些初始数据,为了录入数据方便,我们需要用到django自带的admin组件。
django给你提供了一个可视化的界面,用来让你方便的对你的模型表进行数据的增删改查操作,如果你想要使用amdin后台管理操作模型表,你需要先注册你的模型表,告诉admin你需要操作哪些表,去你的应用下的admin.py中注册你的模型表。
创建管理员用户
python3 manage.py createsuperuser
admin.py 注册模型表
from django.contrib import admin
from app01 import models
# Register your models here.
admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Tag)
admin.site.register(models.Categorize)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
访问admin路由,页面显示如下:
admin后台默认展示的是表名s,可以在表模型中自己设置表名。
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class UserInfo(AbstractUser):
phone = models.BigIntegerField(verbose_name='电话号码', null=True)
avatar = models.FileField(verbose_name='用户头像', upload_to='avatar/', default='avatar/default.npg')
create_time = models.DateField(verbose_name='创建时间', auto_now_add=True)
blog = models.OneToOneField(to='Blog', null=True, on_delete=models.CASCADE)
class Meta():
verbose_name_plural = '用户表'
admin会给每一个注册了的模型表自动生成增删改查四条url ,关键点在于urls.py 中的第一条自带的url。
http://127.0.0.1:8000/admin/app01/userinfo/ # 查
http://127.0.0.1:8000/admin/app01/userinfo/add/ # 增
http://127.0.0.1:8000/admin/app01/userinfo/1/change/ # 改
http://127.0.0.1:8000/admin/app01/userinfo/1/delete/ # 删
4.2 手动录入数据
从文章表入手,因为文章表关联了很多外键字段。
文章表关联了站点和分类两个外键,先建站点,站点没有外键,不用关联其他表。
为了方便区分,在模型表中定义一个__str__方法, 就能显示出你定义的字符串。添加了__str__()方法后,admin后台显示的是site_name。
class Blog(models.Model):
site_name = models.CharField(verbose_name='站点名称', max_length=32) # url输入的用户名,用来跳转到用户站点
site_title = models.CharField(verbose_name='站点标题', max_length=32)
site_theme = models.CharField(verbose_name='站点样式', max_length=64) # 用来存css/js的文件路径,用户自定义站点样式保存到文件中
class Meta():
verbose_name_plural = '个人站点表'
def __str__(self):
return self.site_name
再建分类,分类关联站点:
注意:不能把站点A的分类关联到站点B。现在,我们创建了2个站点,每个站点创建了3个分类。接下来,手动录入文章表的其他内容。
录完文章表数据后,我们仍需创建其他表。
特别注意的是:
1、标签表,标签表与文章表的第三方关系表, 标签表与站点表关联。
2、用户表与个人站点表的关联。
绑定用户表与站点表,用户表有blog外键字段。
报错,phone字段在admin后台是必填字段,可以在表模型中设置参数,让其可以为空。
class UserInfo(AbstractUser):
phone = models.BigIntegerField(verbose_name='电话号码', null=True, blank=True)
# null = True 数据库中该字段可以为空
# blank = True admin后台管理该字段可以为空
创建标签表,绑定标签表与站点表的关联。
创建标签表与文章表的第三方关系表,标签和文章多对多,因此一篇文章下绑定多个标签没有问题,但是不要把用户A的文章绑定用户B的标签。
4.3 文章展示
后端查询文章表所有数据,返回给前端。 views.py
def home(request):
# 查询文章表所有数据返回给前端
article_queryset = models.Article.objects.all()
return render(request, 'home.html', locals())
前端展示参考博客园。
拷贝 bootstrap 媒体对象列表模板,在模板基础上调整样式。
<ul class="media-list">
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Media heading</h4>
...
</div>
</li>
</ul>
ul 套 li 标签,利用for循环 article_queryset 获取文章对象,把每一个文章对象渲染到 li 标签内。
<ul class="media-list">
{% for article_obj in article_queryset %}
<h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4> <!--标题 a标签 点击跳转到文章-->
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="{% static 'img/default.png' %}" alt="..."> <!--头像 a标签 点击跳转到个人站点-->
</a>
</div>
<div class="media-body">
{{ article_obj.desc }} <!--简介-->
</div>
</li>
<br>
<div>
<span><a href="#">{{ article_obj.blog.userinfo.username }}</a> </span> <!--用户名 a标签 点击跳转到个人站点-->
<span>{{ article_obj.create_time|date:'Y-m-d' }} </span>
<span><span class="glyphicon glyphicon-thumbs-up"></span> {{ article_obj.up_num }} </span>
<span><span class="glyphicon glyphicon-comment"></span> {{ article_obj.comment_num }}</span>
</div>
<hr>
{% endfor %}
</ul>
4.4 media配置
如下,用模板语法获取avatar字段,无法展示用户头像,因为avatar字段存放的文件路径,图片文件在后端存放在avatar文件夹下,需要后端开放该文件夹资源才能进行访问。
<img class="media-object" src="{{article_obj.blog.userinfo.avatar}}" alt="..." width="60">
网站所使用的静态文件默认放在static文件夹下,用户上传的静态文件(头像、文章、简介)也应该单独放在某个文件夹下。django提供了 media配置
,该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下。
settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 配置用户上传的文件的存储位置
# 配置后,一旦用户上传了头像,会自动创建多级目录 media/avatar,因为我们models.py中指定头像文件保存在avatar文件夹下
配置了media文件夹,还需要在后端开放该文件夹资源。
urls.py
from django.urls import path, re_path
from django.views.static import serve
from newbbs import settings
# 开放后端指定文件夹资源
# media相当于接口前缀/令牌,字典中放入你想要开放的文件夹资源的路径(配置在settings.py中)
re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
浏览器输入路由:http://127.0.0.1:8000/media/avatar/default.png 即可以访问media文件夹下的静态资源。
但是我们的首页,头像图片仍然没有显示,我们打开浏览器调试,定位到img标签。
src 属性对应的路由需要有 /media/前缀,我们的模板语法 src="{{article_obj.blog.userinfo.avatar}}"
只能获取到avatar字段,即图片的存储路径 avatar/xxx.jpg, 我们可以手动在这个路径前拼接/media/前缀。
<a href="#">
<img class="media-object" src="/media/{{article_obj.blog.userinfo.avatar}}" alt="..." width="60">
</a>
至此,我们的首页展示搭建完成,页面如下:
4.5 自定义分页器的使用
在首页使用分页,应用下创建一个utils文件夹,建一个mypage.py文件,把封装好的自定义分页器代码拷贝进去。
views.py
from app01.utils.mypage import Pagination
def home(request):
# 查询文章表所有数据返回给前端
article_queryset = models.Article.objects.all()
# 分页器
curren_page = request.GET.get('page', 1)
all_count = article_queryset.count()
page_obj = Pagination(current_page=curren_page, all_count=all_count)
page_queryset = article_queryset[page_obj.start: page_obj.end]
return render(request, 'home.html', locals())
home.html
{% for article_obj in page_queryset %} <!--把循环的对象替换成page_queryset-->
...
{% endfor %}
<h5 class="text-center">{{ page_obj.page_html|safe }}</h5> <!--用h5标签套下,可以使分页器居中-->
页面效果如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人