dya72-day75.BBS项目开发流程
Django版本:django4.x或者1.x(区别在于url和models中的外键字段)
数据库:mysql
以下是利用终端开发项目的流程,使用django则更加简单
1.创建项目+创建&注册app(提前安装好django,Navicat等软件)
1.1 windows+r,输入cmd,打开终端,切换到自己要创建项目的目录下
比如:D盘中,输入以下命令,进入D盘中
D:
cd D:\
1.2 输入下面的命令,创建BBS项目:
django-admin startproject BBS
1.3 切换到项目下(cd D:\BBS\),输入下面的命令,创建app:
python manage.py startapp app01
1.4 在settings文件中注册app(最后一行)
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # 该行为注册的app01 ]
1.5 注意:终端命令行创建的django项目中,没有templates文件夹,手动去BBS项目下,创建templates文件夹
1.6 创建好了templates文件夹之后,去配置文件settings.py中配置templates路径
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,'templates')], # 该行需要配置,记得导入import os 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
2.1 在Navicat中创建数据库(步骤省略)
2.2 在settings文件中配置数据库信息(由于django自带的sqlite3数据库对日期不敏感,所以我们在这个项目中使用mysql数据库)
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbs02', 'USER': 'root', 'PASSWORD':'123', 'HOST':'127.0.0.1', 'PORT':3306, 'CHARSET':'utf8', } }
2.3 在项目或者应用下的__init__中书写如下代码
import pymysql pymysql.install_as_MySQLdb()
3.1 在app01下的models.py 中创建表(由于是django 4.X版本,所以外键字段&OneToOneField中需要加上 :on_delete=models.CASCADE,Django 1.X版本则不需要)
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. """我们不用django的Auth表,自己创建一张UserInfo表""" class UserInfo(AbstractUser): phone = models.BigIntegerField(verbose_name='手机号',null=True) avatar = models.FileField(verbose_name='用户头像',upload_to='avatar/',default='avatar/default.png') create_time = models.DateField(verbose_name='创建时间',auto_now_add=True) blog = models.OneToOneField(to='Blog',null=True,on_delete=models.CASCADE) class Blog(models.Model): site_name = models.CharField(verbose_name='站点名称',max_length=32) site_title = models.CharField(verbose_name='站点标题',max_length=32) site_theme = models.CharField(verbose_name='站点样式',max_length=64) class Category(models.Model): name = models.CharField(verbose_name='分类名称',max_length=32) blog = models.ForeignKey(to='Blog',null=True,on_delete=models.CASCADE) class Tag(models.Model): name = models.CharField(verbose_name='标签名称',max_length=32) blog = models.ForeignKey(to='Blog',null=True,on_delete=models.CASCADE) class Article(models.Model): title = models.CharField(verbose_name='文章标题',max_length=64) desc = models.CharField(verbose_name='文章简介',max_length=255) content = models.TextField(verbose_name='文章内容') create_time = models.DateField(verbose_name='创建时间',auto_now_add=True) # 数据库字段优化设计 up_num = models.BigIntegerField(verbose_name='点赞数',default=0) down_num = models.BigIntegerField(verbose_name='点踩数',default=0) comment_num = models.BigIntegerField(verbose_name='评论数',default=0) # 外键字段 blog = models.ForeignKey(to='Blog',null=True,on_delete=models.CASCADE) category = models.ForeignKey(to='Category',null=True,on_delete=models.CASCADE) tags = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag')) class Article2Tag(models.Model): article = models.ForeignKey(to='Article',on_delete=models.CASCADE) tag = models.ForeignKey(to='Tag',on_delete=models.CASCADE) class UpAndDown(models.Model): user = models.ForeignKey(to='UserInfo',on_delete=models.CASCADE) article = models.ForeignKey(to='Article',on_delete=models.CASCADE) is_up = models.BooleanField() class Comment(models.Model): user = models.ForeignKey(to='UserInfo',on_delete=models.CASCADE) article = models.ForeignKey(to='Article',on_delete=models.CASCADE) cotent = models.CharField(verbose_name='评论内容',max_length=255) comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True) # 自关联 parent = models.ForeignKey(to='self',null=True,on_delete=models.CASCADE) #一定要指定null=True,因为有些评论是根评论
3.2 由于创建的表中,有我们自己创建的UserInfo表,因此,需要在settings.py 中配置
AUTH_USER_MODEL = 'app01.UserInfo'
3.3 表创建完成,执行数据库迁移命令,创建表
python manage.py makemigrations
python manage.py migrate
path('register/', views.register, name='register'),
4.2 由于要进行用户名和密码的校验等功能,所以我们考虑使用forms组件,在app01下创建一个myforms.py的文件,书写如下代码:
from django import forms from app01 import models class MyRegForm(forms.Form): username = forms.CharField( label='用户名', min_length=3, max_length=8, error_messages={ 'required':'用户名不能为空', 'min_length':'用户名最少为3位', 'max_length':'用户名最多为8位', }, widget = forms.widgets.TextInput(attrs={'class':'form-control'}) ) password = forms.CharField( label='密码', min_length=3, max_length=8, error_messages={ 'required':'密码不能为空', 'min_length':'密码最少为3位', 'max_length':'密码最多为8位', }, widget = forms.widgets.PasswordInput(attrs={'class':'form-control'}) ) confirm_password = forms.CharField( label='确认密码', min_length=3, max_length=8, error_messages={ 'required':'确认密码不能为空', 'min_length':'确认密码最少为3位', 'max_length':'确认密码最多为8位', }, widget = forms.widgets.PasswordInput(attrs={'class':'form-control'}) ) email = forms.EmailField( label='邮箱', error_messages={ 'required':'邮箱不能为空', 'invalid':'邮箱格式不正确' }, widget = forms.widgets.EmailInput(attrs={'class':'form-control'}) ) # 钩子函数 def clean_username(self): username=self.cleaned_data.get('username') is_exist=models.UserInfo.objects.filter(username=username) if is_exist: self.add_error('username','用户名已存在') return username def clean(self): password=self.cleaned_data.get('password') confirm_password=self.cleaned_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password','两次输入的密码不一致') return self.cleaned_data
4.3 在views.py中书写后端代码(register部分)
def register(request): form_obj = MyRegForm() back_dic = {'code':1000,'msg':''} if request.method == 'POST': form_obj = MyRegForm(request.POST) if form_obj.is_valid(): clean_data = form_obj.cleaned_data clean_data.pop('confirm_password') file_obj = request.FILES.get('avatar') if file_obj: clean_data['avatar']=file_obj models.UserInfo.objects.create_user(**clean_data) back_dic['url']='/login/' # 为了跳转准备 else: back_dic['code'] = 2000 back_dic['msg'] = form_obj.errors return JsonResponse(back_dic) return render(request,'register.html', locals())
4.4 在templates文件夹下创建register.html代码,输写以下代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> {% load static %} </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">注册页面</h1> <form id="myform"> {% csrf_token %} {% for form in form_obj %} <div class="form-group"> <label for="{{ form.auto_id }}">{{ form.label }}</label> {{form}} <span style="color:red" class="pull-right"></span> </div> {% endfor %} <div class="form-group"> <label for="myfile">头像 <img src="{% static 'img/default.png' %}" alt="" id="myimg" width="100" style="margin-left:10px"> </label> <input type='file' id='myfile' name="avatar" style="display:none"> </div> <input type="button" id="id_commit" class="btn btn-primary pull-right" value="注册"> </form> </div> </div> </div> <script> // 头像展示相关 $("#myfile").change(function(){ // alert(123) let myFileReadObj = new FileReader(); let fileObj = $(this)[0].files[0]; myFileReadObj.readAsDataURL(fileObj); myFileReadObj.onload = function(){ $("#myimg").attr('src',myFileReadObj.result) } }); // ajax提交数据并展示错误信息 $("#id_commit").click(function(){ let formDataObj = new FormData(); // 添加普通键值对 $.each($("#myform").serializeArray(),function(index,obj){ formDataObj.append(obj.name,obj.value) }) // 添加文件 formDataObj.append('avatar',$("#myfile")[0].files[0]) $.ajax({ url:'', type:'post', data:formDataObj, contentType:false, processData:false, success:function(args){ if(args.code==1000){ window.location.href=args.url }else{ // 展示错误信息 $.each(args.msg,function(index,obj){ // console.log(index,obj) let targetId = '#id_' + index $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }) $('input').focus(function(){ $(this).next().text('').parent().removeClass('has-error') }) </script> </body> </html>
# 登录功能 path('login/', views.login, name='login'), # 图片验证码相关 path('get_code/', views.get_code, name='get_code'),
def login(request): return render(request,'login.html') # 图片验证码相关 from PIL import Image,ImageDraw,ImageFont import random from io import BytesIO, StringIO def get_random(): return random.randint(0,255),random.randint(0,255),random.randint(0,255) def get_code(request): img_obj = Image.new('RGB',(400,35),get_random()) img_draw = ImageDraw.Draw(img_obj) img_font = ImageFont.truetype('static/font/333.ttf',35) code = '' for i in range(5): rand_upper = chr(random.randint(65,90)) rand_lower = chr(random.randint(97,122)) rand_int = str(random.randint(0,9)) tmp = random.choice([rand_upper,rand_lower,rand_int]) img_draw.text((i*60+60,-5),tmp,get_random(),img_font) code += tmp request.session['code'] = code io_obj = BytesIO() img_obj.save(io_obj,'png') return HttpResponse(io_obj.getvalue())
5.3 在templates文件夹下创建login.html代码,输写以下代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> {% load static %} </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">登录页面</h1> <div class="form-group"> <label>用户名</label> <input type="text" id="id_username" name="id_username" class="form-control"> </div> <div class="form-group"> <label>密码</label> <input type="password" id="id_password" name="id_password" class="form-control"> </div> <div class="form-group"> <label>验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="id_code" name="id_code" class="form-control"> </div> <div class="col-md-6"> <img src="/get_code/" alt="" id="id_img" width="400" height="35"> </div> </div> </div> <input type="button" class="btn btn-success" id="id_commit" value="登录"> </div> </div> </div> <script> $("#id_img").click(function(){ let oldVal = $(this).attr('src') $(this).attr('src', oldVal += "?") }) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> {% load static %} </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">登录页面</h1> <div class="form-group"> <label for="">用户名</label> <input type="text" id="id_username" class="form-control"> </div> <div class="form-group"> <label for="">密码</label> <input type="password" id="id_password" class="form-control"> </div> <div class="form-group"> <label for="">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="id_code" class="form-control"> </div> <div class="col-md-6"> <img src="/get_code/" alt="" width="400" height="35" id="myimg"> <input type="button" value="看不清?换一张" id="id_change_code"> </div> </div> </div> <button class="btn btn-success" id="id_button">登陆</button> <span style="color:red" id="id_error"></span> </div> </div> </div> <script> // 随机验证码点击更换 $("#id_change_code").click(function () { let oldVal = $("#myimg").attr('src') $("#myimg").attr('src', oldVal += '?') }) // 后端发送ajax请求的关键代码 $("#id_button").click(function () { $.ajax({ url:'', type:'post', data:{ 'username':$("#id_username").val(), 'password':$("#id_password").val(), 'code':$("#id_code").val(), 'csrfmiddlewaretoken':'{{ csrf_token }}' // 一定不要忘记发送csrfmiddlewaretoken数据 }, // 接收后端返回的数据,利用if做判断 success:function (args) { if(args.code==1000){ window.location.href=args.url }else{ $("#id_error").text(args.msg) } } }) }) </script> </body> </html>
1.2 后端接收前端数据(后端login处理前端传过来的数据)
# 登陆功能 def login(request): if request.method == 'POST': back_dic = {'code':1000,'msg':''} username = request.POST.get('username') password = request.POST.get('password') code = request.POST.get('code') if request.session.get('code').upper() == code.upper(): user_obj = auth.authenticate(request,username=username,password=password) if user_obj: auth.login(request,user_obj) back_dic['url'] = '/home/' else: back_dic['code'] = 2000 back_dic['msg'] = '用户名或者密码不正确' else: back_dic['code'] = 3000 back_dic['msg'] = '验证码不正确' return JsonResponse(back_dic) return render(request, 'login.html')
# 首页 url(r'^home/', views.home, name='home'),
# 首页 def home(request): return render(request, 'home.html', locals())
判断用户是否登陆,如果登陆,则在导航条右边显示登陆用户的用户名+更多操作,如果没有登陆,导航条右端显示注册+登陆。如下图
未登录状态
登陆状态
<nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">BBS</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> <li><a href="#">文章</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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <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="#" 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> </li> {% else %} <li><a href="{% url 'register' %}">注册</a></li> <li><a href="{% url 'login' %}">登陆</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav>
# 修改密码 url(r'^set_password/', views.set_password, name='set_password'),
由于登陆的用户才能修改密码,因此,需要在views.py 中导入auth模块及auth模块的装饰器
关键:request.user.check_password、request.user.set_password、request.user.save()
from django.contrib import auth from django.contrib.auth.decorators import login_required
# 修改密码 @login_required def set_password(request): if request.is_ajax(): back_dic = {'code':1000, 'msg':''} if request.method == 'POST': 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 new_password == confirm_password: request.user.set_password(new_password) request.user.save() back_dic['url'] = '/login/' back_dic['msg'] = '密码修改成功' else: back_dic['code'] = 1001 back_dic['msg'] = '两次密码不一致' else: back_dic['code'] = 1002 back_dic['msg'] = '原密码错误' return JsonResponse(back_dic)
拷贝bootstrap中的模态框,注意以下这一句,拷贝的代码中class里面少了:bs-example-modal-lg
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> {#导航条#} <nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">BBS</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> <li><a href="#">文章</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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <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="#" 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> <!-- Large modal --> <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 value="{{ request.user.username }}" class="form-control"> </div> <div class="form-group"> <label for="">原密码</label> <input type="password" id="id_old_password" class="form-control"> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="id_new_password" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="id_confirm_password" 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" id="id_change_password">修改</button> <span style="color: red" id="id_error"></span> </div> </div> </div> </div> </div> </div> </li> {% else %} <li><a href="{% url 'register' %}">注册</a></li> <li><a href="{% url 'login' %}">登陆</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <script> $("#id_change_password").click(function () { $.ajax({ url:'/set_password/', type:'post', data:{ 'old_password':$("#id_old_password").val(), 'new_password':$("#id_new_password").val(), 'confirm_password':$("#id_confirm_password").val(), 'csrfmiddlewaretoken':'{{ csrf_token }}' }, success:function (args) { if(args.code==1000){ window.location.reload() }else{ $("#id_error").text(args.msg) } } }) }) </script> </body> </html>
url
# 退出登陆 url(r'^logout/',views.logout, name='logout'),
views.py
# 退出登陆 @login_required def logout(request): auth.logout(request) return redirect('/home/')
home页面的导航条搭建好了之后,需要搭建侧边栏,布局采用2-8-2的形式,侧边栏两边展示广告,中间展示文章,这个时候,中间的文章,我们没有数据,因此需要在admin后台管理中去创建数据
4.1 侧边栏展示页搭建
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> {#导航条#} <nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">BBS</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> <li><a href="#">文章</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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <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="#" 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> <!-- Large modal --> <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 value="{{ request.user.username }}" class="form-control"> </div> <div class="form-group"> <label for="">原密码</label> <input type="password" id="id_old_password" class="form-control"> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="id_new_password" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="id_confirm_password" 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" id="id_change_password">修改</button> <span style="color: red" id="id_error"></span> </div> </div> </div> </div> </div> </div> </li> {% else %} <li><a href="{% url 'register' %}">注册</a></li> <li><a href="{% url 'login' %}">登陆</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> {#侧边栏及文章部分展示#} <div class="container-fluid"> <div class="row"> {# 左侧边栏#} <div class="col-md-2"> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> </div> {# 中间#} <div class="col-md-8"> </div> {# 右侧边栏#} <div class="col-md-2"> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> </div> </div> </div> <script> $("#id_change_password").click(function () { $.ajax({ url:'/set_password/', type:'post', data:{ 'old_password':$("#id_old_password").val(), 'new_password':$("#id_new_password").val(), 'confirm_password':$("#id_confirm_password").val(), 'csrfmiddlewaretoken':'{{ csrf_token }}' }, success:function (args) { if(args.code==1000){ window.location.reload() }else{ $("#id_error").text(args.msg) } } }) }) </script> </body> </html>
Django框架中的auth模块提供了图形化界面,方便管理和操作数据,创建数据之前要先创建一个超级用户
4.2 创建Django-admin超级用户(在pycharm的terminal里面书写如下代码)
python3 manage.py createsuperuser
4.3 注册所有表
由于django-admin页面登陆之后,需要先注册所有表,才能在admin页面显示出来,因此先去app01下的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.Category) admin.site.register(models.Tag) admin.site.register(models.Article2Tag) admin.site.register(models.Article) admin.site.register(models.UpAndDown) admin.site.register(models.Comment)
此时,admin页面中展示了所有表,但是表名都是英文,如果要让表名显示成中文,需要在models.py中的每个表下书写下面的代码
class Meta: verbose_name_plural = '你想要的表的中文名字'
4.4 创建数据
在admin页面中依次创建category,blog,article,tag,user,article2tag中的数据,注意3点:
1.在关联user和blog表的时候,UserInfo中的phone字段中,需要指定blank=True,否则在关联的时候会让你填写手机号,比较麻烦
2.在每个表下面书写如下代码,返回的数据是一个可区分的字段,否则在做关联的时候,所有的外键字段都是以对象的形式展示的,没法作区分
3. 创建数据的时候,一定要注意绑定关系,不要把一个人的文章或者博客绑定到另外一个人下面,否则在后面做查询的时候,结果会不准确,然后找
bug非常不好找,因此一定一定要注意绑定关系!
def __str__(self): return self.site_name
如果不写上面的代码,关联的时候,blog就这样显示
5.1 展示文章数据,后端代码
# 首页 def home(request): article_queryset = models.Article.objects.all() # 查询出所有文章,传递给login.html页面 return render(request, 'home.html', locals())
5.2 文章展示页面
文章下面的一些展示项目也需要书写,比如:Milton 发布于
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> {#导航条#} <nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">BBS</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> <li><a href="#">文章</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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <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="#" 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> <!-- Large modal --> <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 value="{{ request.user.username }}" class="form-control"> </div> <div class="form-group"> <label for="">原密码</label> <input type="password" id="id_old_password" class="form-control"> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="id_new_password" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="id_confirm_password" 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" id="id_change_password">修改</button> <span style="color: red" id="id_error"></span> </div> </div> </div> </div> </div> </div> </li> {% else %} <li><a href="{% url 'register' %}">注册</a></li> <li><a href="{% url 'login' %}">登陆</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> {#侧边栏及文章部分展示#} <div class="container-fluid"> <div class="row"> {# 左侧边栏#} <div class="col-md-2"> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> </div> {# 中间#} {#文章展示的核心代码#} <div class="col-md-8"> {% for article_obj in article_queryset %} <ul class="media-list"> <li class="media"> <h4 class="media-heading"><a href="">{{ article_obj.title}}</a></h4> <div class="media-left"> <a href="#"> {#用户头像展示核心代码#} <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80"> </a> </div> <div class="media-body"> {{article_obj.desc}} </div> <br> <span><a href="/{{ article_obj.blog.userinfo.username }}/">{{ article_obj.blog.userinfo.username }} </a></span> <span>发布于 </span> <span>{{ article_obj.create_time|date:'Y-m-d' }} </span> <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }}) </span> <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }}) </span> </li> </ul> <hr> {% endfor %} </div> {# 右侧边栏#} <div class="col-md-2"> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">广告位标题</h3> </div> <div class="panel-body"> 广告位内容 </div> </div> </div> </div> </div> <script> $("#id_change_password").click(function () { $.ajax({ url:'/set_password/', type:'post', data:{ 'old_password':$("#id_old_password").val(), 'new_password':$("#id_new_password").val(), 'confirm_password':$("#id_confirm_password").val(), 'csrfmiddlewaretoken':'{{ csrf_token }}' }, success:function (args) { if(args.code==1000){ window.location.reload() }else{ $("#id_error").text(args.msg) } } }) }) </script> </body> </html>
5.3 用户头像展示
5.3.1 我们在书写项目的时候,项目用到的文件,我们存放在static文件夹下面
而用户上传的头像,简历等文件,我们也需要放在一个文件夹下,通常是media文件夹
首先去settings.py中配置media文件路径
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
5.3.2 光是配置media文件夹是没有作用的,需要将media文件夹中的内容暴露给外界使用,因此需要在urls.py 中书写如下代码:
# 用户头像资源暴露 url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),
5.3.3 在home.html中给用户头像配置路径及头像的文件数据
<a href="#"> <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80"> </a>
6.1 urls.py
# 个人站点 url(r'^(?P<username>\w+)/$', views.site, name='site'),
6.2 view.py
# 个人站点 def site(request,username,**kwargs): user_obj = models.UserInfo.objects.filter(username=username).first()
# 1.判断用户是否存在 if not user_obj: # 2.如果用户不存在,则返回一个错误的404页面 return render(request, 'error.html') # 3.如果用户存在,返回一个 个人站点页面 return render(request, 'site.html', locals())
6.3 errorr.html
直接抄写博客园的错误页面代码即可
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> {% load static %} </head> <body> <!DOCTYPE html> <html> <head> <meta charset='utf-8'> <link rel="icon" href="//common.cnblogs.com/favicon.ico" type="image/x-icon" /> <title>404 页面不存在 - 博客园</title> <style type='text/css'> body { margin: 8% auto 0; max-width: 400px; min-height: 200px; padding: 10px; font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; font-size: 14px; padding-right: 200px; position: relative; } p { color: #555;margin: 15px 0px; } img { border: 0px; } .d { color: #404040; } .robot img { max-width: 192px; } .robot { position: absolute; top: 0; right: 0; } </style> </head> <body> <p style="margin-left: 5px;"><a href="/home/"><img src="{% static 'img/blog.jpg' %}" style="height:45px" alt="cnblogs"></a></p> <div style="margin-top:20px"> <p style=""><b style="">404.</b> 抱歉,您访问的资源不存在。</p> <p class="d">可能是网址有误,或者对应的内容被删除,或者处于私有状态。</p> <p style="color:#777;"><a href="/home/">返回网站首页</a></p> </div> <div class="robot"><a href="/home/"><img src="{% static 'img/404-robot.png' %}" alt="404 robot" /></a></div> <script async src="https://www.googletagmanager.com/gtag/js?id=G-4CQQXWHK3C"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-4CQQXWHK3C'); </script> </body> </html> </body> </html>
6.4 site.html
个人站点页面布局:导航条+2/8布局
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">{{ blog.site_title }}</a> {# 这里不再展示BBS,而是展示每个人的站点名称 #} </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> <li><a href="#">文章</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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <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="#" 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> <!-- Large modal --> <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 value="{{ request.user.username }}" class="form-control"> </div> <div class="form-group"> <label for="">原密码</label> <input type="password" id="id_old_password" class="form-control"> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="id_new_password" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="id_confirm_password" 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" id="id_change_password">修改</button> <span style="color: red" id="id_error"></span> </div> </div> </div> </div> </div> </div> </li> {% else %} <li><a href="{% url 'register' %}">注册</a></li> <li><a href="{% url 'login' %}">登陆</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="cotainer-fluid"> <div class="row"> {# 以下不再是广告内容,而是文章的分类、标签、日期归档 #} <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">文章分类</h3> </div> <div class="panel-body"> ... </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">文章标签</h3> </div> <div class="panel-body"> ... </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">日期归档</h3> </div> <div class="panel-body"> ... </div> </div> </div> <div class="col-md-9"> {# 以下是文章部分,直接拷贝home.html页面中的代码即可 ,只需要其中的article_queryset换成article_list即可#} {% for article_obj in article_list %} <ul class="media-list"> <li class="media"> <h4 class="media-heading"><a href="">{{ article_obj.title}}</a></h4> <div class="media-left"> <a href="#"> <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80"> </a> </div> <div class="media-body"> {{article_obj.desc}} </div> <div class="pull-right"> <span>posted </span> <span>@ </span> <span>{{ article_obj.create_time|date:'Y-m-d' }}</span> <span>{{ article_obj.blog.userinfo.username }} </span> <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }}) </span> <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }}) </span> <span><a href="">编辑</a> </span> </div> </li> </ul> <hr> {% endfor %} </div> </div> </div> </body> </html>
7.1 urls.py
关键点:利用有名分组的特性,合并侧边栏筛选功能
# 侧边栏筛选功能 url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site)
7.1 views.py
关键点1:3条orm分组查询必须得会(重点是正反向查询)
# 1.查询每个用户所有的文章分类及分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # 2.查询每个用户的所有标签及标签下的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # 3.按照年月筛选文章(这一条参考django提供的语法) date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num')
关键点2:对article_list 做进一步筛选,利用字符串的切片操作以及神奇的双下划线查询。
# 个人站点 def site(request,username,**kwargs): user_obj = models.UserInfo.objects.filter(username=username).first() # 1.判断用户是否存在 if not user_obj: # 2.如果用户不存在,则返回一个错误的404页面 return render(request, 'error.html') blog = user_obj.blog article_list = models.Article.objects.filter(blog=blog) if kwargs: # print(kwargs) condition = kwargs.get('condition') param = kwargs.get('param') if condition == 'category': article_list = article_list.filter(category_id=param) # print(article_list) elif condition == 'tag': article_list = article_list.filter(tags__pk=param) # 多对多查询,使用__ else: year,month = param.split('-') article_list = article_list.filter(create_time__month=month,create_time__year=year) # 1.查询每个用户所有的文章分类及分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # 2.查询每个用户的所有标签及标签下的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # 3.按照年月筛选文章 date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num') # 3.如果用户存在,返回一个个人站点页面 return render(request, 'site.html', locals())
7.2 site.html
关键点1:利用模版语法的点点点的功能,展示筛选出来的数据
关键点2:构造url:<a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">{{ blog.site_title }}</a> {# 这里不再展示BBS,而是展示每个人的站点名称 #} </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> <li><a href="#">文章</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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <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="#" 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> <!-- Large modal --> <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 value="{{ request.user.username }}" class="form-control"> </div> <div class="form-group"> <label for="">原密码</label> <input type="password" id="id_old_password" class="form-control"> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="id_new_password" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="id_confirm_password" 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" id="id_change_password">修改</button> <span style="color: red" id="id_error"></span> </div> </div> </div> </div> </div> </div> </li> {% else %} <li><a href="{% url 'register' %}">注册</a></li> <li><a href="{% url 'login' %}">登陆</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="cotainer-fluid"> <div class="row"> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">文章分类</h3> </div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.2 }}/">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">文章标签</h3> </div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">日期归档</h3> </div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div> <div class="col-md-9"> {% for article_obj in article_list %} <ul class="media-list"> <li class="media"> <h4 class="media-heading"><a href="">{{ article_obj.title}}</a></h4> <div class="media-left"> <a href="#"> <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80"> </a> </div> <div class="media-body"> {{article_obj.desc}} </div> <div class="pull-right"> <span>posted </span> <span>@ </span> <span>{{ article_obj.create_time|date:'Y-m-d' }}</span> <span>{{ article_obj.blog.userinfo.username }} </span> <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }}) </span> <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }}) </span> <span><a href="">编辑</a> </span> </div> </li> </ul> <hr> {% endfor %} </div> </div> </div> </body> </html>