基于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文件夹。

基于Vue和Django的个人博客系统
* 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修改个人相关信息!

&#x4E3B;&#x8981;&#x4FEE;&#x6539;&#xFF1A;
_accountSid
_accountToken
_appId
ccp.send_template_sms

在项目根目录下创建utils文件夹!

将response_code.py放到utils文件夹下!

  • 在user.view.py中编写视图类
&#x65E5;&#x5FD7;&#x64CD;&#x4F5C;
import logging

logger = logging.getLogger('django')

&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x89C6;&#x56FE;
class SmsCodeView(View):

    def get(self, request):
        # &#x63A5;&#x6536;&#x53C2;&#x6570;
        mobile = request.GET.get('mobile')
        image_code = request.GET.get('image_code')
        uuid = request.GET.get('uuid')
        # &#x9A8C;&#x8BC1;&#x53C2;&#x6570; all() &#x51FD;&#x6570;&#x7528;&#x4E8E;&#x5224;&#x65AD;&#x7ED9;&#x5B9A;&#x7684;&#x53EF;&#x8FED;&#x4EE3;&#x53C2;&#x6570; iterable &#x4E2D;&#x7684;&#x6240;&#x6709;&#x5143;&#x7D20;&#x662F;&#x5426;&#x90FD;&#x4E3A; TRUE
        if not all([mobile, image_code, uuid]):
            # &#x53C2;&#x6570;&#x4E0D;&#x9F50;&#x5168;
            return JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '&#x7F3A;&#x5C11;&#x5FC5;&#x8981;&#x7684;&#x53C2;&#x6570;'})
        redis_conn = get_redis_connection('default')
        redis_image_code = redis_conn.get('img:%s' % uuid)
        if redis_image_code is None:
            # &#x5224;&#x65AD;&#x56FE;&#x7247;&#x9A8C;&#x8BC1;&#x7801;&#x662F;&#x5426;&#x5B58;&#x5728;
            return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '&#x56FE;&#x7247;&#x9A8C;&#x8BC1;&#x7801;&#x5DF2;&#x8FC7;&#x671F;'})
        # &#x5982;&#x679C;&#x62FF;&#x5230;&#x4E86;&#x90A3;&#x5C31;&#x5220;&#x9664; &#x7531;&#x4E8E;&#x6D89;&#x53CA;&#x5230;redis&#x5220;&#x9664; &#x6545;&#x9700;&#x8981;&#x5F02;&#x5E38;&#x6355;&#x83B7;
        try:
            redis_conn.delete('img:%s' % uuid)
        except Exception as e:
            logger.error(e)
        # &#x6BD4;&#x5BF9;&#x56FE;&#x7247;&#x9A8C;&#x8BC1;&#x7801;&#x65F6;&#x6CE8;&#x610F;&#x5927;&#x5C0F;&#x5199;&#x5904;&#x7406;
        if redis_image_code.decode().lower() != image_code.lower():
            return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '&#x56FE;&#x7247;&#x9A8C;&#x8BC1;&#x7801;&#x9519;&#x8BEF;'})
        # &#x751F;&#x6210;&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;
        sms_code = '%06d' % randint(0, 999999)
        # &#x4E3A;&#x4E86;&#x540E;&#x671F;&#x6BD4;&#x5BF9;&#x65B9;&#x4FBF; &#x6211;&#x4EEC;&#x53EF;&#x4EE5;&#x5C06;&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x8BB0;&#x5F55;&#x5230;&#x65E5;&#x5FD7;&#x4E2D;
        logger.info(sms_code)
        # &#x4FDD;&#x5B58;&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x5230;redis&#x4E2D;
        redis_conn.setex('sms:%s' % mobile, 300, sms_code)
        # &#x53D1;&#x9001;&#x77ED;&#x4FE1;
        CCP().send_template_sms(mobile, [sms_code, 5], 1)
        # &#x8FD4;&#x56DE;&#x54CD;&#x5E94;
        return JsonResponse({'code': RETCODE.OK, 'errmsg': '&#x77ED;&#x4FE1;&#x53D1;&#x9001;&#x6210;&#x529F;'})

  • 在user.urls.py中编写路由
path('smscode/', SmsCodeView.as_view(), name='smscode')

注册的功能

  • 在user.views.py中添加post处理方法
    def post(self, request):
        # &#x63A5;&#x6536;&#x6570;&#x636E;
        mobile = request.POST.get('mobile')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        smscode = request.POST.get('sms_code')
        # &#x9A8C;&#x8BC1;&#x6570;&#x636E;
        if not all([mobile, password, password2, smscode]):
            return HttpResponseBadRequest('&#x7F3A;&#x5C11;&#x5FC5;&#x8981;&#x7684;&#x53C2;&#x6570;')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseBadRequest('&#x624B;&#x673A;&#x53F7;&#x4E0D;&#x7B26;&#x5408;&#x89C4;&#x5219;')
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return HttpResponseBadRequest('&#x8BF7;&#x8F93;&#x5165;8-20&#x4F4D;&#x5BC6;&#x7801;&#xFF0C;&#x5BC6;&#x7801;&#x662F;&#x6570;&#x5B57;&#x5B57;&#x6BCD;&#xFF01;')
        if password != password2:
            return HttpResponseBadRequest('&#x4E24;&#x6B21;&#x5BC6;&#x7801;&#x4E0D;&#x4E00;&#x81F4;')
        redis_conn = get_redis_connection('default')
        redis_sms_code = redis_conn.get('sms:%s' % mobile)
        if redis_sms_code is None:
            return HttpResponseBadRequest('&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x5DF2;&#x8FC7;&#x671F;')
        if smscode != redis_sms_code.decode():
            return HttpResponseBadRequest('&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x4E0D;&#x4E00;&#x81F4;')
        # &#x4FDD;&#x5B58;&#x6CE8;&#x518C;&#x4FE1;&#x606F;
        # create_user&#x53EF;&#x4EE5;&#x5BF9;&#x5BC6;&#x7801;&#x52A0;&#x5BC6; &#x6570;&#x636E;&#x64CD;&#x4F5C;&#x9700;&#x8981;&#x5F02;&#x5E38;&#x6355;&#x83B7;
        try:
            user = User.objects.create_user(username=mobile, mobile=mobile, password=password)
        except DatabaseError as e:
            logger.error(e)
            return HttpResponseBadRequest('&#x6CE8;&#x518C;&#x5931;&#x8D25;')
        # &#x8FD4;&#x56DE;&#x54CD;&#x5E94;&#x8DF3;&#x8F6C;&#x5230;&#x6307;&#x5B9A;&#x9875;&#x9762;
        return HttpResponse('&#x6CE8;&#x518C;&#x6210;&#x529F;')

首页面展示

  • 创建home子应用
python manage.py startapp home
  • 注册home子应用
home.apps.HomeConfig
  • 将index.html放在templates文件夹
  • 在home.views.py中编写视图
&#x9996;&#x9875;&#x89C6;&#x56FE;
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。

&#x8C03;&#x7528;login&#x5B9E;&#x73B0;&#x6CE8;&#x518C;&#x6210;&#x529F;&#x540E;&#x72B6;&#x6001;&#x4FDD;&#x6301;
        login(request, user)

浏览器测试的时候查看session和cookie。

查看redis。

redis-cli.exe -h 127.0.0.1 -p 6379

keys *

select 1

keys *

FLUSHdb

基于Vue和Django的个人博客系统
然后在login后,设置cookie。
 # &#x8C03;&#x7528;login&#x5B9E;&#x73B0;&#x6CE8;&#x518C;&#x6210;&#x529F;&#x540E;&#x72B6;&#x6001;&#x4FDD;&#x6301;
        login(request, user)

        # &#x8FD4;&#x56DE;&#x54CD;&#x5E94;&#x8DF3;&#x8F6C;&#x5230;&#x6307;&#x5B9A;&#x9875;&#x9762;
        # reverse&#x662F;&#x901A;&#x8FC7;namespace:name&#x6765;&#x83B7;&#x53D6;&#x89C6;&#x56FE;&#x5BF9;&#x5E94;&#x7684;&#x8DEF;&#x7531;
        response = redirect(reverse('home:index'))
        # &#x8BBE;&#x7F6E;&#x8DF3;&#x8F6C;&#x5230;&#x9996;&#x9875;&#x5C55;&#x793A;&#x7528;&#x6237;&#x4FE1;&#x606F; cookie
        response.set_cookie('is_login', True)
        response.set_cookie('username', user.username, max_age=7 * 24 * 3600)
        return response

刷新后在浏览器端测试。

基于Vue和Django的个人博客系统
查看redis。
基于Vue和Django的个人博客系统

登录功能

  • 在user.views.py文件中定义视图。
&#x767B;&#x5F55;&#x89C6;&#x56FE;
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&#x540D;&#x79F0;:&#x8DEF;&#x5F84;&#x7684;name" %}

登录的实现

  • 在user.views.py文件中定义视图。
    def post(self, request):
        # &#x63A5;&#x6536;&#x53C2;&#x6570;
        mobile = request.POST.get('mobile')
        password = request.POST.get('password')
        remember = request.POST.get('remember')
        # &#x9A8C;&#x8BC1;&#x53C2;&#x6570;
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseBadRequest('&#x624B;&#x673A;&#x53F7;&#x4E0D;&#x7B26;&#x5408;&#x89C4;&#x5219;')
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return HttpResponseBadRequest('&#x5BC6;&#x7801;&#x4E0D;&#x7B26;&#x5408;&#x89C4;&#x5219;')
        # &#x8BA4;&#x8BC1;&#x767B;&#x5F55;
        # authenticate &#x7CFB;&#x7EDF;&#x81EA;&#x5E26;&#x7684;&#x8BA4;&#x8BC1;
        user = authenticate(mobile=mobile, password=password)
        if user is None:
            return HttpResponseBadRequest('&#x7528;&#x6237;&#x540D;&#x6216;&#x8005;&#x5BC6;&#x7801;&#x9519;&#x8BEF;')
        # &#x72B6;&#x6001;&#x4FDD;&#x6301;  &#x62FF;&#x5230;user &#x5C06;user&#x4F20;&#x9012;&#x8FC7;&#x53BB;
        login(request,user)
        response=redirect(reverse('home:index'))
        # &#x9875;&#x9762;&#x8DF3;&#x8F6C;
        if remember !='on':
            # &#x6CA1;&#x6709;&#x8BB0;&#x4F4F;&#x4FE1;&#x606F;&#x5219;&#x662F;&#x6D4F;&#x89C8;&#x5668;&#x5173;&#x95ED;&#x540E;
            request.session.set_expiry(0)
            # &#x8BBE;&#x7F6E;cookie&#x4FE1;&#x606F;
            response.set_cookie('is_login',True)
            response.set_cookie('username',user.username,max_age=14*24*3600)
        else:
            # &#x8BB0;&#x4F4F;&#x4FE1;&#x606F;&#x5219;&#x662F;&#x9ED8;&#x8BA4;&#x4E24;&#x5468;
            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)
        # &#x8FD4;&#x56DE;&#x54CD;&#x5E94;
        return response
  • 在user.models.py文件中修改模型类。
 # &#x4FEE;&#x6539;&#x8BA4;&#x8BC1;&#x5B57;&#x6BB5;&#x4E3A;&#x624B;&#x673A;&#x53F7;
    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文件中定义视图。
&#x9000;&#x51FA;&#x767B;&#x5F55;&#x89C6;&#x56FE;
class LogoutView(View):

    def get(self, request):
        # session&#x6570;&#x636E;&#x6E05;&#x9664;
        logout(request)
        # &#x5220;&#x9664;&#x90E8;&#x5206;session&#x6570;&#x636E;
        response = redirect(reverse('home:index'))
        response.delete_cookie('is_login')
        # &#x8DF3;&#x8F6C;&#x5230;&#x9996;&#x9875;
        return response
  • 在index.html中修改href。
 <a class="dropdown-item" href="{% url 'user:logout' %}">&#x9000;&#x51FA;&#x767B;&#x5F55;</a>

忘记了密码

  • 在user.views.py中编写视图。
&#x5FD8;&#x8BB0;&#x5BC6;&#x7801;&#x89C6;&#x56FE;
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):
        # &#x63A5;&#x6536;&#x6570;&#x636E;
        mobile = request.POST.get('mobile')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        smscode = request.POST.get('sms_code')
        # &#x9A8C;&#x8BC1;&#x6570;&#x636E;
        if not all([mobile, password, password2, smscode]):
            return HttpResponseBadRequest('&#x7F3A;&#x5C11;&#x5FC5;&#x8981;&#x7684;&#x53C2;&#x6570;')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseBadRequest('&#x624B;&#x673A;&#x53F7;&#x4E0D;&#x7B26;&#x5408;&#x89C4;&#x5219;')
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return HttpResponseBadRequest('&#x8BF7;&#x8F93;&#x5165;8-20&#x4F4D;&#x5BC6;&#x7801;&#xFF0C;&#x5BC6;&#x7801;&#x662F;&#x6570;&#x5B57;&#x5B57;&#x6BCD;&#xFF01;')
        if password != password2:
            return HttpResponseBadRequest('&#x4E24;&#x6B21;&#x5BC6;&#x7801;&#x4E0D;&#x4E00;&#x81F4;')
        redis_conn = get_redis_connection('default')
        redis_sms_code = redis_conn.get('sms:%s' % mobile)
        if redis_sms_code is None:
            return HttpResponseBadRequest('&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x5DF2;&#x8FC7;&#x671F;')
        if smscode != redis_sms_code.decode():
            return HttpResponseBadRequest('&#x77ED;&#x4FE1;&#x9A8C;&#x8BC1;&#x7801;&#x4E0D;&#x4E00;&#x81F4;')
        # &#x4FDD;&#x5B58;&#x6CE8;&#x518C;&#x4FE1;&#x606F;
        # create_user&#x53EF;&#x4EE5;&#x5BF9;&#x5BC6;&#x7801;&#x52A0;&#x5BC6; &#x6570;&#x636E;&#x64CD;&#x4F5C;&#x9700;&#x8981;&#x5F02;&#x5E38;&#x6355;&#x83B7;
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            try:
                # &#x6CA1;&#x6709;&#x67E5;&#x51FA;&#x5219;&#x521B;&#x5EFA;&#x65B0;&#x7528;&#x6237;
                User.objects.create_user(username=mobile, mobile=mobile, password=password)
            except Exception:
                return HttpResponseBadRequest("&#x4FEE;&#x6539;&#x5931;&#x8D25;&#xFF0C;&#x4E0B;&#x6B21;&#x518D;&#x8BD5;&#x8BD5;&#xFF01;")
        else:
            # &#x67E5;&#x51FA;&#x5219;&#x4FEE;&#x6539;&#x7528;&#x6237;&#x5BC6;&#x7801;
            user.set_password(password)
            # &#x4FDD;&#x5B58; &#x7528;&#x6237;&#x4FE1;&#x606F;
            user.save()

        # &#x8FD4;&#x56DE;&#x54CD;&#x5E94;&#x8DF3;&#x8F6C;&#x5230;&#x6307;&#x5B9A;&#x9875;&#x9762;
        # reverse&#x662F;&#x901A;&#x8FC7;namespace:name&#x6765;&#x83B7;&#x53D6;&#x89C6;&#x56FE;&#x5BF9;&#x5E94;&#x7684;&#x8DEF;&#x7531;
        response = redirect(reverse('user:login'))
        return response
  • 在浏览器测试修改密码并再次登录。

用户中心

  • 将center.html放到templates文件夹。
  • 在user.views.py中编写视图。
&#x7528;&#x6237;&#x4E2D;&#x5FC3;&#x89C6;&#x56FE;
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。
&#x4FEE;&#x6539;&#x7CFB;&#x7EDF;&#x7684;&#x672A;&#x767B;&#x5F55;&#x8DF3;&#x8F6C;&#x94FE;&#x63A5;
LOGIN_URL = '/login/'
  • 在登录视图中设置页面未登录逻辑判断。
 # &#x6839;&#x636E;next&#x53C2;&#x6570;&#x6765;&#x8FDB;&#x884C;&#x9875;&#x9762;&#x7684;&#x8DF3;&#x8F6C;
        next_page = request.GET.get('next')
        if next_page:
            response = redirect(next_page)
        else:
            response = redirect(reverse('home:index'))

这样就可以实现,当用户未登录时,若想要查看个人信息,就会跳转到登录页面,然后登录后,就到个人中心。

信息的展示

  • 在user.views.py中修改视图。
&#x7528;&#x6237;&#x4E2D;&#x5FC3;&#x89C6;&#x56FE;
LoginRequiredMixin&#x5C01;&#x88C5;&#x4E86;&#x5224;&#x65AD;&#x7528;&#x6237;&#x662F;&#x5426;&#x767B;&#x5F55;
&#x5982;&#x679C;&#x672A;&#x767B;&#x5F55;&#x76F4;&#x63A5;&#x8DF3;&#x8F6C;&#x5230; http://127.0.0.1:8000/accounts/login/?next=/usercenter/
class UserCenterView(LoginRequiredMixin, View):

    def get(self, request):
        # &#x83B7;&#x53D6;&#x7528;&#x6237;&#x4FE1;&#x606F;
        user = request.user
        # &#x7EC4;&#x7EC7;&#x83B7;&#x53D6;&#x7528;&#x6237;&#x7684;&#x4FE1;&#x606F;
        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 %}

                    <div class="col-md-4">&#x5934;&#x50CF;</div>
                    <img src="{{ avatar }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4">
                    {% else %}
                    <br><h5 class="col-md-4">&#x6682;&#x65E0;&#x5934;&#x50CF;</h5>
                    {% endif %}
  • 在浏览器端测试。

信息的修改

  • 在user.views.py中修改视图。
    def post(self, request):
        # &#x83B7;&#x53D6;&#x53C2;&#x6570;
        user = request.user
        # &#x5982;&#x679C;&#x6CA1;&#x6709;&#x83B7;&#x53D6;&#x5230;&#x4FEE;&#x6539; &#x5C31;&#x4F7F;&#x7528;&#x539F;&#x6765;&#x7684;
        username = request.POST.get('username', user.username)
        user_desc = request.POST.get('user_desc', user.user_desc)
        avatar = request.FILES.get('avatar')
        # &#x4FDD;&#x5B58;&#x53C2;&#x6570; &#x51E1;&#x662F;&#x6D89;&#x53CA;&#x5230;&#x6570;&#x636E;&#x5E93;&#x64CD;&#x4F5C;&#x7684;&#x90FD;&#x5F02;&#x5E38;&#x6355;&#x83B7;
        try:
            user.username = username
            user.user_desc = user_desc
            if avatar:
                # avatar&#x662F;&#x56FE;&#x7247;&#x8DEF;&#x5F84; &#x7C7B;&#x578B;&#x4E3A;ImageField
                user.avatar = avatar
            user.save()
        except Exception as e:
            logger.error(e)
            return HttpResponseBadRequest('&#x4FEE;&#x6539;&#x5931;&#x8D25;&#xFF0C;&#x8BF7;&#x7A0D;&#x540E;&#x518D;&#x8BD5;&#xFF01;')
        # &#x66F4;&#x65B0;cookie
        # &#x5237;&#x65B0;&#x5F53;&#x524D;&#x9875;&#x9762;
        response = redirect(reverse('user:usercenter'))
        response.set_cookie('username', user.username, max_age=14 * 24 * 3600)

        # &#x8FD4;&#x56DE;&#x54CD;&#x5E94;
        return response

  • 在settings.py文件中设置图片上传的路径并新建文件夹media。
&#x8BBE;&#x7F6E;&#x4E0A;&#x4F20;&#x7684;&#x5934;&#x50CF;&#x5230;media
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'
  • 在工程的urls.py文件中设置设置路由匹配规则。
&#x8BBE;&#x7F6E;media&#x56FE;&#x7247;
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

博客编写

  • 将write_blog.html放到templates文件夹。
  • 在user.views.py文件中定义视图。
&#x5199;&#x535A;&#x5BA2;&#x89C6;&#x56FE;
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.

&#x6587;&#x7AE0;&#x6A21;&#x578B;&#x7C7B;
from django.utils import timezone

class ArticleCategory(models.Model):
    # &#x5206;&#x7C7B;&#x6807;&#x9898;
    title = models.CharField(max_length=100, blank=True)
    # &#x5206;&#x7C7B;&#x7684;&#x521B;&#x5EFA;&#x65F6;&#x95F4;
    created = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title

    class Meta:
        db_table = 'tb_category'
        verbose_name = '&#x7C7B;&#x522B;&#x7BA1;&#x7406;'
        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类。
&#x521B;&#x5EFA;&#x8D85;&#x7EA7;&#x7BA1;&#x7406;&#x5458;&#x5FC5;&#x987B;&#x8F93;&#x5165;&#x7684;&#x5B57;&#x6BB5;
    REQUIRED_FIELDS = ['username','email']
  • 在控制台创建超级用户并按要求输入相应信息。
python manage.py createsuperuser
  • 登录后台管理。
  • 在home.admin.py中注册模型。
from django.contrib import admin

Register your models here.

&#x6CE8;&#x518C;&#x6A21;&#x578B;
from home.models import ArticleCategory

admin.site.register(ArticleCategory)

分类的展示

  • 在user.views.py中修改视图。
&#x5199;&#x535A;&#x5BA2;&#x89C6;&#x56FE;  &#x767B;&#x5F55;&#x7528;&#x6237;&#x624D;&#x53EF;&#x4EE5;&#x8BBF;&#x95EE;&#x89C6;&#x56FE;
class WriteBlogView(LoginRequiredMixin,View):
    def get(self, request):
        # &#x67E5;&#x8BE2;&#x6240;&#x6709;&#x5206;&#x7C7B;&#x6A21;&#x578B;
        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">&#x680F;&#x76EE;</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中编写视图。
&#x6587;&#x7AE0;&#x6A21;&#x578B;&#x7C7B;
class Article(models.Model):
    # &#x4F5C;&#x8005;
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    # &#x6807;&#x9898;&#x56FE;
    avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)
    # &#x6807;&#x9898;
    title = models.CharField(max_length=20, blank=True)
    # &#x5206;&#x7C7B;
    # blank&#x8868;&#x793A;&#x586B;&#x5199;&#x8868;&#x5355;&#x65F6;&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A; null&#x8868;&#x793A;&#x6570;&#x636E;&#x5E93;&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A; related_name&#x7528;&#x4E8E;&#x5916;&#x952E;&#x53CD;&#x5411;&#x67E5;&#x8BE2;
    category = models.ForeignKey(ArticleCategory, null=True, blank=True, on_delete=models.CASCADE,
                                 related_name='article')
    # &#x6807;&#x7B7E;
    tags = models.CharField(max_length=20, blank=True)
    # &#x6458;&#x8981;&#x4FE1;&#x606F;
    summary = models.CharField(max_length=200, null=False, blank=False)
    # &#x6587;&#x7AE0;&#x6B63;&#x6587;
    content = models.TextField()
    # &#x6D4F;&#x89C8;&#x91CF;
    total_views = models.PositiveIntegerField(default=0)
    # &#x8BC4;&#x8BBA;&#x91CF;
    comments = models.PositiveIntegerField(default=0)
    # &#x6587;&#x7AE0;&#x7684;&#x521B;&#x5EFA;&#x65F6;&#x95F4;
    created = models.DateTimeField(default=timezone.now)
    # &#x6587;&#x7AE0;&#x7684;&#x4FEE;&#x6539;&#x65F6;&#x95F4;
    updated = models.DateTimeField(auto_now=True)

    # &#x4FEE;&#x6539;&#x8868;&#x540D;&#x4EE5;&#x53CA;admin&#x5C55;&#x793A;&#x7684;&#x914D;&#x7F6E;&#x4FE1;&#x606F;
    class Meta:
        db_table = 'tb_article'
        ordering = ('-created',)
        verbose_name = '&#x6587;&#x7AE0;&#x7BA1;&#x7406;'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title
  • 生成迁移文件。
python manage.py makemigrations
python manage.py migrate

博客的保存

  • 在home.views.py中编写视图。
&#x5199;&#x535A;&#x5BA2;&#x89C6;&#x56FE;  &#x767B;&#x5F55;&#x7528;&#x6237;&#x624D;&#x53EF;&#x4EE5;&#x8BBF;&#x95EE;&#x89C6;&#x56FE;
class WriteBlogView(LoginRequiredMixin, View):
    def get(self, request):
        # &#x67E5;&#x8BE2;&#x6240;&#x6709;&#x5206;&#x7C7B;&#x6A21;&#x578B;
        categories = ArticleCategory.objects.all()
        context = {
            'categories': categories,
        }
        return render(request, 'write_blog.html', context=context)

    def post(self, request):
        # &#x63A5;&#x6536;&#x6570;&#x636E;
        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
        # &#x9A8C;&#x8BC1;&#x6570;&#x636E;
        if not all([avatar,title,category_id,summary,content]):
            return HttpResponseBadRequest('&#x53C2;&#x6570;&#x4E0D;&#x5168;')
        try:
            category=ArticleCategory.objects.get(id=category_id)
        except ArticleCategory.DoesNotExist:
            return HttpResponseBadRequest('&#x6CA1;&#x6709;&#x6B64;&#x5206;&#x7C7B;')
        # &#x6570;&#x636E;&#x5165;&#x5E93;
        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('&#x53D1;&#x5E03;&#x5931;&#x8D25;&#xFF0C;&#x8BF7;&#x7A0D;&#x540E;&#x518D;&#x8BD5;&#xFF01;')
        # &#x8DF3;&#x8F6C;&#x9875;&#x9762;
        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>

分类的实现

  • 在home.views.py中编写视图。
class IndexView(View):

    def get(self, request):
        # &#x83B7;&#x53D6;&#x6240;&#x6709;&#x5206;&#x7C7B;&#x4FE1;&#x606F;
        categories = ArticleCategory.objects.all()
        # &#x63A5;&#x53D7;&#x7528;&#x6237;&#x70B9;&#x51FB;&#x7684;&#x5206;&#x7C7B;id
        cat_id = request.GET.get('cat_id', 1)
        # &#x6839;&#x636E;&#x5206;&#x7C7B;id&#x8FDB;&#x884C;&#x5206;&#x7C7B;&#x7684;&#x67E5;&#x8BE2;
        try:
            category = ArticleCategory.objects.get(id=cat_id)
        except ArticleCategory.DoesNotExist:
            return HttpResponseNotFound('&#x6CA1;&#x6709;&#x6B64;&#x5206;&#x7C7B;')
        # &#x83B7;&#x53D6;&#x5206;&#x9875;&#x53C2;&#x6570;
        page_num = request.GET.get('page_num', 1)
        page_size = request.GET.get('page_size', 10)
        # &#x6839;&#x636E;&#x5206;&#x7C7B;&#x4FE1;&#x606F;&#x67E5;&#x8BE2;&#x6587;&#x7AE0;&#x6570;&#x636E;
        articles = Article.objects.filter(category=category)
        # &#x521B;&#x5EFA;&#x5206;&#x9875;&#x5668;
        paginator = Paginator(articles, per_page=page_size)
        # &#x8FDB;&#x884C;&#x5206;&#x9875;&#x5904;&#x7406;
        try:
            page_articles = paginator.page(page_num)
        except EmptyPage:
            return HttpResponseNotFound('empty page')
        # &#x603B;&#x9875;&#x6570;
        total_page = paginator.num_pages
        # &#x7EC4;&#x7EC7;&#x6570;&#x636E;&#x4F20;&#x9012;&#x7ED9;&#x6A21;&#x677F;
        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">
                <!-- 栏目 -->
                <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>
                    <span> {{article.summary}}</span>
                </div>
                <!-- 注脚 -->
                <span> <!-- 查看、评论、时间 -->
                    <span>{{article.total_views}}&#xA0;</span>
                    <span>{{article.comments}}&#xA0;&#xA0;</span>
                    <span>{{article.created|date}}</span></span>
            </div>
            <hr style="width: 100%;">
        </div>

        {% endfor %}

        <!-- 页码导航 -->

    </div>

博客详情

  • 在home.views.py中编写视图。
&#x8BE6;&#x60C5;&#x9875;&#x9762;&#x89C6;&#x56FE;
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中编写视图。
&#x8BE6;&#x60C5;&#x9875;&#x9762;&#x89C6;&#x56FE;
class DetailView(View):
    def get(self, request):
        # &#x63A5;&#x53D7;&#x6587;&#x7AE0;id&#x4FE1;&#x606F;
        id = request.GET.get('id')
        # &#x6839;&#x636E;&#x6587;&#x7AE0;id&#x8FDB;&#x884C;&#x6587;&#x7AE0;&#x6570;&#x636E;&#x7684;&#x67E5;&#x8BE2;
        try:
            article = Article.objects.get(id=id)
        except Article.DoesNotExist:
            pass
        # &#x67E5;&#x8BE2;&#x5206;&#x7C7B;&#x6570;&#x636E;
        categories = ArticleCategory.objects.all()
        # &#x7EC4;&#x7EC7;&#x6A21;&#x677F;&#x6570;&#x636E;
        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 class="col-9">
                <!-- 标题及作者 -->
                <h1 class="mt-4 mb-4">{{article.title}}</h1>
                <div class="alert alert-success">
                    <div>&#x4F5C;&#x8005;&#xFF1A;<span>{{article.author.username}}</span></div>
                    <div>&#x6D4F;&#x89C8;&#xFF1A;{{article.total_views}}</div>
                </div>
                <!-- 文章正文 -->
                <div class="col-12" style="word-break: break-all;word-wrap: break-word;">
                    <span> </span><span> {{article.content|safe}}</span><span> </span>
                </div>

</div>

错误的页面

  • 将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中编写视图(文章点击一次,访问量加一)。
&#x8BE6;&#x60C5;&#x9875;&#x9762;&#x89C6;&#x56FE;
class DetailView(View):
    def get(self, request):
        # &#x63A5;&#x53D7;&#x6587;&#x7AE0;id&#x4FE1;&#x606F;
        id = request.GET.get('id')
        # &#x6839;&#x636E;&#x6587;&#x7AE0;id&#x8FDB;&#x884C;&#x6587;&#x7AE0;&#x6570;&#x636E;&#x7684;&#x67E5;&#x8BE2;
        try:
            article = Article.objects.get(id=id)
        except Article.DoesNotExist:
            return render(request, '404.html')
        else:
            # &#x8BA9;&#x6D4F;&#x89C8;&#x91CF;&#x52A0;&#x4E00;
            article.total_views += 1
            article.save()
        # &#x67E5;&#x8BE2;&#x5206;&#x7C7B;&#x6570;&#x636E;
        categories = ArticleCategory.objects.all()
        # &#x67E5;&#x8BE2;&#x6D4F;&#x89C8;&#x91CF;&#x524D;&#x5341;&#x7684;&#x6587;&#x7AE0;&#x6570;&#x636E;
        hot_articles = Article.objects.order_by('-total_views')[:9]
        # &#x7EC4;&#x7EC7;&#x6A21;&#x677F;&#x6570;&#x636E;
        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">
                <div class="sidebar__inner">
                    <h4><strong>&#x63A8;&#x8350;</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>
                    {% endfor %}
                </div>
            </div>

评论模型类

  • 在home.models.py中编写评论模型类。
&#x8BC4;&#x8BBA;&#x6A21;&#x578B;&#x7C7B;
class Comment(models.Model):
    # &#x8BC4;&#x8BBA;&#x5185;&#x5BB9;
    content = models.TextField()
    # &#x8BC4;&#x8BBA;&#x6587;&#x7AE0;
    article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True)
    # &#x8BC4;&#x8BBA;&#x7528;&#x6237;
    user = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True)
    # &#x8BC4;&#x8BBA;&#x65F6;&#x95F4;
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.article.title

    # &#x4FEE;&#x6539;&#x8868;&#x540D;&#x4EE5;&#x53CA;admin&#x5C55;&#x793A;&#x7684;&#x914D;&#x7F6E;&#x4FE1;&#x606F;
    class Meta:
        db_table = 'tb_comment'
        verbose_name = '&#x8BC4;&#x8BBA;&#x7BA1;&#x7406;'
        verbose_name_plural = verbose_name
  • 执行迁移数据库文件。
python manage.py makemigrations
python manage.py migrate
  • 在浏览器端测试。

评论的发布

  • 在home.views.py中编写视图。
&#x8BE6;&#x60C5;&#x9875;&#x9762;&#x89C6;&#x56FE;
class DetailView(View):

    def get(self, request):
        # &#x63A5;&#x53D7;&#x6587;&#x7AE0;id&#x4FE1;&#x606F;
        id = request.GET.get('id')
        # &#x6839;&#x636E;&#x6587;&#x7AE0;id&#x8FDB;&#x884C;&#x6587;&#x7AE0;&#x6570;&#x636E;&#x7684;&#x67E5;&#x8BE2;
        try:
            article = Article.objects.get(id=id)
        except Article.DoesNotExist:
            return render(request, '404.html')
        else:
            # &#x8BA9;&#x6D4F;&#x89C8;&#x91CF;&#x52A0;&#x4E00;
            article.total_views += 1
            article.save()
        # &#x67E5;&#x8BE2;&#x5206;&#x7C7B;&#x6570;&#x636E;
        categories = ArticleCategory.objects.all()
        # &#x67E5;&#x8BE2;&#x6D4F;&#x89C8;&#x91CF;&#x524D;&#x5341;&#x7684;&#x6587;&#x7AE0;&#x6570;&#x636E;
        hot_articles = Article.objects.order_by('-total_views')[:9]
        # &#x7EC4;&#x7EC7;&#x6A21;&#x677F;&#x6570;&#x636E;
        context = {
            'categories': categories,
            'article': article,
            'category': article.category,
            'hot_articles': hot_articles,
        }
        return render(request, "detail.html", context=context)

    def post(self, request):
        # &#x63A5;&#x53D7;&#x7528;&#x6237;&#x4FE1;&#x606F;
        user = request.user
        # &#x5224;&#x65AD;&#x7528;&#x6237;&#x662F;&#x5426;&#x767B;&#x5F55;
        if user and user.is_authenticated:
            # &#x767B;&#x5F55;&#x7528;&#x6237;&#x5219;&#x53EF;&#x4EE5;&#x63A5;&#x6536;form&#x6570;&#x636E;
            # &#x6587;&#x7AE0;&#x7684;id
            id = request.POST.get('id')
            # &#x8BC4;&#x8BBA;&#x7684;&#x5185;&#x5BB9;
            content = request.POST.get('content')
            # &#x6587;&#x7AE0;&#x662F;&#x5426;&#x5B58;&#x5728;
            try:
                article = Article.objects.get(id=id)
            except Article.DoesNotExist:
                return HttpResponseNotFound('&#x6CA1;&#x6709;&#x6B64;&#x6587;&#x7AE0;')
            # &#x4FDD;&#x5B58;&#x8BC4;&#x8BBA;&#x6570;&#x636E;
            Comment.objects.create(content=content, article=article, user=user)
            # &#x4FEE;&#x6539;&#x6587;&#x7AE0;&#x7684;&#x8BC4;&#x8BBA;&#x6570;&#x636E;
            article.comments += 1
            article.save()

            # &#x5237;&#x65B0;&#x5F53;&#x524D;&#x9875;&#x9762;
            path = reverse('home:detail') + '?id={}'.format(article.id)
            return redirect(path)
        else:
            # &#x672A;&#x767B;&#x5F55;&#x7528;&#x6237;&#x5219;&#x8DF3;&#x8F6C;&#x5230;&#x767B;&#x5F55;&#x9875;&#x9762;
            return redirect(reverse('user:login'))
  • 在detail.html中编写页面。
 {% csrf_token %}
                        <!--增加id 一并提交过去-->
                        <input type="hidden" name="id" value="{{article.id}}">

评论的显示

  • 在home.views.py中编写视图。
&#x8BE6;&#x60C5;&#x9875;&#x9762;&#x89C6;&#x56FE;
class DetailView(View):

    def get(self, request):
        # &#x63A5;&#x53D7;&#x6587;&#x7AE0;id&#x4FE1;&#x606F;
        id = request.GET.get('id')
        # &#x6839;&#x636E;&#x6587;&#x7AE0;id&#x8FDB;&#x884C;&#x6587;&#x7AE0;&#x6570;&#x636E;&#x7684;&#x67E5;&#x8BE2;
        try:
            article = Article.objects.get(id=id)
        except Article.DoesNotExist:
            return render(request, '404.html')
        else:
            # &#x8BA9;&#x6D4F;&#x89C8;&#x91CF;&#x52A0;&#x4E00;
            article.total_views += 1
            article.save()
        # &#x67E5;&#x8BE2;&#x5206;&#x7C7B;&#x6570;&#x636E;
        categories = ArticleCategory.objects.all()
        # &#x67E5;&#x8BE2;&#x6D4F;&#x89C8;&#x91CF;&#x524D;&#x5341;&#x7684;&#x6587;&#x7AE0;&#x6570;&#x636E;
        hot_articles = Article.objects.order_by('-total_views')[:9]
        # &#x83B7;&#x53D6;&#x5206;&#x9875;&#x8BF7;&#x6C42;&#x53C2;&#x6570;
        page_num = request.GET.get('page_num', 1)
        page_size = request.GET.get('page_size', 10)
        # &#x6839;&#x636E;&#x6587;&#x7AE0;&#x4FE1;&#x606F;&#x67E5;&#x8BE2;&#x8BC4;&#x8BBA;&#x6570;&#x636E;
        comments = Comment.objects.filter(article=article).order_by('-created')
        # &#x83B7;&#x53D6;&#x8BC4;&#x8BBA;&#x603B;&#x6570;
        total_count = comments.count()
        # &#x521B;&#x5EFA;&#x5206;&#x9875;&#x5668;
        paginator = Paginator(comments, page_size)
        # &#x8FDB;&#x884C;&#x5206;&#x9875;&#x5904;&#x7406;
        try:
            page_comments = paginator.page(page_num)
        except EmptyPage:
            return HttpResponseNotFound('empty page')
        # &#x603B;&#x9875;&#x6570;
        total_page = paginator.num_pages
        # &#x7EC4;&#x7EC7;&#x6A21;&#x677F;&#x6570;&#x636E;
        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):
        # &#x63A5;&#x53D7;&#x7528;&#x6237;&#x4FE1;&#x606F;
        user = request.user
        # &#x5224;&#x65AD;&#x7528;&#x6237;&#x662F;&#x5426;&#x767B;&#x5F55;
        if user and user.is_authenticated:
            # &#x767B;&#x5F55;&#x7528;&#x6237;&#x5219;&#x53EF;&#x4EE5;&#x63A5;&#x6536;form&#x6570;&#x636E;
            # &#x6587;&#x7AE0;&#x7684;id
            id = request.POST.get('id')
            # &#x8BC4;&#x8BBA;&#x7684;&#x5185;&#x5BB9;
            content = request.POST.get('content')
            # &#x6587;&#x7AE0;&#x662F;&#x5426;&#x5B58;&#x5728;
            try:
                article = Article.objects.get(id=id)
            except Article.DoesNotExist:
                return HttpResponseNotFound('&#x6CA1;&#x6709;&#x6B64;&#x6587;&#x7AE0;')
            # &#x4FDD;&#x5B58;&#x8BC4;&#x8BBA;&#x6570;&#x636E;
            Comment.objects.create(content=content, article=article, user=user)
            # &#x4FEE;&#x6539;&#x6587;&#x7AE0;&#x7684;&#x8BC4;&#x8BBA;&#x6570;&#x636E;
            article.comments += 1
            article.save()

            # &#x5237;&#x65B0;&#x5F53;&#x524D;&#x9875;&#x9762;
            path = reverse('home:detail') + '?id={}'.format(article.id)
            return redirect(path)
        else:
            # &#x672A;&#x767B;&#x5F55;&#x7528;&#x6237;&#x5219;&#x8DF3;&#x8F6C;&#x5230;&#x767B;&#x5F55;&#x9875;&#x9762;
            return redirect(reverse('user:login'))
  • 在detail.html中编写页面。
 <!-- 显示评论 -->
                <h4>&#x5171;&#x6709;{{total_count}}&#x6761;&#x8BC4;&#x8BBA;</h4>
                <div class="row">
                    {% for comment in comments %}
                    <div class="col-12">
                        <hr>
                        <span> </span>
                        <div>
                            <div><span><strong>{{comment.user.username}}</strong></span>&#xA0;<span style="color: gray">{{comment.created|date:'Y-m-d H:i'}}</span>
                            </div>

                            <span> {{comment.content|safe}}</span>
                        </div>
                    </div>
                    {% endfor %}

                </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日 王晓曼!

等到毕业了我再发这篇文章!

Original: https://blog.csdn.net/qq_43779149/article/details/123398422
Author: 雾里看花花里看雾
Title: 基于Vue和Django的个人博客系统

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/732799/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Python项目实践之一:武装飞船

    Python项目实践之一:武装飞船 一、规划项目 1、游戏规则设定 在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格…

    Python 2023年9月21日
    058
  • 20202127 实验二《Python程序设计》实验报告

    20202127 2021-2022-2 《Python程序设计》实验二报告 课程:《Python程序设计》班级: 2021姓名: 马艺洲学号:20202127实验教师:王志强实验…

    Python 2023年6月10日
    075
  • python打包flask 项目_使用pyinstaller将flask应用打包

    Pyinstaller 用户将python程序打包成各个平台可直接运行的程序,也可以算作是对代码加密的一种方式。pyinstaller的安装及使用方式请参考官网。 注:该文章的系统…

    Python 2023年8月13日
    040
  • Flask Web——Jinjia2模板的使用

    静态模板的使用 Jinjia2是Flask使用的html渲染模板,Jinjia原意为日本的神社,英文为temple,与模板的英文template相近,故取名jinjia。首先先演示…

    Python 2023年8月9日
    083
  • python print输出字符串报错

    python print输出字符串报错 原创 CorwinPC2022-07-18 17:46:31博主文章分类:Python ©著作权 文章标签 python 字符串 ico 文…

    Python 2023年5月25日
    063
  • SPL工业智能:发现时序数据的异常

    基本问题 工业生产过程中会产生大量的数据,比如电压、温度、流量等等,它们随时间推移而不断产生,这些数据在多数情况下是正常的,否则生产无法正常进行;少数情况下,数据是异常的,生产效率…

    Python 2023年9月26日
    054
  • python测试框架之pytest (一)

    概述 pytest官方文档介绍: pytest: helps you write better programs pytest is a framework that makes …

    Python 2023年9月11日
    033
  • Python模块注入

    注:只是自己学习记录,若有不对的地方请指出,如果觉着我写的不好或者什么的,可以去看我放在下面的大师傅的讲解,都是比较详细的 简单的flask from flask import F…

    Python 2023年8月10日
    074
  • 偏最小二乘(PLS)原理分析&Python实现

    目录 1 偏最小二乘的意义​​​​​​​ 2​ ​​​​​​PLS实现步骤 3 弄懂PLS要回答的问题 4 PLS的原理分析 4.1 自变量和因变量的主成分求解原理 4.1.1 确…

    Python 2023年8月30日
    060
  • 大数据MapReduce学习案例:TopN

    文章目录 一,案例分析 * (一)TopN分析法介绍 (二)案例需求 二,案例实施 * (一)准备数据文件 – (1)启动hadoop服务 (2)在虚拟机上创建文本文件…

    Python 2023年10月27日
    072
  • Backbone 网络-ResNetv2 论文解读

    前言 本文的主要贡献在于通过理论分析和大量实验证明使用恒等映射( identity mapping)作为快捷连接( skip connection)对于残差块的重要性。同时,将 B…

    Python 2023年10月13日
    045
  • 使用jupyter中的matplotlib库绘制简单图表4

    一、设置图表样式与映射表 (1)使用rc()函数修改图表样式 (2)设置颜色映射表可以使用关键字cmap或者直接调用set_cmap()函数进行设置1、代码 import nump…

    Python 2023年8月31日
    048
  • 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(中)

    第一种创建Blazor应用程序的方式。在Visual Studio 2022启动界面中选择”创建新项目”,如下图。 图2-5 第二种创建Blazor应用程序…

    Python 2023年10月23日
    061
  • 求解三维装箱问题的启发式深度优先搜索算法(python)

    ⭐️ 问题描述 给定一个容器(其体积为V V V) 和一系列待装载的箱子,容器和箱子的形状都是长方体。问题的目标是要确定一个可行的箱子放置方案使得在满足给定装载约束的情况下,容器中…

    Python 2023年8月2日
    076
  • python绘制热力图

    1.seaborn 绘制热力图官方说明:https://seaborn.pydata.org/generated/seaborn.heatmap.html 语法: seaborn….

    Python 2023年8月16日
    071
  • 文件服务器 — File Browser

    前言 一直想部署一套文件服务器,供队友之间相互传输文件。平时用微信发送文件真的太烦了,每发送或者接收一次都会有一个新的文件,造成重复文件太多了。文件服务器统一管理,自己需要什么文件…

    Python 2023年10月13日
    0100
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球