博客园步骤
一、新建项目以及app名字
在pycharm左上角file中点击 new project... 点击左侧django 在右面板写项目名、app名以及选择Python解释器,点击apply 再确定
之后新建静态文件夹static,在static文件夹下新建CSS和js文件,再去setting中配置如下:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
# 扩写auth_user表
AUTH_USER_MODEL='blog.UserInfo'
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
# BLOG=os.path.join(BASE_DIR,'blog')
#修改链接数据库,USER可能会写成os.getenv('user')(环境变量去进行添加操作)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'bbs',
'HOST':'127.0.0.1',
'POST':3306,
'USER':'root',
'PASSWORD':'123321',
}
}
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
media的配置
-在setting中配置mdeia路径
-re_path('^media/(?P<path>.*)$', serve,{'document_root': settings.MEDIA_ROOT}),
<img src='127.0.0.1/media/avatar/4.jpg'>
-路由设计
-re_path('^(?P<name>\w+)$',views.site)
import pymysql
pymysql.install_as_MySQLdb()
wsgi.py:
import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BBS_s15.settings") application = get_wsgi_application()
models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.CharField(max_length=32,null=True,default='123')
# upload_to文件上传以后存放的路径
# FileField本质是varchar类型
# 坑()
avatar = models.FileField(upload_to='avatar/', default='avatar/default.png')
# avatar = models.CharField()
blog = models.OneToOneField(to='Blog',on_delete=models.CASCADE,null=True)
class Blog(models.Model):
site_title = models.CharField(max_length=32)
site_name = models.CharField(max_length=32)
# 每个人样式不同(文件地址)
site_style = models.CharField(max_length=32)
def __str__(self):
return self.site_title
class Meta:
verbose_name_plural='博客表'
class Tag(models.Model):
name = models.CharField(max_length=32)
blog = models.ForeignKey(to='Blog',on_delete=models.CASCADE)
def __str__(self):
return self.name
class Meta:
verbose_name_plural='标签表'
class Category(models.Model):
name = models.CharField(max_length=32)
blog = models.ForeignKey(to='Blog',on_delete=models.CASCADE)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=32,verbose_name='文章标题')
desc = models.CharField(max_length=128,verbose_name='文章摘要',help_text='小伙子,这里填文章摘要')
# 大文本
content = models.TextField()
create_time = models.DateTimeField(auto_now_add=True)
# 优化字段,减少数据库查询操作,评论数,点赞数
up_num=models.IntegerField(default=0)
down_num=models.IntegerField(default=0)
commit_num=models.IntegerField(default=0)
# 关联关系
blog = models.ForeignKey(to='Blog',on_delete=models.CASCADE)
category = models.ForeignKey(to='Category',on_delete=models.CASCADE)
# 多对多关系
tag = models.ManyToManyField(to='Tag', through='TagToArticle', through_fields=('article', 'tag'))
def __str__(self):
try:
return self.title+'-------'+self.blog.site_title
except:
return self.title
class TagToArticle(models.Model):
tag = models.ForeignKey(to='Tag',on_delete=models.CASCADE)
article = models.ForeignKey(to='Article',on_delete=models.CASCADE)
class UpAndDown(models.Model):
user = models.ForeignKey(to='UserInfo',on_delete=models.CASCADE)
article = models.ForeignKey(to='Article',on_delete=models.CASCADE)
# 实质存的时候,是0和1
is_up = models.BooleanField()
create_time = models.DateTimeField(auto_now_add=True)
class Commit(models.Model):
user = models.ForeignKey(to='UserInfo',on_delete=models.CASCADE)
article = models.ForeignKey(to='Article',on_delete=models.CASCADE)
content = models.CharField(max_length=256)
create_time = models.DateTimeField(auto_now_add=True)
# 存父评论的id号
# commit_id=models.IntegerField()
# commit=models.ForeignKey(to='Commit')
# 自关联(不能叫表明小写)
commit_id=models.ForeignKey(to='self',on_delete=models.CASCADE)
再去setting中配置:
#扩写auth_UserInfo表,是app名字+user表名 AUTH_USER_MODEL='blog.UserInfo'
去数据库新建数据库:
数据库名:bbs
字符集:utf8--UTF-8 Unicode
建立好新表之后再进行迁移
在blog文件夹中新建blog_form.py:
#校验
from django import forms
from django.forms import widgets
from blog import models
from django.forms import ValidationError
class RegisterForm(forms.Form):
username = forms.CharField(required=True, max_length=18, min_length=3,label='用户名',
error_messages={'required': '该字段必填',
'max_length': '做大长度为18',
'min_length': '最短为3'},
widgets=widgets.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(required=True, max_length=18, min_length=3,label='密码',
error_messages={'required': '该字段必填',
'max_length': '做大长度为18',
'min_length': '最短为3'},
widgets=widgets.PasswordInput(attrs={'class': 'form-control'}))
re_password = forms.CharField(required=True, max_length=18, min_length=3,label='确认密码',
error_messages={'required': '该字段必填',
'max_length': '做大长度为18',
'min_length': '最短为3'},
widgets=widgets.PasswordInput(attrs={'class': 'form-control'}))
email = forms.EmailField(required=True,label='邮箱',
error_messages={'required': '该字段必填'},
widgets=widgets.EmailInput(attrs={'class': 'form-control'}))
#用户名如果存在了 就不能注册
def clean_username(self):
username=self.cleaned_data.get('username')
#去数据库查询
user=models.UserInfo.objects.filter(username=username).count()
if user: #不合法
raise ValidationError('用户名已经存在')
else:
return username
#校验两次密码是否一致
def clean(self):
password=self.cleaned_data.get('password')
re_password=self.cleaned_data.get('re_password')
if re_password==password:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致')
urls.py:
from django.contrib import admin
from django.urls import path, re_path
from blog import views
from django.views.static import serve
## 建议使用这个setting
from django.conf import settings # 整个项目的配置文件
## setting有两套:内置的,项目中的,
# from BBS_s15 import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.index),
path('register/', views.register),
path('login/', views.login),
path('get_valid/', views.get_valid),
path('check_username/', views.check_username),
path('index/', views.index),
path('logout/', views.logout),
path('get_banner/', views.get_banner),
path('upanddown/', views.upanddown),
path('comment/', views.comment),
path('backend_index/', views.backend_index),
path('new_article/', views.new_article),
path('upload_img/', views.upload_img),
path('update_head/', views.update_head),
path('update_pwd/', views.update_pwd),
re_path('update_article/(?P<id>\d+)', views.update_article),
# 开启media访问
# url(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})
re_path('^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
# re_path('^static/(?P<path>.*)$', serve,{'document_root': settings.STATICFILES_DIRS[0]}),
## 左侧筛选
# re_path('^(?P<name>\w+)/category/(?P<category_id>\d+).html$', views.site),
# re_path('^(?P<name>\w+)/tag/(?P<tag_id>\d+).html$', views.site),
# re_path('^(?P<name>\w+)/archive/(?P<year>.*).html$', views.site),
## 文章详情路由
re_path('^(?P<name>\w+)/article/(?P<id>\d+).html$', views.article_detail),
## 路由改成一条
# http://192.168.11.211/lqz/category/1.html
# http://192.168.11.211/lqz/tag/1.html
# http://192.168.11.211/lqz/archive/2020/09.html
# name = lqz query =tag condition=1
re_path('^(?P<name>\w+)/(?P<query>category|tag|archive)/(?P<condition>.*).html$', views.site),
## 个人站点路由设计
# /人名
re_path('^(?P<name>\w+)$', views.site)
]
views.py:
from django.shortcuts import render
from django.http import JsonResponse
from blog import models
# Create your views here.
from blog.blog_form import RegisterForm
def register(request):
if request.method == 'GET':
#实例化 传到register.html中就可以使用
form=RegisterForm()
return render(request,'register.html',context={'form':form})
elif request.method=='POST':
#校验数据是否合法
res={'code':100,'msg':'注册成功'}
form=RegisterForm(data=request.POST)
if form.is_valid():
#保存到数据库
data=form.cleaned_data #{username:zs,password:123,avatar:文件对象}
data.pop('re_password')#在字典中 所以需要删除 得到上面这个字典
if not data.get('avatar'):
data.pop('avatar')
models.UserInfo.objects.create_user(**data)
#返回会被ajax接收到
return JsonResponse(res)
else:
#数据校验不通过
res['code']=101 #101标识注册失败
res['msg']='数据验证失败'
res['error']=form.errors
return JsonResponse(res)
//需要迁移数据库
def check_username(request):
res={'code':101,'msg':'该用户已经存在'}
username=request.GET.get('username')
user=models.UserInfo.objects.filter(username=username).count()
if not user:
res['code']=100
res['msg']='不存在'
return JsonResponse(res)
views.py终极版:
from django.shortcuts import render, HttpResponse, redirect
# Create your views here.
from blog.blog_form import RegisterForm
from django.http import JsonResponse
from blog import models
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import random
from django.contrib import auth
from django.contrib.auth.decorators import login_required
def register(request):
if request.method == 'GET':
form = RegisterForm()
return render(request, 'register.html', context={'form': form})
elif request.method == 'POST':
# 校验数据是否合法
res = {'code': 100, 'msg': '注册成功'}
form = RegisterForm(data=request.POST)
if form.is_valid():
# 保存到数据库
data = form.cleaned_data # {username:lqz,password:123,re_password:123,email:3@qq.com}
data.pop('re_password') # {username:lqz,password:123,email:3@qq.com}
file = request.FILES.get('avatar')
if file:
data['avatar'] = file # {username:lqz,password:123,email:3@qq.com,'avatar':文件对象}
print(data)
models.UserInfo.objects.create_user(**data)
# res['url']='/index/'
res['url'] = '/login/'
'''
FileField自动干了这个事
with open('media/avatar/%s'%file.name,'wb') as f:
for line in file:
f.write(line)
path='media/avatar/%s'%file.name
'''
# models.UserInfo.objects.create_user(username=username,password=password,email=email,avatar=path)
# 返回,会被ajax接收到
return JsonResponse(res)
else:
# 数据校验不通过
res['code'] = 101 # 101表示注册失败
res['msg'] = '数据验证失败'
res['error'] = form.errors
return JsonResponse(res)
def check_username(request):
print('xxx')
res = {'code': 101, 'msg': '该用户已经存在了小兄弟'}
username = request.GET.get('username')
user = models.UserInfo.objects.filter(username=username).count()
if not user:
res['code'] = 100
res['msg'] = '不存在'
return JsonResponse(res)
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
res = {'code': 100, 'msg': None}
username = request.POST.get('username')
password = request.POST.get('password')
valid_code = request.POST.get('valid_code')
# 校验验证码是否正确,忽略大小写
if request.session.get('valid_code').upper() == valid_code.upper():
# 不使用这个
# models.UserInfo.objects.filter(username=username,password=password)
user = auth.authenticate(username=username, password=password)
if user:
# 调用一些login
auth.login(request, user)
res['msg'] = '登录成功'
res['url'] = '/index/'
else:
res['code'] = 101
res['msg'] = '用户名或密码错误'
else:
res['code'] = 102
res['msg'] = '验证码错误'
return JsonResponse(res)
# 随机生成rgb数字的函数
def get_rgb():
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
def get_valid(request):
# 方式一,直接返回一张图片
# with open('./lhf.jpg','rb') as f:
# data=f.read()
# return HttpResponse(data)
# 方式二:自己生成一张图片,返回
# pillow模块:图片处理模块,pip3 install pillow
# 生成一张图片(Image模块下的new函数,返回一个Image对象)
# img = Image.new('RGB', (450, 30), (123, 123, 255))
# # 图片保存(写到硬盘)
# with open('a.png', 'wb') as f:
# img.save(f)
# # 打开图片
# with open('./a.png', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
## 方式三:把生成的图片写到内存中
# img = Image.new('RGB', (450, 30), (123, 123, 255))
# # 图片保存(写到内存中)
# f=BytesIO()
# img.save(f,'png') # 指定图片格式
# # 打开图片
# data=f.getvalue() # 把内容全取出来
# return HttpResponse(data)
## 方法四:在图片上写文字
# img = Image.new('RGB', (450, 30), (123, 123, 255))
#
# # 把图片放到画板上
# img_draw=ImageDraw.Draw(img)
# # 写文字
# img_draw.text((0,0),'python')
#
# f=BytesIO()
# img.save(f,'png')
# data=f.getvalue()
# return HttpResponse(data)
## 方式五:图片上写文字,字体是指定的字体,字的颜色随机
# img = Image.new('RGB', (450, 30), (123, 123, 255))
#
# img_draw = ImageDraw.Draw(img)
# img_font = ImageFont.truetype('./static/font/xgdl.ttf', 23)
# img_draw.text((0, 0), 'python', (255, 0, 255), img_font)
#
# f = BytesIO()
# img.save(f, 'png')
# data = f.getvalue()
# return HttpResponse(data)
## 方式6 最终方案
# img = Image.new('RGB', (450, 30), get_rgb())
img = Image.new('RGB', (450, 30), (255, 255, 255))
img_draw = ImageDraw.Draw(img)
img_font = ImageFont.truetype('./static/font/ss.TTF', 25)
# 随机生成5个大写字母,小写字母,数字
valid_code = ''
for i in range(5):
low_char = chr(random.randint(97, 122))
num_char = random.randint(0, 9)
upper_char = chr(random.randint(65, 90))
res = random.choice([low_char, num_char, upper_char])
valid_code += str(res)
img_draw.text((i * 63 + 50, 0), str(res), get_rgb(), img_font)
print(valid_code)
# 把验证码存到session中
request.session['valid_code'] = valid_code
# 画线和点圈
width = 450
height = 30
for i in range(10):
x1 = random.randint(0, width)
x2 = random.randint(0, width)
y1 = random.randint(0, height)
y2 = random.randint(0, height)
# 在图片上画线
img_draw.line((x1, y1, x2, y2), fill=get_rgb())
for i in range(50):
# 画点
img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_rgb())
x = random.randint(0, width)
y = random.randint(0, height)
# 画弧形
img_draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_rgb())
f = BytesIO()
img.save(f, 'png')
data = f.getvalue()
return HttpResponse(data)
def index(request):
# 通过模板语言把文章渲染在页面中
article_list = models.Article.objects.all().order_by('-create_time')
ll = [{'url': 'http://www.baidu.com',
'img_url': '/static/img/banner1.jpg',
'msg': '1024程序员节'},
{'url': 'http://www.bilibi.com',
'img_url': '/static/img/banner2.jpg',
'msg': '我们跟bilibil合作了'}
]
return render(request, 'index.html', context={'article_list': article_list, 'll': ll})
def logout(request):
auth.logout(request)
return redirect('/')
def get_banner(request):
# 正常逻辑,有个表存banner图
# 我们写死了
ll = [{'url': 'http://www.baidu.com',
'img_url': '/static/img/banner1.jpg',
'msg': '1024程序员节'},
{'url': 'http://www.bilibi.com',
'img_url': '/static/img/banner2.jpg',
'msg': '我们跟bilibil合ffffsgdfgsdfgsgdf作了'}
]
return JsonResponse(ll, safe=False)
from django.db.models import Count
# def site(request, name, **kwargs):
# user = models.UserInfo.objects.filter(username=name).first()
# if user:
# # 这个人写的所有文章
# article_list = user.blog.article_set.all()
# # print(kwargs)
# # category_id = ''
# # tag_id = ''
# # year_month = ''
# # category_id = kwargs.get('category_id', None)
# # article_list = article_list.filter(category_id=category_id)
# # if not category_id:
# # tag_id = kwargs.get('tag_id', None)
# # article_list = article_list.filter(tag_id=tag_id)
# # if not tag_id:
# # year_month = kwargs.get('year', None)
# # # 2019/05
# # year, month = year_month.split('/')
# # article_list = article_list.filter(create_time__year=year, create_time__month=month)
#
# # 这个人所有的分类及分类下的文章(分组查询)
# # 查询所有分类下的文章数
# # res_category=models.Category.objects.all().values('pk').annotate(num=Count('article__id')).values('name','num')
# # 查询当前站点下所有分类下的文章数
# '''
# SELECT
# a. NAME,
# COUNT(b.id)
# FROM
# blog_category AS a,
# blog_article AS b
# WHERE
# a.id = b.category_id
# AND a.blog_id = 1
# GROUP BY
# a.id;
# '''
# res_category = models.Category.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list(
# 'name', 'num')
# print(res_category)
# # 查询当前站点下所有标签下的文章数
# res_tag = models.Tag.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name',
# 'num')
# print(res_tag)
# # 查询当前站点下某年某月的文章数(分组依据,日期:年月),不需要连表
# from django.db.models.functions import TruncMonth, TruncDay, TruncYear, TruncHour
# '''
# Sales.objects
# .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
# .values('month') # Group By month
# .annotate(c=Count('id')) # Select the count of the grouping
# .values('month', 'c') # (might be redundant, haven't tested) select month and count
# '''
# res_month = models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time')).values(
# 'month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
# print(res_month)
# return render(request, 'site.html', locals())
# else:
# return render(request, 'error.html')
from django.db.models.functions import TruncMonth
# name=lqz kwargs={query:tag|category|archive,condition:1|2|2020/9}
# def site(request, name, **kwargs):
# # 分三种情况(根据标签过滤,根据分类过滤,根据时间过滤)
# user = models.UserInfo.objects.filter(username=name).first()
# if user:
# article_list = user.blog.article_set.all()
# # 根据不同情况对article_list进行过滤,article_list是queryset对象,可以继续filter
# query = kwargs.get('query', None)
# if query == 'category': # 说明走的是过滤的路由
# condition = kwargs.get('condition')
# article_list = article_list.filter(category_id=condition)
# elif query == 'tag':
# condition = kwargs.get('condition')
# article_list = article_list.filter(tag__id=condition)
# elif query == 'archive':
# year, month = kwargs.get('condition').split('/') # 2020/09
# article_list = article_list.filter(create_time__year=year, create_time__month=month)
#
# res_category = models.Category.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list(
# 'name', 'num', 'id')
# res_tag = models.Tag.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name',
# 'num', 'id')
# res_month = models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time')).values(
# 'month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
#
# return render(request, 'site.html', locals())
# else:
# return render(request, 'error.html')
# def site_query(request, name, query, condition):
# # 分三种情况(根据标签过滤,根据分类过滤,根据时间过滤)
#
# user = models.UserInfo.objects.filter(username=name).first()
# if user:
# article_list = user.blog.article_set.all()
# if query == 'category':
# article_list = article_list.filter(category_id=condition)
# elif query == 'tag':
# article_list = article_list.filter(tag__id=condition)
# elif query == 'archive':
# year, month = condition.split('/')
# article_list = article_list.filter(create_time__year=year, create_time__month=month)
#
# res_category = models.Category.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list(
# 'name', 'num','id')
# print(res_category)
# res_tag = models.Tag.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name',
# 'num','id')
# res_month = models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time')).values(
# 'month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
#
# return render(request, 'site.html', locals())
# else:
# return render(request, 'error.html')
### site终极
def site(request, name, **kwargs):
# 分三种情况(根据标签过滤,根据分类过滤,根据时间过滤)
user = models.UserInfo.objects.filter(username=name).first()
if user:
article_list = user.blog.article_set.all()
# 根据不同情况对article_list进行过滤,article_list是queryset对象,可以继续filter
query = kwargs.get('query', None)
if query == 'category': # 说明走的是过滤的路由
condition = kwargs.get('condition')
article_list = article_list.filter(category_id=condition)
elif query == 'tag':
condition = kwargs.get('condition')
article_list = article_list.filter(tag__id=condition)
elif query == 'archive':
year, month = kwargs.get('condition').split('/') # 2020/09
article_list = article_list.filter(create_time__year=year, create_time__month=month)
return render(request, 'site.html', locals())
else:
return render(request, 'error.html')
def article_detail(request, name, id):
user = models.UserInfo.objects.get(username=name)
article = models.Article.objects.get(id=id)
comment_list = models.Commit.objects.filter(article=article)
return render(request, 'article_detail.html', locals())
from django.db import transaction
from django.db.models import F
import json
def upanddown(request):
res = {'code': 100, 'msg': ''}
if request.user.is_authenticated:
# article表中数字加1,在点赞点踩表中记录一条
# 这个人对该文章只能点赞或者点踩一次
# 先查一下,如果有记录了,就不能再点了
article_id = request.POST.get('article_id')
user_id = request.user.id
is_up = request.POST.get('is_up') # is_up是一个字符串
print(type(is_up))
# if is_up=='true':
# is_up=True
# else:
# is_up = False
is_up = json.loads(is_up)
print(type(is_up))
res_1 = models.UpAndDown.objects.filter(article_id=article_id, user_id=user_id).count()
if res_1:
res['code'] = 101
res['msg'] = '已经点过了'
else:
with transaction.atomic():
models.UpAndDown.objects.create(article_id=article_id, user_id=user_id, is_up=is_up)
if is_up:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
res['msg'] = '点赞成功'
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
res['msg'] = '点踩成功'
else:
res['code'] = 109
res['msg'] = '请先<a href="/login/">登录</a>'
return JsonResponse(res)
def comment(request):
res = {'code': 100, 'msg': ''}
if request.is_ajax():
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent = request.POST.get('parent')
if request.user.is_authenticated:
article = models.Commit.objects.create(user=request.user, article_id=article_id, content=content,
commit_id_id=parent)
models.Article.objects.filter(pk=article_id).update(commit_num=F('commit_num') + 1)
res['msg'] = '评论成功'
res['username'] = article.user.username
res['content'] = article.content
if parent:
res['parent_name'] = article.commit_id.user.username
# 发送邮件(同步操作)
from django.core.mail import send_mail
# send_mail('您的文章:%s被评论了'%'git从入门到放弃','%s评论了%s'%(request.user.username,'写的真好'),settings.EMAIL_HOST_USER,
# ["1063926627@qq.com",'laichuangdelaoji@gmail.com'])
# 通过多线程,异步操作
from threading import Thread
t=Thread(target=send_mail,args=('您的文章:%s被评论了'%'git从入门到放弃','%s评论了%s'%(request.user.username,'写的真好'),settings.EMAIL_HOST_USER,["1063926627@qq.com",'laichuangdelaoji@gmail.com']))
t.start()
else:
res['code'] = 109
res['msg'] = '请先登录'
return JsonResponse(res)
@login_required(login_url='/login/')
def backend_index(request):
article_list = models.Article.objects.filter(blog=request.user.blog)
return render(request, 'backend/backend_index.html', locals())
# 学爬虫会详细讲
from bs4 import BeautifulSoup
@login_required(login_url='/login/')
def new_article(request):
if request.method == 'GET':
category_list = models.Category.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
return render(request, 'backend/add_article.html', locals())
else:
title = request.POST.get('title')
content = request.POST.get('content')
category = request.POST.get('category')
tags = request.POST.getlist('tag')
# desc = content[0:90] # 会带着标签
soup = BeautifulSoup(content, 'html.parser')
desc = soup.text[0:90] # 去掉所有标签后的文本
# 把script标签干掉
res_script = soup.find_all('script')
for script in res_script:
script.decompose() # 删除script标签
article = models.Article.objects.create(title=title, content=str(soup), desc=desc, blog=request.user.blog,
category_id=category)
# 存tag(自动生成第三张表)
# article.tag.add(*tag)
# 手动做
# for tag in tags:
# models.TagToArticle.objects.create(article_id=article.pk,tag_id=tag)
# 批量插入
ll = []
for tag in tags:
ll.append(models.TagToArticle(article_id=article.pk, tag_id=tag))
models.TagToArticle.objects.bulk_create(ll)
return redirect('/backend_index/')
import os
from django.conf import settings
# 局部禁用csrf
def upload_img(request):
res = {'error': 0}
print(request.FILES)
try:
# 存图片
file = request.FILES.get('myfile')
path = os.path.join(settings.BASE_DIR, 'media', 'img', file.name)
with open(path, 'wb') as f:
for line in file:
f.write(line)
res['url'] = '/media/img/' + file.name
except Exception as e:
res['error'] = 1
res['message'] = str(e)
return JsonResponse(res)
@login_required(login_url='/login/')
def update_head(request):
if request.method == 'GET':
return render(request, 'backend/update_head.html')
else:
head = request.FILES.get('myfile')
# 方式一
request.user.avatar = head
request.user.save()
return redirect('/')
# 方式二(不可以,它不能自动添加上avatar/前缀)
# models.UserInfo.objects.filter(pk=request.user.id).update(avatar=head)
# return redirect('/')
def update_pwd(request):
res = {'code': 100, 'msg': '密码修改成功'}
try:
pwd = request.POST.get('pwd')
print(pwd)
request.user.set_password(pwd)
request.user.save()
except Exception:
res['code'] = 101
res['msg'] = '密码修改失败'
return JsonResponse(res)
@login_required(login_url='/login/')
def update_article(request,id):
if request.method=='GET':
article=models.Article.objects.get(id=id)
category_list=models.Category.objects.filter(blog=request.user.blog)
tag_list=models.Tag.objects.filter(blog=request.user.blog)
return render(request,'backend/update_article.html',locals())
else:
title = request.POST.get('title')
content = request.POST.get('content')
category = request.POST.get('category')
tags = request.POST.getlist('tag')
# desc = content[0:90] # 会带着标签
soup = BeautifulSoup(content, 'html.parser')
desc = soup.text[0:90] # 去掉所有标签后的文本
# 把script标签干掉
res_script = soup.find_all('script')
for script in res_script:
script.decompose() # 删除script标签
article = models.Article.objects.filter(pk=id)
article.update(title=title, content=str(soup), desc=desc,category_id=category)
# 存tag(自动生成第三张表)
# article.tag.add(*tag)
# 手动做
# for tag in tags:
# models.TagToArticle.objects.create(article_id=article.pk,tag_id=tag)
# 批量插入
# ll = []
# for tag in tags:
# ll.append(models.TagToArticle(article_id=article.pk, tag_id=tag))
# models.TagToArticle.objects.bulk_create(ll)
return redirect('/backend_index/')
register.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap-theme.min.css">
<script scr="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
<title>注册</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1 class="text-center">注册功能</h1>
<div class="col-md=6 col-md-offset-3">
<form id=""form>
{% csrf_token %}
{% for foo in form %}
<div class="form-group">
<label for="{{ foo.auto_id }}">{{ foo.label }}</label>
{{ foo }}
</div>
{% endfor %}
<div class="form-group">
{# 焦点 实现点击图片和头像文字也可以上传头像#}
<label for="id_myfile">头像</label>
{# 默认头像图片路径以及图片高宽#}
<img src="/static/img/default.png" alt="" id="id_img" height="80" width="80" style="margin-left: 10px ">
{# 根据id实现label中焦点 将上传文件按键以及文字隐藏更加美观#}
<input type="file" name="myfile" id="id_myfile" style="display: none">
</div>
<div class="text-center">
<input type="button" value="注册" id="id_submit" class="btn btn-danger"><span class=" error text-danger"></span>
</div>
</form>>
</div>
</div>
</div>
</body>
<script>
//放文件的标签发生变化,我们把文件搞出来,放到img标签中
$("#id_myfile").change(function(){
//借助于文件阅读器
var filereader = new FileReader()
//把图片读到filereader对象中
//$('id_myfile')[0].files[0]
filereader.readAsDataURL($('#id_myfile')[0].files[0])
//$('#id_img').attr('src','https://account.cnblogs.com/images/registersideimg
//$('#id_img').attr('src',filereader.result)//这样不行,文件没读完
filereader.onload=function(){
//文件完全读到文件阅读器以后再执行
$('#id_img').attr('src',filereader.result)
}
})
$("#id_submit").click(function () {
var formdata=new FormData()
formdata.append('avatar',$('#id_myfile')[0].files[0])
var ser=$('#form').serializeArray()
$.each(ser,function(k,v){
formdata.append(v.name,v.value)
})
$.ajax({
url:'/register/',
method:'post',
processData:false,
contentType:false,
data:formdata,
success:function(data){
if (data.code==100){
console.log(data.msg)
//js控制的跳转 本地跳转
location.href=data.url
}else {
//有错误 需要渲染页面
$.each(data.error,function(k,v){
if (k='__all__'){
$(".error").html(v)
}
$("#id_"+k).next().html(v[0]).parent().addClass('has-error')
})
//过了三秒钟(3000毫秒)
setTimeout(function(){
$('.text-danger').html("").parent().removeClass('has-error')
},3000)
}
}
})
})
//当光标再username空间上就发送ajax请求,去后台查询
$('id_username').blur(function(){
//this原生空间,使用它$(),它才变成jq,使用jq的方法
//alert($(this).val())
//alert($('#id_username').val()
$.ajax({
url:'/check_username/?username='+$(this).val(),
method:'get',
{#success:function (data) {}#}
success:(data)=>{
if(data.code !== 100){
$(this).next().html(data.msg).parent().addClass('has-error')
}
}
})
})
</script>
</html>
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
<title>登录</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1 class="text-center">登录功能</h1>
<div class="col-md-6 col-md-offset-3">
<form id="form">
{% 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_valid/" height="30" width="450" id="id_valid_code">
</div>
</div>
</div>
<div class="text-center">
<input type="button" value="登录" id="id_submit" class="btn btn-danger"><span
class="error text-danger" style="margin-left: 10px"></span>
</div>
</form>
</div>
</div>
</div>
</body>
<script>
$("#id_valid_code").click(function () {
var url = $("#id_valid_code")[0].src
$("#id_valid_code")[0].src = url + '?'
})
$("#id_submit").click(function () {
//不建议在js中写模板语法
$.ajax({
url: '/login/',
method: 'post',
data: {
'username': $('[name="username"]').val(),
'password': $('[name="password"]').val(),
'valid_code': $('[name="valid_code"]').val(),
'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()
},
success:function (data) {
if(data.code==100){
location.href=data.url
}else {
$('.error').html(data.msg)
}
}
})
})
</script>
</html>
def comment(request): res = {'code': 100, 'msg': ''} if request.is_ajax(): article_id = request.POST.get('article_id') content = request.POST.get('content') parent = request.POST.get('parent') if request.user.is_authenticated: article = models.Commit.objects.create(user=request.user, article_id=article_id, content=content, commit_id_id=parent) models.Article.objects.filter(pk=article_id).update(commit_num=F('commit_num') + 1) res['msg'] = '评论成功' res['username'] = article.user.username res['content'] = article.content if parent: res['parent_name'] = article.commit_id.user.username else: res['code'] = 109 res['msg'] = '请先登录' return JsonResponse(res)
setting.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/2.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '$o%_r3jf$6-8sgk^h0j$0an9^2id3_=l4-85q+y$co#@-bk!1r'
# 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',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'BBS_s15.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_s15.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bbs',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123',
}
}
# Password validation
# https://docs.djangoproject.com/en/2.0/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/2.0/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
# 扩写auth_user表
AUTH_USER_MODEL='blog.UserInfo'
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
# BLOG=os.path.join(BASE_DIR,'blog')
EMAIL_HOST = 'smtp.qq.com' # 如果是 163 改成 smtp.163.com 以什么邮箱发送
EMAIL_PORT = 465 # 端口号
EMAIL_HOST_USER = 'xxxxxxxxx@qq.com' # 帐号 发送者邮箱账号
EMAIL_HOST_PASSWORD = '' # 密码 不是密码, 授权码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
EMAIL_USE_SSL = True #使用ssl,qq只支持这种
article_datail.html:
{% extends 'base.html' %} {% block title %} {{ article.title }} {% endblock %} {% block css %} <link rel="stylesheet" href="/static/css/mycss.css"> {% endblock %} {% block content %} <div> <h3 class="text-center">{{ article.title }}</h3> <hr> <div> {{ article.content|safe }} </div> <!-- 点赞点踩 --> <div class="clearfix"> <div id="div_digg"> <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article.up_num }}</span> </div> <div class="buryit action"> <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> <hr> <!-- 评论列表和评论输入框 #1楼 2018-03-22 13:33 Yif.Z 回复 峰哥,什么视频时候这个在线上班更新啊 --> <div> 评论列表 <ul class="list-group"> {% for comment in comment_list %} <li class="list-group-item"> <div> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.create_time|date:'Y-m-d H-i-s' }}</span> <span><a href="/{{ comment.user.username }}">{{ comment.user.username }}</a></span> <span class="pull-right id_replay" username="{{ comment.user.username }} " parent="{{ comment.pk }}"><a>回复</a></span> </div> <hr> <div> {% if comment.commit_id_id %} <p>@{{ comment.commit_id.user.username }}</p> <p>{{ comment.content }}</p> {% else %} {{ comment.content }} {% endif %} </div> </li> {% endfor %} </ul> </div> {% if request.user.is_authenticated %} <div> <p class="glyphicon glyphicon-copyright-mark">发表评论</p> <p><textarea name="" id="id_text" cols="180" rows="10"></textarea></p> <p> <button class="btn btn-success" id="id_comment">发表评论</button> </p> </div> {% else %} <div> 登录后才能发表评论,立即 <a href="/login/">登录</a> 或 <a href="/register/">注册</a>, 访问 网站首页 </div> {% endif %} </div> {% endblock %} {% block script %} <script> //把parent_id定义成全局变量 var parent_id = '' $(".action").click(function () { var is_up = $(this).hasClass('diggit') var span = $(this).children('span') $.ajax({ url: '/upanddown/', method: 'post', data: { article_id: '{{ article.id }}', is_up: is_up, csrfmiddlewaretoken: '{{ csrf_token }}' }, success: function (data) { console.log(data) $('#digg_tips').html(data.msg) if (data.code == 100) { //点赞或者点踩的数字加一 var num = Number(span.html()) + 1 span.html(num) } } }) }) $('#id_comment').click(function () { let content = $('#id_text').val() if (parent_id) { //这表示子评论 //取到字符串第一个\n的位置索引,是一个数字 let i = content.indexOf('\n') + 1 //从 \n+1的位置开始截取,截取到最后 content = content.slice(i) } $.ajax({ url: '/comment/', method: 'post', data: { article_id: '{{ article.id }}', content: content, parent: parent_id, csrfmiddlewaretoken: '{{ csrf_token }}' }, success: function (data) { console.log(data) if (data.code == 100) { let username = data.username let res_content = data.content let parent_name = data.parent_name let ss = `` if (parent_id) { ss = `<li class="list-group-item"> <div> <span class="glyphicon glyphicon-comment">${username}</span> </div> <div> <p>@${parent_name}</p> ${res_content} </div> </li>` } else { ss = `<li class="list-group-item"> <div> <span class="glyphicon glyphicon-comment">${username}</span> </div> <div> ${res_content} </div> </li>` } //清空输入框 $('#id_text').val('') //把ss追加到评论列表的后面 $('.list-group').append(ss) //把parent_id置空 parent_id = '' } } }) }) $('.id_replay').click(function () { let username = $(this).attr('username') parent_id = $(this).attr('parent') $('#id_text').val('@' + username + '\n').focus() }) </script> {% endblock %}
base.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script> <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.css"> <title>{% block title %} {% endblock %}</title> {% block css %} {% endblock %} </head> <body> <div class="head"> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">{{ user.blog.site_title }}</a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="/index/">首页 <span class="sr-only">(current)</span></a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> </div> <div class="container-fluid"> <div class="row"> <div class="col-md-2"> {% load mytag %} {% xxx name %} </div> <div class="col-md-10"> {% block content %} {% endblock %} </div> </div> </div> </body> {% block script %} {% endblock %}
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.css"> <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> <title>博客园首页</title> <style> .article_bottom { margin-top: 10px; } .article_bottom span { margin-left: 10px; } </style> </head> <body> <div class="head"> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">博客园</a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="/index/">首页 <span class="sr-only">(current)</span></a></li> <li><a href="#">新闻</a></li> </ul> {% 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 data-toggle="modal" data-target="#myModal">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="/backend_index/">后台管理</a></li> <li role="separator" class="divider"></li> <li><a href="/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="body"> <div class="row"> <div class="left_content col-md-2"> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">广告位招商</h3> </div> <div class="panel-body"> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">重金求子</h3> </div> <div class="panel-body"> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> <hr> 详情联系:<a href="">点我</a> </div> </div> </div> <div class="middle_content col-md-7"> <div class="lbt"> <div id="carousel-example-generic" class="carousel slide" data-ride="carousel"> <!-- Indicators --> <ol class="carousel-indicators"> <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li> <li data-target="#carousel-example-generic" data-slide-to="1"></li> <li data-target="#carousel-example-generic" data-slide-to="2"></li> </ol> <!-- Wrapper for slides --> <div class="carousel-inner" role="listbox"> <div class="item active"> <a href="http://www.baidu.com"><img src="{{ ll.0.img_url }}" alt="..." class="img"></a> <div class="carousel-caption"> 广告位招商 </div> </div> <div class="item"> <a href=""><img src="{{ ll.1.img_url }}" alt="..." class="img"></a> <div class="carousel-caption"> 点我看美女 </div> </div> </div> <!-- Controls --> <a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev"> <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> <span class="sr-only">Previous</span> </a> <a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next"> <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> <span class="sr-only">Next</span> </a> </div> </div> <div class="article"> {% for article in article_list %} <hr> <div class="media"> <h4 class="media-heading"><a href="/{{ article.blog.userinfo.username }}/article/{{ article.id }}.html">{{ article.title }}</a> </h4> <div class="media-left"> <a href="#"> <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." width="60" height="60"> </a> </div> <div class="media-body"> {{ article.desc }} </div> <div class="article_bottom"><span><a href="/{{ article.blog.userinfo.username }}">{{ article.blog.userinfo.username }}</a></span> <span>{{ article.create_time|date:'Y-m-d H-i-s' }}</span> <span><i class="fa fa-stethoscope fa-lg"></i> {{ article.up_num }}</span><span class="glyphicon glyphicon-comment"><i class="fa fa-flickr1 fa-lg"></i> {{ article.commit_num }}</span> </div> </div> {% endfor %} </div> <div> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel">修改密码</h4> </div> <div class="modal-body"> <div class="alert alert-warning alert-dismissible fade in hidden" role="alert" id="id_alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span></button> <strong>修改密码成功</strong> </div> <form action=""> <div class="form-group"> <label for="">原密码</label> <input type="password" id="id_old_pwd" class="form-control"> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="id_new_pwd" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="id_conf_pwd" class="form-control"> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary" id="btn_pwd_submit">确认</button> </div> </div> </div> </div> </div> </div> <div class="right_content col-md-3"> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">广告位招商</h3> </div> <div class="panel-body"> 详情联系:<a href="">点我</a> </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">重金求子</h3> </div> <div class="panel-body"> 详情联系:<a href="">点我</a> </div> </div> </div> </div> </div> </div> </body> <script> //页面加载完成,向后端发送请求 $(function () { let images = $('.img') $.ajax({ url: '/get_banner/', method: 'get', success: function (data) { /* $.each(data, function (k, v) { console.log(k) console.log(v) $('.img')[k].src = v.img_url $('.img').val(v.msg) $($('.img')[k]).parent().next()[0].innerHTML=v.msg $($('.img')[k]).parent().attr('href',v.url) }) */ $.each(images, function (k, v) { $(v).attr('src', data[k].img_url) $(v).parent().attr('href', data[k].url) $(v).parent().next().html(data[k].msg) }) } }) }) $('#btn_pwd_submit').click(function () { $(this).prop('disabled', true) $.ajax({ url: '/update_pwd/', method: 'post', data: { pwd: $('#id_new_pwd').val(), csrfmiddlewaretoken: '{{ csrf_token }}', }, success: function (data) { $('#btn_pwd_submit').prop('disabled', false) $('#id_alert').removeClass('hidden').children('strong').html(data.msg) setTimeout(function () { $('#id_alert').addClass('hidden') },3000) if (data.code == 100) { //模态框销毁 $('#myModal').modal('hide') } } }) }) </script> </html>
left.html:
<div> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">我的标签</h3> </div> <div class="panel-body"> {% for tag in res_tag %} <p><a href="/{{ name }}/tag/{{ tag.2 }}.html">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">随笔分类</h3> </div> <div class="panel-body"> {% for category in res_category %} <p><a href="/{{ name }}/category/{{ category.2 }}.html">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">随笔档案</h3> </div> <div class="panel-body"> {% for month in res_month %} <p> <a href="/{{ name }}/archive/{{ month.0|date:'Y/m' }}.html">{{ month.0|date:'Y年m月' }}({{ month.1 }})</a> </p> {% endfor %} </div> </div> </div>
error.html;
<body> <div class="text-center"> <div><img src="//static.hdslb.com/error/very_sorry.png"></div> <div class="error-manga"><img src="http://i0.hdslb.com/bfs/activity-plat/cover/20171017/n6jl5yq9yp.png"><a class="change-img-btn" href="#up">换一张</a><a></a></div> </div> </body>
site.html:
{% extends 'base.html' %} {% block title %} {{ user.username }}-博客园 {% endblock %} {% block content %} <div class="article"> {% for article in article_list %} <hr> <div class="media"> <h4 class="media-heading"><a href="/{{ user.username }}/article/{{ article.id }}.html">{{ article.title }}</a></h4> <div class="media-body"> {{ article.desc }} </div> <div class="article_bottom pull-right"> <span>posted@</span> <span>{{ article.create_time|date:'Y-m-d H-i-s' }}</span> <span>{{ user.username }}</span> <span><i class="fa fa-stethoscope fa-lg"></i> {{ article.up_num }}</span><span class="glyphicon glyphicon-comment"><i class="fa fa-flickr1 fa-lg"></i> {{ article.commit_num }}</span> <span><a href="">编辑</a></span> </div> </div> {% endfor %} </div> {% endblock %}
site.html 修改版:
<div class="container-fluid">
<div class="row:>
<div class="col-md-2">
{% load mytag %}
{% left name %}
</div>
根评论功能:
#1 js中 es6语法,反引号的用法
var content='sb'
`egon is ${content}`
<div> 评论列表 <ul class="list-group"> {% for comment in comment_list %} <li class="list-group-item"> <div> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.create_time|date:'Y-m-d H-i-s' }}</span> <span><a href="/{{ comment.user.username }}">{{ comment.user.username }}</a></span> <span class="pull-right id_replay" username="{{ comment.user.username }} " parent="{{ comment.pk }}"><a>回复</a></span> </div> <hr> <div> {% if comment.commit_id_id %} <p>@{{ comment.commit_id.user.username }}</p> <p>{{ comment.content }}</p> {% else %} {{ comment.content }} {% endif %} </div> </li> {% endfor %} </ul> </div>
$('#id_comment').click(function () { let content = $('#id_text').val() if (parent_id) { //这表示子评论 //取到字符串第一个\n的位置索引,是一个数字 let i = content.indexOf('\n') + 1 //从 \n+1的位置开始截取,截取到最后 content = content.slice(i) } $.ajax({ url: '/comment/', method: 'post', data: { article_id: '{{ article.id }}', content: content, parent: parent_id, csrfmiddlewaretoken: '{{ csrf_token }}' }, success: function (data) { console.log(data) if (data.code == 100) { let username = data.username let res_content = data.content let parent_name = data.parent_name let ss = `` if (parent_id) { ss = `<li class="list-group-item"> <div> <span class="glyphicon glyphicon-comment">${username}</span> </div> <div> <p>@${parent_name}</p> ${res_content} </div> </li>` } else { ss = `<li class="list-group-item"> <div> <span class="glyphicon glyphicon-comment">${username}</span> </div> <div> ${res_content} </div> </li>` } //清空输入框 $('#id_text').val('') //把ss追加到评论列表的后面 $('.list-group').append(ss) //把parent_id置空 parent_id = '' } } }) }) $('.id_replay').click(function () { let username = $(this).attr('username') parent_id = $(this).attr('parent') $('#id_text').val('@' + username + '\n').focus() })
{% if request.user.is_authenticated %} <div> <p class="glyphicon glyphicon-copyright-mark">发表评论</p> <p><textarea name="" id="id_text" cols="180" rows="10"></textarea></p> <p> <button class="btn btn-success" id="id_comment">发表评论</button> </p> </div> {% else %} <div> 登录后才能发表评论,立即 <a href="/login/">登录</a> 或 <a href="/register/">注册</a>, 访问 网站首页 </div> {% endif %}
$('#id_comment').click(function () { let content = $('#id_text').val() $.ajax({ url: '/comment/', method: 'post', data: { article_id: '{{ article.id }}', content: content, parent: parent_id, csrfmiddlewaretoken: '{{ csrf_token }}' }, success: function (data) { if (data.code == 100) { let username = data.username let res_content = data.content let ss = `` ss = `<li class="list-group-item"> <div> <span class="glyphicon glyphicon-comment">${username</span> </div> <div> <p>@${parent_name}</p> ${res_content} </div> </li>` //清空输入框 $('#id_text').val('') //把ss追加到评论列表的后面 $('.list-group').append(ss) } } }) })
<!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/font-awesome-4.7.0/css/font-awesome.css"> <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> <style> </style> </head> <body> <div> <div class="header"> <nav class="navbar navbar-default navbar-inverse"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">后台管理</a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="/index/">首页 <span class="sr-only">(current)</span></a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> </div> <div class="container-fluid"> <div class="row"> <div class="left_content 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> </h4> </div> <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne"> <div class="panel-body"> <ul class="nav"> <li><a href="/new_article/">新增文章</a></li> <li><a href="">新增随笔</a></li> </ul> </div> </div> </div> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingTwo"> <h4 class="panel-title"> <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> 分类 </a> </h4> </div> <div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo"> <div class="panel-body"> </div> </div> </div> </div> </div> <div class="right_content 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> </div> </div> </body> {% block script %} {% endblock %} </html>
{% extends 'backend/base.html' %} {% block content %} <table class="table table-hover table-striped"> <thead> <tr> <th>标题</th> <th>评论数</th> <th>点赞数</th> <th>操作</th> <th>操作</th> </tr> </thead> <tbody > {% for article in article_list %} <tr> <td><a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}.html">{{ article.title }}</a></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 %}
@login_required(login_url='/login/') def backend_index(request): article_list = models.Article.objects.filter(blog=request.user.blog) return render(request, 'backend/backend_index.html', locals())
# pip3 install beautifulsoup4 from bs4 import BeautifulSoup @login_required(login_url='/login/') def new_article(request): if request.method == 'GET': category_list = models.Category.objects.filter(blog=request.user.blog) tag_list = models.Tag.objects.filter(blog=request.user.blog) return render(request, 'backend/add_article.html', locals()) else: title = request.POST.get('title') content = request.POST.get('content') category = request.POST.get('category') tags = request.POST.getlist('tag') # desc = content[0:90] # 会带着标签 soup = BeautifulSoup(content,'html.parser') desc=soup.text[0:90] # 去掉所有标签后的文本 # 把script标签干掉 res_script=soup.find_all('script') for script in res_script: script.decompose() # 删除script标签 article = models.Article.objects.create(title=title, content=str(soup), desc=desc, blog=request.user.blog, category_id=category) # 存tag(自动生成第三张表) # article.tag.add(*tag) # 手动做 # for tag in tags: # models.TagToArticle.objects.create(article_id=article.pk,tag_id=tag) # 批量插入 ll=[] for tag in tags: ll.append(models.TagToArticle(article_id=article.pk,tag_id=tag)) models.TagToArticle.objects.bulk_create(ll) return redirect('/backend_index/')
# 1 下载kindeditor:http://kindeditor.net/down.php # 2 解压缩,把整个文件加放入static目录下 # 3 再add_article.html中 <div class="form-group"> <label for="">内容</label> <textarea name="content" id="editor_id" cols="80" rows="10" class="form-control"> </textarea> </div> # 3 js代码 <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function (K) { window.editor = K.create('#editor_id', { width: '100%', height: '500px', resizeType: 1, }); }); 上传图片: KindEditor.ready(function (K) { window.editor = K.create('#editor_id', { width: '100%', height: '500px', resizeType: 1, uploadJson:'/upload_img/', filePostName:'myfile', //额外带的参数 extraFileUploadParams : { csrfmiddlewaretoken: '{{ csrf_token }}', } }); }); 后: def upload_img(request): res={'error':0} print(request.FILES) try: # 存图片 file=request.FILES.get('myfile') path=os.path.join(settings.BASE_DIR,'media','img',file.name) with open(path,'wb') as f: for line in file: f.write(line) res['url']='/media/img/'+file.name except Exception as e: res['error']=1 res['message']=str(e) return JsonResponse(res)
修改头像
后: @login_required(login_url='/login/') def update_head(request): if request.method=='GET': return render(request,'backend/update_head.html') else: head=request.FILES.get('myfile') # 方式一 request.user.avatar=head request.user.save() return redirect('/') # 方式二(不可以,它不能自动添加上avatar/前缀) # models.UserInfo.objects.filter(pk=request.user.id).update(avatar=head) # return redirect('/') 前: {% extends 'backend/base.html' %} {% block head %} <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <label for="">原头像</label> <img src="/media/{{ request.user.avatar }}" height="80" width="80"> </div> <div class="form-group"> <label for="id_myfile">头像 <img src="/static/img/default.png" alt="" id="id_img" height="80" width="80" style="margin-left: 10px"> </label> <input type="file" accept="image/*" name="myfile" id="id_myfile" style="display: none"> </div> <div class="text-center"> <input type="submit" value="修改" id="id_submit" class="btn btn-danger "><span class="error text-danger" style="margin-left: 10px"></span> </div> </form> {% endblock %} {% block script %} <script> $("#id_myfile").change(function () { //借助于文件阅读器 let filereader = new FileReader() //把图片读到filereader对象中 //$('#id_myfile')[0].files[0] filereader.readAsDataURL($('#id_myfile')[0].files[0]) //$('#id_img').attr('src','https://account.cnblogs.com/images/registersideimg.png') //$('#id_img').attr('src',filereader.result) //这样不行,文件没读完 filereader.onload = function () { //文件完全读到文件阅读器以后,再执行 $('#id_img').attr('src', filereader.result) } }) </script> {% endblock %}
修改密码:
前: $('#btn_pwd_submit').click(function () { $(this).prop('disabled', true) $.ajax({ url: '/update_pwd/', method: 'post', data: { pwd: $('#id_new_pwd').val(), csrfmiddlewaretoken: '{{ csrf_token }}', }, success: function (data) { $('#btn_pwd_submit').prop('disabled', false) $('#id_alert').removeClass('hidden').children('strong').html(data.msg) setTimeout(function () { $('#id_alert').addClass('hidden') },3000) if (data.code == 100) { //模态框销毁 $('#myModal').modal('hide') } } }) }) 后: def update_pwd(request): res = {'code': 100, 'msg': '密码修改成功'} try: pwd = request.POST.get('pwd') print(pwd) request.user.set_password(pwd) request.user.save() except Exception: res['code'] = 101 res['msg'] = '密码修改失败' return JsonResponse(res)
# 评论成功发送邮件 # django现成的函数,可以直接发送邮件 #1 setting中配置 EMAIL_HOST = 'smtp.qq.com' # 如果是 163 改成 smtp.163.com 以什么邮箱发送 EMAIL_PORT = 465 # 端口号 EMAIL_HOST_USER = '306334678@qq.com' # 帐号 发送者邮箱账号 EMAIL_HOST_PASSWORD = '' # 密码 不是密码, 授权码 DEFAULT_FROM_EMAIL = EMAIL_HOST_USER EMAIL_USE_SSL = True #使用ssl,qq只支持这种 # 2 视图函数中使用 from django.core.mail import send_mail send_mail('您的文章:%s被评论了'%'git从入门到放弃','%s评论了%s'%(request.user.username,'写的真好'),settings.EMAIL_HOST_USER, ["1063926627@qq.com",'laichuangdelaoji@gmail.com'])
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战