diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..4da86dc --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openvj.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/openvj/__init__.py b/openvj/__init__.py index 9bad579..e69de29 100644 --- a/openvj/__init__.py +++ b/openvj/__init__.py @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/openvj/local_settings.py b/openvj/local_settings.py new file mode 100644 index 0000000..d15e39f --- /dev/null +++ b/openvj/local_settings.py @@ -0,0 +1,21 @@ +# coding=utf-8 +import os + + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} \ No newline at end of file diff --git a/openvj/server_settings.py b/openvj/server_settings.py new file mode 100644 index 0000000..d15e39f --- /dev/null +++ b/openvj/server_settings.py @@ -0,0 +1,21 @@ +# coding=utf-8 +import os + + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} \ No newline at end of file diff --git a/openvj/settings.py b/openvj/settings.py index a3e129a..81376f9 100644 --- a/openvj/settings.py +++ b/openvj/settings.py @@ -1,12 +1,118 @@ # coding=utf-8 +""" +Django settings for openvj project. -DB_HOST = "127.0.0.1" -DB_USER = "root" -DB_PASSWORD = "root" -DB_DB = "openvj" +Generated by 'django-admin startproject' using Django 1.9.1. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.9/ref/settings/ +""" + +import os + +# 判断运行环境 +ENV = os.environ.get("oj_env", "local") + +if ENV == "local": + from .local_settings import * +elif ENV == "server": + from .server_settings import * + +from .custom_settings import * + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -INSTALLED_ROBOTS = {"pat": {"robot": "robots.pat.PATRobot"}, - "hduoj": {"robot": "robots.hduoj.HduojRobot"}} +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ -LOG_LEVEL = "DEBUG" + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'server', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'openvj.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'openvj.wsgi.application' + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'zh-hans' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/openvj/urls.py b/openvj/urls.py new file mode 100644 index 0000000..fe7dbf4 --- /dev/null +++ b/openvj/urls.py @@ -0,0 +1,21 @@ +"""openvj URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.9/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), +] diff --git a/openvj/wsgi.py b/openvj/wsgi.py new file mode 100644 index 0000000..9180ccf --- /dev/null +++ b/openvj/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for openvj project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openvj.settings") + +application = get_wsgi_application() diff --git a/server/__init__.py b/server/__init__.py index 9bad579..e69de29 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/server/admin.py b/server/admin.py new file mode 100644 index 0000000..1168097 --- /dev/null +++ b/server/admin.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from django.contrib import admin +from .models import OJ, RobotUser, RobotStatusInfo, Problem, Submission + +admin.site.register(OJ) +admin.site.register(RobotUser) +admin.site.register(RobotStatusInfo) +admin.site.register(Problem) +admin.site.register(Submission) diff --git a/server/apps.py b/server/apps.py new file mode 100644 index 0000000..65b3302 --- /dev/null +++ b/server/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class ServerConfig(AppConfig): + name = 'server' diff --git a/server/db.py b/server/db.py deleted file mode 100644 index 18fcdf4..0000000 --- a/server/db.py +++ /dev/null @@ -1,48 +0,0 @@ -# coding=utf-8 -import pymysql -from openvj.settings import DB_HOST, DB_USER, DB_PASSWORD, DB_DB - - -class ObjectDoesNotExist(Exception): - pass - - -class MultiObjectReturned(Exception): - pass - - -class DBHandler(object): - def __enter__(self): - self.connection = pymysql.connect(host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, - db=DB_DB, - charset="utf8", - cursorclass=pymysql.cursors.DictCursor) - return self - - def filter(self, sql, args): - with self.connection.cursor() as cursor: - cursor.execute(sql, args) - return cursor.fetchall() - - def get(self, sql, args): - r = self.filter(sql, args) - if not r: - raise ObjectDoesNotExist() - if len(r) > 1: - raise MultiObjectReturned() - return r - - def first(self, sql, args): - r = self.filter(sql, args) - if not r: - raise ObjectDoesNotExist() - else: - return r[0] - - def _close(self): - self.connection.close() - - def __exit__(self, exc_type, exc_val, exc_tb): - self._close() diff --git a/server/db.sql b/server/db.sql deleted file mode 100644 index 98d416e..0000000 --- a/server/db.sql +++ /dev/null @@ -1,73 +0,0 @@ -CREATE DATABASE IF NOT EXISTS openvj DEFAULT CHARACTER SET utf8; -use openvj; - - -CREATE TABLE IF NOT EXISTS oj ( - id VARCHAR(40) PRIMARY KEY, - name VARCHAR(50) NOT NULL UNIQUE, - status SMALLINT DEFAULT 1 NOT NULL -); - - -CREATE TABLE IF NOT EXISTS robot_user( - id VARCHAR(40) PRIMARY KEY, - oj_id VARCHAR(40) NOT NULL, - username VARCHAR(50) NOT NULL, - password VARCHAR(50) NOT NULL, - status SMALLINT NOT NULL, - last_login_time DATETIME NOT NULL, - FOREIGN KEY (oj_id) REFERENCES oj(id), - UNIQUE KEY oj_username (oj_id, username) -); - - -CREATE TABLE IF NOT EXISTS robot_status( - name VARCHAR(40) NOT NULL, - status SMALLINT NOT NULL, - info TEXT NOT NULL, - oj_id VARCHAR(40) NOT NULL, - robot_user_id VARCHAR(40) NOT NULL, - FOREIGN KEY (oj_id) REFERENCES oj(id), - FOREIGN KEY (robot_user_id) REFERENCES robot_user(id) -); - - -CREATE TABLE IF NOT EXISTS apikey ( - apikey VARCHAR(40) PRIMARY KEY, - name VARCHAR(50) NOT NULL , - create_time DATETIME DEFAULT NOW() NOT NULL, - is_valid SMALLINT DEFAULT 1 NOT NULL -); - - -CREATE TABLE IF NOT EXISTS problem ( - id VARCHAR(40) PRIMARY KEY, - oj_id VARCHAR(40) NOT NULL, - spj SMALLINT NOT NULL, - url VARCHAR(200) NOT NULL, - submit_url VARCHAR(200) NOT NULL, - title VARCHAR(100) NOT NULL, - description TEXT NOT NULL, - time_limit INTEGER NOT NULL, - memory_limit INTEGER NOT NULL, - input_description TEXT NOT NULL, - output_desription TEXT NOT NULL, - samples TEXT NOT NULL, - is_valid SMALLINT DEFAULT 1 NOT NULL, - /* 可能是分为正在爬取 爬取完成 爬取失败等状态 */ - status SMALLINT NOT NULL, - FOREIGN KEY (oj_id) REFERENCES oj(id) -); - - -CREATE TABLE IF NOT EXISTS submission ( - id VARCHAR(40) PRIMARY KEY, - problem_id VARCHAR(40) NOT NULL, - result SMALLINT NOT NULL, - cpu_time INTEGER, - memory INTEGER, - info TEXT, - user_id VARCHAR(40) NOT NULL, - FOREIGN KEY (problem_id) REFERENCES problem(id), - FOREIGN KEY (user_id) REFERENCES robot_user(id) -); \ No newline at end of file diff --git a/server/migrations/0001_initial.py b/server/migrations/0001_initial.py new file mode 100644 index 0000000..9ebf2d8 --- /dev/null +++ b/server/migrations/0001_initial.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-08 08:20 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='APIKey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('api_key', models.CharField(max_length=40)), + ('name', models.CharField(max_length=40)), + ('is_valid', models.BooleanField(default=True)), + ('create_time', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'db_table': 'api_key', + }, + ), + migrations.CreateModel( + name='OJ', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30)), + ('is_valid', models.BooleanField(default=True)), + ], + options={ + 'db_table': 'oj', + }, + ), + migrations.CreateModel( + name='Problem', + 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()), + ('is_valid', models.BooleanField(default=True)), + ('status', models.IntegerField()), + ('task_id', models.CharField(max_length=40)), + ('oj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.OJ')), + ], + options={ + 'db_table': 'problem', + }, + ), + migrations.CreateModel( + name='RobotStatusInfo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status_info', models.TextField()), + ('last_update_time', models.DateTimeField(auto_now=True)), + ], + options={ + 'db_table': 'robot_status_info', + }, + ), + migrations.CreateModel( + name='RobotUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('username', models.CharField(max_length=30)), + ('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)])), + ('oj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.OJ')), + ], + options={ + 'db_table': 'robot_user', + }, + ), + migrations.CreateModel( + name='Submission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('result', models.IntegerField()), + ('cpu_time', models.IntegerField()), + ('memory', models.IntegerField()), + ('info', models.TextField()), + ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.Problem')), + ('robot_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.RobotUser')), + ], + options={ + 'db_table': 'submission', + }, + ), + migrations.AddField( + model_name='robotstatusinfo', + name='robot_user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='server.RobotUser'), + ), + migrations.AlterUniqueTogether( + name='robotuser', + unique_together=set([('oj', 'username')]), + ), + ] diff --git a/server/migrations/__init__.py b/server/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/models.py b/server/models.py new file mode 100644 index 0000000..62420cd --- /dev/null +++ b/server/models.py @@ -0,0 +1,90 @@ +# coding=utf-8 +from __future__ import unicode_literals + +from django.db import models + + +class OJ(models.Model): + name = models.CharField(max_length=30) + is_valid = models.BooleanField(default=True) + + class Meta: + db_table = "oj" + + +class APIKey(models.Model): + api_key = models.CharField(max_length=40) + name = models.CharField(max_length=40) + is_valid = models.BooleanField(default=True) + create_time= models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = "api_key" + + +class RobotUserStatus(object): + occupied = 1 + free = 0 + + +class RobotUser(models.Model): + oj = models.ForeignKey(OJ) + username = models.CharField(max_length=30) + password = models.CharField(max_length=30) + is_valid = models.BooleanField(default=True) + last_login_time = models.DateTimeField() + status = models.IntegerField(choices=((RobotUserStatus.occupied, "Occupied"), (RobotUserStatus.free, "Free"))) + + class Meta: + db_table = "robot_user" + unique_together = (("oj", "username"), ) + + +class RobotStatusInfo(models.Model): + status_info = models.TextField() + robot_user = models.ForeignKey(RobotUser) + last_update_time = models.DateTimeField(auto_now=True) + + class Meta: + db_table = "robot_status_info" + + +class ProblemStatus(models.Model): + done = 0 + crawling = 1 + failed = 2 + + +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() + is_valid = models.BooleanField(default=True) + status = models.IntegerField(choices=((ProblemStatus.done, "Done"), + (ProblemStatus.crawling, "Crawling"), + (ProblemStatus.failed, "Failed"))) + task_id = models.CharField(max_length=40) + + class Meta: + db_table = "problem" + + +class Submission(models.Model): + problem = models.ForeignKey(Problem) + result = models.IntegerField() + cpu_time = models.IntegerField() + memory = models.IntegerField() + info = models.TextField() + robot_user = models.ForeignKey(RobotUser) + + class Meta: + db_table = "submission" diff --git a/server/server.py b/server/server.py deleted file mode 100644 index 6f97096..0000000 --- a/server/server.py +++ /dev/null @@ -1,101 +0,0 @@ -# coding=utf-8 -import json -import logging -import traceback -from bottle import route, get, post, run, response, request, Bottle, install - -from openvj.settings import INSTALLED_ROBOTS, LOG_LEVEL -from .db import DBHandler, ObjectDoesNotExist - - -logging.basicConfig(filename='server.log', level=LOG_LEVEL, - format="%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] " - "[%(module)s:%(funcName)s] [%(levelname)s]- %(message)s") - - -app = Bottle() - - -def content_type_plugin(callback): - def wrapper(*args, **kwargs): - body = callback(*args, **kwargs) - response.content_type = "application/json; charset=utf-8" - return body - return wrapper - - -def apikey_auth_plugin(callback): - def wrapper(*args, **kwargs): - ''' - api_key = request.headers.get("VJ_API_KEY") - if not api_key: - return error("Invalid VJ_API_KEY") - with DBHandler() as db: - try: - db.get("SELECT apikey FROM apikey WHERE apikey = %s and is_valid = 1", (api_key, )) - except ObjectDoesNotExist: - return error("VJ_API_KEY does not exist") - ''' - request.api_key = "123456" - return callback(*args, **kwargs) - return wrapper - - -def server_log_plugin(callback): - def wrapper(*args, **kwargs): - try: - return callback(*args, **kwargs) - except Exception: - logging.error(traceback.format_exc()) - return error("Server error") - return wrapper - - -def error(reason): - return json.dumps({"code": 1, "data": reason}) - - -def success(data): - return json.dumps({"code": 0, "data": data}) - - -def parameter_error(message="参数错误"): - return error(message) - - -@route("/") -def index(): - return success("It works") - - -def import_class(cl): - d = cl.rfind(".") - classname = cl[d+1:len(cl)] - m = __import__(cl[0:d], globals(), locals(), [classname]) - return getattr(m, classname) - - -@get("/problem/") -def get_problem(): - oj = request.GET.get("oj") - url = request.GET.get("url") - if not (oj and url): - return parameter_error() - if oj not in INSTALLED_ROBOTS: - return error("oj不存在") - Robot = import_class(INSTALLED_ROBOTS[oj]["robot"]) - with DBHandler() as db: - robot_info = db.first("select robot_status.info " - "from robot_status, oj " - "where oj.name = %s and robot_status.oj_id = oj.id", (oj, )) - robot = Robot(**json.loads(robot_info["info"])) - if not robot.check_url(url): - return error("url格式错误") - problem = robot.get_problem(url) - return success(problem) - - -install(content_type_plugin) -install(apikey_auth_plugin) -install(server_log_plugin) -run(host='127.0.0.1', port=8081, server='gunicorn', workers=4, debug=True) \ No newline at end of file diff --git a/server/tasks.py b/server/tasks.py deleted file mode 100644 index 9bad579..0000000 --- a/server/tasks.py +++ /dev/null @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/server/tests.py b/server/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/views.py b/server/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/server/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.