Accept Merge Request #21 : (virusdefender-dev -> dev)

Merge Request: 增加通用分页函数和对应的测试
Created By: @virusdefender
Accepted By: @virusdefender
URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/21
This commit is contained in:
virusdefender
2015-08-05 19:46:06 +08:00
12 changed files with 313 additions and 12 deletions

View File

@@ -47,6 +47,7 @@ INSTALLED_APPS = (
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'account', 'account',
'utils',
'rest_framework', 'rest_framework',
'rest_framework_swagger', 'rest_framework_swagger',

View File

@@ -5,11 +5,12 @@ from django.views.generic import TemplateView
from account.views import UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, UserChangePasswordAPIView from account.views import UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, UserChangePasswordAPIView
from announcement.views import AnnouncementAPIView from announcement.views import AnnouncementAPIView
from admin.views import AdminTemplateView
urlpatterns = [ urlpatterns = [
url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"), url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"),
url(r'^docs/', include('rest_framework_swagger.urls')), url(r'^docs/', include('rest_framework_swagger.urls')),
url(r'^admin/$', TemplateView.as_view(template_name="admin/index.html"), name="admin_index_page"), url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"), url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"), name="user_register_page"), url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"), name="user_register_page"),
url(r'^change_password/$', TemplateView.as_view(template_name="oj/account/change_password.html"), name="user_change_password_page"), url(r'^change_password/$', TemplateView.as_view(template_name="oj/account/change_password.html"), name="user_change_password_page"),
@@ -22,4 +23,5 @@ urlpatterns = [
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"), url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"),
url(r'^problems/$', TemplateView.as_view(template_name="oj/problem/problem_list.html"), name="problem_list_page"), url(r'^problems/$', TemplateView.as_view(template_name="oj/problem/problem_list.html"), name="problem_list_page"),
url(r'^admin/template/(?P<template_dir>\w+)/(?P<template_name>\w+).html', AdminTemplateView.as_view(), name="admin_template")
] ]

View File

@@ -0,0 +1,31 @@
define("admin", ["jquery", "avalon"], function($, avalon){
function li_active(selector){
$(selector).attr("class", "list-group-item active");
}
function li_inactive(selector){
$(".list-group-item").attr("class", "list-group-item");
}
var hash = window.location.hash.substring(1);
if(hash){
li_active("#li-" + hash);
}else {
li_active("#li-index");
}
window.onhashchange = function() {
var hash = window.location.hash.substring(1);
if(hash){
li_inactive(".list-group-item");
li_active("#li-" + hash);
vm.template_url = "template/index/" + hash + ".html";
}
};
var vm = avalon.define({
$id: "admin",
template_url: "template/index/index.html"
});
});

View File

@@ -15,6 +15,7 @@ var require = {
submit_code: "app/oj/problem/submit_code", submit_code: "app/oj/problem/submit_code",
contest: "app/admin/contest/contest", contest: "app/admin/contest/contest",
csrf: "utils/csrf", csrf: "utils/csrf",
admin: "app/admin/admin",
//formValidation 不要在代码中单独使用而是使用和修改utils/validation //formValidation 不要在代码中单独使用而是使用和修改utils/validation
base: "lib/formValidation/base", base: "lib/formValidation/base",

122
template/admin/admin.html Normal file
View File

@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="renderer" content="webkit">
<title>在线评测系统 - 后台管理</title>
<!-- custom css begin -->
{% block css_block %}{% endblock %}
<!-- custom css end -->
<!-- global css begin -->
<link href="/static/css/admin.css" rel="stylesheet">
<!-- global css end -->
</head>
<body>
<!-- nav begin -->
<nav class="navbar navbar-masthead navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">qduoj admin</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">主页</a></li>
<li><a href="#about">题目</a></li>
<li><a href="#contact">提交</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
李扬
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">我的提交</a></li>
<li><a href="#">我的资料</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- nav end -->
<!--browser happy begin -->
<!--[if lt IE 9]>
<div class="alert alert-danger text-center" role="alert">
当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.
</div>
<![endif]-->
<!-- browser happy end -->
<div class="container" ms-controller="admin">
<div class="row">
<!-- admin left begin-->
<div class="col-md-2">
<ul class="list-group">
<li class="list-group-header">List header</li>
<li class="list-group-item" id="li-index"><a href="#index">主页</a></li>
<li class="list-group-item" id="li-announcement"><a href="#announcement">公告</a></li>
<li class="list-group-item"><a href="#">Applications</a></li>
<li class="list-group-header">Another list header</li>
<li class="list-group-item"><a href="#">Help</a></li>
</ul>
</div>
<!-- admin left end -->
<!-- custom body begin -->
<div ms-include-src="template_url"></div>
<!-- custom body end -->
</div>
</div>
<div class="modal fade" id="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">提示</h4>
</div>
<div class="modal-body">
<p id="modal-text"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<script src="/static/js/config.js"></script>
<script src="/static/js/require.js"></script>
<script>
require(["bootstrap", "admin"]);
</script>
<!-- footer begin -->
<div class="footer">
<p class="text-muted text-center">Copyright © 2015 青岛大学信息工程学院 创新实验室</p>
</div>
<!-- footer end -->
</body>
</html>

View File

@@ -1,4 +0,0 @@
{% extends "admin_base.html" %}
{% block body %}
Hello world
{% endblock %}

View File

@@ -0,0 +1 @@
<h1>Hello world</h1>

View File

@@ -65,14 +65,14 @@
<![endif]--> <![endif]-->
<!-- browser happy end --> <!-- browser happy end -->
<div class="container"> <div class="container" ms-controller="admin">
<div class="row"> <div class="row">
<!-- admin left begin--> <!-- admin left begin-->
<div class="col-md-2"> <div class="col-md-2">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-header">List header</li> <li class="list-group-header">List header</li>
<li class="list-group-item active"><a href="#">Home</a></li> <li class="list-group-item" id="li-index"><a href="#index">主页</a></li>
<li class="list-group-item"><a href="#">Library</a></li> <li class="list-group-item" id="li-announcement"><a href="#announcement">公告</a></li>
<li class="list-group-item"><a href="#">Applications</a></li> <li class="list-group-item"><a href="#">Applications</a></li>
<li class="list-group-header">Another list header</li> <li class="list-group-header">Another list header</li>
<li class="list-group-item"><a href="#">Help</a></li> <li class="list-group-item"><a href="#">Help</a></li>
@@ -108,7 +108,7 @@
<script src="/static/js/config.js"></script> <script src="/static/js/config.js"></script>
<script src="/static/js/require.js"></script> <script src="/static/js/require.js"></script>
<script> <script>
require(["bootstrap"]); require(["bootstrap", "admin"]);
</script> </script>
{% block js_block %}{% endblock %} {% block js_block %}{% endblock %}
<!-- footer begin --> <!-- footer begin -->

View File

@@ -1,4 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
coverage run --source='.' manage.py test coverage run --source='.' manage.py test
coverage html test_result=$?
open htmlcov/index.html if [ "$test_result" -eq 0 ];then
coverage html
open htmlcov/index.html
fi

View File

@@ -1,4 +1,6 @@
# coding=utf-8 # coding=utf-8
from django.core.paginator import Paginator
from rest_framework.response import Response from rest_framework.response import Response
@@ -11,4 +13,73 @@ def serializer_invalid_response(serializer):
def success_response(data): def success_response(data):
return Response(data={"code": 0, "data": data}) return Response(data={"code": 0, "data": data})
def paginate(request, query_set, object_serializer):
"""
用于分页的函数
如果 url 里面不含有paging=true那么将返回全部数据。类似
[
{
"username": "1111111",
"password": "123456"
}
]
如果 url 中有 paging=true 的参数,
然后还需要读取其余的两个参数page=[int]需要的页码p
age_size=[int],一页的数据条数
参数错误的时候,返回{"code": 1, "data": u"参数错误"}
返回的数据格式
{
"code": 0,
"data": {
"previous_page": null,
"results": [
{
"username": "1111111",
"password": "123456"
}
],
"next_page": 2
}
}
:param query_set 数据库查询结果
:param object_serializer: 序列化单个object的serializer
:return response
"""
need_paginate = request.GET.get("paging", None)
# 如果请求的参数里面没有paging=true的话 就返回全部数据
if need_paginate != "true":
return success_response(data=object_serializer(query_set, many=True).data)
page_size = request.GET.get("page_size", None)
if not page_size:
return error_response(u"参数错误")
try:
page_size = int(page_size)
except Exception:
return error_response(u"参数错误")
paginator = Paginator(query_set, page_size)
page = request.GET.get("page", None)
try:
current_page = paginator.page(page)
except Exception:
return error_response(u"参数错误")
data = {"results": object_serializer(current_page, many=True).data, "previous_page": None, "next_page": None}
try:
data["previous_page"] = current_page.previous_page_number()
except Exception:
pass
try:
data["next_page"] = current_page.next_page_number()
except Exception:
pass
return success_response(data)

9
utils/test_urls.py Normal file
View File

@@ -0,0 +1,9 @@
# coding=utf-8
from django.conf.urls import include, url
urlpatterns = [
url(r'^paginate_test/$', "utils.tests.pagination_test_func"),
]

64
utils/tests.py Normal file
View File

@@ -0,0 +1,64 @@
# coding=utf-8
from rest_framework.test import APIClient, APITestCase
from rest_framework import serializers
from rest_framework.decorators import api_view
from account.models import User
from .shortcuts import paginate
class PaginationTestSerialiser(serializers.Serializer):
username = serializers.CharField(max_length=100)
@api_view(["GET"])
def pagination_test_func(request):
return paginate(request, User.objects.all(), PaginationTestSerialiser)
class PaginatorTest(APITestCase):
urls = "utils.test_urls"
def setUp(self):
self.client = APIClient()
self.url = "/paginate_test/"
User.objects.create(username="test1")
User.objects.create(username="test2")
def test_no_paginate(self):
response = self.client.get(self.url)
self.assertEqual(response.data["code"], 0)
self.assertNotIn("next_page", response.data["data"])
self.assertNotIn("previous_page", response.data["data"])
def test_error_parameter(self):
response = self.client.get(self.url + "?paging=true")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
response = self.client.get(self.url + "?paging=true&page_size=-1")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
response = self.client.get(self.url + "?paging=true&page_size=aa")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
response = self.client.get(self.url + "?paging=true&page_size=1&page=-1")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
response = self.client.get(self.url + "?paging=true&page_size=aaa&page=1")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
response = self.client.get(self.url + "?paging=true&page_size=1&page=aaa")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
def test_correct_paginate(self):
response = self.client.get(self.url + "?paging=true&limit=1&page_size=1&page=1")
self.assertEqual(response.data["code"], 0)
self.assertEqual(response.data["data"]["previous_page"], None)
self.assertEqual(response.data["data"]["next_page"], 2)
self.assertEqual(len(response.data["data"]["results"]), 1)
response = self.client.get(self.url + "?paging=true&limit=1&page_size=2&page=1")
self.assertEqual(response.data["code"], 0)
self.assertEqual(response.data["data"]["previous_page"], None)
self.assertEqual(response.data["data"]["next_page"], None)
self.assertEqual(len(response.data["data"]["results"]), 2)