# BBS **Repository Path**: heart99999/bbs ## Basic Information - **Project Name**: BBS - **Description**: BBS...更新中... - **Primary Language**: Python - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-29 - **Last Updated**: 2024-06-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # BBS博客园项目 ## BBS表设计 ```python 1. 用户表 继承AbstractUser 扩展 phone 电话号码 avatar 用户头像 is_deleted 是否删除 create_time 创建时间 外键字段 一对一个人站点表 2. 个人站点表 site_time 站点名称 site_title 站点标题 site_theme 站点样式 3. 文章标签表 name 标签名 外键字段 一对多个人站点 4. 文章分类表 name 分类名 外键字段 一对多个人站点 5. 文章表 title 文章标题 desc 文章简介 content 文章内容 create_time 发布时间 数据库字段设计优化 (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是效率很低) up_num 点赞数 down_num 点踩数 comment_num 评论数 外键字段 一对多个人站点 多对多文章标签表 一对多文章分类 6. 点赞点踩表 (记录哪个用户给哪篇文章点了赞还是点了踩) user 用户 ForeignKey(to="User") article 文章 ForeignKey(to="Article") is_up 点赞点踩 BooleanField() 7. 文章评论表 (记录哪个用户给哪篇文章写了哪些评论内容) user 用户 ForeignKey(to="User") article 文章 ForeignKey(to="Article") content 评论 CharField() comment_time 评论时间 DateField() # ORM专门提供的自关联写法 parent ForeignKey(to="self",null=True) 根评论和子评论的概念 根评论就是直接评论当前发布的内容的 子评论是评论别人的评论 根评论与子评论是一对多的关系 外键字段建在子评论里 ``` - user/models ```python from django.db import models from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): phone = models.CharField(max_length=11, verbose_name='手机号', help_text='这是手机号', null=True) # 给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.jpg avatar = models.FileField(upload_to='avatar/', verbose_name='用户头像', help_text='这是用户头像', default='avatar/default.png') is_deleted = models.BooleanField(verbose_name="是否删除", help_text="是否已删除", default=False) create_time = models.DateField(auto_now_add=True, verbose_name='创建时间', help_text='这是创建时间') blog = models.OneToOneField(to='blog.Blog', null=True, blank=True, on_delete=models.CASCADE) ``` - blog/models ```python from django.db import models from django.contrib.auth.models import AbstractUser class Blog(models.Model): site_name = models.CharField(max_length=32, verbose_name='站点名称', help_text='这是站点名称') site_title = models.CharField(max_length=32, verbose_name='站点标题', help_text='这是站点标题') site_theme = models.CharField(max_length=64, verbose_name='站点样式', help_text='这是站点样式') def __str__(self): return self.site_name class Adv(models.Model): title = models.CharField(max_length=64, verbose_name='广告标题', help_text='这是广告标题') content = models.TextField(verbose_name='广告内容', help_text='这是广告内容') create_time = models.DateTimeField(auto_now=True, verbose_name='创建时间', help_text='这是创建时间') update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间', help_text='这是更新时间') mobile = models.CharField(max_length=11, verbose_name='手机号', help_text='这是手机号', default='', blank=True) img = models.FileField(upload_to='advImg/', verbose_name='广告图片', help_text='这是广告图片', default='advImg/kaixin.png') is_background_img = models.BooleanField(default=False, verbose_name='是否为背景图', help_text='这是是否为背景图') ``` - article/models ```python from django.db import models class Category(models.Model): name = models.CharField(max_length=32, verbose_name='文章分类', help_text='这是文章分类') blog = models.ForeignKey(to='blog.Blog', on_delete=models.CASCADE, null=True) def __str__(self): return self.name class Tag(models.Model): name = models.CharField(max_length=32, verbose_name='文章标签', help_text='这是文章标签') blog = models.ForeignKey(to='blog.Blog', on_delete=models.CASCADE, null=True) def __str__(self): return self.name class Article(models.Model): title = models.CharField(max_length=64, verbose_name='文章标题', help_text='这是文章标题') desc = models.CharField(max_length=255, verbose_name='文章简介', help_text='这是文章简介') content = models.TextField(verbose_name='文章内容', help_text='这是文章内容') create_time = models.DateField(auto_now_add=True, verbose_name='创建时间', help_text='这是创建时间') up_num = models.BigIntegerField(default=0, verbose_name='点赞数', help_text='这是点赞数') up_down = models.BigIntegerField(default=0, verbose_name='点踩数', help_text='这是点踩数') comment_num = models.BigIntegerField(default=0, verbose_name='评论数', help_text='这是评论数') blog = models.ForeignKey(to='blog.Blog', on_delete=models.CASCADE, null=True) category = models.ForeignKey(to='Category', on_delete=models.CASCADE, null=True) tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) def __str__(self): return self.title 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='user.UserInfo', on_delete=models.CASCADE) article = models.ForeignKey(to='Article', on_delete=models.CASCADE) is_up = models.BooleanField(verbose_name='是否点赞', help_text='这是是否点赞') class Comment(models.Model): user = models.ForeignKey(to='user.UserInfo', on_delete=models.CASCADE) article = models.ForeignKey(to='Article', on_delete=models.CASCADE) content = models.CharField(max_length=255, verbose_name='评论内容', help_text='这是评论内容') comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间', help_text='这是评论时间') parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True, blank=True, verbose_name='父评论', help_text='这是父评论') ``` ## 前期配置 - settings ```python BASE_DIR = Path(__file__).resolve().parent.parent INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user', 'blog', 'article' ] # 给每个 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates', os.path.join('user','templates'), os.path.join('blog','templates'), os.path.join('article','templates'), ] , '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', ], }, }, ] # 用mysql数据库 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbs01', 'USER': 'root', 'PASSWORD': '123456', 'PORT': 3306, 'HOST': '127.0.0.1', 'CHARSET': 'utf8mb4', } } # 汉化 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' # 给每个static都配置 STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] # 把django自带的表换成自己的 AUTH_USER_MODEL = 'user.UserInfo' ``` - urls 路由分发 ```python from django.contrib import admin from django.urls import path, include, re_path from user import urls as user_urls from blog import urls as blog_urls from article import urls as article_urls from backend import urls as backend_urls from article.views import article_detail from blog import views as blog_index_views from BBS01 import settings from django.views.static import serve from article.views import site urlpatterns = [ path('admin/', admin.site.urls, name='admin'), path('', blog_index_views.index, name='index'), path('user/', include(user_urls)), path('blog/', include(blog_urls)), path('article/', include(article_urls)), path('backend/', include(backend_urls)), path('/article//', article_detail,name='article_detail'), re_path('media/(?P.*)', serve, {'document_root': settings.MEDIA_ROOT}), path('/', site, name='site'), re_path(r"^(?P\w+)/(?Pcategory|tag|archive)/(?P.*)/$", site, name="article_left"), ] ``` - 给pymysql打猴子补丁 ```python import pymysql pymysql.install_as_MySQLdb() ``` - html引入js/css ```python ``` # user 用户功能 ## 路由配置 ```python urlpatterns = [ path('register/',views.register, name='register'), path('login/',views.login, name='login'), path('get_code/',views.get_code, name='get_code'), path('logout/', views.logout, name='logout'), ] ``` ## 注册功能 - views ```python from django.http import JsonResponse from django.shortcuts import render from user.MyForms import MyRegForm from user import models def register(request): form_obj = MyRegForm() # 实例化一个form对象 if request.method == 'POST': back_dic = {'code': 1000, 'msg': ''} # 校验数据是否合法 form_obj = MyRegForm(request.POST) if form_obj.is_valid(): # 校验通过后的数据 # print(form_obj.cleaned_data) 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()) # 返回一个html页面 ``` - Forms组件 ```python # -*- coding: utf-8 -*- # author : heart # blog_url : https://www.cnblogs.com/ssrheart/ # time : 2024/3/13 from django import forms from user import models class MyRegForm(forms.Form): username = forms.CharField(label='用户名', max_length=8, min_length=3, error_messages={ 'required': '用户名不能为空', 'max_length': '用户名最长8位', 'min_length': '用户名最短3位', }, widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) ) password = forms.CharField(label='密码', max_length=8, min_length=3, error_messages={ 'required': '密码不能为空', 'max_length': '密码最长8位', 'min_length': '密码最短3位', }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) confirm_password = forms.CharField(label='确认密码', max_length=8, min_length=3, error_messages={ 'required': '确认密码不能为空', 'max_length': '确认密码最长8位', 'min_length': '确认密码最短3位', }, 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 password != confirm_password: self.add_error('confirm_password', '两次密码不一致') return self.cleaned_data ``` - html ```html {% load static %} Title

注册

{% csrf_token %} {% for form in form_obj %}
{{ form }}
{% endfor %}
``` ![image-20240315145616526](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240315145616526.png) > 提供的代码实现了Django中的用户注册功能,包括表单验证、客户端交互和服务器端处理。 > > 以下是注册功能的流程总结: > > 1. **URL映射**: > - 在项目user的`urls.py`文件中有一个URL模式映射到`register`函数。这个URL模式指向视图中的`register`函数。 > > 2. **视图函数 (`register`) 分析**: > - `register`函数处理GET和POST请求。 > - 当接收到GET请求时,它渲染 'register.html' 模板以及表单。 > - 当接收到POST请求时,它处理表单数据。 > - 如果表单数据有效,则使用提供的数据创建新用户。 > - 如果表单数据无效,则返回适当的错误消息。 > > 3. **表单验证 (`MyRegForm`)**: > - `MyRegForm`是一个Django表单类,用于处理用户注册。 > - 它定义了用户名、密码、确认密码和电子邮件字段。 > - 它指定了字段约束,如最小和最大长度,并提供了验证失败的错误消息。 > - 它包括本地和全局表单验证钩子(`clean_username`和`clean`),用于检查用户名唯一性和密码确认。 > > 4. **HTML模板 (`register.html`) 分析**: > - HTML模板包括一个用户注册表单。 > - 它对表单字段进行迭代,并渲染它们以及相应的标签和输入元素。 > - 它包括一个文件输入字段,用于用户的头像选择。 > - 包含JavaScript代码以处理头像文件选择和通过AJAX提交表单。 > > 5. **JavaScript功能**: > - JavaScript代码处理文件输入变化事件,以显示所选头像的预览。 > - 当用户提交注册表单时,它通过AJAX序列化表单数据并将其提交。 > - 在成功注册时,用户将被重定向到登录页面。 > - 如果由于验证错误而注册失败,则错误消息将在相应字段旁边显示。 > > 6. **流程总结**: > - 用户通过相应的URL访问注册页面。 > - 显示注册表单。 > - 用户填写注册详情,包括选择头像图片。 > - 提交表单后,客户端JavaScript捕获表单数据,并将其异步发送到服务器。 > - 服务器端的Django视图验证表单数据。 > - 如果验证通过,则创建新的用户账户,并将用户重定向到登录页面。 > - 如果验证失败,则错误消息返回客户端,并重新渲染表单,显示相应字段旁边的错误消息。 ## 登录功能 ### (1)验证码 - 验证码推导一 ```python def get_code(request): # 方式一:直接获取后端现成的二进制图片二进制数据发送给前端 with open('user/static/img/1.jpg', 'rb') as f: data = f.read() return HttpResponse(data) # 返回一个图片 ``` ![image-20240315145601738](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240315145601738.png) - 验证码推导二 - 文件存储繁琐 IO操作效率低 - 需要用到相关模块 `Pillow` ```python from PIL import Image, ImageDraw, ImageFont ''' Image : 生成图片 ImageDraw : 能够在图片上乱涂乱画 ImageFont : 控制字体样式 ''' ``` ```python import random def get_random(): return random.randint(0,255),random.randint(0,255),random.randint(0,255) def get_code(request): # 方式二:利用pillow模块动态生成图片 # img_obj = Image.new('RGB',(430,35),'red') # 可以指定颜色 # img_obj = Image.new('RGB',(430,35),(255,255,255)) # 还可以放rbg参数 img_obj = Image.new('RGB',(430,35),get_random()) # 随机颜色 # 将图片对象保存起来 with open('xxx.png','wb')as f: img_obj.save(f,'png') with open('xxx.png','rb')as f: data = f.read() return HttpResponse(data) # 返回一个图片 ``` ![image-20240315151124680](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240315151124680.png) - 验证码推导三 - 需要用到相关模块 `io` ```python from io import BytesIO,StringIO """ 内存管理器模块 BytesIO : 临时保存数据 返回的时候数据是二进制 StringIO : 临时保存数据 返回的时候数据是字符串 """ ``` ```python import random def get_random(): return random.randint(0,255),random.randint(0,255),random.randint(0,255) def get_code(request): # 方式三:利用io模块将图片保存在内存中 img_obj = Image.new('RGB', (430, 35), get_random()) # 先生成一个内存管理器对象 可以看成是文件聚柄 io_obj = BytesIO() img_obj.save(io_obj, 'png') return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端 ``` - 验证码最终步骤四 - 写图片验证码 ```python from PIL import Image, ImageDraw, ImageFont from io import BytesIO, StringIO import random 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', (430, 35), get_random()) img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象 img_font = ImageFont.truetype('static/font/春联标准行书体.ttf', 30) # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 code = '' for i in range(5): random_num = str(random.randint(0, 9)) random_lower = chr(random.randint(97, 122)) random_upper = chr(random.randint(65, 90)) # 从上面三个随机选一个 random_choice = random.choice([random_num, random_lower, random_upper]) # 将产生的随机字符串写到图片上 img_draw.text((i * 60 +60, 0), random_choice, get_random(), img_font) code += random_choice print(code) # 随机验证码在登录的视图函数里面要用到 要比对 所以要存到session中 request.session['code'] = code io_obj = BytesIO() img_obj.save(io_obj, 'png') return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端 ``` ```html ``` ![image-20240315154104125](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240315154104125.png) ### (2)登录功能 ```python import random from PIL import Image, ImageDraw, ImageFont from io import BytesIO, StringIO from django.http import JsonResponse, HttpResponse from django.shortcuts import render from user.MyForms import MyRegForm from user import models from django.contrib import auth def login(request): if request.method == 'POST': back_dict = {'code': 1000, 'msg': ''} data = request.POST username = data.get('username') password = data.get('password') code = data.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_dict['url'] = '/blog/index/' else: back_dict['code'] = 2000 back_dict['msg'] = '用户名或密码错误' else: back_dict['code'] = 3000 back_dict['msg'] = '验证码错误' return JsonResponse(back_dict) return render(request, 'login.html',locals()) 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', (430, 35), get_random()) img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象 img_font = ImageFont.truetype('static/font/春联标准行书体.ttf', 30) # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 code = '' for i in range(5): random_num = str(random.randint(0, 9)) random_lower = chr(random.randint(97, 122)) random_upper = chr(random.randint(65, 90)) # 从上面三个随机选一个 random_choice = random.choice([random_num, random_lower, random_upper]) # 将产生的随机字符串写到图片上 img_draw.text((i * 60 + 60, 0), random_choice, get_random(), img_font) code += random_choice print(code) # 随机验证码在登录的视图函数里面要用到 要比对 所以要存到session中 request.session['code'] = code io_obj = BytesIO() img_obj.save(io_obj, 'png') return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端 ``` ```html {% load static %} 登录
{% csrf_token %}

登陆页面

``` ## 注销功能 `request.session.flush()` ```python def logout(request): if request.method == 'POST': request.session.flush() return render(request, 'login.html', locals()) return render(request, 'login.html', locals()) ``` ```js ``` ## 修改密码 - py文件 ```python @login_required def change_password(request): back_dict = {'code': 1000, 'msg': '修改密码成功!'} if request.is_ajax(): if request.method == 'POST': data = request.POST old_password = data.get('old_password') new_password = data.get('new_password') confirm_password = data.get('confirm_password') print(data) if request.user.check_password(old_password): if old_password == new_password: back_dict['code'] = 1004 back_dict['msg'] = '新密码不能与原密码相同' return JsonResponse(back_dict) if new_password == confirm_password: request.user.set_password(new_password) request.user.save() back_dict['url'] = '/user/login/' return JsonResponse(back_dict) else: back_dict['code'] = 1001 back_dict['msg'] = '两次密码不一致' return JsonResponse(back_dict) else: back_dict['code'] = 1002 back_dict['msg'] = '原密码错误' return JsonResponse(back_dict) else: return redirect('login') ``` - html ```html
  • 修改密码
  • ``` - js ```js ``` ## 修改头像 - 点击页面上的头像跳出模态框,上传文件修改 - py ```python def index(request): avatar_obj = UserInfo.objects.filter(username=request.user).values( 'avatar' ) if request.method == 'POST' and request.is_ajax(): data = request.POST avatar = request.FILES.get('avatar') if avatar: res = UserInfo.objects.filter(username=request.user).first() res.avatar = avatar res.save() return JsonResponse({'code': 200, 'msg': '头像上传成功'}) return render(request, 'index.html', locals()) ``` - html ```html ``` - js ```js ``` ![image-20240329092142778](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240329092142778.png) # blog 首页 ## 路由配置 ```python from django.urls import path from blog import views urlpatterns = [ path('index/', views.index, name='index'), path('change_password/', views.change_password, name='change_password'), path('adv/', views.adv, name='adv'), path('edit_adv/', views.edit_adv, name='edit_adv'), ] ``` ## 百度搜索功能添加 ```html ``` ![image-20240319193744483](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240319193744483.png) ## 广告中心(添加、编辑、删除) - 添加、删除 ```python import os.path from django.http import JsonResponse from django.shortcuts import render, HttpResponse, redirect from django.contrib.auth.decorators import login_required from blog import models from blog.fenyeqi import Pagination from article import models as article_models from user.models import UserInfo def adv(request): adv_data = models.Adv.objects.all() current_page = request.GET.get("page", 1) all_count = adv_data.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = adv_data[page_obj.start:page_obj.end] cebian_gg = models.Adv.objects.all()[:3] avatar_obj = UserInfo.objects.filter(username=request.user).values( 'avatar' ) if request.is_ajax(): back_dic = {'code': 2000, 'msg': '', 'url': ''} data = request.POST title = data.get('title') content = data.get('content') phone = data.get('phone') lunbo = data.get('lunbo') adv_img = request.FILES.get('adv_img') delete = data.get('delete') action = data.get('action') if action == 'del': models.Adv.objects.filter(id=delete).delete() if not adv_img: adv_img = 'advImg/kaixin.png' if not all([title, content, phone, lunbo]): back_dic['code'] = 2001 back_dic['msg'] = '请补齐参数!' return JsonResponse(back_dic) models.Adv.objects.create(title=title, content=content, mobile=phone, img=adv_img, is_background_img=int(lunbo)) back_dic['code'] = 2000 back_dic['msg'] = '添加广告成功!' back_dic['url'] = '/blog/adv/' return JsonResponse(back_dic) return render(request, 'detail.html', locals()) ``` ```html ``` ![image-20240319193825187](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240319193825187.png) ![image-20240319193836038](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240319193836038.png) - 编辑广告 ```python def edit_adv(request): back_dic = {'code': 2000, 'msg': '修改成功!', 'url': ''} if request.is_ajax(): data = request.POST advtitle = data.get('advtitle') advcontent = data.get('advcontent') advphone = data.get('advphone') advlunbo = data.get('advlunbo') adv_img = request.FILES.get('advedit_img') adv_id = data.get('adv_id') if not adv_img: back_dic['code'] = 2002 back_dic['msg'] = '图片必须修改!' return JsonResponse(back_dic) else: path = os.path.dirname(os.path.dirname(__file__)) + '/media' + "/advImg/" + adv_img.name with open(path, 'wb') as f: for i in adv_img.chunks(): f.write(i) adv_img = "adVimg/" + str(adv_img) if not all([advtitle, advcontent, advphone, advlunbo]): back_dic['code'] = 2001 back_dic['msg'] = '请补齐参数!' return JsonResponse(back_dic) models.Adv.objects.filter(pk=adv_id).update(title=advtitle, content=advcontent, mobile=advphone, img=adv_img, is_background_img=int(advlunbo)) back_dic['code'] = 2000 back_dic['msg'] = '修改成功!' return JsonResponse(back_dic) return JsonResponse(back_dic) ``` ![image-20240319193813692](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240319193813692.png) - html ```html {% extends 'index.html' %} {% block guanggao %} {% load static %}

    广告中心

    {% for adv in page_queryset %} {# 删除#} {% endfor %}
    序号 标题 内容 创建时间 更新时间 手机号 广告图片 轮播图 操作
    {{ forloop.counter }} {{ adv.title }} {{ adv.content }} {{ adv.create_time }} {{ adv.update_time }} {{ adv.mobile }} {{ adv.is_background_img }}
    {% csrf_token %}
    {{ page_obj.page_html|safe }}
    {% endblock %} ``` ![image-20240319193752814](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240319193752814.png) ## 轮播图 ```html ``` # article 个人站点 ## 路由配置 ``` from django.urls import path, re_path from article import views urlpatterns = [ path('up_down/', views.up_down, name='up_down'), path('comment/', views.comment, name='comment'), ] ``` ## 首页搭建 - py ```python def site(request, username, **kwargs): article_obj = Article.objects.filter(blog__userinfo__username=username) blog_data = Blog.objects.get(userinfo__username=username) current_page = request.GET.get("page", 1) all_count = article_obj.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = article_obj[page_obj.start:page_obj.end] category_data = Category.objects.filter(blog__userinfo__username=username).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') condition = kwargs.get('condition') param = kwargs.get('param') if condition and param: if condition == 'category': page_queryset = Article.objects.filter(category__pk=int(param)) elif condition == 'tag': page_queryset = Article.objects.filter(tags__pk=int(param)) elif condition == 'archive': year, month = param.split('-') page_queryset = Article.objects.filter(create_time__year=year, create_time__month=month) else: return redirect('site') Tag_data = Tag.objects.filter(blog__userinfo__username=username).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') time_data = Article.objects.filter(blog__userinfo__username=username).annotate( create_num=TruncMonth('create_time')).values('create_num').annotate( article_num=Count('pk') ).values('create_num', 'article_num') dianzan_data = Article.objects.filter(blog__userinfo__username=username).order_by( '-up_num').values('title', 'up_num') return render(request, 'site.html', locals()) ``` - html ```html {% extends 'index.html' %} {% block fluid %} {% load static %}

    我的分类

    {% for cat_obj in category_data %} {% endfor %}

    随笔标签

    {% for tag_obj in Tag_data %} {% endfor %}

    随笔档案

    {% for time_obj in time_data %} {% endfor %}

    点赞排行

    {% for dianzan_obj in dianzan_data %} {% endfor %}
    {% block detail %}
    {% for atc_data in page_queryset %}

    {{ atc_data.title }}

    摘要:   {{ atc_data.desc }}

    posted @ {{ atc_data.create_time }} {{ atc_data.blog.userinfo.username }} {{ atc_data.up_num }} {{ atc_data.up_down }} {{ atc_data.comment_num }}


    {% endfor %}
    {% endblock %}
    {{ page_obj.page_html|safe }}
    {% endblock %} ``` ![image-20240329093949428](D:\2023python+go全栈笔记\Python基础\BBS\BBS.assets\image-20240329093949428.png) ## 详情页搭建 - py ```python def article_detail(request, username, pk, **kwargs): detail_art = Article.objects.get(pk=pk) article_obj = Article.objects.filter(blog__userinfo__username=username) article_obj1 = Article.objects.filter(blog__userinfo__username=username).first() blog_data = Blog.objects.get(userinfo__username=username) current_page = request.GET.get("page", 1) all_count = article_obj.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = article_obj[page_obj.start:page_obj.end] category_data = Category.objects.filter(blog__userinfo__username=username).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') condition = kwargs.get('condition') param = kwargs.get('param') comment_obj = Comment.objects.filter(article_id=pk) if condition and param: if condition == 'category': page_queryset = Article.objects.filter(category__pk=int(param)) elif condition == 'tag': page_queryset = Article.objects.filter(tags__pk=int(param)) elif condition == 'archive': year, month = param.split('-') page_queryset = Article.objects.filter(create_time__year=year, create_time__month=month) else: return redirect('site') Tag_data = Tag.objects.filter(blog__userinfo__username=username).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') time_data = Article.objects.filter(blog__userinfo__username=username).annotate( create_num=TruncMonth('create_time')).values('create_num').annotate( article_num=Count('pk') ).values('create_num', 'article_num') dianzan_data = Article.objects.filter(blog__userinfo__username=username).order_by( '-up_num').values('title', 'up_num') return render(request, 'article_detail.html', locals()) ``` - html ```html {% extends 'site.html' %} {% load static %} {% block detail %}
    {% csrf_token %}
    随笔 - {{ all_count }} 点赞数 - {{ detail_art.up_num }} 点踩数 - {{ detail_art.up_down }} 评论数 - {{ detail_art.comment_num }}

    {{ detail_art.content|safe }}

    {#点赞#}
    {{ detail_art.up_num }}
    {{ detail_art.up_down }}
    posted @ {{ detail_art.create_time|date:'Y-m-d' }} {{ article_obj1.blog.site_name }} 评论({{ detail_art.comment_num }})

    {% if request.user.is_authenticated %}

    评论列表


    {% for comment in comment_obj %} {% if not comment.parent %}

    #{{ forloop.counter }}楼 {{ comment.comment_time|date:'Y-m-d H:i:s' }} @{{ comment.user.username }} 回复   

    {{ comment.content }}

    {% for comment1 in comment_obj %} {% if comment1.parent.pk == comment.pk %} @ {{ comment1.parent.user.username }}

    {{ comment1.content }}

    {% endif %} {% endfor %}

    {% endif %} {% endfor %}

    发表评论


    {% else %}

    登录后才能查看或发表评论,立即登录

    {% endif %}
    {% endblock %} ``` ## 点赞、评论功能 - py ```python def up_down(request): if request.method == 'POST' and request.is_ajax(): back_dict = {'code': 1000, 'msg': '点赞成功!'} if not request.user.is_authenticated: back_dict['code'] = 1001 back_dict['msg'] = '请先登录!' return JsonResponse(back_dict) data = request.POST article_id = data.get('id') up_down = json.loads(data.get('up_down')) # True False user_obj = request.user article_obj = Article.objects.filter(pk=int(article_id)).first() up_down_obj = UpAndDown.objects.filter(user=user_obj, article=article_obj) if up_down_obj: back_dict['code'] = 1002 back_dict['msg'] = '当前用户已经点赞或点踩过了!' return JsonResponse(back_dict) UpAndDown.objects.create(user=user_obj, article=article_obj, is_up=up_down) updown_obj = Article.objects.filter(pk=int(article_id)).first() if up_down: updown_obj.up_num += 1 updown_obj.save() back_dict['code'] = 1000 back_dict['msg'] = '点赞成功!' return JsonResponse(back_dict) else: updown_obj.up_down += 1 updown_obj.save() back_dict['code'] = 1000 back_dict['msg'] = '点踩成功!' return JsonResponse(back_dict) return JsonResponse(back_dict) return HttpResponse('ok') def comment(request): back_dict = {'code': 1000, 'msg': '评论成功!'} if request.method == 'POST' and request.is_ajax(): # 评论 data = request.POST content = data.get('content') article_id = data.get('article_id') parent_id = data.get('parentID') if not content: back_dict['code'] = 1001 back_dict['msg'] = '评论内容不能为空!' return JsonResponse(back_dict) Comment.objects.create(user=request.user, article_id=int(article_id), content=content, parent_id=parent_id) res = Comment.objects.filter(article_id=article_id).first() res.article.comment_num += 1 res.article.save() return JsonResponse(back_dict) ``` # admin 管理 ## user/admin - 显示头像 ```python from django.utils.html import format_html class ... def display_avatar(self, obj): # 获取默认的avatar路径 avatar_path = obj.avatar if obj.avatar else 'static/avatar/default.png' # 拼接完整的URL avatar_url = f'http://127.0.0.1:8000/media/{avatar_path}' return format_html( f'' f'请稍后再试哦' f'') display_avatar.short_description = "用户头像" ``` - 完整配置 ```python from django.contrib import admin # Register your models here. from user.models import UserInfo from django.utils.html import format_html @admin.register(UserInfo) class UserInfoAdmin(admin.ModelAdmin): ordering = ['id'] # 想要对某个字段进行额外的处理渲染指定的样式 需要重写一个函数 def display_avatar(self, obj): # 获取默认的avatar路径 avatar_path = obj.avatar if obj.avatar else 'static/avatar/default.png' # 拼接完整的URL avatar_url = f'http://127.0.0.1:8000/media/{avatar_path}' return format_html( f'' f'请稍后再试哦' f'') display_avatar.short_description = "用户头像" list_display = ['id', 'username', 'is_superuser', 'email', 'phone', 'display_avatar', 'create_time', 'is_deleted','blog'] ``` # backend 后台管理 ## 路由配置 ```python # -*- coding: utf-8 -*- # author : heart # blog_url : https://www.cnblogs.com/ssrheart/ # time : 2024/3/27 from django.urls import path, re_path from backend import views urlpatterns = [ path('', views.backend, name='backend'), path('backendlogout/', views.backendlogout, name='backendlogout'), path('backend_category/', views.backend_category, name='backend_category'), path('backend_tag/', views.backend_tag, name='backend_tag'), path('backend_comment/', views.backend_comment, name='backend_comment'), path('backend_add_article/', views.backend_add_article, name='backend_add_article'), ] ``` ```python from django.shortcuts import render from django.db.models import Count from django.db.models.functions import TruncMonth from django.http import JsonResponse from django.shortcuts import render, redirect, HttpResponse from article.models import Article, Category, Tag, UpAndDown, Comment from blog.fenyeqi import Pagination from blog.models import Blog def backend(request): user = request.user category_data = Category.objects.filter(blog__userinfo__username=user).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') tag_data = Tag.objects.filter(blog__userinfo__username=user).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') article_data = Article.objects.filter(blog__userinfo__username=user) current_page = request.GET.get("page", 1) all_count = article_data.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = article_data[page_obj.start:page_obj.end] article_category = Category.objects.filter(blog__userinfo__username=user) article_tag = Tag.objects.filter(blog__userinfo__username=user) if request.method == 'POST' and request.is_ajax(): data = request.POST pk = data.get('pk') tag = data.get('tag') art_pk = data.get('art_pk') title = data.get('art_title') art_content = data.get('art_content') art_desc = data.get('art_desc') art_tag = data.get('art_tag') cate_a = data.get('cate_a') tag_a = data.getlist('tag_a[]') if tag == 'delete': Article.objects.filter(pk=pk).delete() return JsonResponse({'code': 404, 'msg': '删除成功!'}) if art_tag == 'edit': if not all([title, art_content, art_desc, cate_a, tag_a]): return JsonResponse({'code': 404, 'msg': '请填写完整!'}) cate_obj = Category.objects.filter(pk=cate_a).first() res = Article.objects.filter(pk=art_pk).first() res.title = title res.content = art_content res.desc = art_desc res.category = cate_obj res.tags.set([i for i in tag_a]) res.save() return JsonResponse({'code': 2000, 'msg': '修改成功!'}) return render(request, 'backend.html', locals()) def backend_category(request): user = request.user category_data = Category.objects.filter(blog__userinfo__username=user).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') tag_data = Tag.objects.filter(blog__userinfo__username=user).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') category_obj = Category.objects.filter(blog__userinfo__username=user).values( 'article__title', 'name' ) current_page = request.GET.get("page", 1) all_count = category_obj.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = category_obj[page_obj.start:page_obj.end] return render(request, 'backend_category.html', locals()) def backend_tag(request): user = request.user category_data = Category.objects.filter(blog__userinfo__username=user).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') tag_data = Tag.objects.filter(blog__userinfo__username=user).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') tag_obj = Tag.objects.filter(blog__userinfo__username=user).values( 'article__title', 'name' ) current_page = request.GET.get("page", 1) all_count = tag_obj.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = tag_obj[page_obj.start:page_obj.end] return render(request, 'backend_tag.html', locals()) def backend_comment(request): user = request.user category_data = Category.objects.filter(blog__userinfo__username=user).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') tag_data = Tag.objects.filter(blog__userinfo__username=user).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') comment_obj = Comment.objects.filter(user=user).values( 'article__title', 'content', 'comment_time' ) current_page = request.GET.get("page", 1) all_count = comment_obj.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = comment_obj[page_obj.start:page_obj.end] return render(request, 'backend_comment.html', locals()) def backend_add_article(request): user = request.user category_data = Category.objects.filter(blog__userinfo__username=user).annotate( article_num=Count('article__pk') ).values('id', 'name', 'article_num') tag_data = Tag.objects.filter(blog__userinfo__username=user).annotate( tag_num=Count('article__pk') ).values('id', 'name', 'tag_num') article_category = Category.objects.filter(blog__userinfo__username=user) article_tag = Tag.objects.filter(blog__userinfo__username=user) back_dict = {'code': 2000, 'msg': '添加成功!'} if request.method == 'POST' and request.is_ajax(): data = request.POST biaoti = data.get('biaoti') neirong = data.get('neirong') fenlei = data.get('fenlei') tag = data.getlist('tag[]') desc = data.get('desc') if not all([biaoti, neirong, fenlei, tag]): back_dict['code'] = 2001 back_dict['msg'] = '请填写完整!' return JsonResponse(back_dict) category_obj = Category.objects.filter(name=fenlei).first() blog_obj = Blog.objects.filter(userinfo__username=user).first() Article.objects.create(title=biaoti, content=neirong, desc=desc, blog=blog_obj, category=category_obj) article = Article.objects.filter(title=biaoti).first() for i in tag: article.tags.add(i) article.save() return JsonResponse(back_dict) return render(request, 'backend_add_article.html', locals()) def backendlogout(request): request.session.flush() return redirect('index') ``` - backend.html ```html {% load static %} Title {#导航栏#}




    分类管理

    新建分类

    分类模版


    分类展示


    {% for fenlei_obj in category_data %} {% endfor %}

    标签管理

    新建标签

    标签模版


    标签展示


    {% for tag_obj in tag_data %}
    {{ tag_obj.name }}({{ tag_obj.tag_num }})

    {% endfor %}
    {% block backend_right %}

    {% csrf_token %} {% for article_obj in page_queryset %} {% endfor %}
    标题 发布时间 点赞数 点踩数 评论数 操作
    {{ article_obj.title }} {{ article_obj.create_time|date:'Y-m-d' }} {{ article_obj.up_num }} {{ article_obj.up_down }} {{ article_obj.comment_num }}
    {{ page_obj.page_html|safe }}
    {% endblock %}
    ``` - backend_add_article.html ```html {% extends 'backend.html' %} {% block backend_right %} {% load static %}

    {% csrf_token %}


    {% for tag_obj in article_tag %} {{ tag_obj.name }} {% endfor %}
    {% endblock %} ``` - backend_category.html ```html {% extends 'backend.html' %} {% block backend_right %}

    {% for cate_obj in page_queryset %} {% endfor %}
    文章标题 分类名称 操作
    {{ cate_obj.article__title }} {{ cate_obj.name }}
    {{ page_obj.page_html|safe }}
    {% endblock %} ``` - backend_comment.html ```html {% extends 'backend.html' %} {% block backend_right %}

    {% for comment_data in page_queryset %} {% endfor %}
    文章名称 评论内容 评论时间 操作
    {{ comment_data.article__title }} {{ comment_data.content }} {{ comment_data.comment_time }}
    {{ page_obj.page_html|safe }}
    {% endblock %} ``` - backend_tag.html ```html {% extends 'backend.html' %} {% block backend_right %}

    {% for tag_data in page_queryset %} {% endfor %}
    文章名称 标签名称 操作
    {{ tag_data.article__title }} {{ tag_data.name }}
    {{ page_obj.page_html|safe }}
    {% endblock %} ``` # 常用方法 `更新中...` ## 实时渲染头像 ```js ``` ## swal提示框 ```js success: function (args) { if (args.code === 2000) { swal({ title: args.msg, icon: "success", button: true, }).then(() => { window.location.href = args.url; }); } else { swal({ title: args.msg, icon: "error", button: true, }).then(() => { window.location.reload(); }); } } ``` ## 上传文件、form表单循环取值 ```js let formData = new FormData(); $.each($('#myform').serializeArray(), function (index, obj) { formData.append(obj.name, obj.value); }) formData.append('adv_img', $('#myfile')[0].files[0]); $.ajax({ url: '', type: 'post', data: formData, processData: false, contentType: false, success: function (args) { } ``` ## 分页器 ```python {{ page_obj.page_html|safe }} ``` ```python current_page = request.GET.get("page", 1) all_count = article_data.count() page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=5) page_queryset = article_data[page_obj.start:page_obj.end] ``` ```python # -*- coding: utf-8 -*- # author : heart # blog_url : https://www.cnblogs.com/ssrheart/ # time : 2024/3/18 class Pagination(object): def __init__(self, current_page, all_count, per_page_num=2, pager_count=11): """ 封装分页相关数据 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param per_page_num: 每页显示的数据条数 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count self.pager_count_half = int((pager_count - 1) / 2) @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示11/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_end = self.all_pager + 1 pager_start = self.all_pager - self.pager_count + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] # 添加前面的nav和ul标签 page_html_list.append(''' ''') return ''.join(page_html_list) ``` ## Django后台UI美化界面 - settings里注册`simpleui` ```python pip install django-simpleui ``` ```python INSTALLED_APPS = [ 'simpleui', ] ```