基于Vue和Django的个人博客系统
毕业设计 ——基于Vue和Django的个人博客系统准备工作注册功能用户模型类图片验证码短信验证码注册的功能首页面展示状态再保持准备工作创建项目django-admin startproject blogsettings.py中配置mysqlDATABASES = {'default': {'ENGINE': 'django.db.backends.mysql',# 数据库名字'NAME': 'b
基于Vue和Django的个人博客系统
准备工作
-
创建项目
django-admin startproject blog
-
settings.py中配置mysql
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库名字 'NAME': 'blog', # 用户名 'USER': 'root', # 密码 'PASSWORD': 'wxm20010428', # 主机 'HOST': 'localhost', # 端口 'PORT': '3306', } }
-
settings.py中配置redis
pip install django-redis
CACHES = { "default": { # 默认 "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/0", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, "session": { # session "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, } SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "session"
cmd进入Redis安装目录
redis-server.exe redis.windows.conf
-
settings.py中配置log
LOGGING = { 'version': 1, 'disable_existing_loggers': False, # 是否禁用已经存在的日志器 'formatters': { # 日志信息显示的格式 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(module)s %(lineno)d %(message)s' }, }, 'filters': { # 对日志进行过滤 'require_debug_true': { # django在debug模式下才输出日志 '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { # 日志处理方法 'console': { # 向终端中输出日志 'level': 'INFO', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 向文件中输出日志 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(BASE_DIR, 'logs/blog.log'), # 日志文件的位置 'maxBytes': 300 * 1024 * 1024, 'backupCount': 10, 'formatter': 'verbose' }, }, 'loggers': { # 日志器 'django': { # 定义了一个名为django的日志器 'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志 'propagate': True, # 是否继续传递日志信息 'level': 'INFO', # 日志器接收的最低日志级别 }, } }
准备日志文件目录,在根项目目录blog下创建logs文件夹。
-
settings.py中配置static
# 配置静态文件加载路径 STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
注册功能
- 创建user子应用
python manage.py startapp user
- 注册user子应用
user.apps.UserConfig
- 创建templates文件夹
将register.html放入templates文件夹'DIRS': [os.path.join(BASE_DIR, 'templates')],
- 在user.views.py文件中定义类视图
# 导入Django的view from django.views import View # 注册视图 class RegisterView(View): def get(self, request): return render(request, 'register.html')
- 在user子应用文件夹中创建urls.py文件配置路由
# 进行user子应用的视图路由 from django.urls import path from blog.user.views import RegisterView urlpatterns = [ # 第二个参数是视图函数 这里使用类视图转换为视图函数 path('register/', RegisterView.as_view(), name='register') ]
- 在项目的总路由文件下urls.py中配置子应用路由
urlpatterns = [ path('admin/', admin.site.urls), # 配置user子应用的路由 include的第一个参数是一个元组 包含子应用的路由和子应用名 path('', include(('user.urls', 'user'), namespace='user')) ]
用户模型类
- 在user.models.py中定义用户模型
from django.db import models from django.contrib.auth.models import AbstractUser # 用户信息 class User(AbstractUser): # 电话号码字段 # unique 为唯一性字段 mobile = models.CharField(max_length=11, unique=True, blank=False) # 头像 # upload_to为保存到响应的子目录中 avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True) # 个人简介 user_desc = models.TextField(max_length=500, blank=True) # 内部类 class Meta 用于给 model 定义元数据 class Meta: db_table = 'tb_user' # 修改默认的表名 verbose_name = '用户信息' # Admin后台显示 verbose_name_plural = verbose_name # Admin后台显示 def __str__(self): return self.mobile
- 在settings.py中修改默认用户认证
# 替换系统的User 来使用我们自己定义的User # 配置信息为 ‘子应用名.模型类型’ AUTH_USER_MODEL = 'user.User'
- 执行数据库迁移
python manage.py makemigrations python manage.py migrate
图片验证码
-
在项目根目录下创建libs文件夹
-
在网上下载图片验证码的库captcha
-
将captcha放到libs目录下
-
在user.view.py中编写验证码视图
# 验证码视图 class ImageCodeView(View): def get(self, request): # 接收前端传递来的uuid uuid = request.GET.get('uuid') # 判断uuid是否获取到 if uuid is None: return HttpResponseBadRequest("没有传递uuid") # 通过调用captcha来生成图片验证码 # text是图片二进制 image是图片内容 text, image = captcha.generate_captcha() # 将图片内容保存到redis # uuid是key 图片内容是value redis_conn = get_redis_connection('default') redis_conn.setex('img:%s' % uuid, 300, text) # 返回图片内容 return HttpResponse(image, content_type='image/jpeg')
-
在user.urls.py中编写路由
path('imagecode/', ImageCodeView.as_view(), name='imagecode')
-
在浏览器输入url测试
http://127.0.0.1:8000/imagecode/?uuid=123
-
在register.html中找到验证码处使用:绑定register.js中的image_code_url
<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
-
在浏览器中http://127.0.0.1:8000/register/进行刷新查看验证码变化
短信验证码
此处选择容联云平台!
注册登录绑定测试号码!
将yuntongxun文件夹解压到libs目录下!
打开sms.py修改个人相关信息!
主要修改:
_accountSid
_accountToken
_appId
ccp.send_template_sms
在项目根目录下创建utils文件夹!
将response_code.py放到utils文件夹下!
-
在user.view.py中编写视图类
# 日志操作 import logging logger = logging.getLogger('django') # 短信验证码视图 class SmsCodeView(View): def get(self, request): # 接收参数 mobile = request.GET.get('mobile') image_code = request.GET.get('image_code') uuid = request.GET.get('uuid') # 验证参数 all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE if not all([mobile, image_code, uuid]): # 参数不齐全 return JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少必要的参数'}) redis_conn = get_redis_connection('default') redis_image_code = redis_conn.get('img:%s' % uuid) if redis_image_code is None: # 判断图片验证码是否存在 return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图片验证码已过期'}) # 如果拿到了那就删除 由于涉及到redis删除 故需要异常捕获 try: redis_conn.delete('img:%s' % uuid) except Exception as e: logger.error(e) # 比对图片验证码时注意大小写处理 if redis_image_code.decode().lower() != image_code.lower(): return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图片验证码错误'}) # 生成短信验证码 sms_code = '%06d' % randint(0, 999999) # 为了后期比对方便 我们可以将短信验证码记录到日志中 logger.info(sms_code) # 保存短信验证码到redis中 redis_conn.setex('sms:%s' % mobile, 300, sms_code) # 发送短信 CCP().send_template_sms(mobile, [sms_code, 5], 1) # 返回响应 return JsonResponse({'code': RETCODE.OK, 'errmsg': '短信发送成功'})
-
在user.urls.py中编写路由
path('smscode/', SmsCodeView.as_view(), name='smscode')
注册的功能
- 在user.views.py中添加post处理方法
def post(self, request): # 接收数据 mobile = request.POST.get('mobile') password = request.POST.get('password') password2 = request.POST.get('password2') smscode = request.POST.get('sms_code') # 验证数据 if not all([mobile, password, password2, smscode]): return HttpResponseBadRequest('缺少必要的参数') if not re.match(r'^1[3-9]\d{9}$', mobile): return HttpResponseBadRequest('手机号不符合规则') if not re.match(r'^[0-9A-Za-z]{8,20}$', password): return HttpResponseBadRequest('请输入8-20位密码,密码是数字字母!') if password != password2: return HttpResponseBadRequest('两次密码不一致') redis_conn = get_redis_connection('default') redis_sms_code = redis_conn.get('sms:%s' % mobile) if redis_sms_code is None: return HttpResponseBadRequest('短信验证码已过期') if smscode != redis_sms_code.decode(): return HttpResponseBadRequest('短信验证码不一致') # 保存注册信息 # create_user可以对密码加密 数据操作需要异常捕获 try: user = User.objects.create_user(username=mobile, mobile=mobile, password=password) except DatabaseError as e: logger.error(e) return HttpResponseBadRequest('注册失败') # 返回响应跳转到指定页面 return HttpResponse('注册成功')
首页面展示
-
创建home子应用
python manage.py startapp home
-
注册home子应用
home.apps.HomeConfig
-
将index.html放在templates文件夹
-
在home.views.py中编写视图
# 首页视图 from django.views import View class IndexView(View): def get(self, request): return render(request, 'index.html')
-
在home.urls.py中编写路由
from django.urls import path from home.views import IndexView urlpatterns = [ path('', IndexView.as_view(), name='index') ]
-
在项目目录下编写路由
path('', include(('home.urls', 'home'), namespace='home'))
-
在user.views.py中更改
return redirect(reverse('home:index'))
状态的保持
在上述的重定向之前,调用login。
# 调用login实现注册成功后状态保持
login(request, user)
浏览器测试的时候查看session和cookie。
查看redis。
redis-cli.exe -h 127.0.0.1 -p 6379
keys *
select 1
keys *
FLUSHdb
然后在login后,设置cookie。
# 调用login实现注册成功后状态保持
login(request, user)
# 返回响应跳转到指定页面
# reverse是通过namespace:name来获取视图对应的路由
response = redirect(reverse('home:index'))
# 设置跳转到首页展示用户信息 cookie
response.set_cookie('is_login', True)
response.set_cookie('username', user.username, max_age=7 * 24 * 3600)
return response
刷新后在浏览器端测试。
查看redis。
登录功能
-
在user.views.py文件中定义视图。
# 登录视图 class LoginView(View): def get(self, request): return render(request, 'login.html')
-
在user.urls.py文件中定义路由。
path('login/', LoginView.as_view(), name='login')
-
修改login.html中的资源加载方式。
{% url "app名称:路径的name" %}
登录的实现
-
在user.views.py文件中定义视图。
def post(self, request): # 接收参数 mobile = request.POST.get('mobile') password = request.POST.get('password') remember = request.POST.get('remember') # 验证参数 if not re.match(r'^1[3-9]\d{9}$', mobile): return HttpResponseBadRequest('手机号不符合规则') if not re.match(r'^[0-9A-Za-z]{8,20}$', password): return HttpResponseBadRequest('密码不符合规则') # 认证登录 # authenticate 系统自带的认证 user = authenticate(mobile=mobile, password=password) if user is None: return HttpResponseBadRequest('用户名或者密码错误') # 状态保持 拿到user 将user传递过去 login(request,user) response=redirect(reverse('home:index')) # 页面跳转 if remember !='on': # 没有记住信息则是浏览器关闭后 request.session.set_expiry(0) # 设置cookie信息 response.set_cookie('is_login',True) response.set_cookie('username',user.username,max_age=14*24*3600) else: # 记住信息则是默认两周 request.session.set_expiry(None) response.set_cookie('is_login', True,max_age=14*24*3600) response.set_cookie('username', user.username, max_age=14 * 24 * 3600) # 返回响应 return response
-
在user.models.py文件中修改模型类。
# 修改认证字段为手机号 USERNAME_FIELD = 'mobile'
-
在浏览器中测试http://127.0.0.1:8000/login/登录。
首页的展示
-
在index.html中修改admin。
[[username]]
-
在index.js的mounted()中修改is_login。
this.is_login=getCookie('is_login')
退出了登录
-
在user.views.py文件中定义视图。
# 退出登录视图 class LogoutView(View): def get(self, request): # session数据清除 logout(request) # 删除部分session数据 response = redirect(reverse('home:index')) response.delete_cookie('is_login') # 跳转到首页 return response
-
在index.html中修改href。
<a class="dropdown-item" href="{% url 'user:logout' %}">退出登录</a>
忘记了密码
-
在user.views.py中编写视图。
# 忘记密码视图 class ForgetPasswordView(View): def get(self, request): return render(request, 'forget_password.html')
-
在user.urls.py中编写路由。
path('forgetpassword/', ForgetPasswordView.as_view(), name='forgetpassword'),
-
在user.views.py中编写视图。
def post(self, request): # 接收数据 mobile = request.POST.get('mobile') password = request.POST.get('password') password2 = request.POST.get('password2') smscode = request.POST.get('sms_code') # 验证数据 if not all([mobile, password, password2, smscode]): return HttpResponseBadRequest('缺少必要的参数') if not re.match(r'^1[3-9]\d{9}$', mobile): return HttpResponseBadRequest('手机号不符合规则') if not re.match(r'^[0-9A-Za-z]{8,20}$', password): return HttpResponseBadRequest('请输入8-20位密码,密码是数字字母!') if password != password2: return HttpResponseBadRequest('两次密码不一致') redis_conn = get_redis_connection('default') redis_sms_code = redis_conn.get('sms:%s' % mobile) if redis_sms_code is None: return HttpResponseBadRequest('短信验证码已过期') if smscode != redis_sms_code.decode(): return HttpResponseBadRequest('短信验证码不一致') # 保存注册信息 # create_user可以对密码加密 数据操作需要异常捕获 try: user = User.objects.get(mobile=mobile) except User.DoesNotExist: try: # 没有查出则创建新用户 User.objects.create_user(username=mobile, mobile=mobile, password=password) except Exception: return HttpResponseBadRequest("修改失败,下次再试试!") else: # 查出则修改用户密码 user.set_password(password) # 保存 用户信息 user.save() # 返回响应跳转到指定页面 # reverse是通过namespace:name来获取视图对应的路由 response = redirect(reverse('user:login')) return response
-
在浏览器测试修改密码并再次登录。
用户中心
-
将center.html放到templates文件夹。
-
在user.views.py中编写视图。
# 用户中心视图 class UserCenterView(View): def get(self,request): return render(request,'center.html')
-
在user.urls.py中编写路由。
path('usercenter/', UserCenterView.as_view(), name='usercenter')
页面的展示
-
在settings.py中修改用户未登录url。
# 修改系统的未登录跳转链接 LOGIN_URL = '/login/'
-
在登录视图中设置页面未登录逻辑判断。
# 根据next参数来进行页面的跳转 next_page = request.GET.get('next') if next_page: response = redirect(next_page) else: response = redirect(reverse('home:index'))
这样就可以实现,当用户未登录时,若想要查看个人信息,就会跳转到登录页面,然后登录后,就到个人中心。
信息的展示
-
在user.views.py中修改视图。
# 用户中心视图 # LoginRequiredMixin封装了判断用户是否登录 # 如果未登录直接跳转到 http://127.0.0.1:8000/accounts/login/?next=/usercenter/ class UserCenterView(LoginRequiredMixin, View): def get(self, request): # 获取用户信息 user = request.user # 组织获取用户的信息 context = { 'username': user.username, 'mobile': user.mobile, 'avatar': user.avatar.url if user.avatar else None, 'user_desc': user.user_desc } return render(request, 'center.html', context=context)
-
在center.html中修改渲染内容。
<!--<br><h5 class="col-md-4">暂无头像</h5><br>--> {% if avatar %} <br> <div class="col-md-4">头像</div> <img src="{{ avatar }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4"><br> {% else %} <br><h5 class="col-md-4">暂无头像</h5><br> {% endif %}
-
在浏览器端测试。
信息的修改
-
在user.views.py中修改视图。
def post(self, request): # 获取参数 user = request.user # 如果没有获取到修改 就使用原来的 username = request.POST.get('username', user.username) user_desc = request.POST.get('user_desc', user.user_desc) avatar = request.FILES.get('avatar') # 保存参数 凡是涉及到数据库操作的都异常捕获 try: user.username = username user.user_desc = user_desc if avatar: # avatar是图片路径 类型为ImageField user.avatar = avatar user.save() except Exception as e: logger.error(e) return HttpResponseBadRequest('修改失败,请稍后再试!') # 更新cookie # 刷新当前页面 response = redirect(reverse('user:usercenter')) response.set_cookie('username', user.username, max_age=14 * 24 * 3600) # 返回响应 return response
-
在settings.py文件中设置图片上传的路径并新建文件夹media。
# 设置上传的头像到media MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') MEDIA_URL = '/media/'
-
在工程的urls.py文件中设置设置路由匹配规则。
# 设置media图片 urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
博客编写
- 将write_blog.html放到templates文件夹。
- 在user.views.py文件中定义视图。
# 写博客视图 class WriteBlogView(View): def get(self, request): return render(request, 'write_blog.html')
- 在users.urls.py文件中定义路由。
path('writeblog/', WriteBlogView.as_view(), name='writeblog'),
- 修改center.html中的资源加载方式。
分类模型类
-
在home.models.py中编写分类模型类。
from django.db import models # Create your models here. # 文章模型类 from django.utils import timezone class ArticleCategory(models.Model): # 分类标题 title = models.CharField(max_length=100, blank=True) # 分类的创建时间 created = models.DateTimeField(default=timezone.now) def __str__(self): return self.title class Meta: db_table = 'tb_category' verbose_name = '类别管理' verbose_name_plural = verbose_name
-
在控制台生成迁移文件。
python manage.py makemigrations python manage.py migrate
后台的管理
Django后台管理:http://127.0.0.1:8000/admin/。
使用Django的管理模块, 需要按照如下步骤操作 :
1.管理界面本地化。
2.创建管理员。
3.注册模型类。
4.发布内容到数据库。
-
在settings.py中设置。
LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
-
在user.models.py中修改User类。
# 创建超级管理员必须输入的字段 REQUIRED_FIELDS = ['username','email']
-
在控制台创建超级用户并按要求输入相应信息。
python manage.py createsuperuser
-
登录后台管理。
-
在home.admin.py中注册模型。
from django.contrib import admin # Register your models here. # 注册模型 from home.models import ArticleCategory admin.site.register(ArticleCategory)
分类的展示
-
在user.views.py中修改视图。
# 写博客视图 登录用户才可以访问视图 class WriteBlogView(LoginRequiredMixin,View): def get(self, request): # 查询所有分类模型 categories = ArticleCategory.objects.all() context = { 'categories':categories, } return render(request, 'write_blog.html',context=context)
-
在write_blog.html中修改接口。
<!-- 文章栏目 --> <div class="form-group"> <label for="category">栏目</label> <select class="form-control col-3" id="category" name="category"> {% for category in categories %} <option value="{{category.id}}">{{category.title}}</option> {% endfor %} </select> </div>
-
在浏览器测试。
文章模型类
-
在home.views.py中编写视图。
# 文章模型类 class Article(models.Model): # 作者 author = models.ForeignKey(User, on_delete=models.CASCADE) # 标题图 avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True) # 标题 title = models.CharField(max_length=20, blank=True) # 分类 # blank表示填写表单时不能为空 null表示数据库不能为空 related_name用于外键反向查询 category = models.ForeignKey(ArticleCategory, null=True, blank=True, on_delete=models.CASCADE, related_name='article') # 标签 tags = models.CharField(max_length=20, blank=True) # 摘要信息 summary = models.CharField(max_length=200, null=False, blank=False) # 文章正文 content = models.TextField() # 浏览量 total_views = models.PositiveIntegerField(default=0) # 评论量 comments = models.PositiveIntegerField(default=0) # 文章的创建时间 created = models.DateTimeField(default=timezone.now) # 文章的修改时间 updated = models.DateTimeField(auto_now=True) # 修改表名以及admin展示的配置信息 class Meta: db_table = 'tb_article' ordering = ('-created',) verbose_name = '文章管理' verbose_name_plural = verbose_name def __str__(self): return self.title
-
生成迁移文件。
python manage.py makemigrations python manage.py migrate
博客的保存
-
在home.views.py中编写视图。
# 写博客视图 登录用户才可以访问视图 class WriteBlogView(LoginRequiredMixin, View): def get(self, request): # 查询所有分类模型 categories = ArticleCategory.objects.all() context = { 'categories': categories, } return render(request, 'write_blog.html', context=context) def post(self, request): # 接收数据 avatar=request.FILES.get('avatar') title = request.POST.get('title') category_id = request.POST.get('category') tags = request.POST.get('tags') summary = request.POST.get('sumary') content = request.POST.get('content') user = request.user # 验证数据 if not all([avatar,title,category_id,summary,content]): return HttpResponseBadRequest('参数不全') try: category=ArticleCategory.objects.get(id=category_id) except ArticleCategory.DoesNotExist: return HttpResponseBadRequest('没有此分类') # 数据入库 try: article=Article.objects.create(author=user,avatar=avatar,category=category,tags=tags,summary=summary,content=content) except Exception as e: logger.error(e) return HttpResponseBadRequest('发布失败,请稍后再试!') # 跳转页面 return redirect(reverse('home:index'))
博客首页
-
在home.views.py中编写视图。
``` class IndexView(View): def get(self, request): # 获取所有分类信息 categories = ArticleCategory.objects.all() # 接受用户点击的分类id cat_id = request.GET.get('cat_id', 1) # 根据分类id进行分类的查询 try: category = ArticleCategory.objects.get(id=cat_id) except ArticleCategory.DoesNotExist: return HttpResponseNotFound('没有此分类') # 组织数据传递给模板 context = { 'categories': categories, 'category': category, } return render(request, 'index.html', context=context) ```
- 在index.html中编写渲染接口。
<!-- 分类 --> <div class="collapse navbar-collapse"> <div> <ul class="nav navbar-nav"> {% for cat in categories %} {% if cat.id == category.id %} <li class="nav-item active"> <a class="nav-link mr-2" href="/?cat_id={{cat.id}}">{{cat.title}}</a> </li> {% else %} <li class="nav-item"> <a class="nav-link mr-2" href="/?cat_id={{cat.id}}">{{cat.title}}</a> </li> {% endif %} {% endfor %} </ul> </div> </div> </div>
分类的实现
-
在home.views.py中编写视图。
class IndexView(View): def get(self, request): # 获取所有分类信息 categories = ArticleCategory.objects.all() # 接受用户点击的分类id cat_id = request.GET.get('cat_id', 1) # 根据分类id进行分类的查询 try: category = ArticleCategory.objects.get(id=cat_id) except ArticleCategory.DoesNotExist: return HttpResponseNotFound('没有此分类') # 获取分页参数 page_num = request.GET.get('page_num', 1) page_size = request.GET.get('page_size', 10) # 根据分类信息查询文章数据 articles = Article.objects.filter(category=category) # 创建分页器 paginator = Paginator(articles, per_page=page_size) # 进行分页处理 try: page_articles = paginator.page(page_num) except EmptyPage: return HttpResponseNotFound('empty page') # 总页数 total_page = paginator.num_pages # 组织数据传递给模板 context = { 'categories': categories, 'category': category, 'articles': articles, 'page_size': page_size, 'total_page': total_page, 'page_num': page_num, } return render(request, 'index.html', context=context)
-
在index.html中编写渲染接口。
<!-- content --> <div class="container"> <!-- 列表循环 --> {% for article in articles %} <div class="row mt-2"> <!-- 文章内容 --> <!-- 标题图 --> <div class="col-3"> <img src="{{article.avatar.url}}" alt="avatar" style="max-width:100%; border-radius: 20px"> </div> <div class="col"> <!-- 栏目 --> <a role="button" href="#" class="btn btn-sm mb-2 btn-warning">{{article.category.title}}</a> <!-- 标签 --> <span> <a href="#" class="badge badge-secondary">{{article.tags}}</a> </span> <!-- 标题 --> <h4> <b><a href="{% static 'detail.html' %}" style="color: black;">{{article.title}}</a></b> </h4> <!-- 摘要 --> <div> <p style="color: gray;"> {{article.summary}} </p> </div> <!-- 注脚 --> <p> <!-- 查看、评论、时间 --> <span><i class="fas fa-eye" style="color: lightskyblue;"></i>{{article.total_views}} </span> <span><i class="fas fa-comments" style="color: yellowgreen;"></i>{{article.comments}} </span> <span><i class="fas fa-clock" style="color: pink;"></i>{{article.created|date}}</span> </p> </div> <hr style="width: 100%;"/> </div> {% endfor %} <!-- 页码导航 --> <div class="pagenation" style="text-align: center"> <div id="pagination" class="page"></div> </div> </div>
博客详情
-
在home.views.py中编写视图。
# 详情页面视图 class DetailView(View): def get(self, request): return render(request, "detail.html")
-
在home.urls.py中编写路由。
path('detail/', DetailView.as_view(), name='detail'),
-
将detail.html放到templates文件夹,并且更改detail.html中的资源加载方式。
详情的展示
-
在home.views.py中编写视图。
# 详情页面视图 class DetailView(View): def get(self, request): # 接受文章id信息 id = request.GET.get('id') # 根据文章id进行文章数据的查询 try: article = Article.objects.get(id=id) except Article.DoesNotExist: pass # 查询分类数据 categories = ArticleCategory.objects.all() # 组织模板数据 context = { 'categories': categories, 'article': article, 'category': article.category, } return render(request, "detail.html", context=context)
-
在detail.html中编写渲染接口。
<!-- 分类 --> <div class="collapse navbar-collapse" id="navbarNav"> <div> <ul class="nav navbar-nav"> {% for cat in categories %} {% if cat.id == category.id %} <li class="nav-item active"> <a class="nav-link mr-2" href="/?cat_id={{cat.id}}">{{cat.title}}</a> </li> {% else %} <li class="nav-item"> <a class="nav-link mr-2" href="/?cat_id={{cat.id}}">{{cat.title}}</a> </li> {% endif %} {% endfor %} </ul> </div> </div> </div>
<!--文章详情--> <div class="col-9"> <!-- 标题及作者 --> <h1 class="mt-4 mb-4">{{article.title}}</h1> <div class="alert alert-success"> <div>作者:<span>{{article.author.username}}</span></div> <div>浏览:{{article.total_views}}</div> </div> <!-- 文章正文 --> <div class="col-12" style="word-break: break-all;word-wrap: break-word;"> <p> <p>{{article.content|safe}}</p></p> </div> <br>
错误的页面
- 将404.html放到templates文件夹,并且修改资源加载路径。
- 在home.views.py中编写视图。
except Article.DoesNotExist: return render(request, '404.html')
- 在浏览器测试一个不存在的id。
http://127.0.0.1:8000/detail/?id=30
文章的推荐
-
在home.views.py中编写视图(文章点击一次,访问量加一)。
# 详情页面视图 class DetailView(View): def get(self, request): # 接受文章id信息 id = request.GET.get('id') # 根据文章id进行文章数据的查询 try: article = Article.objects.get(id=id) except Article.DoesNotExist: return render(request, '404.html') else: # 让浏览量加一 article.total_views += 1 article.save() # 查询分类数据 categories = ArticleCategory.objects.all() # 查询浏览量前十的文章数据 hot_articles = Article.objects.order_by('-total_views')[:9] # 组织模板数据 context = { 'categories': categories, 'article': article, 'category': article.category, 'hot_articles': hot_articles, } return render(request, "detail.html", context=context)
-
在detail.html中编写渲染接口。
<!-- 推荐 --> <div class="col-3 mt-4" id="sidebar" class="sidebar"> <div class="sidebar__inner"> <h4><strong>推荐</strong></h4> <hr> {% for hot_article in hot_articles %} <a href="{% url 'home:detail' %}?id={{hot_article.id}}" style="color: black">{{hot_article.title}}</a><br> {% endfor %} </div> </div>
评论模型类
-
在home.models.py中编写评论模型类。
# 评论模型类 class Comment(models.Model): # 评论内容 content = models.TextField() # 评论文章 article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True) # 评论用户 user = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True) # 评论时间 created = models.DateTimeField(auto_now_add=True) def __str__(self): return self.article.title # 修改表名以及admin展示的配置信息 class Meta: db_table = 'tb_comment' verbose_name = '评论管理' verbose_name_plural = verbose_name
-
执行迁移数据库文件。
python manage.py makemigrations python manage.py migrate
-
在浏览器端测试。
评论的发布
-
在home.views.py中编写视图。
# 详情页面视图 class DetailView(View): def get(self, request): # 接受文章id信息 id = request.GET.get('id') # 根据文章id进行文章数据的查询 try: article = Article.objects.get(id=id) except Article.DoesNotExist: return render(request, '404.html') else: # 让浏览量加一 article.total_views += 1 article.save() # 查询分类数据 categories = ArticleCategory.objects.all() # 查询浏览量前十的文章数据 hot_articles = Article.objects.order_by('-total_views')[:9] # 组织模板数据 context = { 'categories': categories, 'article': article, 'category': article.category, 'hot_articles': hot_articles, } return render(request, "detail.html", context=context) def post(self, request): # 接受用户信息 user = request.user # 判断用户是否登录 if user and user.is_authenticated: # 登录用户则可以接收form数据 # 文章的id id = request.POST.get('id') # 评论的内容 content = request.POST.get('content') # 文章是否存在 try: article = Article.objects.get(id=id) except Article.DoesNotExist: return HttpResponseNotFound('没有此文章') # 保存评论数据 Comment.objects.create(content=content, article=article, user=user) # 修改文章的评论数据 article.comments += 1 article.save() # 刷新当前页面 path = reverse('home:detail') + '?id={}'.format(article.id) return redirect(path) else: # 未登录用户则跳转到登录页面 return redirect(reverse('user:login'))
-
在detail.html中编写页面。
{% csrf_token %} <!--增加id 一并提交过去--> <input type="hidden" name="id" value="{{article.id}}">
评论的显示
-
在home.views.py中编写视图。
# 详情页面视图 class DetailView(View): def get(self, request): # 接受文章id信息 id = request.GET.get('id') # 根据文章id进行文章数据的查询 try: article = Article.objects.get(id=id) except Article.DoesNotExist: return render(request, '404.html') else: # 让浏览量加一 article.total_views += 1 article.save() # 查询分类数据 categories = ArticleCategory.objects.all() # 查询浏览量前十的文章数据 hot_articles = Article.objects.order_by('-total_views')[:9] # 获取分页请求参数 page_num = request.GET.get('page_num', 1) page_size = request.GET.get('page_size', 10) # 根据文章信息查询评论数据 comments = Comment.objects.filter(article=article).order_by('-created') # 获取评论总数 total_count = comments.count() # 创建分页器 paginator = Paginator(comments, page_size) # 进行分页处理 try: page_comments = paginator.page(page_num) except EmptyPage: return HttpResponseNotFound('empty page') # 总页数 total_page = paginator.num_pages # 组织模板数据 context = { 'categories': categories, 'article': article, 'category': article.category, 'hot_articles': hot_articles, 'total_count': total_count, 'comments': page_comments, 'page_size': page_size, 'total_page': total_page, 'page_num': page_num, } return render(request, "detail.html", context=context) def post(self, request): # 接受用户信息 user = request.user # 判断用户是否登录 if user and user.is_authenticated: # 登录用户则可以接收form数据 # 文章的id id = request.POST.get('id') # 评论的内容 content = request.POST.get('content') # 文章是否存在 try: article = Article.objects.get(id=id) except Article.DoesNotExist: return HttpResponseNotFound('没有此文章') # 保存评论数据 Comment.objects.create(content=content, article=article, user=user) # 修改文章的评论数据 article.comments += 1 article.save() # 刷新当前页面 path = reverse('home:detail') + '?id={}'.format(article.id) return redirect(path) else: # 未登录用户则跳转到登录页面 return redirect(reverse('user:login'))
-
在detail.html中编写页面。
<!-- 显示评论 --> <h4>共有{{total_count}}条评论</h4> <div class="row"> {% for comment in comments %} <div class="col-12"> <hr> <p><strong style="color: pink"></strong></p> <div> <div><span><strong>{{comment.user.username}}</strong></span> <span style="color: gray">{{comment.created|date:'Y-m-d H:i'}}</span> </div> <br> <p>{{comment.content|safe}}</p> </div> </div> {% endfor %} <div class="pagenation" style="text-align: center"> <div id="pagination" class="page"></div> </div> </div>
<script type="text/javascript"> $(function () { $('#pagination').pagination({ currentPage: {{page_num}}, totalPage: {{total_page}}, callback:function (current) { location.href = '/detail/?id={{article.id}}&page_size={{page_size}}&page_num='+current; } }) }); </script>
大功告成!
2022年3月14日 王晓曼!
等到毕业了我再发这篇文章!
更多推荐
所有评论(0)