diff --git a/.gitignore b/.gitignore index 12a8c85..73adda5 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,6 @@ db.db db.sqlite3 .DS_Store log/ -release/ \ No newline at end of file +release/ +tmp/ +test_case/ \ No newline at end of file diff --git a/group/migrations/0001_initial.py b/group/migrations/0001_initial.py index f996a63..68da3ae 100644 --- a/group/migrations/0001_initial.py +++ b/group/migrations/0001_initial.py @@ -22,7 +22,6 @@ class Migration(migrations.Migration): ('join_group_setting', models.IntegerField()), ('visible', models.BooleanField(default=True)), ('admin', models.ForeignKey(related_name='my_groups', to=settings.AUTH_USER_MODEL)), - ('members', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), ], options={ 'db_table': 'group', @@ -42,4 +41,21 @@ class Migration(migrations.Migration): 'db_table': 'join_group_request', }, ), + migrations.CreateModel( + name='UserGroupRelation', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('join_time', models.DateTimeField(auto_now_add=True)), + ('group', models.ForeignKey(to='group.Group')), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'user_group_relation', + }, + ), + migrations.AddField( + model_name='group', + name='members', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='group.UserGroupRelation'), + ), ] diff --git a/group/models.py b/group/models.py index aa255f4..0508dc0 100644 --- a/group/models.py +++ b/group/models.py @@ -11,7 +11,7 @@ class Group(models.Model): admin = models.ForeignKey(User, related_name="my_groups") # 0是公开 1是需要申请后加入 2是不允许任何人加入 join_group_setting = models.IntegerField() - members = models.ManyToManyField(User) + members = models.ManyToManyField(User, through="UserGroupRelation") # 解散小组后,这一项改为False visible = models.BooleanField(default=True) @@ -19,6 +19,15 @@ class Group(models.Model): db_table = "group" +class UserGroupRelation(models.Model): + group = models.ForeignKey(Group) + user = models.ForeignKey(User) + join_time = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = "user_group_relation" + + class JoinGroupRequest(models.Model): group = models.ForeignKey(User) user = models.ForeignKey(User, related_name="my_join_group_requests") diff --git a/group/serializers.py b/group/serializers.py index bd655ed..6c40eae 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -1,7 +1,8 @@ # coding=utf-8 from rest_framework import serializers -from .models import Group +from account.serializers import UserSerializer +from .models import Group, UserGroupRelation class CreateGroupSerializer(serializers.Serializer): @@ -11,17 +12,31 @@ class CreateGroupSerializer(serializers.Serializer): class EditGroupSerializer(serializers.Serializer): + group_id = serializers.IntegerField() name = serializers.CharField(max_length=20) description = serializers.CharField(max_length=300) join_group_setting = serializers.IntegerField() class JoinGroupRequestSerializer(serializers.Serializer): - group = serializers.IntegerField() + group_id = serializers.IntegerField() message = serializers.CharField(max_length=30) class GroupSerializer(serializers.ModelSerializer): class Meta: model = Group - exclude = ["members"] \ No newline at end of file + exclude = ["members"] + + +class GroupMemberSerializer(serializers.ModelSerializer): + user = UserSerializer() + + class Meta: + model = UserGroupRelation + exclude = ["id", "group"] + + +class EditGroupMemberSerializer(serializers.Serializer): + group_id = serializers.IntegerField() + members = serializers.ListField(child=serializers.IntegerField()) \ No newline at end of file diff --git a/group/views.py b/group/views.py index 5ccdcd9..cd8ac54 100644 --- a/group/views.py +++ b/group/views.py @@ -5,18 +5,45 @@ from rest_framework.views import APIView from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN +from account.decorators import login_required -from .models import Group, JoinGroupRequest +from .models import Group, JoinGroupRequest, UserGroupRelation from .serializers import (CreateGroupSerializer, EditGroupSerializer, - JoinGroupRequestSerializer, GroupSerializer) + JoinGroupRequestSerializer, GroupSerializer, + GroupMemberSerializer, EditGroupMemberSerializer) -class GroupAdminAPIView(APIView): +class GroupAPIViewBase(object): + def get_group(self, request, group_id): + """ + 根据group_id查询指定的小组的信息,结合判断用户权限 + 管理员可以查询所有的小组,其他用户查询自己创建的自傲组 + """ + if request.user.admin_type == SUPER_ADMIN: + group = Group.objects.get(id=group_id, visible=True) + else: + group = Group.objects.get(id=group_id, visible=True, admin=request.user) + return group + + def get_groups(self, request): + """ + 如果是超级管理员,就返回全部的小组 + 如果是管理员,就返回他创建的全部小组 + """ + if request.user.admin_type == SUPER_ADMIN: + groups = Group.objects.filter(visible=True) + else: + groups = Group.objects.filter(admin=request.user, visible=True) + return groups + + +class GroupAdminAPIView(APIView, GroupAPIViewBase): def post(self, request): """ 创建小组的api --- request_serializer: CreateGroupSerializer + response_serializer: GroupSerializer """ serializer = CreateGroupSerializer(data=request.data) if serializer.is_valid(): @@ -34,12 +61,13 @@ class GroupAdminAPIView(APIView): 修改小组信息的api --- request_serializer: EditGroupSerializer + response_serializer: GroupSerializer """ serializer = EditGroupSerializer(data=request.data) if serializer.is_valid(): data = serializer.data try: - group = Group.objects.get(id=data["id"], admin=request.user) + group = self.get_group(request, data["group_id"]) except Group.DoesNotExist: return error_response(u"小组不存在") group.name = data["name"] @@ -52,21 +80,96 @@ class GroupAdminAPIView(APIView): def get(self, request): """ - 查询小组列表或者单个小组的信息 + 查询小组列表或者单个小组的信息,查询单个小组需要传递group_id参数,否则返回全部 + --- + response_serializer: GroupSerializer """ group_id = request.GET.get("group_id", None) if group_id: try: - if request.user.admin_type == SUPER_ADMIN: - group = Group.object.get(id=group_id) - else: - group = Group.object.get(id=group_id, admin=request.user) + group = self.get_group(request, group_id) return success_response(GroupSerializer(group).data) except Group.DoesNotExist: return error_response(u"小组不存在") else: - if request.user.admin_type == SUPER_ADMIN: - groups = Group.objects.filter(visible=True) - else: - groups = Group.objects.filter(admin=request.user, visible=True) - return paginate(request, groups, GroupSerializer) \ No newline at end of file + groups = self.get_groups(request) + return paginate(request, groups, GroupSerializer) + + +class GroupMemberAdminAPIView(APIView, GroupAPIViewBase): + def get(self, request): + """ + 查询小组成员的api,需要传递group_id参数 + --- + response_serializer: GroupMemberSerializer + """ + group_id = request.GET.get("group_id", None) + if not group_id: + return error_response(u"参数错误") + try: + group = self.get_group(request, group_id) + except Group.DoesNotExist: + return error_response(u"小组不存在") + + return paginate(request, UserGroupRelation.objects.filter(group=group), GroupMemberSerializer) + + def put(self, request): + """ + 删除小组成员的api接口 + --- + request_serializer: EditGroupMemberSerializer + """ + serializer = EditGroupMemberSerializer(data=request.data) + if serializer.is_valid(): + try: + group = self.get_group(request, serializer.data["group_id"]) + except Group.DoesNotExist: + return error_response(u"小组不存在") + user_id_list = serializer.data["members"] + UserGroupRelation.objects.filter(group=group, user__id__in=user_id_list).delete() + return success_response(u"删除成功") + else: + return serializer_invalid_response(serializer) + + +def join_group(user, group): + return UserGroupRelation.objects.create(user=user, group=group) + + +class JoinGroupAPIView(APIView): + @login_required + def post(self, request): + """ + 加入某个小组的api + --- + request_serializer: JoinGroupRequestSerializer + """ + serializer = JoinGroupRequestSerializer(data=request.data) + if serializer.is_valid(): + data = serializer.data + try: + group = Group.objects.get(id=data["group_id"]) + except Group.DesoNotExist: + return error_response(u"小组不存在") + if group.join_group_setting == 0: + join_group(request.user, group) + return success_response(u"你已经成功的加入该小组") + elif group.join_group_setting == 1: + return success_response(u"申请提交成功,请等待审核") + elif group.join_group_setting == 2: + return error_response(u"该小组不允许任何人加入") + else: + return serializer_invalid_response(serializer) + + def get(self, request): + """ + 搜素小组的api,需要传递keyword参数 + --- + response_serializer: GroupSerializer + """ + keyword = request.GET.get("keyword", None) + if not keyword: + return error_response(u"参数错误") + # 搜索包含这个关键词的 没有解散的 而且允许加入的小组 + groups = Group.objects.filter(name__contains=keyword, visible=True, join_group_setting__lte=2) + return paginate(request, groups, GroupSerializer) diff --git a/install/__init__.py b/install/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/install/admin.py b/install/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/install/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/install/migrations/__init__.py b/install/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/install/models.py b/install/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/install/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/install/tests.py b/install/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/install/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/install/views.py b/install/views.py new file mode 100644 index 0000000..521d8a6 --- /dev/null +++ b/install/views.py @@ -0,0 +1,12 @@ +# coding=utf-8 +from django.shortcuts import render +from django.http import HttpResponse + +from account.models import User + + +def install(request): + user = User.objects.create(username="root", admin_type=2) + user.set_password("root") + user.save() + return HttpResponse("success") \ No newline at end of file diff --git a/oj/urls.py b/oj/urls.py index b2eecb9..bcfb211 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -9,8 +9,11 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView from group.views import GroupAdminAPIView from admin.views import AdminTemplateView +from problem.views import TestCaseUploadAPIView + urlpatterns = [ + url(r'^install/$', "install.views.install"), url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"), url(r'^docs/', include('rest_framework_swagger.urls')), url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"), @@ -27,10 +30,12 @@ urlpatterns = [ url(r'^problem/(?P\d+)/$', "problem.views.problem_page", name="problem_page"), url(r'^api/announcements/$', AnnouncementAPIView.as_view(), name="announcement_list_api"), - url(r'^api/users/$', UserAPIView.as_view(), name="user_list_api"), + url(r'^api/admin/users/$', UserAPIView.as_view(), name="user_list_api"), 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'^admin/template/(?P\w+)/(?P\w+).html', AdminTemplateView.as_view(), name="admin_template"), url(r'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"), + + url(r'^api/admin/test_case_upload/$', TestCaseUploadAPIView.as_view(), name="test_case_upload_api"), ] diff --git a/problem/views.py b/problem/views.py index 58fb9e6..0d9d2b1 100644 --- a/problem/views.py +++ b/problem/views.py @@ -1,7 +1,98 @@ # coding=utf-8 +import zipfile +import re +import os +import hashlib +import json + from django.shortcuts import render +from rest_framework.views import APIView + +from utils.shortcuts import rand_str, error_response, success_response + def problem_page(request, problem_id): # todo return render(request, "oj/problem/problem.html") + + +class TestCaseUploadAPIView(APIView): + def _is_legal_test_case_file_name(self, file_name): + # 正整数开头的 .in 或者.out 结尾的 + regex = r"^[1-9]\d*\.(in|out)$" + return re.compile(regex).match(file_name) is not None + + def post(self, request): + if "file" not in request.FILES: + return error_response(u"文件上传失败") + + f = request.FILES["file"] + + tmp_zip = "tmp/" + rand_str() + ".zip" + with open(tmp_zip, "wb") as test_case_zip: + for chunk in f: + test_case_zip.write(chunk) + + test_case_file = zipfile.ZipFile(tmp_zip, 'r') + name_list = test_case_file.namelist() + + l = [] + + # 如果文件是直接打包的,那么name_list 就是["1.in", "1.out"]这样的 + # 如果文件还有一层文件夹test_case,那么name_list就是["test_case/", "test_case/1.in", "test_case/1.out"] + # 现在暂时只支持第一种,先判断一下是什么格式的 + + # 第一种格式的 + if "1.in" in name_list and "1.out" in name_list: + for file_name in name_list: + if self._is_legal_test_case_file_name(file_name): + name = file_name.split(".") + # 有了.in 判断对应的.out 在不在 + if name[1] == "in": + if (name[0] + ".out") in name_list: + l.append(file_name) + else: + return error_response(u"测试用例文件不完整,缺少" + name[0] + ".out") + else: + # 有了.out 判断对应的 .in 在不在 + if (name[0] + ".in") in name_list: + l.append(file_name) + else: + return error_response(u"测试用例文件不完整,缺少" + name[0] + ".in") + + problem_test_dir = rand_str() + test_case_dir = "test_case/" + problem_test_dir + "/" + + # 得到了合法的测试用例文件列表 然后去解压缩 + os.mkdir(test_case_dir) + for name in l: + f = open(test_case_dir + name, "wb") + f.write(test_case_file.read(name)) + f.close() + l.sort() + + file_info = {"test_case_number": len(l) / 2, "test_cases": {}} + + # 计算输出文件的md5 + for i in range(len(l) / 2): + md5 = hashlib.md5() + f = open(test_case_dir + str(i + 1) + ".out", "r") + while True: + data = f.read(2 ** 8) + if not data: + break + md5.update(data) + + file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in", + "output_name": str(i + 1) + ".out", + "output_md5": md5.hexdigest(), + "output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")} + # 写入配置文件 + open(test_case_dir + "info", "w").write(json.dumps(file_info)) + + return success_response({"test_case_id": problem_test_dir, + "file_list": {"input": l[0::2], + "output": l[1::2]}}) + else: + return error_response(u"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩") diff --git a/static/src/js/utils/csrf.js b/static/src/js/utils/csrf.js index 767542b..6a004b4 100644 --- a/static/src/js/utils/csrf.js +++ b/static/src/js/utils/csrf.js @@ -9,8 +9,15 @@ define("csrf",function(){ } return ""; } - function csrfHeader(xhr){ - xhr.setRequestHeader("X-CSRFToken", get_cookie("csrftoken")); + function csrfHeader(){ + // jquery的请求 + if(arguments.length == 1) { + arguments[0].setRequestHeader("X-CSRFToken", get_cookie("csrftoken")); + } + // 百度webuploader 的请求 + else if(arguments.length == 3){ + arguments[2]["X-CSRFToken"] = get_cookie("csrftoken"); + } } return csrfHeader; }); diff --git a/utils/shortcuts.py b/utils/shortcuts.py index 44610a9..a254346 100644 --- a/utils/shortcuts.py +++ b/utils/shortcuts.py @@ -1,4 +1,8 @@ # coding=utf-8 +import hashlib +import time +import random + from django.core.paginator import Paginator from rest_framework.response import Response @@ -87,3 +91,8 @@ def paginate(request, query_set, object_serializer): pass return success_response(data) + + +def rand_str(length=32): + string = hashlib.md5(str(time.time()) + str(random.randrange(1, 9999999900))).hexdigest() + return string[0:length] \ No newline at end of file