基于Vue和Django的个人博客系统
基于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)