新闻发布与评论
- 81.新闻发布页面布局完成
- 82.解决首页下拉菜单的小bug
- 83.新闻分类模板完成
- 84.添加新闻分类前后台功能完成
- 85.新闻分类的编辑和删除功能实现
- 86.新闻分类细节补充(不能错过)
- 87.使用ajax上传缩略图到自己的服务器
- 88.使用ajax上传图片到七牛云
- 89.ajax上传图片到七牛细节处理
- 90.UEditor富文本编辑器的集成
- 91.发布新闻功能完成
- 92.将首页新闻列表改成活的数据
- 93.djangorestframework实现新闻列表功能
- 94.将JSON数据渲染成html页面
- 95.给arttemplate添加时间处理过滤器
- 96.切换分类异步加载文章
- 97.新闻详情页完成
- 98.新闻查询性能优化
- 99.django-debug-toolbar使用详解
- 100.新闻评论后端功能实现
- 101.新闻评论前端功能实现
- 102.新闻详情页ORM性能优化
- 103.自定义login_required限制访问
新闻发布页面布局完成
解决首页下拉菜单的小bug
新闻分类模板完成
@require_GET
@permission_required(perm="news.add_newscategory",login_url='/')
def news_category(request):
categories = NewsCategory.objects.all()
context = {
'categories': categories
}
return render(request,'cms/news_category.html',context=context)
@require_POST
@permission_required(perm="news.add_newscategory",login_url='/')
def add_news_category(request):
name = request.POST.get('name')
exists = NewsCategory.objects.filter(name=name).exists()
if not exists:
NewsCategory.objects.create(name=name)
return restful.ok()
else:
return restful.params_error(message='该分类已经存在!')
{% extends 'cms/base.html' %}
{% block title %}
新闻分类
{% endblock %}
{% block head %}
<script src="{% static 'js/news_category.min.js' %}"></script>
{% endblock %}
{% block content-header %}
<h1>新闻分类</h1>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header">
<button id="add-btn" class="btn btn-primary pull-right">添加分类</button>
</div>
<div class="box-body">
<table class="table table-bordered">
<thead>
<tr>
<th>分类名称</th>
<th>新闻数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr data-pk="{{ category.pk }}" data-name="{{ category.name }}">
<td>{{ category.name }}</td>
<td>20</td>
<td>
<button class="btn btn-warning btn-xs edit-btn">编辑</button>
<button class="btn btn-danger btn-xs delete-btn">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
class NewsCategory(models.Model):
name = models.CharField(max_length=100)
makemigrations
migrate
添加新闻分类前后台功能完成
/**
* Created by hynev on 2018/7/2.
*/
function NewsCategory() {
};
NewsCategory.prototype.run = function () {
var self = this;
self.listenAddCategoryEvent();
self.listenEditCategoryEvent();
self.listenDeleteCategoryEvent();
};
NewsCategory.prototype.listenAddCategoryEvent = function () {
var addBtn = $('#add-btn');
addBtn.click(function () {
xfzalert.alertOneInput({
'title': '添加新闻分类',
'placeholder': '请输入新闻分类',
'confirmCallback': function (inpuValue) {
xfzajax.post({
'url': '/cms/add_news_category/',
'data': {
'name': inpuValue
},
'success': function (result) {
if(result['code'] === 200){
console.log(result);
window.location.reload();
}else{
xfzalert.close();
}
}
});
}
});
});
};
NewsCategory.prototype.listenEditCategoryEvent = function () {
var self = this;
var editBtn = $(".edit-btn");
editBtn.click(function () {
var currentBtn = $(this);
var tr = currentBtn.parent().parent();
var pk = tr.attr('data-pk');
var name = tr.attr('data-name');
xfzalert.alertOneInput({
'title': '修改分类名称',
'placeholder': '请输入新的分类名称',
'value': name,
'confirmCallback': function (inputValue) {
xfzajax.post({
'url': '/cms/edit_news_category/',
'data': {
'pk': pk,
'name': inputValue
},
'success': function (result) {
if(result['code'] === 200){
window.location.reload();
}else{
xfzalert.close();
}
}
});
}
});
});
};
NewsCategory.prototype.listenDeleteCategoryEvent = function () {
var deleteBtn = $(".delete-btn");
deleteBtn.click(function () {
var currentBtn = $(this);
var tr = currentBtn.parent().parent();
var pk = tr.attr('data-pk');
xfzalert.alertConfirm({
'title': '您确定要删除这个分类吗?',
'confirmCallback': function () {
xfzajax.post({
'url': '/cms/delete_news_category/',
'data': {
'pk': pk
},
'success': function (result) {
if(result['code'] === 200){
window.location.reload();
}
}
});
}
});
});
};
$(function () {
var category = new NewsCategory();
category.run();
});
新闻分类的编辑和删除功能实现
@permission_required(perm="news.change_newscategory",login_url='/')
def edit_news_category(request):
form = EditNewsCategoryForm(request.POST)
if form.is_valid():
pk = form.cleaned_data.get('pk')
name = form.cleaned_data.get('name')
try:
NewsCategory.objects.filter(pk=pk).update(name=name)
return restful.ok()
except:
return restful.params_error(message='该分类不存在!')
else:
return restful.params_error(message=form.get_error())
class EditNewsCategoryForm(forms.Form):
pk = forms.IntegerField(error_messages={"required":"必须传入分类的id!"})
name = forms.CharField(max_length=100)
NewsCategory.prototype.listenEditCategoryEvent = function () {
var self = this;
var editBtn = $(".edit-btn");
editBtn.click(function () {
var currentBtn = $(this);
var tr = currentBtn.parent().parent();
var pk = tr.attr('data-pk');
var name = tr.attr('data-name');
xfzalert.alertOneInput({
'title': '修改分类名称',
'placeholder': '请输入新的分类名称',
'value': name,
'confirmCallback': function (inputValue) {
xfzajax.post({
'url': '/cms/edit_news_category/',
'data': {
'pk': pk,
'name': inputValue
},
'success': function (result) {
if(result['code'] === 200){
window.location.reload();
}else{
xfzalert.close();
}
}
});
}
});
});
};
xfzajax.post({
'url': '/cms/edit_news_category/',
'data': {
'pk': pk,
'name': inputValue
},
'success': function (result) {
if(result['code'] === 200){
window.location.reload();
}else{
xfzalert.close();
}
}
});
@require_POST
@permission_required(perm="news.delete_newscategory",login_url='/')
def delete_news_category(request):
pk = request.POST.get('pk')
try:
NewsCategory.objects.filter(pk=pk).delete()
return restful.ok()
except:
return restful.unauth(message='该分类不存在!')
NewsCategory.prototype.listenDeleteCategoryEvent = function () {
var deleteBtn = $(".delete-btn");
deleteBtn.click(function () {
var currentBtn = $(this);
var tr = currentBtn.parent().parent();
var pk = tr.attr('data-pk');
xfzalert.alertConfirm({
'title': '您确定要删除这个分类吗?',
'confirmCallback': function () {
xfzajax.post({
'url': '/cms/delete_news_category/',
'data': {
'pk': pk
},
'success': function (result) {
if(result['code'] === 200){
window.location.reload();
}
}
});
}
});
});
};
新闻分类细节补充(不能错过)
使用ajax上传缩略图到自己的服务器
@method_decorator(permission_required(perm="news.add_news",login_url='/'),name='dispatch')
class WriteNewsView(View):
def get(self,request):
categories = NewsCategory.objects.all()
context = {
'categories': categories
}
return render(request,'cms/write_news.html',context=context)
def post(self,request):
form = WriteNewsForm(request.POST)
if form.is_valid():
title = form.cleaned_data.get('title')
desc = form.cleaned_data.get('desc')
thumbnail = form.cleaned_data.get('thumbnail')
content = form.cleaned_data.get('content')
category_id = form.cleaned_data.get('category')
category = NewsCategory.objects.get(pk=category_id)
News.objects.create(title=title,desc=desc,thumbnail=thumbnail,content=content,category=category,author=request.user)
return restful.ok()
else:
return restful.params_error(message=form.get_errors())
<div class="form-group">
<label for="category-form">分类</label>
<select name="category" id="category-form" class="form-control">
{% for category in categories %}
{% if news and news.category_id == category.pk %}
<option value="{{ category.pk }}" selected>{{ category.name }}</option>
{% else %}
<option value="{{ category.pk }}">{{ category.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
@require_POST
@staff_member_required(login_url='index')
def upload_file(request):
file = request.FILES.get('file')
name = file.name
with open(os.path.join(settings.MEDIA_ROOT,name),'wb') as fp:
for chunk in file.chunks():
fp.write(chunk)
url = request.build_absolute_uri(settings.MEDIA_URL+name)
# http://127.0.1:8000/media/abc.jpg
return restful.result(data={'url':url})
News.prototype.listenUploadFielEvent = function () {
var uploadBtn = $('#thumbnail-btn');
uploadBtn.change(function () {
var file = uploadBtn[0].files[0];
var formData = new FormData();
formData.append('file',file);
xfzajax.post({
'url': '/cms/upload_file/',
'data': formData,
'processData': false,
'contentType': false,
'success': function (result) {
if(result['code'] === 200){
var url = result['data']['url'];
var thumbnailInput = $("#thumbnail-form");
thumbnailInput.val(url);
}
}
});
});
};
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
urlpatterns = [
path('',views.index,name='index'),
# path('search/',include('haystack.urls')),
path('news/', include("apps.news.urls")),
path('cms/',include('apps.cms.urls')),
path('account/', include("apps.xfzauth.urls")),
path('course/',include('apps.course.urls')),
path('payinfo/',include('apps.payinfo.urls')),
path('ueditor/',include('apps.ueditor.urls'))
] + static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
@require_POST
@staff_member_required(login_url='index')
def upload_file(request):
file = request.FILES.get('file')
name = file.name
with open(os.path.join(settings.MEDIA_ROOT,name),'wb') as fp: #存放在MEDIA_ROOT
for chunk in file.chunks():
fp.write(chunk)
url = request.build_absolute_uri(settings.MEDIA_URL+name) # 获得图片的url
# http://127.0.1:8000/media/abc.jpg
# build_absolute_uri= http://127.0.1:8000/
return restful.result(data={'url':url})
使用ajax上传图片到七牛云
@require_GET
@staff_member_required(login_url='index')
def qntoken(request):
access_key = settings.QINIU_ACCESS_KEY
secret_key = settings.QINIU_SECRET_KEY
bucket = settings.QINIU_BUCKET_NAME
q = qiniu.Auth(access_key,secret_key)
token = q.upload_token(bucket)
return restful.result(data={"token":token})
News.prototype.listenQiniuUploadFileEvent = function () {
var self = this;
var uploadBtn = $('#thumbnail-btn');
uploadBtn.change(function () {
var file = this.files[0];
xfzajax.get({
'url': '/cms/qntoken/',
'success': function (result) {
if(result['code'] === 200){
var token = result['data']['token'];
// a.b.jpg = ['a','b','jpg']
// 198888888 + . + jpg = 1988888.jpg
var key = (new Date()).getTime() + '.' + file.name.split('.')[1];
var putExtra = {
fname: key,
params:{},
mimeType: ['image/png','image/jpeg','image/gif','video/x-ms-wmv']
};
var config = {
useCdnDomain: true,
retryCount: 6,
region: qiniu.region.z0
};
var observable = qiniu.upload(file,key,token,putExtra,config);
observable.subscribe({
'next': self.handleFileUploadProgress,
'error': self.handleFileUploadError,
'complete': self.handleFileUploadComplete
});
}
}
});
});
};
ajax上传图片到七牛细节处理
显示图片上传进度条
<div id="progress-group" class="form-group" style="display: none;">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar"
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0">
0%
</div>
</div>
</div>
News.prototype.handleFileUploadProgress = function (response) {
var total = response.total;
var percent = total.percent;
var percentText = percent.toFixed(0)+'%';
// 24.0909,89.000....
var progressGroup = News.progressGroup;
progressGroup.show();
var progressBar = $(".progress-bar");
progressBar.css({"width":percentText});
progressBar.text(percentText);
};
News.prototype.handleFileUploadComplete = function (response) {
console.log(response);
var progressGroup = $("#progress-group");
progressGroup.hide();
var domain = 'http://7xqenu.com1.z0.glb.clouddn.com/';
var filename = response.key;
var url = domain + filename;
var thumbnailInput = $("input[name='thumbnail']");
thumbnailInput.val(url);
};
UEditor富文本编辑器的集成
from django.urls import path
from . import views
from django.conf import settings
app_name = 'ueditor'
urlpatterns = [
path("upload/",views.UploadView.as_view(),name='upload')
]
if hasattr(settings,"UEDITOR_UPLOAD_PATH"):
urlpatterns += [
path("f/<filename>",views.send_file,name='send_file')
]
#encoding: utf-8
import json
import re
import string
import time
import hashlib
import random
import base64
import sys
import os
from urllib import parse
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import reverse
from django.views.decorators.csrf import csrf_exempt
from django.http import FileResponse
from django.views.generic import View
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_http_methods
# 更改工作目录。这么做的目的是七牛qiniu的sdk
# 在设置缓存路径的时候默认会设置到C:/Windows/System32下面
# 会造成没有权限创建。
#os.chdir(os.path.dirname(__file__))
try:
import qiniu
except:
pass
from io import BytesIO
UEDITOR_QINIU_ACCESS_KEY = ""
UEDITOR_QINIU_SECRET_KEY = ""
UEDITOR_QINIU_BUCKET_NAME = ""
UEDITOR_QINIU_DOMAIN = ""
UEDITOR_UPLOAD_PATH = ""
UEDITOR_UPLOAD_TO_QINIU = False
UEDITOR_UPLOAD_TO_SERVER = False
# 用来判断是否要将文件上传到自己的服务器
try:
UEDITOR_UPLOAD_TO_SERVER = settings.UEDITOR_UPLOAD_TO_SERVER
if UEDITOR_UPLOAD_TO_SERVER:
UEDITOR_UPLOAD_PATH = settings.UEDITOR_UPLOAD_PATH
if not os.path.exists(UEDITOR_UPLOAD_PATH):
os.mkdir(UEDITOR_UPLOAD_PATH)
except:
pass
# 用来判断是否要将文件上传到七牛
try:
UEDITOR_UPLOAD_TO_QINIU = settings.UEDITOR_UPLOAD_TO_QINIU
except:
pass
# 如果既没有配置上传到本地,又没有配置上传到七牛,那么就抛出异常
if not UEDITOR_UPLOAD_PATH and not UEDITOR_UPLOAD_TO_QINIU:
raise RuntimeError("UEditor的UEDITOR_UPLOAD_TO_SERVER或者UEDITOR_UPLOAD_TO_QINIU必须配置一项!")
# 判断是否配置了config.json文件的路径
try:
UEDITOR_CONFIG_PATH = settings.UEDITOR_CONFIG_PATH
except:
raise RuntimeError("请配置UEditor的配置文件的路径!")
# 如果配置了七牛的配置信息
if UEDITOR_UPLOAD_TO_QINIU:
try:
UEDITOR_QINIU_ACCESS_KEY = settings.UEDITOR_QINIU_ACCESS_KEY
UEDITOR_QINIU_SECRET_KEY = settings.UEDITOR_QINIU_SECRET_KEY
UEDITOR_QINIU_BUCKET_NAME = settings.UEDITOR_QINIU_BUCKET_NAME
UEDITOR_QINIU_DOMAIN = settings.UEDITOR_QINIU_DOMAIN
except Exception as e:
option = e.args[0]
raise RuntimeError('请在app.config中配置%s!'%option)
@method_decorator([csrf_exempt,require_http_methods(['GET','POST'])],name='dispatch')
class UploadView(View):
def __init__(self):
super(UploadView, self).__init__()
def _random_filename(self,rawfilename):
"""
随机的文件名,保证文件名称不会冲突
"""
letters = string.ascii_letters
random_filename = str(time.time()) + "".join(random.sample(letters, 5))
filename = hashlib.md5(random_filename.encode('utf-8')).hexdigest()
subffix = os.path.splitext(rawfilename)[-1]
return filename + subffix
def _json_result(self,state='', url='', title='', original=''):
"""
返回指定格式的json数据的
"""
result = {
'state': state,
'url': url,
'title': title,
'original': original
}
return JsonResponse(result)
def _upload_to_qiniu(self,upfile,filename):
"""
上传文件到七牛
"""
if not sys.modules.get('qiniu'):
raise RuntimeError('没有导入qiniu模块!')
q = qiniu.Auth(UEDITOR_QINIU_ACCESS_KEY, UEDITOR_QINIU_SECRET_KEY)
token = q.upload_token(UEDITOR_QINIU_BUCKET_NAME)
buffer = BytesIO()
for chunk in upfile.chunks():
buffer.write(chunk)
buffer.seek(0)
ret, info = qiniu.put_data(token, filename, buffer.read())
if info.ok:
url = parse.urljoin(UEDITOR_QINIU_DOMAIN, ret['key'])
return 'SUCCESS', url, ret['key'], ret['key']
else:
return 'FAIL',None,None,None
def _upload_to_server(self,upfile,filename):
"""
上传文件到自己的服务器
"""
with open(os.path.join(UEDITOR_UPLOAD_PATH, filename), 'wb') as fp:
for chunk in upfile.chunks():
fp.write(chunk)
url = reverse("ueditor:send_file", kwargs={"filename": filename})
return 'SUCCESS', url, filename, filename
def _action_config(self):
"""
处理configl类型的响应
"""
config_path = UEDITOR_CONFIG_PATH
with open(config_path, 'r', encoding='utf-8') as fp:
result = json.loads(re.sub(r'\/\*.*\*\/', '', fp.read()))
return JsonResponse(result)
def _action_upload(self,request):
"""
处理文件(图片,视频,普通文件)上传
"""
upfile = request.FILES.get("upfile")
filename = self._random_filename(upfile.name)
qiniu_result = None
server_result = None
if UEDITOR_UPLOAD_TO_QINIU:
qiniu_result = self._upload_to_qiniu(upfile,filename)
if UEDITOR_UPLOAD_TO_SERVER:
server_result = self._upload_to_server(upfile,filename)
if qiniu_result and qiniu_result[0] == 'SUCCESS':
return self._json_result(*qiniu_result)
elif server_result and server_result[0] == 'SUCCESS':
return self._json_result(*server_result)
else:
return self._json_result()
def _action_scrawl(self,request):
base64data = request.form.get("upfile")
img = base64.b64decode(base64data)
filename = self._random_filename('xx.png')
with open(os.path.join(UEDITOR_UPLOAD_PATH, filename), 'wb') as fp:
fp.write(img)
url = reverse('ueditor:send_file', kwargs={"filename": filename})
return self._json_result('SUCCESS', url, filename, filename)
def dispatch(self, request, *args, **kwargs):
super(UploadView, self).dispatch(request,*args,**kwargs)
action = request.GET.get('action')
if action == 'config':
return self._action_config()
elif action in ['uploadimage','uploadvideo','uploadfile']:
return self._action_upload(request)
elif action == 'uploadscrawl':
return self._action_scrawl(request)
else:
return self._json_result()
def send_file(request,filename):
fp = open(os.path.join(UEDITOR_UPLOAD_PATH,filename),'rb')
response = FileResponse(fp)
response['Content-Type'] = "application/octet-stream"
return response
News.prototype.initUEditor = function () {
window.ue = UE.getEditor('editor',{
'initialFrameHeight': 400,
'serverUrl': '/ueditor/upload/'
});
};
# 七牛和自己的服务器,最少要配置一个
# UEditor配置
UEDITOR_UPLOAD_TO_QINIU = True
UEDITOR_QINIU_ACCESS_KEY = QINIU_ACCESS_KEY
UEDITOR_QINIU_SECRET_KEY = QINIU_SECRET_KEY
UEDITOR_QINIU_BUCKET_NAME = QINIU_BUCKET_NAME
UEDITOR_QINIU_DOMAIN = QINIU_DOMAIN
UEDITOR_UPLOAD_TO_SERVER = True
UEDITOR_UPLOAD_PATH = MEDIA_ROOT
发布新闻功能完成
@method_decorator(permission_required(perm="news.add_news",login_url='/'),name='dispatch')
class WriteNewsView(View):
def get(self,request):
categories = NewsCategory.objects.all()
context = {
'categories': categories
}
return render(request,'cms/write_news.html',context=context)
def post(self,request):
form = WriteNewsForm(request.POST)
if form.is_valid():
title = form.cleaned_data.get('title')
desc = form.cleaned_data.get('desc')
thumbnail = form.cleaned_data.get('thumbnail')
content = form.cleaned_data.get('content')
category_id = form.cleaned_data.get('category')
category = NewsCategory.objects.get(pk=category_id)
News.objects.create(title=title,desc=desc,thumbnail=thumbnail,content=content,category=category,author=request.user)
return restful.ok()
else:
return restful.params_error(message=form.get_errors())
class News(models.Model):
title = models.CharField(max_length=200)
desc = models.CharField(max_length=200)
thumbnail = models.URLField()
content = models.TextField()
pub_time = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey('NewsCategory',on_delete=models.SET_NULL,null=True)
author = models.ForeignKey('xfzauth.User',on_delete=models.SET_NULL,null=True)
class Meta:
ordering = ['-pub_time']
class WriteNewsForm(forms.ModelForm,FormMixin):
category = forms.IntegerField()
class Meta:
model = News
exclude = ['category','author','pub_time']
News.prototype.listenSubmitEvent = function () {
var submitBtn = $("#submit-btn");
submitBtn.click(function (event) {
event.preventDefault();
var btn = $(this);
var pk = btn.attr('data-news-id');
var title = $("input[name='title']").val();
var category = $("select[name='category']").val();
var desc = $("input[name='desc']").val();
var thumbnail = $("input[name='thumbnail']").val();
var content = window.ue.getContent();
var url = '';
if(pk){
url = '/cms/edit_news/';
}else{
url = '/cms/write_news/';
}
xfzajax.post({
'url': url,
'data': {
'title': title,
'category': category,
'desc': desc,
'thumbnail': thumbnail,
'content': content,
'pk': pk
},
'success': function (result) {
if(result['code'] === 200){
xfzalert.alertSuccess('恭喜!新闻发表成功!',function () {
window.location.reload();
});
}
}
});
});
};
将首页新闻列表改成活的数据
def index(request):
count = settings.ONE_PAGE_NEWS_COUNT
newses = News.objects.select_related('category','author').all()[0:count]
categories = NewsCategory.objects.all()
context = {
'newses': newses,
'categories': categories,
'banners': Banner.objects.all()
}
return render(request,'news/index.html',context=context)
djangorestframework实现新闻列表功能
pip install djangorestframework
# 一次加载多少篇文章
ONE_PAGE_NEWS_COUNT = 2
def index(request):
count = settings.ONE_PAGE_NEWS_COUNT
newses = News.objects.select_related('category','author').all()[0:count]
categories = NewsCategory.objects.all()
context = {
'newses': newses,
'categories': categories,
'banners': Banner.objects.all()
}
return render(request,'news/index.html',context=context)
path('list/',views.news_list,name='news_list'),
def news_list(request):
# 通过p参数,来指定要获取第几页的数据
# 并且这个p参数,是通过查询字符串的方式传过来的/news/list/?p=2
page = int(request.GET.get('p',1))
# 分类为0:代表不进行任何分类,直接按照时间倒序排序
category_id = int(request.GET.get('category_id',0))
# 0,1
# 2,3
# 4,5
start = (page-1)*settings.ONE_PAGE_NEWS_COUNT
end = start + settings.ONE_PAGE_NEWS_COUNT
if category_id == 0:
# QuerySet
# {'id':1,'title':'abc',category:{"id":1,'name':'热点'}}
newses = News.objects.select_related('category','author').all()[start:end]
else:
newses = News.objects.select_related('category','author').filter(category__id=category_id)[start:end]
serializer = NewsSerializer(newses,many=True)
data = serializer.data
return restful.result(data=data)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'haystack',
'apps.xfzauth',
'apps.cms',
'apps.news',
'apps.course',
'apps.payinfo',
'apps.ueditor',
'rest_framework',
'debug_toolbar'
]
#encoding: utf-8
from rest_framework import serializers
from .models import News,NewsCategory,Comment,Banner
from apps.xfzauth.serializers import UserSerializer
class NewsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = NewsCategory
fields = ('id','name')
class NewsSerializer(serializers.ModelSerializer):
category = NewsCategorySerializer()
author = UserSerializer()
class Meta:
model = News
fields = ('id','title','desc','thumbnail','pub_time','category','author')
class CommentSerizlizer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Comment
fields = ('id','content','author','pub_time')
class BannerSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
fields = ('id','image_url','priority','link_to')
#encoding: utf-8
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('uid','telephone','username','email','is_staff','is_active')
将JSON数据渲染成html页面
art-template
function Index() {
var self = this;
self.page = 2;
self.category_id = 0;
self.loadBtn = $("#load-more-btn");
}
Index.prototype.listenLoadMoreEvent = function () {
var self = this;
var loadBtn = $("#load-more-btn");
loadBtn.click(function () {
xfzajax.get({
'url': '/news/list/',
'data':{
'p': self.page,
'category_id': self.category_id
},
'success': function (result) {
if(result['code'] === 200){
var newses = result['data'];
if(newses.length > 0){
var tpl = template("news-item",{"newses":newses});
var ul = $(".list-inner-group");
ul.append(tpl);
self.page += 1;
}else{
loadBtn.hide();
}
}
}
});
});
};
<script id="news-item" type="text/html">
{% verbatim %}
{{ each newses news index }}
<li>
<div class="thumbnail-group">
<a href="/news/{{ news.id }}/">
<img src="{{ news.thumbnail }}" alt="">
</a>
</div>
<div class="news-group">
<p class="title">
<a href="/news/{{ news.id }}/">{{ news.title }}</a>
</p>
<p class="desc">
{{ news.desc }}
</p>
<p class="more">
<span class="category">{{ news.category.name }}</span>
<span class="pub-time">{{ news.pub_time|timeSince }}</span>
<span class="author">{{ news.author.username }}</span>
</p>
</div>
</li>
{{ /each }}
{% endverbatim %}
</script>
给arttemplate添加时间处理过滤器
切换分类异步加载文章
<div class="list-outer-group">
<ul class="list-tab">
<li data-category="0" class="active"><a href="javascript:void(0);">最新资讯</a></li>
{% for category in categories %}
<li data-category="{{ category.pk }}"><a href="javascript:void(0);">{{ category.name }}</a></li>
{% endfor %}
</ul>
<ul class="list-inner-group">
{% for news in newses %}
<li>
<div class="thumbnail-group">
<a href="{% url 'news:news_detail' news_id=news.pk %}">
<img src="{{ news.thumbnail }}" alt="">
</a>
</div>
<div class="news-group">
<p class="title">
<a href="{% url 'news:news_detail' news_id=news.pk %}">{{ news.title }}</a>
</p>
<p class="desc">
{{ news.desc }}
</p>
<p class="more">
<span class="category">{{ news.category.name }}</span>
<span class="pub-time">{{ news.pub_time|time_since }}</span>
<span class="author">{{ news.author.username }}</span>
</p>
</div>
</li>
{% endfor %}
</ul>
<div class="load-more-group">
<button class="load-more" id="load-more-btn">查看更多</button>
</div>
</div>
Index.prototype.listenCategorySwitchEvent = function () {
var self = this;
var tabGroup = $(".list-tab");
tabGroup.children().click(function () {
// this:代表当前选中的这个li标签
var li = $(this);
var category_id = li.attr("data-category");
var page = 1;
xfzajax.get({
'url': '/news/list/',
'data': {
'category_id': category_id,
'p': page
},
'success': function (result) {
if(result['code'] === 200){
var newses = result['data'];
var tpl = template("news-item",{"newses":newses});
// empty:可以将这个标签下的所有子元素都删掉
var newsListGroup = $(".list-inner-group");
newsListGroup.empty();
newsListGroup.append(tpl);
self.page = 2;
self.category_id = category_id;
li.addClass('active').siblings().removeClass('active');
self.loadBtn.show();
}
}
});
});
};
新闻详情页完成
def news_detail(request,news_id):
try:
news = News.objects.select_related('category','author').prefetch_related("comments__author").get(pk=news_id)
context = {
'news': news
}
return render(request,'news/news_detail.html',context=context)
except News.DoesNotExist:
raise Http404
{% block main %}
<div class="main">
<div class="wrapper">
<div class="main-content-wrapper">
<div class="news-wrapper">
<h1 class="title">{{ news.title }}</h1>
<div class="news-info">
<div class="info-group">
<span class="author">{{ news.author.username }}</span>
<span class="pub-time">{{ news.pub_time|time_since }}</span>
<span class="category">{{ news.category.name }}</span>
</div>
<div class="share-group">
<span>分享至:</span>
<a class="weixin share-item"></a>
<a href="#" class="weibo share-item"></a>
</div>
</div>
<article class="article">
{{ news.content|safe }}
</article>
</div>
<div class="comment-wrapper">
<h3 class="title">文章评论(0)</h3>
<textarea name="comment" class="comment-textarea logined-textarea" placeholder="立即登录,参与评论~"></textarea>
<div class="submit-btn-group">
<button class="submit-btn" data-news-id="{{ news.pk }}">立即评论</button>
</div>
<ul class="comment-list">
{% for comment in news.comments.all %}
<li>
<div class="comment-info">
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528129845916&di=536993042d5223862f8c4ab157ba6e72&imgtype=0&src=http%3A%2F%2Fpic1.ipadown.com%2Fimgs%2F201206120933354195.jpg" alt="" class="avatar">
<span class="author">{{ comment.author.username }}</span>
<span class="pub-time">{{ comment.pub_time|time_since }}</span>
</div>
<p class="comment-content">{{ comment.content }}</p>
</li>
{% endfor %}
</ul>
</div>
</div>
{% include 'common/sidebar.html' %}
</div>
</div>
{% endblock %}
<article class="article">
{{ news.content|safe }}
</article>
<script id="news-item" type="text/html">
{% verbatim %}
{{ each newses news index }}
<li>
<div class="thumbnail-group">
<a href="/news/{{ news.id }}/">
<img src="{{ news.thumbnail }}" alt="">
</a>
</div>
<div class="news-group">
<p class="title">
<a href="/news/{{ news.id }}/">{{ news.title }}</a>
</p>
<p class="desc">
{{ news.desc }}
</p>
<p class="more">
<span class="category">{{ news.category.name }}</span>
<span class="pub-time">{{ news.pub_time|timeSince }}</span>
<span class="author">{{ news.author.username }}</span>
</p>
</div>
</li>
{{ /each }}
{% endverbatim %}
</script>
def news_detail(request,news_id):
try:
news = News.objects.select_related('category','author').prefetch_related("comments__author").get(pk=news_id)
context = {
'news': news
}
return render(request,'news/news_detail.html',context=context)
except News.DoesNotExist:
raise Http404
新闻查询性能优化
def index(request):
count = settings.ONE_PAGE_NEWS_COUNT
newses = News.objects.select_related('category','author').all()[0:count] # 减少一倍的查询次数,减少外键查询
categories = NewsCategory.objects.all()
context = {
'newses': newses,
'categories': categories,
'banners': Banner.objects.all()
}
return render(request,'news/index.html',context=context)
django-debug-toolbar使用详解
pip install django-debug-toolbar
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'haystack',
'apps.xfzauth',
'apps.cms',
'apps.news',
'apps.course',
'apps.payinfo',
'apps.ueditor',
'rest_framework',
'debug_toolbar'
]
if settings.DEBUG:
import debug_toolbar
urlpatterns.append(path("__debug__/",include(debug_toolbar.urls)))
from django.middleware.csrf import CsrfViewMiddleware
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'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',
]
# Django-Debug-Toolbar相关的配置
INTERNAL_IPS = ['127.0.0.1']
DEBUG_TOOLBAR_PANELS = [
# 代表是哪个django版本
'debug_toolbar.panels.versions.VersionsPanel',
# 用来计时的,判断加载当前页面总共花的时间
'debug_toolbar.panels.timer.TimerPanel',
# 读取django中的配置信息
'debug_toolbar.panels.settings.SettingsPanel',
# 看到当前请求头和响应头信息
'debug_toolbar.panels.headers.HeadersPanel',
# 当前请求的想信息(视图函数,Cookie信息,Session信息等)
'debug_toolbar.panels.request.RequestPanel',
# 查看SQL语句
'debug_toolbar.panels.sql.SQLPanel',
# 静态文件
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
# 模板文件
'debug_toolbar.panels.templates.TemplatesPanel',
# 缓存
'debug_toolbar.panels.cache.CachePanel',
# 信号
'debug_toolbar.panels.signals.SignalsPanel',
# 日志
'debug_toolbar.panels.logging.LoggingPanel',
# 重定向
'debug_toolbar.panels.redirects.RedirectsPanel',
]
DEBUG_TOOLBAR_CONFIG = {
'JQUERY_URL': ''
}
新闻评论后端功能实现
class Comment(models.Model):
content = models.TextField()
pub_time = models.DateTimeField(auto_now_add=True)
news = models.ForeignKey("News",on_delete=models.CASCADE,related_name='comments') # 新闻删除,评论也跟着删除
author = models.ForeignKey("xfzauth.User",on_delete=models.CASCADE)
class Meta:
ordering = ['-pub_time']
class PublicCommentForm(forms.Form,FormMixin):
content = forms.CharField()
news_id = forms.IntegerField()
@xfz_login_required
def public_comment(request):
form = PublicCommentForm(request.POST)
if form.is_valid():
news_id = form.cleaned_data.get('news_id')
content = form.cleaned_data.get('content')
news = News.objects.get(pk=news_id)
comment = Comment.objects.create(content=content,news=news,author=request.user)
serizlize = CommentSerizlizer(comment)
return restful.result(data=serizlize.data)
else:
return restful.params_error(message=form.get_errors())
class CommentSerizlizer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Comment
fields = ('id','content','author','pub_time')
新闻评论前端功能实现
function NewsList() {
}
NewsList.prototype.listenSubmitEvent = function () {
var submitBtn = $('.submit-btn');
var textarea = $("textarea[name='comment']");
submitBtn.click(function () {
var content = textarea.val();
var news_id = submitBtn.attr('data-news-id');
xfzajax.post({
'url': '/news/public_comment/',
'data':{
'content': content,
'news_id': news_id
},
'success': function (result) {
console.log(result);
if(result['code'] === 200){
var comment = result['data'];
var tpl = template('comment-item',{"comment":comment});
var commentListGroup = $(".comment-list");
commentListGroup.prepend(tpl);
window.messageBox.showSuccess('评论发表成功!');
textarea.val("");
}else{
window.messageBox.showError(result['message']);
}
}
});
});
};
NewsList.prototype.run = function () {
this.listenSubmitEvent();
};
$(function () {
var newsList = new NewsList();
newsList.run();
});
path('public_comment/',views.public_comment,name='public_comment')
<div class="comment-wrapper">
<h3 class="title">文章评论(0)</h3>
<textarea name="comment" class="comment-textarea logined-textarea" placeholder="立即登录,参与评论~"></textarea>
<div class="submit-btn-group">
<button class="submit-btn" data-news-id="{{ news.pk }}">立即评论</button>
</div>
<ul class="comment-list">
{% for comment in news.comments.all %}
<li>
<div class="comment-info">
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528129845916&di=536993042d5223862f8c4ab157ba6e72&imgtype=0&src=http%3A%2F%2Fpic1.ipadown.com%2Fimgs%2F201206120933354195.jpg" alt="" class="avatar">
<span class="author">{{ comment.author.username }}</span>
<span class="pub-time">{{ comment.pub_time|time_since }}</span>
</div>
<p class="comment-content">{{ comment.content }}</p>
</li>
{% endfor %}
</ul>
</div>
新闻详情页ORM性能优化
class Comment(models.Model):
content = models.TextField()
pub_time = models.DateTimeField(auto_now_add=True)
news = models.ForeignKey("News",on_delete=models.CASCADE,related_name='comments')
author = models.ForeignKey("xfzauth.User",on_delete=models.CASCADE)
class Meta:
ordering = ['-pub_time']
def news_detail(request,news_id):
try:
news = News.objects.select_related('category','author').prefetch_related("comments__author").get(pk=news_id) # 优化
context = {
'news': news
}
return render(request,'news/news_detail.html',context=context)
except News.DoesNotExist:
raise Http404
自定义login_required限制访问
def xfz_login_required(func):
def wrapper(request,*args,**kwargs): # *args,**kwargs 可以接受任意所有的参数
if request.user.is_authenticated:
return func(request,*args,**kwargs)
else:
if request.is_ajax():
return restful.unauth(message='请先登录!')
else:
return redirect('/')
return wrapper
@xfz_login_required
def public_comment(request):
form = PublicCommentForm(request.POST)
if form.is_valid():
news_id = form.cleaned_data.get('news_id')
content = form.cleaned_data.get('content')
news = News.objects.get(pk=news_id)
comment = Comment.objects.create(content=content,news=news,author=request.user)
serizlize = CommentSerizlizer(comment)
return restful.result(data=serizlize.data)
else:
return restful.params_error(message=form.get_errors())
NewsList.prototype.listenSubmitEvent = function () {
var submitBtn = $('.submit-btn');
var textarea = $("textarea[name='comment']");
submitBtn.click(function () {
var content = textarea.val();
var news_id = submitBtn.attr('data-news-id');
xfzajax.post({
'url': '/news/public_comment/',
'data':{
'content': content,
'news_id': news_id
},
'success': function (result) {
console.log(result);
if(result['code'] === 200){
var comment = result['data'];
var tpl = template('comment-item',{"comment":comment});
var commentListGroup = $(".comment-list");
commentListGroup.prepend(tpl);
window.messageBox.showSuccess('评论发表成功!');
textarea.val("");
}else{
window.messageBox.showError(result['message']);
}
}
});
});
};