BBS

__init__.py

# 这个告诉程序用的是什么数据库
import pymysql

pymysql.install_as_MySQLdb()

settings.py

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 = 'td+4##as0fkta96a+87b+@g=u8of&s*%h253s*z^$yu8++$7jj'

# 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',
'bbs01.apps.Bbs01Config',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'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',
'HOST':'127.0.0.1',
'POST': 3306,
'USER': 'root'
}
}


# 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'

TIME_ZONE = 'Asia/Shanghai'

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路径,不设置程序引入js或者bootstricp的时候会找不到路径
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static')
]

AUTH_USER_MODEL = "bbs01.UserInfo" # 告诉程序用这个auth模块的表

'''这个就是配置一个文件夹的根路径,用户上传的头像文件存放的文件夹'''
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

urls.py

from django.conf.urls import url
from django.contrib import admin
from bbs01 import views
from django.views.static import serve # 导入这个模块用来配置自定义文件夹路径的路由
from BBS import settings # setting里面已经配置好头像的存放路径

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.index),
url(r'^register/$', views.register),
url(r'^login/$', views.login_in),
url(r'^get_code/', views.get_code),
url(r'^index/$', views.index),
url(r'^user_logout/$', views.user_logout),
url(r'^backend/$', views.backend),
url(r'^add_article/$', views.add_article),
url(r'^upload_file/$', views.upload_file),
# 这里用有名分组,然后自动传入后面的serve中,后面一个值就是路径以字典的方式,这是一个固定的写法,用处就是能够让首页的中间的显示用户头像,就是开启一个media文件夹的路径
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),

# 点赞点踩路由
url(r'^diggit/', views.diggit),

# 评论路由
url(r'^commit/', views.commit),

# 设计个人站点过滤路由,利用正则表达式 | 和贪婪匹配实现个人站点下的路由
url(r'(?P<username>w+)/(?P<condition>tag|category|archive)/(?P<param>.*)', views.site_home),
# 文章详情的路由
url(r'(?P<username>w+)/articles/(?P<pk>d+)', views.article_detail),

# 个人路由设计,使用有名分组,进入个人站点
url(r'(?P<username>w+)', views.site_home),

]

admin.py

from django.contrib import admin
from bbs01 import models

#想要在后台看到那张表,就要把表填在括号里,这样就能在后台看到表
admin.site.register(models.UserInfo)
admin.site.register(models.Article)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Blog)
admin.site.register(models.ArticleTOTag)
admin.site.register(models.Commit)
admin.site.register(models.UpAndDown)

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
'''创建用户信息表'''
id = models.AutoField(primary_key=True)
# blank = True 就是admin中表单提交的时候做校验,如果设置成True,就是不校验了,其实就是在后台界面创建用户的账户的时候,只要字段有了blank=True,就可以为空创建,verbose_name就是让表的字段以中文在admin后台显示
phone = models.CharField(max_length=32, null=True, blank=True,verbose_name='手机号')
# avatar 就是用户的头像,这个地方只是存一个路径,实际的文件是存在某一个位置,这样表文件不会很大,查询效率也会高
'''upload_to就是指定文件存储的位置,default就是默认值,如果没有传文件默认就是调用设置的文件'''
avatar = models.FileField(upload_to='avatar/', default='avatar/default.png') # 这样写FileField上传的文件会自动保存到某个位置,然后把路径放到该字段中
blog = models.OneToOneField(to='Blog', to_field='nid', null=True,blank=True)

class Meta:
verbose_name = '用户表' # 这个让后台显示我们自定的表名
verbose_name_plural = verbose_name # 因为是外国人设计的,如果不写这个会在名字后面跟一个s


class Blog(models.Model):
'''个人站点表'''
nid = models.AutoField(primary_key=True)
# title就是地址后面跟的名字,地址后家名字就是跳转到这个名字的页面下
title = models.CharField(max_length=64)
site_name = models.CharField(max_length=32)
# theme就是主题样式
theme = models.CharField(max_length=64)

'''print的时候显示'''
def __str__(self):
return self.site_name


class Category(models.Model):
'''文章分类表'''
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64)
#unique =True,唯一的约束性,给哪个添加就表示哪个字段只能等于一个值,通常可以用在账号名、手机号等
blog = models.ForeignKey(to='Blog', to_field='nid', null=True)

def __str__(self):
return self.title


class Tag(models.Model):
'''标签表'''
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64)
blog = models.ForeignKey(to='Blog', to_field='nid', null=True)

def __str__(self):
return self.title


class Article(models.Model):
'''文章表'''
nid = models.AutoField(primary_key=True)
'''文章标题'''
title = models.CharField(max_length=64, verbose_name='文章标题')
# 文章简介
desc = models.CharField(max_length=255)
# 文章内容,用TextField,可以存储大文本,就是一个文本类型
content = models.TextField()
# 文章的创建时间,auto_now当这条数据修改的时候更新成当前时间,auto_now_add当新增数据的时候更新当前时间,这两个属性只有DateTimeField和DateField才有
create_time = models.DateTimeField(auto_now_add=True)

'''因为查询多,写入少,所以加这三个字段,以后就不需要连表查询了'''
#这个表中添加了三个字段,评论数、点赞数、点踩数,是数字,以后再查询的时候直接在表中取这个字段的数字即可
commit_num = models.IntegerField(default=0)
up_num = models.IntegerField(default=0)
down_num = models.IntegerField(default=0)


# 文章和个人站点是一对多,一个站点下有多篇文章,所以关联字段写在多的一方
blog = models.ForeignKey(to='Blog', to_field='nid', null=True)
# 文章和分类,一个分类下有多篇文章,多以关联字段也是写在多的一方
category = models.ForeignKey(to='Category', to_field='nid', null=True)
# 文章和标签是多对多的关系,这篇文章属于哪个标签,这个需要手动创建第三章表, through就是通过那个表建立关系,through_fields通过那两个字段建立管理
tag = models.ManyToManyField(to='Tag', through='ArticleTOTag', through_fields=('article', 'tag'))
# '''tag = models.ManyToManyField(to='tag) 这个是自动创建第三章表,如果我们需要自定义第三章表,则使用手动创建'''
def __str__(self):
return self.title

class ArticleTOTag(models.Model):
'''这是手动创建的第三张表,表明多表的关联'''
nid = models.AutoField(primary_key=True)
# 文章的ID
article = models.ForeignKey(to='Article', to_field='nid')
#标签的id
tag = models.ForeignKey(to='Tag', to_field='nid')



class Commit(models.Model):
'''用户评论表'''
nid = models.AutoField(primary_key=Tag)
# 谁
user = models.ForeignKey(to='UserInfo', to_field='id')
# 对那篇文章
article = models.ForeignKey(to='Article', to_field='nid')
# 评论的内容
content = models.CharField(max_length=255)
# 创建的时间
create_time = models.DateTimeField(auto_now_add=True)
'''这个是子评论,就是给哪个评论后面再评论,空位null,如果有评论主键评论的主键'''
# 自关联,to自己的id,to_field就是主键,让一个字段可以关联本表的主键,就是自己关联自己
parent = models.ForeignKey(to='self', to_field='nid', null=True, blank=True)


class UpAndDown(models.Model):
'''点赞点踩表'''
nid = models.AutoField(primary_key=True)
# 谁
user = models.ForeignKey(to='UserInfo', to_field='id')
# 给哪篇文章
article = models.ForeignKey(to='Article', to_field='nid')
# 点了赞或踩,这个BooleanField存的是布尔类型,在数据库表中是以0和1表示,因为数据库是不能存True和False的
is_up = models.BooleanField()

class Meta:
#这个就是联合唯一,就是指定表中user字段只能和article字段关联
unique_together = (('user', 'article'))

myforms.py

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
from bbs01 import models


class RegForms(forms.Form):
username = forms.CharField(min_length=3, max_length=8, label='用户名',
error_messages={'min_length': '用户名太短', 'max_length': '用户名太长了', 'required': '该字段必填'},
widget=widgets.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(min_length=3, max_length=8, label='密码',
error_messages={'min_length': '密码太短', 'max_length': '密码太长了', 'required': '该字段必填'},
widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
re_pwd = forms.CharField(min_length=3, max_length=8, label='确认密码',
error_messages={'min_length': '密码太短', 'max_length': '密码太长了', 'required': '该字段必填'},
widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
email = forms.EmailField(label='邮箱', error_messages={'invalid': '格式不是邮箱格式', 'required': '该字段必填'},
widget=widgets.EmailInput(attrs={'class': 'form-control'}))

# 这里使用局部钩子函数验证用户名有没有被使用
def clean_username(self):
username = self.cleaned_data.get('username')
user = models.UserInfo.objects.filter(username=username).first()
if user:
# 需要弹出错误,要用ValidationError模块弹出
raise ValidationError('该用户名已注册')
else:
return username

# 这里使用全局钩子函数验证两次输入的密码是否一致
def clean(self):
password = self.cleaned_data.get('password')
re_pwd = self.cleaned_data.get('re_pwd')
if password == re_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致')

views.py

from django.shortcuts import render, HttpResponse, redirect
from bbs01.myforms import RegForms # 导入forms组件
from bbs01 import models
from django.http import JsonResponse
from django.contrib import auth # 导入auth模块
from django.contrib.auth import login, logout # django自带的登陆和登出功能模块
import time
from django.db.models import Avg, Max, Sum, Min, Max, Count # django中使用聚合函数
from django.db.models.functions import TruncMonth # 把日期按照规则截断,需要调用 django.db.models.functions 下的模块
from bs4 import BeautifulSoup # 处理XSS攻击的模块

def register(request):
'''注册函数'''
forms = RegForms
if request.method == 'GET':
return render(request, 'register.html', {'forms': forms})
elif request.method == 'POST':
response = {'status': 100, 'msg': None}
# 调用forms组件,先实例化一个forms对象
forms = RegForms(request.POST)
# 校验通过
if forms.is_valid():
# 生成一个清洗后的数据放入一个变量
dic = forms.cleaned_data
dic.pop('re_pwd')

# 取出头像文件的对象
myfile = request.FILES.get('myfile')
# 这里要判断下,有可能用户没有传头像,如果是空的就不添加进字典
if myfile:
# 这里使用时间模块随机定义图片对象的名字
myfile.name = str('%s.png' % time.strftime('%Y-%m-%d-%H-%M-%S'))
dic['avatar'] = myfile

# **forms就是解压后传入
user = models.UserInfo.objects.create_user(**dic)
response['msg'] = '注册成功'
response['url'] = '/login/'
else:
response['status'] = 101
response['msg'] = '注册失败'
response['errors'] = forms.errors
return JsonResponse(response)


def login_in(request):
'''用户登陆函数'''
if request.method == 'GET':
return render(request, 'login.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
valid_code = request.POST.get('valid_code')

# 从session中取出验证码,和提交的验证码做比较,用upper全部转成大写,这样就忽略大小写
if valid_code.upper() == request.session['valid_code'].upper():
# 使用auth模块验证用户信息
user = auth.authenticate(request, username=username, password=password)
if user:
# auth模块登陆验证通过后,需要调用一次login的方法是,这个方法调用后,在所有的视图函数里都能取到request.user对象

login(request, user) # 在前端就可以直接获取用户名放到页面的某个位置
return redirect('/index/')
else:
error = '用户名或密码错误'
return render(request, 'login.html', {'error': error})

else:
error = '验证码错误'
return render(request, 'login.html', {'error': error})


def index(request):
'''首页'''
# 查询所有的文章
article_list = models.Article.objects.all()
print(article_list[1].blog.userinfo.username)
return render(request, 'index.html', {'article_list': article_list})


def user_logout(request):
'''用户退出'''
# 这里使用auth模块的退出方法
logout(request)
# 退出后重定向到首页
return redirect('/index/')


'''验证码模块'''
import random


def get_color():
'''生成随机数,给生成验证码图片的函数使用,效果就是生成一个随机的rgb颜色'''
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))


from io import BytesIO # 这个是内存管理器,用处就是把bytes格式的数据存到内存中
from PIL import Image, ImageFont, ImageDraw # 这个就是导入pillow模块下的功能,image就是图片生成功能,ImageDraw就是生成图片上的线,ImageFont就是生成字体文件的


def get_code(request):
'''生成验证码图片'''
# 使用python中专门用于图像处理的模块pillow模块,生成一个空图片,这个图片用于验证码的底色
img = Image.new('RGB', (300, 35),
get_color()) # Image.new里有三个参数,分别是 mode颜色模式、size图片大小(必须先传width,再传height)、color就是图片的颜色可以传一个元组,就是rgb颜色的数值

# 在图片上生成文字,需要使用ImageDraw模块,生成一个对象,对象里面有text就是生成文字,line就是生成线,arc就是弧形,point就是画点
draw = ImageDraw.Draw(img) # 两个参数,im就是img对象,第二参数不传也可以
# 生成字体文件,font=传一个ttf格式的文件,size就是字体大小
font = ImageFont.truetype(font='static/font/hbyy.ttf', size=25)

# 存储验证码
code_str = ''

# 随机生成5个字母
for i in range(5):
num = str(random.randint(0, 9)) # 随机生成0-9的数字
upper_t = chr(random.randint(65, 90)) # 随机生成65-90的随机数,用ascii码随机生成大写字母,就是用chr转换
lower_t = chr(random.randint(97, 122)) # 随机生成小写字母,原理用ascii码转换,就是用chr转换
t = random.choice([num, upper_t, lower_t]) # 这个就是随机从这个列表里取一个值
code_str += t
# 这里x轴要做一个偏移,否则会叠在一起,fill调用了随机生成颜色数值的函数就生成了随机的字母颜色
draw.text((30 + i * 50, 10), t, fill=get_color(), font=font) # text里面有两个参数,一个是xy轴的值,还有一个就是文字

# 把验证码放到session中,这一句代码做了三件事,先生成一个随机字符串,在cookie中写入sessionid:随机码,在数据库中保存
request.session['valid_code'] = code_str

width = 320
height = 35
'''这个是随机生成线'''
for i in range(10):
x1 = random.randint(0, width)
x2 = random.randint(0, width)
x3 = random.randint(0, width)
x4 = random.randint(0, width)
draw.line((x1, x2, x3, x4), fill=get_color())

'''这个是随机生成弧形'''
for i in range(100):
draw.point([random.randint(0, width), random.randint(0, height)], fill=get_color())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_color())

# 将生成的图片放到内存中,因为这个验证码图片用一次就不会再用,所以可以用内存管理器在使用后直接清理
f = BytesIO() # 这个f是内存上的空间
img.save(f, 'png') # 将生成的图片以bytes格式写到内存空间中
data = f.getvalue() # 将内存空间中的图片放入变量返回到前端
return HttpResponse(data)


def site_home(request, username, *args, **kwargs): # *args,**kwargs为了接收路由里的个人站点过滤的值
# 这个用来接收路由里分组出来的字母名
condition = kwargs.get('condition')
# 这个用来接收路由里传过来的分组出来的日期
param = kwargs.get('param')

# 这里获取用户名,进入数据库查询
user = models.UserInfo.objects.filter(username=username).first()
if not user:
return render(request, 'error.html')

'''查询出当前站点下所有的文章'''
# 获取当前站点,上面从userinfo表里面查出用户的名字后,因为用户有和blog表关联的字段,所以可以 从用户这里直接获取个人站点的所有内容
blog = user.blog
article_list = blog.article_set.all()

# 通过传入的路径,判断要过滤的标签,分类,还是时间
if condition == 'tag':
tag = models.Tag.objects.filter(pk=param)
# 查询标签id为传入的文章,比如传入的tag=1
article_list = article_list.filter(tag=tag)
elif condition == 'category':
# 过滤分类
article_list = article_list.filter(category_id=param)
elif condition == 'archive':
# 过滤时间
# 传入的值是2019-03,进行切分
year, month = param.split('-')
article_list = article_list.filter(create_time__year=year, create_time__month=month)

'''多表查询'''
# 统计每个分类下的文章数,查询当前站点下每个分类的文章数 难度系数最高涉及到分组查询
'''
group by 谁就可以做基表
values 在前表示group by
filter 在前表示where

annotate
filter在后,表示having
values在后,表示取值
'''
# 以Category为基表查询出所有的分类,然后取主键PK,过滤出当前站点下的所有分类,annotate就是分组查询的函数,然后把获取到的分类主键在article表里做统计放入变量c中,最后取分类的title和c得出结果
ret_category = models.Category.objects.all().filter(blog=blog).values('pk').annotate(
c=Count('article')).values_list('title', 'c', 'pk') # values_list 就是把值放元组,values默认是字典
# 当前站点下每个标签的文章数,最后获得三个值,一个是title,一个是获得数计数值,一个就是主键
ret_tag = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('title', 'c', 'pk')

# 查询当前站点下每月的文章数,使用日期截断的方法,order_by('y_m')是给日期排序,order_by('-y_m')括号内加一个 -号就是倒序
ret_month = models.Article.objects.filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
c=Count('pk')).values_list('y_m', 'c').order_by('y_m')
return render(request, 'site_home.html', locals())


def article_detail(request, username, pk):
'''这是里文章详情相关代码'''
user = models.UserInfo.objects.filter(username=username).first()
if not user:
return render(request, 'error.html')
blog = user.blog
# 左侧标签
ret_category = models.Category.objects.all().filter(blog=blog).values('pk').annotate(
c=Count('article')).values_list('title', 'c', 'pk')
ret_tag = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('title', 'c', 'pk')
ret_month = models.Article.objects.filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
c=Count('pk')).values_list('y_m', 'c').order_by('y_m')

article = models.Article.objects.filter(pk=pk).first()
if not article:
return render(request, 'error.html')

# 取出该文章的所有评论
commit_list = article.commit_set.all()

return render(request, 'article_detail.html', locals())


def diggit(request):
'''点赞点踩'''
import json
respones = {'status': 100, 'msg': None}
user = request.user
article_id = request.POST.get('article_id')
is_up = request.POST.get('is_up')
is_up = json.loads(is_up) # 这里因为前端传过来的是字符串,所以要用json模块转一下,变成布尔类型
if user:
# 这里先查询出文章
article = models.Article.objects.filter(pk=article_id).first()
# 先查询当前用户对该文章点过赞或者踩,因为点赞点踩表里面每个数据后都有一个user_id
ret = models.UpAndDown.objects.filter(user=user, article_id=article).first()
if ret:
respones['status'] = 102
respones['msg'] = '已经点过了'
else:
# 写入数据
models.UpAndDown.objects.create(user=user, article_id=article_id, is_up=is_up)
if is_up:
# 这里对踩或点增加
article.up_num += 1
article.save()
respones['msg'] = '点赞成功'
else:
article.down_num += 1
article.save()
respones['msg'] = '点踩成功'
else:
respones['status'] = 101
respones['mas'] = '未登陆状态'
return JsonResponse(respones)


def commit(request):
'''评论相关的内容'''
respones = {'status': 100, 'msg': None}
user = request.user
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent_id = request.POST.get('parent_id')
if user:
article = models.Article.objects.filter(pk=article_id).first()
ret = models.Commit.objects.create(user=user, article=article, content=content, parent_id=parent_id)
article.commit_num += 1
article.save()
if parent_id:
# parent_id有值代表子评论,就将父评论人的名字和评论内容返回给前端
respones['parent_name'] = ret.parent.user.username
respones['parent_content'] = ret.parent.content
# 拿到用户名和评论的内容
respones['username'] = ret.user.username
respones['content'] = ret.content
respones['mas'] = '评论成功'
else:
respones['status'] = 101
respones['mas'] = '未登陆状态'

return JsonResponse(respones)


from django.contrib.auth.decorators import login_required


@login_required(login_url='/login/')
def backend(request):
'''后台模块'''
blog = request.user.blog # 这里有登陆认证装饰器,因为通过了认证才能走这段函数,所以这里的user肯定有值,可以直接获取user里面blog的id
# 查询出当前用户的所有文章
article_list = models.Article.objects.filter(blog=blog)

return render(request, 'backend/backend.html', locals())





@login_required(login_url='/login/')
def add_article(request):
'''添加文章模块'''
if request.method == 'GET':
return render(request, 'backend/add_article.html')
else:
title = request.POST.get('title')
content = request.POST.get('content')
blog = request.user.blog

# 截取摘要的时候只截取文字,去除代码,处理XSS攻击

soup = BeautifulSoup(content, 'html.parser') # 实例化传两个参数,1是要解析的内容,2是解析器

# 处理XSS攻击
tags = soup.find_all() # BeautifulSoup对象的find_all方法能把html内容中所有的标签取出
# 找出script标签并删除
for tag in tags:
if tag.name == 'script':
tag.decompose() # 删除标签,这里删除不是delete,是decompose

# 使用这个模块在后台处理Beautifulsoup4 这个模块爬虫谁用的比较多,用于网页解析

desc = soup.text[0:150] # 这里使用BeautifulSoup对象的方法text,就是把内容变成纯文本,这样在显示的时候就不会以代码+文本形式显示

artivle = models.Article.objects.create(title=title, content=str(soup), desc=desc, blog=blog)

return redirect('/backend/')


def upload_file(request):
'''从富文本编辑器文件上传的模块'''
file = request.FILES.get('imgFile') # 这里获取前端传过来的文件
with open('media/upload/%s'%file.name,'wb')as f: # 保存到指定路径下
for line in file:
f.write(line)
dic = {'error':0,'url':'/media/upload/%s'%file.name} # 这里是富文本编辑器返回的格式
return JsonResponse(dic)

 

media - avatarupload

static - 引入文件

templates

backend文件夹下 # 后台页面文件

add_article.html

{% extends 'backend/backend_base.html' %}

{% block content %}
<div>
<form action="" method="post">
{% csrf_token %}
<h5 class="text-center">添加文章</h5>
<p>标题</p>
<p><input type="text" name="title" class="form-control"></p>
<p>内容(kindeditor编辑器,支持拖放/黏贴/上传图片)</p>
<p><textarea name="content" id="editor_id" cols="30" rows="20"></textarea></p>
<p>
<button class="btn btn-danger">提交</button>
</p>
</form>


</div>

<script src="/static/kindeditor/kindeditor-all.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', { //这里的id和上面的输入框id对上即可
'100%', //这里参数就是控制输入框的大小,是按照比例的
height: '500px', //这里设置输入框高度
resizeType: 0, //这里设置不能拖动
uploadJson: '/upload_file/', //这里设置一个文件上传的路径
//额外上传的参数
extraFileUploadParams: {
csrfmiddlewaretoken: '{{ csrf_token }}'
}
});
});
</script>



{% endblock %}

backend.html

{% extends 'backend/backend_base.html' %}

{% block content %}

<table class="table table-striped">
<thead>
<tr>
<th>标题</th>
<th>评论数</th>
<th>点赞数</th>
<th>操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for article in article_list %}
<tr>
<td>{{ article.title }}</td>
<td>{{ article.commit_num }}</td>
<td>{{ article.up_num }}</td>
<td><a href="">编辑</a></td>
<td><a href="">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}

backend_base.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
<title>后台管理</title>
<style>
.head {
height: 60px;
background-color: black;
}
</style>
</head>
<body>
<div class="head">
<div class="text-center">后台管理</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
aria-expanded="true" aria-controls="collapseOne">
<a href="/backend/">文章</a>
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<a href="/add_article/">添加文章</a>
</div>
<div class="panel-body">
<a href="">添加随笔</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div>

<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
data-toggle="tab">文章</a></li>
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab"
data-toggle="tab">随笔</a>
</li>
<li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
data-toggle="tab">日记</a>
</li>
<li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
data-toggle="tab">评论</a>
</li>
</ul>

<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="home">

{% block content %}



{% endblock %}


</div>
<div role="tabpanel" class="tab-pane" id="profile">随笔相关的</div>
<div role="tabpanel" class="tab-pane" id="messages">日记相关的</div>
<div role="tabpanel" class="tab-pane" id="settings">评论相关的</div>
</div>

</div>
</div>
</div>
</body>
</html>

article_detail

{% extends 'base.html' %}

{% block content %}
<style>
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
125px;
text-align: center;
margin-top: 10px;
}

.diggit {
float: left;
46px;
height: 52px;
background: url(/static/img/upup.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}

.buryit {
float: right;
margin-left: 20px;
46px;
height: 52px;
background: url(/static/img/downdown.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}

.clear {
clear: both;
}

.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: gray;
}

input.author {
background-image: url(/static/img/icon_form.gif);
background-repeat: no-repeat;
border: 1px solid #ccc;
padding: 4px 4px 4px 30px;
300px;
font-size: 13px;
}

</style>
<div>
{% csrf_token %}
{# 这里获取标题 #}
<div class="text-center"><h4><a href="/{{ article.blog.userinfo.username }}/articles/{{ article.pk }}">{{ article.title }}</a></h4></div>
{{ article.content|safe }}
{# 这里获取文章内容,由于网页内文章的内容不是单纯的文字,是html代码组成的,所以要safe去转换 #}

{# 这里会有塌陷,所以要套在一个div下清除浮动 #}
<div class="clearfix">
<div>
{# 点赞点踩相关 #}
<div id="div_digg">
<div class="diggit digg">
<span class="diggnum" id="digg_count">{{ article.up_num }}</span>
</div>
<div class="buryit digg">
<span class="burynum" id="bury_count">{{ article.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
</div>
<div>

</div>
<div>
{# 评论相关 #}
<ul class="list-group append">
{# 拿出所有的评论显示在页面 #}
{% for commit in commit_list %}
<li class="list-group-item">
<div>
{# 使用forloop来创建楼层数 #}
<span>#{{ forloop.counter }}楼</span>
<span>{{ commit.create_time|date:'Y-m-d' }}</span>
{# 这里通过a标签拼接的地址实现评论显示的用户的用户名跳转至这个用户的站点 #}
<span><a href="/{{ commit.user.username }}">{{ commit.user.username }}</a></span>
{# 这里要让a标签点击不发生任何动作,href="javascript:void(0)"这样定义js不会触发任何动作 parent_id就是获取评论表的主键,然后传到下面的实现 #}
<span class="pull-right replay" username="{{ commit.user.username }}" parent_id="{{ commit.pk }}"><a href="javascript:void(0)">回复</a></span>
</div>
<div style="margin-top: 10px">
{# 这里实现子评论 #}
{% if commit.parent %}
<div class="well">
<p>@{{ commit.parent.user.username }}</p> {# 这里从表中反向查询获得用户名 #}
<p>{{ commit.parent.content }}</p>
</div>
{% endif %}
{# 评论内容 #}
{{ commit.content }}
</div>
</li>
{% endfor %}
</ul>

</div>
<div>
{# 评论相关 #}
<p>
昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="clyde_S">
</p>
<p>评论内容:</p>
{# textarea是一个可以输入多行的文本框 #}
<p><textarea name="" id="commit_content" cols="100" rows="10"></textarea></p>
<button class="btn btn-danger" id="sub_btn">提交评论</button>
</div>

<script>
var pid; //这个是定义一个全局变量
//子评论相关
$(".replay").click(function () {
var name=$(this).attr('username') //this就是代表点击的这个按钮获取名字然后放入变量
$("#commit_content").val('@'+name+' ') //将内容写入指定标签
$("#commit_content").focus() //光标定位到这里
pid=$(this).attr('parent_id') //这里就触发修改了全局变量,是来实现给谁回复


})

//评论相关的
$("#sub_btn").click(function () {
var content=$("#commit_content").val() //val就是获取控件的内容然后放入变量
if (pid){
//如果子评论,就要切除头部,否则会以@root AAAA 这种形式出现,切除后就是AAAA这种形式出现
var index = content.indexOf(' ')+1 //获取到content中第一个换行符的位置,+1就是到下一行
content=content.slice(index) //这个就是从第一个换行符开始往后的全部截取
}

$.ajax({
url: '/commit/',
type: 'post',
//data里要放入文章id,评论内容
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'article_id': '{{ article.pk }}',
'parent_id':pid, //这里将全局变量携带至后台存入数据库,实现子评论
'content': content, //这里的content就是截取后没有头部的评论内容
},
success: function (data) {
console.log(data)
//提交评论后清空输入框的内容
$("#commit_content").val("")
if (data.state == 100) {
//实现根评论,需要使用JS中es6的语法,字符串的拼接,以下的写法是固定的
var username = data.username //这里从后台获取用户名
var content = data.content //这里从后台获取评论字符

//insert后面用反引号,中间可以插入一段字符串自己拼接格式,这段拼接的格式内容就是你要在评论后面追加的评论内容
var insert

//如果pid有值就表示父评论,规定父评论的格式
if (pid) {
var parent_name = data.parent_name
var parent_content = data.parent_content
insert=`
<li class="list-group-item">
<div>
<span><a href="">${ username }</a></span>
</div>
<div style="margin-top: 10px">
<div class="well">
<p>@${parent_name}</p>
<p>${parent_content}</p>
</div>
${content}
</div>
</li>
`
}else{
insert= `
<li class="list-group-item">
<div>
<span>${username}</span>
</div>
<div style="margin-top: 10px">
${content}
</div>
</li>
`}
//这个就是在指定的类后面追加内容,固定写法
$(".append").append(insert)
}
}
})
})

//点赞相关的
$(".digg").click(function () {
//$(this)就是取到当前的控件
var is_up = $(this).hasClass('diggit') //当点击的时候就会检测有没有diggit这个类名

//当前空间下都有一个span标签,所以点击那个控件,就获取到点击控件下的span标签
var obj = $(this).children('span')

$.ajax({
url: '/diggit/',
type: 'post',
//传的数据要有用户id,文章id,点赞或点踩is_ip,这里由于已经是用户登陆,所以当前登陆用户在后台用request.user可以获取,所以用户id可以不传,只要传一个文章id
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'article_id': '{{ article.pk }}',
'is_up': is_up
},
success: function (data) {
console.log(data)
if (data.status == 100) {
//取出span标签的内容,因为是字符串所以要转成数字格式,放入变量
var num = Number(obj.text())
//然后再把数字+1放入span,这样就实现页面上点赞或点踩后动态+1的效果
obj.text(num + 1)
{# 上面两句可以缩写成obj.text(Number(obj.text())+1),就是先取出转成数字+1然后再放入 #}
} else {
$("#digg_tips").text(data.msg)
}
}
})
})

</script>
{% endblock %}

base.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
{# 通过这样实现个人站点的样式 #}
<link rel="stylesheet" href="/static/css/{{ blog.theme }}">
<script src="/static/jquery-3.3.1.js"></script>
<title>个人站点</title>
</head>
<body>
<div class="head">
{# 这里显示用户名的顶部的区域 #}
<div><h3>{{ blog.title }}</h3></div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">我的标签</div>
<div class="panel-body">
{% for tag in ret_tag %}
{# 后台是以values_list的形式传过来的,所以取值用索引 #}
<p><a href="/{{ user.username }}/tag/{{ tag.2 }}">{{ tag.0 }}</a>({{ tag.1 }})</p>
{% endfor %}

</div>
</div>

<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">随笔分类</h3>
</div>
<div class="panel-body">
{% for category in ret_category %}
<p>
<a href="/{{ user.username }}/category/{{ category.2 }}">{{ category.0 }}</a>({{ category.1 }})
</p>
{% endfor %}

</div>
</div>

<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">随笔档案</h3>
</div>
<div class="panel-body">
{% for month in ret_month %}
{# 过滤器规定时间的格式 #}
<p>
<a href="/{{ user.username }}/archive/{{ month.0|date:'Y-m' }}">{{ month.0|date:'Y年-m月' }}</a>({{ month.1 }})
</p>
{% endfor %}

</div>
</div>
</div>
<div class="col-md-9">
{% block content %}

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

error.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404页面</title>
<style>
.d1 {
margin-top: 200px;
margin-left: 700px;
}
</style>
</head>
<body>
<div class="d1">
<a href="http://www.cnblogs.com/"><img src="//static.cnblogs.com/images/logo_small.gif" alt="cnblogs"/></a>
<p><b>404.</b> 抱歉! 您访问的资源不存在!</p>
<p class="d">请确认您输入的网址是否正确,如果问题持续存在,请发邮件至contact&#64;cnblogs.com与我们联系。</p>
<p><a href="http://www.cnblogs.com/">返回网站首页</a></p>
</div>

</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
<title>博客园</title>
</head>
<body>
<div class="top">
<nav class="navbar navbar-default">
<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="#">博客园</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="#">文章<span class="sr-only">(current)</span></a></li>
<li><a href="#">随笔</a></li>
</ul>
{#这里判断用户有没有登陆,通过is_authenticated有没有登陆,这个方法是auth模块内的,这样通过在前端调用这个方法 #}
{% if request.user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
{# 这里直接在这个标签位置显示已登陆的用户名 #}
<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="#">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="/user_logout/">退出登陆</a></li>
</ul>
</li>
</ul>
{% else %}
{# 如果没有登录则显示登陆和注册 #}
<ul class="nav navbar-nav navbar-right">
<li><a href="/login/">登陆</a></li>
<li><a href="/register/">注册</a></li>
</ul>
{% endif %}
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>

</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
{#panel-danger 颜色粉色#}
<div class="panel panel-danger">
<div class="panel-heading">广告位</div>
<div class="panel-body">
详情:111111111
</div>
</div>

<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">美女图片</h3>
</div>
<div class="panel-body">
<a href="http://www.baidu.com">点击进入</a>
</div>
</div>
</div>
<div class="col-md-7">
{# 后台已经获取所有文章,这里使用for循环将文章显示在页面中 #}
{% for article in article_list %}
<div>
{# 这里循环先获得文章标题 #}
{# {{ article.blog.userinfo.username }}/articles/{{ article.pk }}这个就是路径的拼接,/lqz/articles/1#}
<a href="/{{ article.blog.userinfo.username }}/articles/{{ article.pk }}"><h4>{{ article.title }}</h4></a>
</div>
<div>
<div class="media">
<div class="media-left">
<a href="#">
{# 这里用 {{ article.blog.userinfo.avatar }}从文章表到blog表然后进行反向索引到userinfo表中取到关联的字段后获取图片地址,必须在urls里配置好图片显示的地址#}
<img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" height="70" width="70">
</a>
</div>
<div class="media-body">
{# 循环体内在获取文章简介 #}
{{ article.desc }}
</div>
<div style="margin-top: 20px">
{# 循环体内再通过反向查询到文章的作者,article表和bolg表有关系,blog表又和userinfo表有关系关联字段在blog,所以反向查询就是表名的小写 #}
<span><a href="/{{ article.blog.userinfo.username }}/">{{ article.blog.userinfo.username }}</a></span>
{# 时间需要一个过滤器定义格式 #}
<span>发布于: {{ article.create_time|date:'Y年m月d日' }}</span>
{# 基于对象的跨表查询,就是一对一、一对多、多对多的查询,表名小写_set.all就是取出所有数据,count就是计数 #}
<span class="glyphicon glyphicon-pencil" style="margin-left: 10px">评论({{ article.commit_num }})</span>
<span class="glyphicon glyphicon-thumbs-up" style="margin-left: 10px">点赞({{ article.up_num }})</span>
</div>
{# hr是页面中画一条横线 #}
<hr>
</div>
</div>
{% endfor %}


</div>
<div class="col-md-2">
{#panel-danger 颜色粉色#}
<div class="panel panel-danger">
<div class="panel-heading">广告位</div>
<div class="panel-body">
详情:111111111
</div>
</div>

<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">美女图片</h3>
</div>
<div class="panel-body">
<a href="http://www.baidu.com">点击进入</a>
</div>
</div>
</div>

</div>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<script src="/static/jquery-3.3.1.js"></script>
<title>登陆页面</title>
<style>
.span_error {
color: red;
margin: 20px;
}
</style>
</head>
<body>
<div class="container-fluid"> {# container-fluid就是栅格系统 #}
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h2>用户登陆</h2>

<form method="post" action="">
{% csrf_token %}
<div class="form-group">
<label for="">用户名</label>
<input type="text" name="username" class="form-control">
</div>
<div class="form-group">
<label for="">密码</label>
<input type="password" name="password" class="form-control">
</div>
<div class="form-group">
<label for="">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" name="valid_code" class="form-control">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" height="35" width="300" id="val_code">
</div>
</div>
</div>
<input type="submit" id="submit-btn" value="提交" class="btn btn-danger "><span
class="span_error">{{ error }}</span>
</form>
</div>
</div>
</div>
</body>
<script>
$('#val_code').click(function () {
//这里触发将src重新赋值
$('#val_code').attr('src',"/get_code/")
//js要获取当前的时间必须定义一个函数
var myDate = new Date()
//$('#val_code')[0].src这个就是获取控件的原生地址,使用js获取当前时间然后拼接在src地址的后面
$('#val_code')[0].src += myDate.getTime()
})
</script>
</html>

register.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<script src="/static/jquery-3.3.1.js"></script>
<title>注册页面</title>
<style>
#head {
display: none;
}
#img{
margin-left: 10px;
}
.errors {
color: red;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-offset-3 col-md-6">
<h2>用户注册</h2>

{#使用forms组件快速生成注册页面#}
<form id="form" action=""> {# 这里form设定一个id名,给下面的ajax取值用,包括用户名等 #}
{% csrf_token %}
{% for form in forms %}
<div class="form-group"> {#form=group就是bootstrap的样式,输入框和提示字会有一点间隔#}
<label for="{{ form.auto_id }}">{{ form.label }}</label> {# 这里给一个form.auto_id,点击提示的字就会自动定位到输入框 #}
{#这里程序会自动创建输入框,然后id名就是对应的后台函数的名字,比如用户名是username,这里自动创建的id格式就是id_username#}
{{ form }} <span class="errors pull-right"></span>
</div>
{% endfor %}

{#选择头像的功能#}
<div class="form-group">
<label for="head">头像 {# 这里写head就会触发被影藏的输入框,效果就是点击图片或者字都可以触发被隐藏的输入框 #}
<img id="img" src="/static/img/timg.jpg" alt="" height="80" width="80">
</label>

{#accept="image/* 就是过滤,只显示图片文件#}
<input accept="image/*" type="file" name="head" id="head"> {# 这里用id在上面写样式用display隐藏输入框 #}
</div>
<input type="button" id="submit-btn" value="提交" class="btn btn-danger ">
</form>
</div>
</div>
</div>
</body>


<script>
//当id为head的标签产生变化发生动作
$("#head").change(function () {
//取出文件对象
var file = $("#head")[0].files[0]

//借助文件阅读器,把文件显示在img标签上
var filereader = new FileReader() //生成一个文件阅读器

//把文件读到文件阅读器中,在img标签上显示文件阅读器中存的图片
filereader.readAsDataURL(file) //这里要传入取出的对象

//等待文件阅读器把文件所有内容读入再进行操作
filereader.onload = function(){
//转成二进制的内容放到img标签上
$("#img").attr({'src':filereader.result}) //将二进制的内容放到src标签上,其实就是修改src的值,
}
})

//ajax上传文件需要借助formdata对象
$('#submit-btn').click(function () {
var formdata = new FormData()
//这里获取文件对象以key:value的形式放入formdata对象里
formdata.append('myfile', $('#head')[0].files[0])

//获取页面输入框的内容
//给form标签取id名,serializeArray方法会将标签内的数据组装起来以列表嵌套字典的形式存放的,通过for循环获取数据,
var form_data = $('#form').serializeArray()

//jquery有for循环,each方法就是jqery的for循环,第一个参数就是循环对象,第二个参数就是一个匿名函数
$.each(form_data, function (key, value) {
//key就是索引位,value内还是以key:value的方式存放值,name就是输入框提示的字,value就是输入的值
console.log(key, '-', value)
formdata.append(value.name, value.value)
})
$.ajax({
url: '/register/',
type: 'post',
processData: false,
contentType: false,
data: formdata,
success: function (data) {
if (data.status==100){
//当注册成功的时候页面跳转到某个地址,地址就是从data里获取,location.href的意思就是修改当前地址,一修改浏览器就会自动朝该地址跳转
location.href=data.url
}else{
//清除上次的错误信息,获取所有为errors的的控件然后删除所有父类(parent)的jq样式
$('.errors').html('').parent().removeClass('has-error')

//data里面是一个字典,有一个后台穿过来的errors的键值,这里的匿名函数括号里的只是形参,key就是空间的名字,value里面是以列表形式存储的错误信息
$.each(data.errors,function (key,value) {
console.log(key) //可以看到这里是输入框的名字
console.log(value) //这里可以看到是列表形式存储的错误信息

//这里获取全局钩子反馈的错误信息到指定的输入框下
if (key=='__all__'){
//这里使用固定的ID名来将错误的信息在指定的输入框下局部刷新出来,然后使用链式操作加入一个jq的样式类名,让输入框错误时候变红提示
$('#id_password').next().html(value[0]).parent().addClass('has-error')
$('#id_re_pwd').next().html(value[0]).parent().addClass('has-error')

}else{
//这里的写法就通过循环获取key:value的值,然后通过拼接id名来获取上面对应的输入框id名,通过获取列表索引位的值来局部刷新错误信息,通过链式操作加入一个jq标红的样式做提示
$('#id_' + key).next().html(value[0]).parent().addClass('has-error')
}

})
//设置一个定时器,清除所有错误信息,第一个是匿名函数里面传清除错误信息的代码,第二个参数是时间以毫秒计时
setTimeout(function () {
$('.errors').html('').parent().removeClass('has-error')
},3000)
}
}
})
})
</script>
</html>

site_home.html

{# 这里是模板的引用 #}
{% extends 'base.html' %}

{% block content %}
{# 显示当前站点下所有的文章 #}
{% for article in article_list %}
<div>
<a href="/{{ username }}/articles/{{ article.pk }}"><h4>{{ article.title }}</h4></a>
{{ article.desc }}
</div>
{# 这里要再套一个div中,然后用bootstrap的样式清除徐浮动,就不会塌陷clearfix #}
<div class="clearfix">
<div style="margin-top: 15px" class="pull-right">
<span>posted @</span>
<span>发布于: {{ article.create_time|date:'Y年m月d日' }}</span>
<span class="glyphicon glyphicon-pencil">评论({{ article.commit_num }})</span>
<span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_num }})</span>
</div>
</div>
<hr>
{% endfor %}
{% endblock %}
原文地址:https://www.cnblogs.com/shizhengquan/p/10755777.html