settings.py
""" 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示例,表示要不要向上传递日志流 } }, }
models.py
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
froms.py
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
urls.py
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) ]
app中的urls.py
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) ]
views.py
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))
my_tags.py
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 }
login.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"> <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>
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>
register.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"> <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>
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>
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 %}
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>
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>
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> <span style="color: gray">{{ comment.create_time|date:"Y-m-d H:i" }}</span> <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 }}: {{ 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(" "); 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> <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") + " "; $("#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 %}
add_article.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> * { margin: 0; } .header { 100%; height: 60px; background-color: #369; } .content { 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=" 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',{ "800", height:"500px", resizeType:0, uploadJson:"/upload/", extraFileUploadParams:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val() }, filePostName:"upload_img" }); }); </script> </body> </html>