Merge branch 'dev' into hohoTT-dev

This commit is contained in:
hohoTT 2015-08-18 16:33:08 +08:00
commit 15febd5cc7
13 changed files with 281 additions and 51 deletions

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('group', '0004_merge'),
('announcement', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='announcement',
name='groups',
field=models.ManyToManyField(to='group.Group'),
),
migrations.AddField(
model_name='announcement',
name='is_global',
field=models.BooleanField(default=True),
preserve_default=False,
),
]

View File

@ -2,6 +2,7 @@
from django.db import models from django.db import models
from account.models import User from account.models import User
from group.models import Group
class Announcement(models.Model): class Announcement(models.Model):
@ -17,6 +18,9 @@ class Announcement(models.Model):
last_update_time = models.DateTimeField(auto_now=True) last_update_time = models.DateTimeField(auto_now=True)
# 是否可见 false的话相当于删除 # 是否可见 false的话相当于删除
visible = models.BooleanField(default=True) visible = models.BooleanField(default=True)
# 公告可见范围 0是全局可见 1是部分小组可见需要在下面的字段中存储可见的小组
is_global = models.BooleanField()
groups = models.ManyToManyField(Group)
class Meta: class Meta:
db_table = "announcement" db_table = "announcement"

View File

@ -8,6 +8,8 @@ from .models import Announcement
class CreateAnnouncementSerializer(serializers.Serializer): class CreateAnnouncementSerializer(serializers.Serializer):
title = serializers.CharField(max_length=50) title = serializers.CharField(max_length=50)
content = serializers.CharField(max_length=10000) content = serializers.CharField(max_length=10000)
is_global = serializers.BooleanField()
groups = serializers.ListField(child=serializers.IntegerField(), allow_empty=True)
class AnnouncementSerializer(serializers.ModelSerializer): class AnnouncementSerializer(serializers.ModelSerializer):
@ -28,3 +30,5 @@ class EditAnnouncementSerializer(serializers.Serializer):
title = serializers.CharField(max_length=50) title = serializers.CharField(max_length=50)
content = serializers.CharField(max_length=10000) content = serializers.CharField(max_length=10000)
visible = serializers.BooleanField() visible = serializers.BooleanField()
is_global = serializers.BooleanField()
groups = serializers.ListField(child=serializers.IntegerField())

View File

@ -5,6 +5,8 @@ from django.shortcuts import render
from utils.shortcuts import serializer_invalid_response, error_response, success_response from utils.shortcuts import serializer_invalid_response, error_response, success_response
from utils.shortcuts import paginate, error_page from utils.shortcuts import paginate, error_page
from account.models import SUPER_ADMIN, ADMIN
from group.models import Group
from .models import Announcement from .models import Announcement
from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer, from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer,
EditAnnouncementSerializer) EditAnnouncementSerializer)
@ -28,9 +30,26 @@ class AnnouncementAdminAPIView(APIView):
serializer = CreateAnnouncementSerializer(data=request.data) serializer = CreateAnnouncementSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
Announcement.objects.create(title=data["title"], groups = []
content=data["content"], # 如果不是全局公告就去查询一下小组的id 列表中的内容,注意用户身份
created_by=request.user) if not data["is_global"]:
if request.user.admin_type == SUPER_ADMIN:
groups = Group.objects.filter(id__in=data["groups"])
else:
groups = Group.objects.filter(id__in=data["groups"], admin=request.user)
if not groups.count():
return error_response(u"至少选择一个小组")
else:
if request.user.admin_type != SUPER_ADMIN:
return error_response(u"只有超级管理员可以创建全局公告")
announcement = Announcement.objects.create(title=data["title"],
content=data["content"],
created_by=request.user,
is_global=data["is_global"])
announcement.groups.add(*groups)
return success_response(u"公告发布成功!") return success_response(u"公告发布成功!")
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
@ -46,26 +65,43 @@ class AnnouncementAdminAPIView(APIView):
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
try: try:
announcement = Announcement.objects.get(id=data["id"]) if request.user.admin_type == SUPER_ADMIN:
announcement = Announcement.objects.get(id=data["id"])
else:
announcement = Announcement.objects.get(id=data["id"], admin=request.user)
except Announcement.DoesNotExist: except Announcement.DoesNotExist:
return error_response(u"该公告不存在!") return error_response(u"公告不存在")
groups = []
if not data["is_global"]:
if request.user.admin_type == SUPER_ADMIN:
groups = Group.objects.filter(id__in=data["groups"])
else:
groups = Group.objects.filter(id__in=data["groups"], admin=request.user)
if not groups.count():
return error_response(u"至少选择一个小组")
announcement.title = data["title"] announcement.title = data["title"]
announcement.content = data["content"] announcement.content = data["content"]
announcement.visible = data["visible"] announcement.visible = data["visible"]
announcement.is_global = data["is_global"]
announcement.save() announcement.save()
# 重建小组和公告的对应关系
announcement.groups.clear()
announcement.groups.add(*groups)
return success_response(AnnouncementSerializer(announcement).data) return success_response(AnnouncementSerializer(announcement).data)
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
class AnnouncementAPIView(APIView):
def get(self, request): def get(self, request):
""" """
公告分页json api接口 公告分页json api接口
--- ---
response_serializer: AnnouncementSerializer response_serializer: AnnouncementSerializer
""" """
announcement = Announcement.objects.all().order_by("-last_update_time") if request.user.admin_type == SUPER_ADMIN:
announcement = Announcement.objects.all().order_by("-last_update_time")
else:
announcement = Announcement.objects.filter(admin=request.user)
visible = request.GET.get("visible", None) visible = request.GET.get("visible", None)
if visible: if visible:
announcement = announcement.filter(visible=(visible == "true")) announcement = announcement.filter(visible=(visible == "true"))

View File

@ -17,7 +17,6 @@ class DBRouter(object):
def allow_migrate(self, db, app_label, model=None, **hints): def allow_migrate(self, db, app_label, model=None, **hints):
if app_label == "submission": if app_label == "submission":
if db == "submission": return db == app_label
return db == app_label
else: else:
return db == "default" return db == "default"

View File

@ -6,7 +6,7 @@ from django.views.generic import TemplateView
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView, UserChangePasswordAPIView, EmailCheckAPIView,
UserAdminAPIView, UserInfoAPIView) UserAdminAPIView, UserInfoAPIView)
from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView from announcement.views import AnnouncementAdminAPIView
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
JoinGroupAPIView, JoinGroupRequestAdminAPIView) JoinGroupAPIView, JoinGroupRequestAdminAPIView)
@ -38,7 +38,6 @@ urlpatterns = [
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"), url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
url(r'^announcement/(?P<announcement_id>\d+)/$', "announcement.views.announcement_page", url(r'^announcement/(?P<announcement_id>\d+)/$', "announcement.views.announcement_page",
name="announcement_page"), name="announcement_page"),
url(r'^api/announcements/$', AnnouncementAPIView.as_view(), name="announcement_list_api"),
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"),
name="add_contest_page"), name="add_contest_page"),
url(r'^problems/$', "problem.views.problem_list_page", name="problem_list_page"), url(r'^problems/$', "problem.views.problem_list_page", name="problem_list_page"),

View File

@ -1,8 +1,5 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"], require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
function ($, avalon, csrfTokenHeader, bsAlert, editor) { function ($, avalon, csrfTokenHeader, bsAlert, editor) {
avalon.ready(function () { avalon.ready(function () {
avalon.vmodels.announcement = null; avalon.vmodels.announcement = null;
@ -20,7 +17,11 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
totalPage: 1, // 总页数 totalPage: 1, // 总页数
showVisibleOnly: false, //仅显示可见公告 showVisibleOnly: false, //仅显示可见公告
// 编辑 // 编辑
newTitle: "",
announcementVisible: 0, announcementVisible: 0,
showGlobalViewRadio: true,
isGlobal: true,
allGroups: [],
getState: function (el) { //获取公告当前状态,显示 getState: function (el) { //获取公告当前状态,显示
if (el.visible) if (el.visible)
return "可见"; return "可见";
@ -47,49 +48,75 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
}, },
editAnnouncement: function (announcement) { editAnnouncement: function (announcement) {
$("#newTitle").val(announcement.title); vm.newTitle = announcement.title;
editAnnouncementEditor.setValue(announcement.content); editAnnouncementEditor.setValue(announcement.content);
vm.announcementVisible = announcement.visible; vm.announcementVisible = announcement.visible;
if (vm.editingAnnouncementId == announcement.id) if (vm.editingAnnouncementId == announcement.id)
vm.editingAnnouncementId = 0; vm.editingAnnouncementId = 0;
else else
vm.editingAnnouncementId = announcement.id; vm.editingAnnouncementId = announcement.id;
vm.isGlobal = announcement.is_global;
for (var i = 0; i < announcement.groups.length; i++) {
for (var j = 0; j < vm.allGroups.length; j++) {
if (announcement.groups[i] == vm.allGroups[j].id) {
vm.allGroups[j].isSelected = true;
}
}
}
editAnnouncementEditor.focus(); editAnnouncementEditor.focus();
}, },
cancelEdit: function () { cancelEdit: function () {
vm.editingAnnouncementId = 0; vm.editingAnnouncementId = 0;
}, },
submitChange: function () { submitChange: function () {
var title = $("#newTitle").val(); var title = vm.newTitle;
var content = editAnnouncementEditor.getValue(); var content = editAnnouncementEditor.getValue();
if (content && title) { if (content == "" || title == "") {
$.ajax({ bsAlert("标题和内容都不能为空");
beforeSend: csrfTokenHeader, return false;
url: "/api/admin/announcement/", }
dataType: "json",
method: "put", var selectedGroups = [];
data: { if (!vm.isGlobal) {
id: vm.editingAnnouncementId, for (var i = 0; i < vm.allGroups.length; i++) {
title: title, if (vm.allGroups[i].isSelected) {
content: content, selectedGroups.push(vm.allGroups[i].id);
visible: vm.announcementVisible
},
success: function (data) {
if (!data.code) {
bsAlert("修改成功");
vm.editingAnnouncementId = 0;
getPageData(1);
}
else {
bsAlert(data.data);
}
} }
}); }
} }
else {
bsAlert("标题和公告内容不得为空"); if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("请至少选择一个小组");
return false;
} }
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/announcement/",
contentType: "application/json",
dataType: "json",
method: "put",
data: JSON.stringify({
id: vm.editingAnnouncementId,
title: title,
content: content,
visible: vm.announcementVisible,
is_global: vm.isGlobal,
groups: selectedGroups
}),
success: function (data) {
if (!data.code) {
bsAlert("修改成功");
vm.editingAnnouncementId = 0;
getPageData(1);
}
else {
bsAlert(data.data);
}
}
});
} }
}); });
vm.$watch("showVisibleOnly", function () { vm.$watch("showVisibleOnly", function () {
@ -98,8 +125,44 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
getPageData(1); getPageData(1);
$.ajax({
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
bsAlert("您的用户权限只能创建组内公告,但是您还没有创建过小组");
return;
}
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["isSelected"] = false;
vm.allGroups.push(item);
}
}
else {
bsAlert(data.data);
}
}
});
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
}
}
});
function getPageData(page) { function getPageData(page) {
var url = "/api/announcements/?paging=true&page=" + page + "&page_size=10"; var url = "/api/admin/announcement/?paging=true&page=" + page + "&page_size=10";
if (vm.showVisibleOnly) if (vm.showVisibleOnly)
url += "&visible=true"; url += "&visible=true";
$.ajax({ $.ajax({
@ -122,9 +185,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
} }
//新建公告表单验证与数据提交 //新建公告表单验证与数据提交
$("#announcement-form").validator().on('submit', function (e) {
$('form').validator().on('submit', function (e) {
if (!e.isDefaultPrevented()) { if (!e.isDefaultPrevented()) {
var title = $("#title").val(); var title = $("#title").val();
var content = createAnnouncementEditor.getValue(); var content = createAnnouncementEditor.getValue();
@ -132,10 +193,29 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
bsAlert("请填写公告内容"); bsAlert("请填写公告内容");
return false; return false;
} }
var selectedGroups = [];
if (!vm.isGlobal) {
for (var i = 0; i < vm.allGroups.length; i++) {
if (vm.allGroups[i].isSelected) {
selectedGroups.push(vm.allGroups[i].id);
}
}
}
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("请至少选择一个小组");
return false;
}
$.ajax({ $.ajax({
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/announcement/", url: "/api/admin/announcement/",
data: {title: title, content: content}, contentType: "application/json",
data: JSON.stringify({
title: title,
content: content,
is_global: vm.isGlobal,
groups: selectedGroups
}),
dataType: "json", dataType: "json",
method: "post", method: "post",
success: function (data) { success: function (data) {
@ -148,7 +228,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
bsAlert(data.data); bsAlert(data.data);
} }
} }
}) });
return false; return false;
} }
}) })

View File

@ -11,7 +11,14 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
method: "post", method: "post",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
window.location.href = "/"; //成功登陆
var ref = document.referrer;
if(ref){
if(ref.split("/")[2] == location.hostname){
location.href = ref;
}
}
location.href = "/";
} }
else { else {
bsAlert(data.data); bsAlert(data.data);

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('submission', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='submission',
name='is_counted',
field=models.BooleanField(default=False),
),
]

View File

@ -18,6 +18,7 @@ class Submission(models.Model):
accepted_answer_time = models.IntegerField(blank=True, null=True) accepted_answer_time = models.IntegerField(blank=True, null=True)
# 这个字段只有在题目是accepted 的时候才会用到,比赛题目的提交可能还会有得分等信息,存储在这里面 # 这个字段只有在题目是accepted 的时候才会用到,比赛题目的提交可能还会有得分等信息,存储在这里面
accepted_answer_info = models.TextField(blank=True, null=True) accepted_answer_info = models.TextField(blank=True, null=True)
is_counted = models.BooleanField(default=False)
class Meta: class Meta:
db_table = "submission" db_table = "submission"

View File

@ -28,6 +28,9 @@ class SubmissionAPIView(APIView):
data = serializer.data data = serializer.data
try: try:
problem = Problem.objects.get(id=data["problem_id"]) problem = Problem.objects.get(id=data["problem_id"])
# 更新问题的总提交计数
problem.total_submit_number += 1
problem.save()
except Problem.DoesNotExist: except Problem.DoesNotExist:
return error_response(u"题目不存在") return error_response(u"题目不存在")
submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]), submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]),
@ -51,6 +54,18 @@ class SubmissionAPIView(APIView):
submission = Submission.objects.get(id=submission_id, user_id=request.user.id) submission = Submission.objects.get(id=submission_id, user_id=request.user.id)
except Submission.DoesNotExist: except Submission.DoesNotExist:
return error_response(u"提交不存在") return error_response(u"提交不存在")
# 标记这个submission 已经被统计
if not submission.is_counted:
submission.is_counted = True
submission.save()
if submission.result == result["accepted"]:
# 更新题目的 ac 计数器
try:
problem = Problem.objects.get(id=submission.problem_id)
problem.total_accepted_number += 1
problem.save()
except Problem.DoesNotExist:
pass
response_data = {"result": submission.result} response_data = {"result": submission.result}
if submission.result == 0: if submission.result == 0:
response_data["accepted_answer_time"] = submission.accepted_answer_time response_data["accepted_answer_time"] = submission.accepted_answer_time

View File

@ -7,6 +7,7 @@
<th>创建时间</th> <th>创建时间</th>
<th>更新时间</th> <th>更新时间</th>
<th>创建者</th> <th>创建者</th>
<td>可见范围</td>
<th>状态</th> <th>状态</th>
<th></th> <th></th>
</tr> </tr>
@ -16,6 +17,7 @@
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td> <td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.last_update_time|date("yyyy-MM-dd HH:mm:ss")}}</td> <td>{{ el.last_update_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td> <td>{{ el.created_by.username }}</td>
<td ms-text="el.is_global?'全局可见':'组内可见'"></td>
<td>{{ getState(el)}}</td> <td>{{ getState(el)}}</td>
<td> <td>
<button class="btn-sm btn-info" ms-click="editAnnouncement(el)">编辑</button> <button class="btn-sm btn-info" ms-click="editAnnouncement(el)">编辑</button>
@ -36,7 +38,7 @@
<div class="form-group"> <div class="form-group">
<label>标题</label> <label>标题</label>
<input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value=""></div> <input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value="" ms-duplex="newTitle"></div>
<div class="form-group"> <div class="form-group">
<label>内容</label> <label>内容</label>
<textarea id="edit-announcement-editor"></textarea> <textarea id="edit-announcement-editor"></textarea>
@ -44,6 +46,25 @@
<div class="form-group"> <div class="form-group">
<label>可见 <input ms-duplex-checked="announcementVisible" type="checkbox"/></label> <label>可见 <input ms-duplex-checked="announcementVisible" type="checkbox"/></label>
</div> </div>
<div class="form-group">
<label>可见范围</label>
<div>
<span ms-if="showGlobalViewRadio">
<input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</span>
<span>
<input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</span>
</div>
</div>
<div class="form-group col-md-12" ms-if="!isGlobal">
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
<div ms-repeat="allGroups" class="col-md-4">
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="form-group"> <div class="form-group">
<button ms-click="submitChange()" class="btn btn-primary">提交</button> <button ms-click="submitChange()" class="btn btn-primary">提交</button>
&nbsp;&nbsp; &nbsp;&nbsp;
@ -65,6 +86,25 @@
</textarea> </textarea>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group">
<label>可见范围</label>
<div>
<span ms-if="showGlobalViewRadio">
<input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</span>
<span>
<input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</span>
</div>
</div>
<div class="form-group col-md-12" ms-if="!isGlobal">
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
<div ms-repeat="allGroups" class="col-md-4">
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-primary">提交</button> <button type="submit" class="btn btn-primary">提交</button>
</div> </div>

View File

@ -30,7 +30,7 @@
<th scope="row"><a href="/problem/{{ item.id }}/">{{ item.id }}</a></th> <th scope="row"><a href="/problem/{{ item.id }}/">{{ item.id }}</a></th>
<td><a href="/problem/{{ item.id }}/">{{ item.title }}</a></td> <td><a href="/problem/{{ item.id }}/">{{ item.title }}</a></td>
<td>{{ item.difficulty }}</td> <td>{{ item.difficulty }}</td>
<td>{{ item|accepted_radio }}</td> <td>{{ item|accepted_radio }}%</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>