准备工作

  • 创建项目

    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}}&nbsp;</span>
                        <span><i class="fas fa-comments" style="color: yellowgreen;"></i>{{article.comments}}&nbsp;&nbsp;</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>&nbsp;<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日 王晓曼!

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

Logo

前往低代码交流专区

更多推荐