准备工作
-
创建项目 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/登录。
首页的展示
退出了登录
-
在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')
页面的展示
这样就可以实现,当用户未登录时,若想要查看个人信息,就会跳转到登录页面,然后登录后,就到个人中心。
信息的展示
-
在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)
```
<!-- 分类 -->
<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>
错误的页面
文章的推荐
-
在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日 王晓曼!
等到毕业了我再发这篇文章!
|