BBS项目

settings.pycss

"""
Django settings for bbs project.

Generated by 'django-admin startproject' using Django 1.11.7.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'zcj3do(fmw&pec7g4%0kw0+-9+hnb)3c=--*zech#ek*lp_ius'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.apps.BlogConfig',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',  # 在这个中间件以后 request.user
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'bbs.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'bbs.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': '127.0.0.1',
        'PORT': 3306
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = False


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static")
]


# Django用户上传的都叫media文件
MEDIA_URL = "/media/"
# media配置,用户上传的文件都默认放在这个文件夹下
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

# 告诉Django项目用哪张表作认证
AUTH_USER_MODEL = 'blog.UserInfo'


# 项目级别的日志配置
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")

LOGGING = {
    'version': 1,  # 保留的参数,默认是1
    'disable_existing_loggers': False,  # 是否禁用已经存在的logger示例,不由用
    'formatters': {
        'standard': {  # 定义一个标准的日志格式化
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {  # 定义一个简单的日志格式化
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {  # 定义一个特殊的日志格式化
            'format': '%(message)s'
        }
    },
    # 过滤器
    'filters': {
        # 只有在deubg=True的过滤器
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    # 处理器
    'handlers': {
        # 定义一个专门往终端打印日志的控制器
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志
            'class': 'logging.StreamHandler',  # 使用什么类去处理日志流
            'formatter': 'simple'
        },
        # 定义一个默认的日志处理器
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "bbs_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,  # 日志文件备份的数量
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        # 定义一个专门及错误日志的处理器
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "bbs_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
    },


    'loggers': {
       # 默认的logger应用以下配置
        '': {
            'handlers': ['default', 'console', 'error'],  # 上线以后能够把'console'移除
            'level': 'DEBUG',
            'propagate': True,  # 若是有父级的logger示例,表示要不要向上传递日志流
        }
    },
}
View Code

 models.pyhtml

 

from django.db import models

# Create your models here.

from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    """
    用户信息表
    """
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=11, null=True, unique=True)
    avatar = models.FileField(upload_to="avatars/", default="avatars/default.png", verbose_name="头像")
    create_time = models.DateTimeField(auto_now_add=True)

    blog = models.OneToOneField(to="Blog", to_field="nid", null=True)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = "用户"
        verbose_name_plural = verbose_name


class Blog(models.Model):
    """
    博客信息
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=64)  # 我的博客标题
    site = models.CharField(max_length=32, unique=True)  # 我的博客后缀
    theme = models.CharField(max_length=32)  # 博客主题

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "blog站点"
        verbose_name_plural = verbose_name


class Category(models.Model):
    """
    我的博客文章分类
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)  # 分类标题
    blog = models.ForeignKey(to="Blog", to_field="nid")  # 外键关联博客,一个博客站点能够有多个分类

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "文章分类"
        verbose_name_plural = verbose_name


class Tag(models.Model):
    """
    标签
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)  # 标签名
    blog = models.ForeignKey(to="Blog", to_field="nid")  # 所属博客

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "标签"
        verbose_name_plural = verbose_name


class Article(models.Model):
    """
    文章
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=50, verbose_name="文章标题")  # 文章标题
    desc = models.CharField(max_length=255)  # 文章描述
    create_time = models.DateTimeField(auto_now_add=True)  # 建立时间  --> datetime()

    # 评论数
    comment_count = models.IntegerField(verbose_name="评论数", default=0)
    # 点赞数
    up_count = models.IntegerField(verbose_name="点赞数", default=0)
    #
    down_count = models.IntegerField(verbose_name="踩数", default=0)

    category = models.ForeignKey(to="Category", to_field="nid", null=True)
    user = models.ForeignKey(to="UserInfo", to_field="nid")
    tags = models.ManyToManyField(  # 中介模型
        to="Tag",
        through="Article2Tag",
        through_fields=("article", "tag"),  # 注意顺序!!!
    )

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "文章"
        verbose_name_plural = verbose_name


class ArticleDetail(models.Model):
    """
    文章详情表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to="Article", to_field="nid")

    class Meta:
        verbose_name = "文章详情"
        verbose_name_plural = verbose_name


class Article2Tag(models.Model):
    """
    文章和标签的多对多关系表
    """
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(to="Article", to_field="nid")
    tag = models.ForeignKey(to="Tag", to_field="nid")

    def __str__(self):
        return "{}-{}".format(self.article.title, self.tag.title)

    class Meta:
        unique_together = (("article", "tag"),)
        verbose_name = "文章-标签"
        verbose_name_plural = verbose_name


class ArticleUpDown(models.Model):
    """
    点赞表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey(to="UserInfo", null=True)
    article = models.ForeignKey(to="Article", null=True)
    is_up = models.BooleanField(default=True)

    class Meta:
        unique_together = (("article", "user"),)
        verbose_name = "文章点赞"
        verbose_name_plural = verbose_name


class Comment(models.Model):
    """
    评论表
    """
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(to="Article", to_field="nid")
    user = models.ForeignKey(to="UserInfo", to_field="nid")
    content = models.CharField(max_length=255)  # 评论内容
    create_time = models.DateTimeField(auto_now_add=True)
    parent_comment = models.ForeignKey("self", null=True, blank=True)  # blank=True 在django admin里面能够不填

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "评论"
        verbose_name_plural = verbose_name
View Code

 

froms.pypython

 

from django import forms
from django.core.exceptions import ValidationError
from blog import models


# 定义一个注册的form类
class RegForm(forms.Form):
    username = forms.CharField(
        max_length=16,
        label="用户名",
        error_messages={
            "max_length": "用户名最长16位",
            "required": "用户名不能为空",
        },

        widget=forms.widgets.TextInput(
            attrs={"class": "form-control"},
        )
    )

    password = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(
            attrs={"class": "form-control"},
            render_value=True,
        ),
        error_messages={
            "min_length": "密码至少要6位!",
            "required": "密码不能为空",
        }
    )

    re_password = forms.CharField(
        min_length=6,
        label="确认密码",
        widget=forms.widgets.PasswordInput(
            attrs={"class": "form-control"},
            render_value=True,
        ),
        error_messages={
            "min_length": "确认密码至少要6位!",
            "required": "确认密码不能为空",
        }
    )

    email = forms.EmailField(
        label="邮箱",
        widget=forms.widgets.EmailInput(
            attrs={"class": "form-control"},

        ),
        error_messages={
            "invalid": "邮箱格式不正确!",
            "required": "邮箱不能为空",
        }
    )

    # 重写username字段的局部钩子
    def clean_username(self):
        username = self.cleaned_data.get("username")
        is_exist = models.UserInfo.objects.filter(username=username)
        if is_exist:
            # 表示用户名已注册
            self.add_error("username", ValidationError("用户名已存在"))
        else:
            return username

    # 重写email字段的局部钩子
    def clean_email(self):
        email = self.cleaned_data.get("email")
        is_exist = models.UserInfo.objects.filter(email=email)
        if is_exist:
            # 表示邮箱已注册
            self.add_error("email", ValidationError("邮箱已被注册"))
        else:
            return email

    # 重写全局的钩子函数,对确认密码作校验
    def clean(self):
        password = self.cleaned_data.get("password")
        re_password = self.cleaned_data.get("re_password")

        if re_password and re_password != password:
            self.add_error("re_password", ValidationError("两次密码不一致"))

        else:

            return self.cleaned_data
View Code

 

urls.pymysql

 

from django.conf.urls import url, include
from django.contrib import admin
from blog import views
from django.views.static import serve
from django.conf import settings
from blog import urls as blog_urls

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    url(r'^login/', views.login),
    url(r'^logout/', views.logout),
    url(r'^reg/', views.register),

    url(r'^index/', views.index),
    # 将全部以blog开头的url都交给app下面的urls.py来处理
    url(r'^blog/', include(blog_urls)),


    url(r'^get_valid_img.png/', views.get_valid_img),

    # 极验滑动验证码 获取验证码的url
    url(r'^pc-geetest/register', views.get_geetest),
    # 专门用来校验用户名是否已被注册的接口
    url(r'^check_username_exist/$', views.check_username_exist),

    # media相关的路由设置
    url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),

    url(r'^upload/', views.upload),

    url(r'^$', views.index)
]
View Code

 

app中的urls.pyjquery

from django.conf.urls import url
from blog import views


urlpatterns = [


    url(r"backend/add_article/",views.add_article),

    url(r"up_down/",views.up_down),
    url(r"comment/",views.comment),
    url(r"comment_tree/(\d+)/",views.comment_tree),
    # /blog/xiaohei/tag/python
    # /blog/xiaohei/category/技术
    # /blog/xiaohei/archive/2018-05
    # url(r'(\w+)/tag/(\w+)', views.tag),
    # url(r'(\w+)/category/(\w+)', views.category),
    # url(r'(\w+)/archive/(.+)', views.archive),

    # 三和一 URL

    url(r'(\w+)/(tag|category|archive)/(.+)/', views.home),  # home(request, username, tag, 'python')

    url(r'(\w+)/article/(\d+)/$', views.article_detail),  # 文章详情  article_detail(request, xiaohei, 1)

    url(r'(\w+)', views.home),  # home(request, username)

]
View Code

views.pygit

from django.shortcuts import render, redirect, HttpResponse
from django.http import JsonResponse
from django.contrib import auth
from geetest import GeetestLib
from blog import forms, models
from django.db.models import Count
import logging

# 生成一个logger实例,专门用来记录日志
logger = logging.getLogger(__name__)
# logger_s10 = logging.getLogger("collect")

# Create your views here.

# VALID_CODE = ""


# 本身生成验证码的登陆
# def login(request):
#     # if request.is_ajax():  # 若是是AJAX请求
#     if request.method == "POST":
#         # 初始化一个给AJAX返回的数据
#         ret = {"status": 0, "msg": ""}
#         # 从提交过来的数据中 取到用户名和密码
#         username = request.POST.get("username")
#         pwd = request.POST.get("password")
#         valid_code = request.POST.get("valid_code")  # 获取用户填写的验证码
#         print(valid_code)
#         print("用户输入的验证码".center(120, "="))
#         if valid_code and valid_code.upper() == request.session.get("valid_code", "").upper():
#             # 验证码正确
#             # 利用auth模块作用户名和密码的校验
#             user = auth.authenticate(username=username, password=pwd)
#             if user:
#                 # 用户名密码正确
#                 # 给用户作登陆
#                 auth.login(request, user)
#                 ret["msg"] = "/index/"
#             else:
#                 # 用户名密码错误
#                 ret["status"] = 1
#                 ret["msg"] = "用户名或密码错误!"
#         else:
#             ret["status"] = 1
#             ret["msg"] = "验证码错误"
#
#         return JsonResponse(ret)
#     return render(request, "login.html")


# 使用极验滑动验证码的登陆

def login(request):
    # if request.is_ajax():  # 若是是AJAX请求
    if request.method == "POST":
        # 初始化一个给AJAX返回的数据
        ret = {"status": 0, "msg": ""}
        # 从提交过来的数据中 取到用户名和密码
        username = request.POST.get("username")
        pwd = request.POST.get("password")
        # 获取极验 滑动验证码相关的参数
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.POST.get(gt.FN_CHALLENGE, '')
        validate = request.POST.get(gt.FN_VALIDATE, '')
        seccode = request.POST.get(gt.FN_SECCODE, '')
        status = request.session[gt.GT_STATUS_SESSION_KEY]
        user_id = request.session["user_id"]

        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        if result:
            # 验证码正确
            # 利用auth模块作用户名和密码的校验
            user = auth.authenticate(username=username, password=pwd)
            if user:
                # 用户名密码正确
                # 给用户作登陆
                auth.login(request, user)  # 将登陆用户赋值给 request.user
                ret["msg"] = "/index/"
            else:
                # 用户名密码错误
                ret["status"] = 1
                ret["msg"] = "用户名或密码错误!"
        else:
            ret["status"] = 1
            ret["msg"] = "验证码错误"

        return JsonResponse(ret)
    return render(request, "login2.html")


# 注销
def logout(request):
    auth.logout(request)
    return redirect("/index/")


def index(request):
    # 查询全部的文章列表
    article_list = models.Article.objects.all()


    return render(request, "index.html", {"article_list": article_list})


# 获取验证码图片的视图
def get_valid_img(request):
    # with open("valid_code.png", "rb") as f:
    #     data = f.read()
    # 本身生成一个图片
    from PIL import Image, ImageDraw, ImageFont
    import random

    # 获取随机颜色的函数
    def get_random_color():
        return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)

    # 生成一个图片对象
    img_obj = Image.new(
        'RGB',
        (220, 35),
        get_random_color()
    )
    # 在生成的图片上写字符
    # 生成一个图片画笔对象
    draw_obj = ImageDraw.Draw(img_obj)
    # 加载字体文件, 获得一个字体对象
    font_obj = ImageFont.truetype("static/font/kumo.ttf", 28)
    # 开始生成随机字符串而且写到图片上
    tmp_list = []
    for i in range(5):
        u = chr(random.randint(65, 90))  # 生成大写字母
        l = chr(random.randint(97, 122))  # 生成小写字母
        n = str(random.randint(0, 9))  # 生成数字,注意要转换成字符串类型

        tmp = random.choice([u, l, n])
        tmp_list.append(tmp)
        draw_obj.text((20+40*i, 0), tmp, fill=get_random_color(), font=font_obj)

    print("".join(tmp_list))
    print("生成的验证码".center(120, "="))
    # 不能保存到全局变量
    # global VALID_CODE
    # VALID_CODE = "".join(tmp_list)

    # 保存到session
    request.session["valid_code"] = "".join(tmp_list)
    # 加干扰线
    # width = 220  # 图片宽度(防止越界)
    # height = 35
    # for i in range(5):
    #     x1 = random.randint(0, width)
    #     x2 = random.randint(0, width)
    #     y1 = random.randint(0, height)
    #     y2 = random.randint(0, height)
    #     draw_obj.line((x1, y1, x2, y2), fill=get_random_color())
    #
    # # 加干扰点
    # for i in range(40):
    #     draw_obj.point((random.randint(0, width), random.randint(0, height)), fill=get_random_color())
    #     x = random.randint(0, width)
    #     y = random.randint(0, height)
    #     draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color())

    # 将生成的图片保存在磁盘上
    # with open("s10.png", "wb") as f:
    #     img_obj.save(f, "png")
    # # 把刚才生成的图片返回给页面
    # with open("s10.png", "rb") as f:
    #     data = f.read()

    # 不须要在硬盘上保存文件,直接在内存中加载就能够
    from io import BytesIO
    io_obj = BytesIO()
    # 将生成的图片数据保存在io对象中
    img_obj.save(io_obj, "png")
    # 从io对象里面取上一步保存的数据
    data = io_obj.getvalue()
    return HttpResponse(data)


# 请在官网申请ID使用,示例ID不可以使用
pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"


# 处理极验 获取验证码的视图
def get_geetest(request):
    user_id = 'test'
    gt = GeetestLib(pc_geetest_id, pc_geetest_key)
    status = gt.pre_process(user_id)
    request.session[gt.GT_STATUS_SESSION_KEY] = status
    request.session["user_id"] = user_id
    response_str = gt.get_response_str()
    return HttpResponse(response_str)


# 注册的视图函数
def register(request):
    if request.method == "POST":
        print(request.POST)
        print("=" * 120)
        ret = {"status": 0, "msg": ""}
        form_obj = forms.RegForm(request.POST)
        print(request.POST)
        # 帮我作校验
        if form_obj.is_valid():

            # 校验经过,去数据库建立一个新的用户
            form_obj.cleaned_data.pop("re_password")
            avatar_img = request.FILES.get("avatar")
            models.UserInfo.objects.create_user(**form_obj.cleaned_data, avatar=avatar_img)
            ret["msg"] = "/index/"
            return JsonResponse(ret)
        else:
            print(form_obj.errors)
            ret["status"] = 1
            ret["msg"] = form_obj.errors
            print(ret)
            print("=" * 120)
            return JsonResponse(ret)
    # 生成一个form对象
    form_obj = forms.RegForm()
    print(form_obj.fields)
    return render(request, "register.html", {"form_obj": form_obj})
    # return render(request, "form_test.html", {"form_obj": form_obj})


# 校验用户名是否已被注册
def check_username_exist(request):
    ret = {"status": 0, "msg": ""}
    username = request.GET.get("username")
    print(username)
    is_exist = models.UserInfo.objects.filter(username=username)
    if is_exist:
        ret["status"] = 1
        ret["msg"] = "用户名已被注册!"
    return JsonResponse(ret)


# 我的博客主页
def home(request, username, *args):
    logger.debug("home视图获取到用户名:{}".format(username))
    # 去UserInfo表里把用户对象取出来
    user = models.UserInfo.objects.filter(username=username).first()
    if not user:
        logger.warning("又有人访问不存在页面了...")
        return HttpResponse("404")
    # 若是用户存在须要将TA写的全部文章找出来
    blog = user.blog
    if not args:
        logger.debug("args没有接收到参数,默认走的是用户的我的博客页面!")
        # 个人文章列表
        article_list = models.Article.objects.filter(user=user)
        # 个人文章分类及每一个分类下文章数
        # 将个人文章按照个人分类分组,并统计出每一个分类下面的文章数
        # category_list = models.Category.objects.filter(blog=blog)
        # category_list = models.Category.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")
        # # [{'title': '技术', 'c': 2}, {'title': '生活', 'c': 1}, {'title': 'LOL', 'c': 1}]
        # # 统计当前站点下有哪一些标签,而且按标签统计出文章数
        # tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")
        #
        # # 按日期归档
        # archive_list = models.Article.objects.filter(user=user).extra(
        #     select={"archive_ym": "date_format(create_time,'%%Y-%%m')"}
        # ).values("archive_ym").annotate(c=Count("nid")).values("archive_ym", "c")
    else:
        logger.debug(args)
        logger.debug("------------------------------")
        # 表示按照文章的分类或tag或日期归档来查询
        # args = ("category", "技术")
        # article_list = models.Article.objects.filter(user=user).filter(category__title="技术")
        if args[0] == "category":
            article_list = models.Article.objects.filter(user=user).filter(category__title=args[1])
        elif args[0] == "tag":
            article_list = models.Article.objects.filter(user=user).filter(tags__title=args[1])
        else:
            # 按照日期归档
            try:
                year, month = args[1].split("-")
                logger.debug("分割获得参数year:{}, month:{}".format(year, month))
                # logger_s10.info("获得年和月的参数啦!!!!")
                logger.debug("************************")
                article_list = models.Article.objects.filter(user=user).filter(
                    create_time__year=year, create_time__month=month
                )
            except Exception as e:
                logger.warning("请求访问的日期归档格式不正确!!!")
                logger.warning((str(e)))
                return HttpResponse("404")
    return render(request, "home.html", {
        "username": username,
        "blog": blog,
        "article_list": article_list,
    })


def get_left_menu(username):
    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog
    category_list = models.Category.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")
    # 按日期归档
    archive_list = models.Article.objects.filter(user=user).extra(
        select={"archive_ym": "date_format(create_time,'%%Y-%%m')"}
    ).values("archive_ym").annotate(c=Count("nid")).values("archive_ym", "c")

    return category_list, tag_list, archive_list


def article_detail(request, username, pk):
    """
    :param username: 被访问的blog的用户名
    :param pk: 访问的文章的主键id值
    :return:
    """
    user = models.UserInfo.objects.filter(username=username).first()
    if not user:
        return HttpResponse("404")
    blog = user.blog
    # 找到当前的文章
    article_obj = models.Article.objects.filter(pk=pk).first()

    # 全部评论列表

    comment_list=models.Comment.objects.filter(article_id=pk)


    return render(
        request,
        "article_detail.html",
        {
            "username": username,
            "article": article_obj,
            "blog": blog,
            "comment_list":comment_list
         }
    )




import json

from django.db.models import F

def up_down(request):
    print(request.POST)
    article_id=request.POST.get('article_id')
    is_up=json.loads(request.POST.get('is_up'))
    user=request.user
    response={"state":True}
    print("is_up",is_up)
    try:
        models.ArticleUpDown.objects.create(user=user,article_id=article_id,is_up=is_up)
        models.Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)

    except Exception as e:
        response["state"]=False
        response["fisrt_action"]=models.ArticleUpDown.objects.filter(user=user,article_id=article_id).first().is_up




    return JsonResponse(response)
    #return HttpResponse(json.dumps(response))












def comment(request):

    print(request.POST)

    pid=request.POST.get("pid")
    article_id=request.POST.get("article_id")
    content=request.POST.get("content")
    user_pk=request.user.pk
    response={}
    if not pid:  #根评论
        comment_obj=models.Comment.objects.create(article_id=article_id,user_id=user_pk,content=content)
    else:
        comment_obj=models.Comment.objects.create(article_id=article_id,user_id=user_pk,content=content,parent_comment_id=pid)



    response["create_time"]=comment_obj.create_time.strftime("%Y-%m-%d")
    response["content"]=comment_obj.content
    response["username"]=comment_obj.user.username

    return JsonResponse(response)




def comment_tree(request,article_id):

    ret=list(models.Comment.objects.filter(article_id=article_id).values("pk","content","parent_comment_id"))
    print(ret)
    return JsonResponse(ret,safe=False)




def add_article(request):

    if request.method=="POST":
        title=request.POST.get('title')
        article_content=request.POST.get('article_content')
        user=request.user

        from bs4 import BeautifulSoup

        bs=BeautifulSoup(article_content,"html.parser")
        desc=bs.text[0:150]+"..."


        # 过滤非法标签
        for tag in bs.find_all():

            print(tag.name)

            if tag.name in ["script", "link"]:
                tag.decompose()

        article_obj=models.Article.objects.create(user=user,title=title,desc=desc)
        models.ArticleDetail.objects.create(content=str(bs),article=article_obj)


        return HttpResponse("添加成功")




    return render(request,"add_article.html")



from bbs import settings
import os,json
def upload(request):
    print(request.FILES)
    obj = request.FILES.get("upload_img")

    print("name",obj.name)

    path=os.path.join(settings.MEDIA_ROOT,"add_article_img",obj.name)

    with open(path,"wb") as f:
        for line in obj:
            f.write(line)


    res={
        "error":0,
        "url":"/media/add_article_img/"+obj.name
    }


    return HttpResponse(json.dumps(res))
View Code

my_tags.pyajax

from django import template
from blog import models
from django.db.models import Count

register = template.Library()


@register.inclusion_tag("left_menu.html")
def get_left_menu(username):
    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog
    # 查询文章分类及对应的文章数
    category_list = models.Category.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")
    # 查文章标签及对应的文章数
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count("article")).values("title", "c")

    # 按日期归档
    archive_list = models.Article.objects.filter(user=user).extra(
        select={"archive_ym": "date_format(create_time,'%%Y-%%m')"}
    ).values("archive_ym").annotate(c=Count("nid")).values("archive_ym", "c")

    return {
        "username": username,
        "category_list" :category_list,
        "tag_list": tag_list,
        "archive_list": archive_list
    }
View Code

 

 login.html 普通验证码sql

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎登陆</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>

<div class="container">
    <div class="row">
        <form class="form-horizontal col-md-6 col-md-offset-3 login-form">
            {% csrf_token %}
            <div class="form-group">
                <label for="username" class="col-sm-2 control-label">用户名</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="username" name="username" placeholder="用户名">
                </div>
            </div>
            <div class="form-group">
                <label for="password" class="col-sm-2 control-label">密码</label>
                <div class="col-sm-10">
                    <input type="password" class="form-control" id="password" name="password" placeholder="密码">
                </div>
            </div>
            <div class="form-group">
                <label for="password" class="col-sm-2 control-label">验证码</label>
                <div class="col-sm-10">
                    <input type="text" name="valid_code" id="valid_code">
                    <img id="valid-img" class="valid-img" src="/get_valid_img.png?" alt="">
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="button" class="btn btn-default" id="login-button">登陆</button>
                    <span class="login-error"></span>
                </div>
            </div>
        </form>
    </div>
</div>

<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<script>
    $("#login-button").click(function () {
        // 1. 取到用户填写的用户名和密码 -> 取input框的值
        var username = $("#username").val();
        var password = $("#password").val();
        var valid_code = $("#valid_code").val();
        // 2. 用AJAX发送到服务端
        $.ajax({
            url: "/login/",
            type: "post",
            data: {
                "username": username,
                "password": password,
                "valid_code": valid_code,
                "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val()
            },
            success: function (data) {
                console.log(data);
                if (data.status){
                    // 有错误,在页面上提示
                    $(".login-error").text(data.msg);
                }else {
                    // 登录成功
                    location.href = data.msg;
                }
            }
        })
    });

    // 当input框获取焦点时将以前的错误清空
    $("#username,#password").focus(function () {
        // 将以前的错误清空
        $(".login-error").text("");
    });

    // 点击验证码图片 刷新验证码
    $("#valid-img").click(function () {
        $(this)[0].src += "?";
    })
</script>
</body>
</html>
View Code

login2.html  极验验证码数据库

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎登陆</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>

<div class="container">
    <div class="row">
        <form class="form-horizontal col-md-6 col-md-offset-3 login-form">
            {% csrf_token %}
            <div class="form-group">
                <label for="username" class="col-sm-2 control-label">用户名</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="username" name="username" placeholder="用户名">
                </div>
            </div>
            <div class="form-group">
                <label for="password" class="col-sm-2 control-label">密码</label>
                <div class="col-sm-10">
                    <input type="password" class="form-control" id="password" name="password" placeholder="密码">
                </div>
            </div>
            <div class="form-group">
                <!-- 放置极验的滑动验证码 -->
                <div id="popup-captcha"></div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="button" class="btn btn-default" id="login-button">登陆</button>
                    <span class="login-error"></span>
                </div>
            </div>
        </form>
    </div>
</div>

<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<!-- 引入封装了failback的接口--initGeetest -->
<script src="http://static.geetest.com/static/tools/gt.js"></script>
<script>

    // 极验 发送登陆数据的
    var handlerPopup = function (captchaObj) {
        // 成功的回调
        captchaObj.onSuccess(function () {
            var validate = captchaObj.getValidate();
            // 1. 取到用户填写的用户名和密码 -> 取input框的值
            var username = $("#username").val();
            var password = $("#password").val();
            $.ajax({
                url: "/login/", // 进行二次验证
                type: "post",
                dataType: "json",
                data: {
                    username: username,
                    password: password,
                    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
                    geetest_challenge: validate.geetest_challenge,
                    geetest_validate: validate.geetest_validate,
                    geetest_seccode: validate.geetest_seccode
                },
                success: function (data) {
                    console.log(data);
                    if (data.status) {
                        // 有错误,在页面上提示
                        $(".login-error").text(data.msg);
                    } else {
                        // 登录成功
                        location.href = data.msg;
                    }
                }
            });
        });

         $("#login-button").click(function () {
            captchaObj.show();
        });
        // 将验证码加到id为captcha的元素里
        captchaObj.appendTo("#popup-captcha");
        // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
    };
    // 当input框获取焦点时将以前的错误清空
    $("#username,#password").focus(function () {
        // 将以前的错误清空
        $(".login-error").text("");
    });

    // 验证开始须要向网站主后台获取id,challenge,success(是否启用failback)
    $.ajax({
        url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
        type: "get",
        dataType: "json",
        success: function (data) {
            // 使用initGeetest接口
            // 参数1:配置参数
            // 参数2:回调,回调的第一个参数验证码对象,以后可使用它作appendTo之类的事件
            initGeetest({
                gt: data.gt,
                challenge: data.challenge,
                product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
                offline: !data.success // 表示用户后台检测极验服务器是否宕机,通常不须要关注
                // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
            }, handlerPopup);
        }
    })


</script>
</body>
</html>
View Code

register.htmldjango

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form novalidate autocomplete="off" action="/reg/" method="post" class="form-horizontal reg-form" enctype="multipart/form-data">
                {% csrf_token %}

                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.username.label }}</label>
                    <div class="col-sm-8">
                        {{ form_obj.username }}
                        <span class="help-block">{{ form_obj.username.errors.0 }}</span>
                    </div>
                </div>

                <div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.password.label }}</label>
                    <div class="col-sm-8">
                        {{ form_obj.password }}
                        <span class="help-block">{{ form_obj.password.errors.0 }}</span>
                    </div>
                </div>

                <div class="form-group">
                    <label for="{{ form_obj.re_password.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.re_password.label }}</label>
                    <div class="col-sm-8">
                        {{ form_obj.re_password }}
                        <span class="help-block">{{ form_obj.re_password.errors.0 }}</span>
                    </div>
                </div>

                <div class="form-group">
                    <label for="{{ form_obj.email.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.email.label }}</label>
                    <div class="col-sm-8">
                        {{ form_obj.email }}
                        <span class="help-block">{{ form_obj.email.errors.0 }}</span>
                    </div>
                </div>

                <div class="form-group">
                    <label
                            class="col-sm-2 control-label">头像</label>
                    <div class="col-sm-8">
                        <label for="id_avatar"><img id="avatar-img" src="/static/img/default.png" alt=""></label>
                        <input accept="image/*" type="file" name="avatar" id="id_avatar" style="display: none">
                        <span class="help-block"></span>
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                        <button type="button" class="btn btn-success" id="reg-submit">注册</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>


<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<script>
    // 找到头像的input标签绑定change事件
    $("#id_avatar").change(function () {
        // 1. 建立一个读取文件的对象
        var fileReader = new FileReader();
        // 取到当前选中的头像文件
        // console.log(this.files[0]);
        // 读取你选中的那个文件
        fileReader.readAsDataURL(this.files[0]);  // 读取文件是须要时间的
        fileReader.onload = function () {
            // 2. 等上一步读完文件以后才 把图片加载到img标签中
            $("#avatar-img").attr("src", fileReader.result);
        };
    });
    // AJAX提交注册的数据
    $("#reg-submit").click(function () {
        // 取到用户填写的注册数据,向后端发送AJAX请求
        var formData = new FormData();
        formData.append("username", $("#id_username").val());
        formData.append("password", $("#id_password").val());
        formData.append("re_password", $("#id_re_password").val());
        formData.append("email", $("#id_email").val());
        formData.append("avatar", $("#id_avatar")[0].files[0]);
        formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());

        $.ajax({
            url: "/reg/",
            type: "post",
            processData: false,  // 告诉jQuery不要处理个人数据
            contentType: false,  // 告诉jQuery不要设置content类型
            data: formData,
            success:function (data) {
                if (data.status){
                    // 有错误就展现错误
                    // console.log(data.msg);
                    // 将报错信息填写到页面上
                    $.each(data.msg, function (k,v) {
                        // console.log("id_"+k, v[0]);
                        // console.log($("#id_"+k));
                        $("#id_"+k).next("span").text(v[0]).parent().parent().addClass("has-error");
                    })

                }else {
                    // 没有错误就跳转到指定页面
                    location.href = data.msg;
                }
            }
        })
    });

    // 将全部的input框绑定获取焦点的事件,将全部的错误信息清空
    $("form input").focus(function () {
        $(this).next().text("").parent().parent().removeClass("has-error");
    });

    // 给username input框绑定一个失去焦点的事件,失去焦点以后就校验用户名是否已被注册
    {#$("#id_username").blur(function () {#}
    $("#id_username").on("input", function () {
        // 取到用户填写的值
        var username = $(this).val();
        // 发请求
        $.ajax({
            url: "/check_username_exist/",
            type: "get",
            data: {"username": username},
            success: function (data) {
                if (data.status){
                    // 用户名已被注册
                    $("#id_username").next().text(data.msg).parent().parent().addClass("has-error");
                }
            }
        })
    })
</script>
</body>
</html>
View Code

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/fontawesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>

<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">The Blog</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
                <li><a href="#">Link</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.username %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">我的中心<span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#">Action</a></li>
                            <li><a href="#">Another action</a></li>
                            <li><a href="#">Something else here</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="/logout/">注销</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/login/">登陆</a></li>
                    <li><a href="/reg/">注册</a></li>
                {% endif %}
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<!-- 主页面 开始-->
<div class="container">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">左侧广告位一</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">左侧广告位二</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
        <div class="col-md-8">
            <!-- 文章列表 开始 -->
            <div class="article-list">
                {% for article in article_list %}
                    <div class="article">
                        <h3><a href="/blog/{{ article.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></h3>
                        <div class="media">
                            <div class="media-left">
                                <a href="#">
                                    <img class="media-object author-img" src="/media/{{ article.user.avatar }}" alt="...">
                                </a>
                            </div>
                            <div class="media-body">
                                <p>{{ article.desc|safe }}</p>
                            </div>
                        </div>
                        <div class="article-footer">
                            <span><a href="/blog/{{ article.user.username }}/">{{ article.user.username }}</a></span>发布于
                            <span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span>
{#                            <span class="glyphicon glyphicon-comment">评论({{ article.comment_count }})</span>#}
{#                            <span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_count }})</span>#}
                            <span><i class="fa fa-commenting-o fa-fw" aria-hidden="true"></i>评论({{ article.comment_count }})</span>
                            <span><i class="fa fa-thumbs-o-up fa-fw" aria-hidden="true"></i>点赞({{ article.up_count }})</span>
                        </div>
                        <hr>
                    </div>
                {% endfor %}

            </div>
            <!-- 文章列表 结束-->

        </div>
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">右侧广告位一</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">右侧广告位二</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
    </div>
</div>
<!-- 主页面 结束-->

<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
View Code

home.html

{% extends 'base.html' %}


{% block page-main %}
 <!-- 我的博客列表 开始-->
        <div class="article-list">
            {% for article in article_list %}
                <div class="article">
                    <p class="article-title"><a href="/blog/{{ article.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></p>
                    <div class="media">
                        <div class="media-left">
                            <a href="#">
                                <img class="media-object author-img" src="/media/{{ article.user.avatar }}" alt="...">
                            </a>
                        </div>
                        <div class="media-body">
                            <p>{{ article.desc }}</p>
                        </div>
                    </div>
                    <div class="article-footer">
                        <span><a href="/blog/{{ article.user.username }}/">{{ article.user.username }}</a></span>发布于
                        <span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span>
                        {#                            <span class="glyphicon glyphicon-comment">评论({{ article.comment_count }})</span>#}
                        {#                            <span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_count }})</span>#}
                        <span><i class="fa fa-commenting-o fa-fw" aria-hidden="true"></i>评论({{ article.comment_count }})</span>
                        <span><i class="fa fa-thumbs-o-up fa-fw"
                                 aria-hidden="true"></i>点赞({{ article.up_count }})</span>
                    </div>
                    <hr>
                </div>
            {% endfor %}

        </div>
        <!-- 我的博客列表 结束-->
{% endblock %}
View Code

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ blog.title }}</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/fontawesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
    <link rel="stylesheet" href="/static/theme/{{ blog.theme }}">
    <script src="/static/jquery-3.3.1.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>

<div class="header">
    <p>{{ blog.title }}</p>
</div>

<div class="container">
    <div class="col-md-3">
            {% load my_tags %}

            {% get_left_menu username %}
        </div>
        <div class="col-md-8">
            {% block page-main %}

            {% endblock %}
        </div>
    </div>
</div>

</body>
</html>
View Code

left_menu.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ blog.title }}</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/fontawesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
    <link rel="stylesheet" href="/static/theme/{{ blog.theme }}">
    <script src="/static/jquery-3.3.1.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>

<div class="header">
    <p>{{ blog.title }}</p>
</div>

<div class="container">
    <div class="col-md-3">
            {% load my_tags %}

            {% get_left_menu username %}
        </div>
        <div class="col-md-8">
            {% block page-main %}

            {% endblock %}
        </div>
    </div>
</div>

</body>
</html>
View Code

article.html

{% extends 'base.html' %}

{% block page-main %}

    <div class="article-detail">
        <h1>{{ article.title }}</h1>
        <p>{{ article.articledetail.content|safe }}</p>
    </div>

    <div class="poll clearfix">
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article.up_count }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red;"></div>
        </div>

    </div>
    <p>评论树</p>

    <div class="comment_tree">

    </div>
    <hr>
    <p>评论列表</p>
    <ul class="comment_list">
        {% for comment in comment_list %}
            <li class="list-group-item">
                <div>
                    <a href="">#{{ forloop.counter }}楼</a> &nbsp;&nbsp;
                    <span style="color: gray">{{ comment.create_time|date:"Y-m-d H:i" }}</span> &nbsp;&nbsp;
                    <a href=""><span>{{ comment.user.username }}</span></a>
                    <a class="pull-right reply_btn" username="{{ comment.user.username }}"
                       comment_pk="{{ comment.pk }}"><span>回复</span></a>
                </div>
                {% if comment.parent_comment_id %}
                    <div class="pid_info well">
                        <p> {{ comment.parent_comment.user.username }}:
                            &nbsp;&nbsp;&nbsp;{{ comment.parent_comment.content }} </p>
                    </div>
                {% endif %}

                <div class="con">
                    <p>
                        {{ comment.content }}
                    </p>
                </div>
            </li>
        {% endfor %}
    </ul>

    {% if request.user.username %}
        <div class="div_comment">
            <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
                         value="{{ request.user.username }}"></p>
            <p>评论内容</p>
            <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            <p>
                <button id="comment_btn">提交评论</button>
            </p>

        </div>
    {% else %}
        <a href="/login/">登陆</a>
    {% endif %}



    <script>
        // 获取评论数据,展现评论树结构
        $.ajax({
            url: "/blog/comment_tree/" + '{{ article.pk }}/',
            success: function (data) {
                console.log(data);

                $.each(data, function (index, comment_dict) {
                    var s = '<div class="comment_item" comment_id=' + comment_dict.pk + '> <span class="content">' + comment_dict.content + '</span> </div>'
                    if (comment_dict.parent_comment_id) {
                        // 子评论
                        var pid=comment_dict.parent_comment_id;
                        $("[comment_id="+pid+"]").append(s);
                    }
                    else {   //  根评论
                        $(".comment_tree").append(s);
                    }
                })

            }
        });


        // 提交评论
        var pid = "";
        $("#comment_btn").click(function () {

            var article_id = $(".info").attr("article_id");
            var content = $("#comment_content").val();
            if (pid) {
                var index = content.indexOf("\n");
                content = content.slice(index + 1)
            }


            $.ajax({
                url: "/blog/comment/",
                type: "post",
                data: {
                    article_id: article_id,
                    content: content,
                    pid: pid,
                    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
                },
                success: function (data) {
                    console.log(data);
                    var create_time = data.create_time;
                    var content = data.content;
                    var username = data.username;


                    var comment_li = '<li class="list-group-item"><div><span style="color: gray">' + create_time + '</span> &nbsp;&nbsp; <a href=""><span>' + username + '</span></a></div> <div class="con"> <p> ' + content + ' </p> </div> </li>';

                    $(".comment_list").append(comment_li);

                    // 清空文本框
                    $("#comment_content").val('');
                    // 清空pid
                    pid = ""
                }
            })


        });


        // 回复按钮事件

        $(".list-group-item .reply_btn").click(function () {

            $("#comment_content").focus();

            var v = "@" + $(this).attr("username") + "\n";
            $("#comment_content").val(v);


            //pid赋值
            pid = $(this).attr("comment_pk")


        })

    </script>








    <div class="info" article_id="{{ article.pk }}" username="{{ request.user.username }}"></div>

    {% csrf_token %}

    <script src="/static/js/article_detail.js"></script>

{% endblock %}
View Code

add_article.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        * {
            margin: 0;
        }

        .header {
            width: 100%;
            height: 60px;
            background-color: #369;
        }

        .content {
            width: 80%;
            margin: 20px auto;
        }
    </style>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
     <script src="/static/jquery-3.3.1.js"></script>
</head>
<body>

<div class="header"></div>
<div class="content">
    <h3>添加文章</h3>
    <form action="" method="post">
        {% csrf_token %}
        <div>
            <label for="">文章标题</label>
            <input type="text" name="title" class="form-control" style="width: 200px">
        </div>

        <div>
            <p>内容(TinyMCE编辑器,支持拖放/粘贴上传图片) </p>
            <textarea name="article_content" id="article_content" cols="60" rows="20"></textarea>
        </div>
        <input type="submit" class="btn btn-info">
    </form>
</div>

<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
<script>
        KindEditor.ready(function(K) {
                window.editor = K.create('#article_content',{
                    width:"800",
                    height:"500px",
                    resizeType:0,
                    uploadJson:"/upload/",
                    extraFileUploadParams:{
                            csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
                    },
                    filePostName:"upload_img"

                });
        });
</script>

</body>
</html>
View Code
相关文章
相关标签/搜索