新闻发布与评论

新闻发布与评论

新闻发布页面布局完成

解决首页下拉菜单的小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上传图片到七牛云

七牛网:https://www.qiniu.com/

@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']);
                }
            }
        });
    });
};
发布了914 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41375318/article/details/104246745