diff --git a/openvj/__init__.py b/openvj/__init__.py index e69de29..ce0e1f4 100644 --- a/openvj/__init__.py +++ b/openvj/__init__.py @@ -0,0 +1,3 @@ +# coding=utf-8 +from __future__ import absolute_import +from .celery import app as celery_app \ No newline at end of file diff --git a/openvj/celery.py b/openvj/celery.py new file mode 100644 index 0000000..3b70532 --- /dev/null +++ b/openvj/celery.py @@ -0,0 +1,19 @@ +# coding=utf-8 +from __future__ import absolute_import +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openvj.settings') + +from django.conf import settings + +app = Celery('openvj') + +# Using a string here means the worker will not have to +# pickle the object when using Windows. +app.config_from_object('django.conf:settings') + +# load task modules from all registered Django app configs. +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) \ No newline at end of file diff --git a/openvj/local_settings.py b/openvj/local_settings.py index d15e39f..709aefe 100644 --- a/openvj/local_settings.py +++ b/openvj/local_settings.py @@ -18,4 +18,19 @@ DATABASES = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } -} \ No newline at end of file +} + + +REDIS = { + "HOST": "127.0.0.1", + "PORT": 6379, + "PASSWORD": "123456" +} + + +# celery配置 +BROKER_URL = "redis://{host}:{port}/{db}".format(host=REDIS["HOST"], port=REDIS["PORT"], db=0) +CELERY_RESULT_BACKEND = "redis" +CELERY_REDIS_HOST = REDIS["HOST"] +CELERY_REDIS_PORT = REDIS["PORT"] +CELERY_REDIS_DB = 0 \ No newline at end of file diff --git a/openvj/server_settings.py b/openvj/server_settings.py index d15e39f..cd8cab9 100644 --- a/openvj/server_settings.py +++ b/openvj/server_settings.py @@ -18,4 +18,6 @@ DATABASES = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } -} \ No newline at end of file +} + +BROKER_URL = 'amqp://guest:guest@localhost//' \ No newline at end of file diff --git a/openvj/settings.py b/openvj/settings.py index 81376f9..b971521 100644 --- a/openvj/settings.py +++ b/openvj/settings.py @@ -44,7 +44,9 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', 'server', + ] MIDDLEWARE_CLASSES = [ @@ -103,7 +105,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'zh-hans' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Shanghai' USE_I18N = True @@ -115,4 +117,4 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.9/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = '/static/' \ No newline at end of file diff --git a/openvj/urls.py b/openvj/urls.py index fe7dbf4..15f5eb6 100644 --- a/openvj/urls.py +++ b/openvj/urls.py @@ -16,6 +16,9 @@ Including another URLconf from django.conf.urls import url from django.contrib import admin +from server.views import ProblemAPIView + urlpatterns = [ url(r'^admin/', admin.site.urls), + url(r'^problem/$', ProblemAPIView.as_view()), ] diff --git a/server/migrations/0001_initial.py b/server/migrations/0001_initial.py index 9ebf2d8..9fc6ec1 100644 --- a/server/migrations/0001_initial.py +++ b/server/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2016-03-08 08:20 +# Generated by Django 1.9.4 on 2016-03-09 06:08 from __future__ import unicode_literals from django.db import migrations, models @@ -32,6 +32,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=30)), + ('robot', models.CharField(max_length=50)), ('is_valid', models.BooleanField(default=True)), ], options={ @@ -43,18 +44,18 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('url', models.URLField()), - ('submit_url', models.URLField()), - ('title', models.CharField(max_length=100)), - ('description', models.TextField()), - ('time_limit', models.IntegerField()), - ('memory_limit', models.IntegerField()), - ('input_description', models.TextField()), - ('output_description', models.TextField()), - ('samples', models.TextField()), - ('spj', models.BooleanField()), - ('hint', models.TextField()), + ('submit_url', models.URLField(blank=True, null=True)), + ('title', models.CharField(blank=True, max_length=100, null=True)), + ('description', models.TextField(blank=True, null=True)), + ('time_limit', models.IntegerField(blank=True, null=True)), + ('memory_limit', models.IntegerField(blank=True, null=True)), + ('input_description', models.TextField(blank=True, null=True)), + ('output_description', models.TextField(blank=True, null=True)), + ('samples', models.TextField(blank=True, null=True)), + ('spj', models.BooleanField(default=False)), + ('hint', models.TextField(blank=True, null=True)), ('is_valid', models.BooleanField(default=True)), - ('status', models.IntegerField()), + ('status', models.IntegerField(choices=[(0, 'Done'), (1, 'Crawling'), (2, 'Failed')])), ('task_id', models.CharField(max_length=40)), ('oj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.OJ')), ], @@ -62,6 +63,12 @@ class Migration(migrations.Migration): 'db_table': 'problem', }, ), + migrations.CreateModel( + name='ProblemStatus', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), migrations.CreateModel( name='RobotStatusInfo', fields=[ @@ -81,7 +88,7 @@ class Migration(migrations.Migration): ('password', models.CharField(max_length=30)), ('is_valid', models.BooleanField(default=True)), ('last_login_time', models.DateTimeField()), - ('status', models.IntegerField(choices=[('Occupied', 1), ('free', 0)])), + ('status', models.IntegerField(choices=[(1, 'Occupied'), (0, 'Free')])), ('oj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.OJ')), ], options={ diff --git a/server/models.py b/server/models.py index 62420cd..060bf6c 100644 --- a/server/models.py +++ b/server/models.py @@ -6,11 +6,15 @@ from django.db import models class OJ(models.Model): name = models.CharField(max_length=30) + robot = models.CharField(max_length=50) is_valid = models.BooleanField(default=True) class Meta: db_table = "oj" + def __str__(self): + return self.name + class APIKey(models.Model): api_key = models.CharField(max_length=40) @@ -21,6 +25,9 @@ class APIKey(models.Model): class Meta: db_table = "api_key" + def __str__(self): + return self.name + class RobotUserStatus(object): occupied = 1 @@ -39,6 +46,9 @@ class RobotUser(models.Model): db_table = "robot_user" unique_together = (("oj", "username"), ) + def __str__(self): + return self.oj.name + " - " + self.username + class RobotStatusInfo(models.Model): status_info = models.TextField() @@ -48,6 +58,9 @@ class RobotStatusInfo(models.Model): class Meta: db_table = "robot_status_info" + def __str__(self): + return self.robot_user.username + self.status_info + class ProblemStatus(models.Model): done = 0 @@ -58,16 +71,16 @@ class ProblemStatus(models.Model): class Problem(models.Model): oj = models.ForeignKey(OJ) url = models.URLField() - submit_url = models.URLField() - title = models.CharField(max_length=100) - description = models.TextField() - time_limit = models.IntegerField() - memory_limit = models.IntegerField() - input_description = models.TextField() - output_description = models.TextField() - samples = models.TextField() - spj = models.BooleanField() - hint = models.TextField() + submit_url = models.URLField(blank=True, null=True) + title = models.CharField(max_length=100, blank=True, null=True) + description = models.TextField(blank=True, null=True) + time_limit = models.IntegerField(blank=True, null=True) + memory_limit = models.IntegerField(blank=True, null=True) + input_description = models.TextField(blank=True, null=True) + output_description = models.TextField(blank=True, null=True) + samples = models.TextField(blank=True, null=True) + spj = models.BooleanField(default=False) + hint = models.TextField(blank=True, null=True) is_valid = models.BooleanField(default=True) status = models.IntegerField(choices=((ProblemStatus.done, "Done"), (ProblemStatus.crawling, "Crawling"), @@ -77,6 +90,12 @@ class Problem(models.Model): class Meta: db_table = "problem" + def __str__(self): + if not self.title: + return self.oj.name + " Problem Status: " + str(self.status) + else: + return self.oj.name + " - " + self.title + class Submission(models.Model): problem = models.ForeignKey(Problem) diff --git a/server/serializers.py b/server/serializers.py new file mode 100644 index 0000000..9295bd2 --- /dev/null +++ b/server/serializers.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from rest_framework import serializers + +from .models import Problem + + +class ProblemSerializer(serializers.ModelSerializer): + class Meta: + model = Problem diff --git a/server/tasks.py b/server/tasks.py new file mode 100644 index 0000000..67fb793 --- /dev/null +++ b/server/tasks.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from __future__ import absolute_import +from celery import shared_task + + +@shared_task +def get_problem(robot, url): + return robot.get_problem(url) \ No newline at end of file diff --git a/server/views.py b/server/views.py index 91ea44a..9d66c5e 100644 --- a/server/views.py +++ b/server/views.py @@ -1,3 +1,80 @@ -from django.shortcuts import render +# coding=utf-8 +import json + +from rest_framework.views import APIView +from rest_framework.response import Response +from celery import states + + +from .serializers import ProblemSerializer +from .models import OJ, Problem, RobotUser, RobotStatusInfo, ProblemStatus +from .tasks import get_problem + + +def error_response(error_reason): + return Response(data={"code": 1, "data": error_reason}) + + +def serializer_invalid_response(serializer): + for k, v in serializer.errors.iteritems(): + return error_response(k + " : " + v[0]) + + +def success_response(data): + return Response(data={"code": 0, "data": data}) + + +def import_class(cl): + d = cl.rfind(".") + class_name = cl[d+1:len(cl)] + m = __import__(cl[0:d], globals(), locals(), [class_name]) + return getattr(m, class_name) + + +class ProblemAPIView(APIView): + def get(self, request): + oj = request.GET.get("oj") + url = request.GET.get("url") + if not (oj and url): + return error_response("参数错误") + try: + problem = Problem.objects.get(url=url, is_valid=True) + if problem.status == ProblemStatus.done: + return success_response(ProblemSerializer(problem).data) + elif problem.status == ProblemStatus.crawling: + task = get_problem.AsyncResult(problem.task_id) + if task.state == states.SUCCESS: + result = task.get() + problem.title = result["title"] + problem.submit_url = result["submit_url"] + problem.description = result["description"] + problem.time_limit = result["time_limit"] + problem.memory_limit = result["memory_limit"] + problem.input_description = result["input_description"] + problem.output_description = result["output_description"] + problem.samples = json.dumps(result["samples"]) + problem.status = ProblemStatus.done + problem.save() + return success_response(ProblemSerializer(problem).data) + elif task.state == states.FAILURE: + problem.status = ProblemStatus.failed + problem.save() + return success_response({"status": ProblemStatus.failed}) + elif problem.status == ProblemStatus.failed: + return success_response({"status": ProblemStatus.failed}) + except Problem.DoesNotExist: + pass + try: + oj = OJ.objects.get(name=oj, is_valid=True) + except OJ.DoesNotExist: + return error_response("不存在该oj") + robot_status = RobotStatusInfo.objects.filter(robot_user__oj=oj).first() + if not robot_status: + return error_response("登录信息错误") + robot = import_class(oj.robot)(**json.loads(robot_status.status_info)) + if not robot.check_url(url): + return error_response("url格式错误") + task_id = get_problem.delay(robot, url).id + Problem.objects.create(oj=oj, url=url, status=ProblemStatus.crawling, task_id=task_id) + return success_response({"status": ProblemStatus.crawling}) -# Create your views here.