diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5cdd4bc --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +SECRET_KEY=change-me +DEBUG=true +ALLOWED_HOSTS=* + +# LLM(OpenAI 兼容接口) +LLM_BASE_URL=https://api.openai.com/v1 +LLM_API_KEY=sk-placeholder +LLM_MODEL=gpt-4o-mini + +# 检测执行 +MAX_UPLOAD_SIZE_MB=200 +STABILITY_BASE_DURATION=30 +STABILITY_RAMPUP_DURATION=60 +STABILITY_CONCURRENCY=20 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ffc76b --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +venv/ +db.sqlite3 +media/ +workspace/ +.env +.pytest_cache/ +.coverage +htmlcov/ +.venv/ +*.log +.DS_Store diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..f86af57 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ojplatform.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/ojplatform/__init__.py b/ojplatform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ojplatform/asgi.py b/ojplatform/asgi.py new file mode 100644 index 0000000..3f84adc --- /dev/null +++ b/ojplatform/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ojplatform project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ojplatform.settings') + +application = get_asgi_application() diff --git a/ojplatform/settings.py b/ojplatform/settings.py new file mode 100644 index 0000000..9160216 --- /dev/null +++ b/ojplatform/settings.py @@ -0,0 +1,110 @@ +""" +Django settings for ojplatform project. +""" + +import os +from pathlib import Path + +from dotenv import load_dotenv + +BASE_DIR = Path(__file__).resolve().parent.parent + +load_dotenv(BASE_DIR / ".env") + +SECRET_KEY = os.getenv("SECRET_KEY", "django-insecure-dev-only-change-me") + +DEBUG = os.getenv("DEBUG", "true").lower() in ("1", "true", "yes") + +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",") + + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'rest_framework.authtoken', + 'corsheaders', + 'requirements', + 'submissions', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'ojplatform.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'ojplatform.wsgi.application' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +AUTH_PASSWORD_VALIDATORS = [ + {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, +] + +LANGUAGE_CODE = 'zh-hans' +TIME_ZONE = 'Asia/Shanghai' +USE_I18N = True +USE_TZ = True + +STATIC_URL = 'static/' +MEDIA_URL = 'media/' +MEDIA_ROOT = BASE_DIR / 'media' + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.TokenAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], + 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', +} + +CORS_ALLOW_ALL_ORIGINS = DEBUG + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# LLM 配置(OpenAI 兼容接口) +LLM_BASE_URL = os.getenv("LLM_BASE_URL", "") +LLM_API_KEY = os.getenv("LLM_API_KEY", "") +LLM_MODEL = os.getenv("LLM_MODEL", "") + +# 检测执行配置 +WORKSPACE_DIR = BASE_DIR / "workspace" +MAX_UPLOAD_SIZE_MB = int(os.getenv("MAX_UPLOAD_SIZE_MB", "200")) +STABILITY_BASE_DURATION = int(os.getenv("STABILITY_BASE_DURATION", "30")) +STABILITY_RAMPUP_DURATION = int(os.getenv("STABILITY_RAMPUP_DURATION", "60")) +STABILITY_CONCURRENCY = int(os.getenv("STABILITY_CONCURRENCY", "20")) diff --git a/ojplatform/urls.py b/ojplatform/urls.py new file mode 100644 index 0000000..f4e6e6b --- /dev/null +++ b/ojplatform/urls.py @@ -0,0 +1,15 @@ +"""OJplatform URL 配置""" +from django.conf import settings +from django.conf.urls.static import static +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('requirements.urls')), + path('api/', include('submissions.urls')), + path('api/auth/login/', include('rest_framework.urls')), +] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/ojplatform/wsgi.py b/ojplatform/wsgi.py new file mode 100644 index 0000000..660370e --- /dev/null +++ b/ojplatform/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ojplatform 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/6.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ojplatform.settings') + +application = get_wsgi_application() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..63ee462 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "ojplatform" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +authors = [ + { name = "hyh", email = "hyh@itouc.cn" } +] +requires-python = ">=3.13" +dependencies = [ + "django>=6.1", + "djangorestframework[authtoken]>=3.18", + "django-cors-headers>=4.7", + "python-dotenv>=1.2.3", +] + +[tool.uv] +package = false + + diff --git a/requirements/__init__.py b/requirements/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements/admin.py b/requirements/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/requirements/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/requirements/apps.py b/requirements/apps.py new file mode 100644 index 0000000..318e6a4 --- /dev/null +++ b/requirements/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class RequirementsConfig(AppConfig): + name = 'requirements' diff --git a/requirements/migrations/__init__.py b/requirements/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements/models.py b/requirements/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/requirements/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/requirements/tests.py b/requirements/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/requirements/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/requirements/views.py b/requirements/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/requirements/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/submissions/__init__.py b/submissions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submissions/admin.py b/submissions/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/submissions/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/submissions/apps.py b/submissions/apps.py new file mode 100644 index 0000000..651db95 --- /dev/null +++ b/submissions/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SubmissionsConfig(AppConfig): + name = 'submissions' diff --git a/submissions/migrations/__init__.py b/submissions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submissions/models.py b/submissions/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/submissions/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/submissions/tests.py b/submissions/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/submissions/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/submissions/views.py b/submissions/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/submissions/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/test_settings.py b/test_settings.py new file mode 100644 index 0000000..e3fe963 --- /dev/null +++ b/test_settings.py @@ -0,0 +1,19 @@ +import os +from pathlib import Path +from dotenv import load_dotenv + +BASE_DIR = Path(__file__).resolve().parent.parent +load_dotenv(BASE_DIR / ".env") + +SECRET_KEY = os.getenv("SECRET_KEY", "django-insecure-dev-only-change-me") +DEBUG = os.getenv("DEBUG", "true").lower() in ("1", "true", "yes") +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",") +INSTALLED_APPS = ["django.contrib.contenttypes", "django.contrib.auth"] +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +USE_TZ = True diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..af55208 --- /dev/null +++ b/uv.lock @@ -0,0 +1,97 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "asgiref" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, +] + +[[package]] +name = "django" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/42/6cb20996733984c1f6661daeda3877990836c76c633c6c8879d39f7120eb/django-6.1.tar.gz", hash = "sha256:86a2aacd59b817e4d6ac2ebfe22356c58f66f7b24e503f71b7c2fead677ee48b", size = 11223034, upload-time = "2026-08-05T19:21:53.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/9c/ce847620134cfab903e75690c498af73b46abbede2912ea89bd76d5c1e76/django-6.1-py3-none-any.whl", hash = "sha256:6c132cd980c9392b06807d4ca52d72530d631dc65a85d9dacede00a780cefbbe", size = 8417399, upload-time = "2026-08-05T19:21:47.285Z" }, +] + +[[package]] +name = "django-cors-headers" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/39/55822b15b7ec87410f34cd16ce04065ff390e50f9e29f31d6d116fc80456/django_cors_headers-4.9.0.tar.gz", hash = "sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8", size = 21458, upload-time = "2025-09-18T10:40:52.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" }, +] + +[[package]] +name = "djangorestframework" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/bc/de04e3d4dc65e8b926700956ee70d4f084f2005603d21122d4d0683006fd/djangorestframework-3.18.0.tar.gz", hash = "sha256:2323a5111837e0b784dcb8323abc78ecc54fa2a5af7aff2677cf50cdd849477f", size = 913667, upload-time = "2026-08-07T14:53:33.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/d7/7461738e819b853275b35fd35712a85806c09a327e1fd5f8ff766bdd317e/djangorestframework-3.18.0-py3-none-any.whl", hash = "sha256:381fc44d3249c9565c5f723850855b734e99030eb30957a49f506d3fe11d7dcb", size = 900032, upload-time = "2026-08-07T14:53:31.881Z" }, +] + +[[package]] +name = "ojplatform" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "django" }, + { name = "django-cors-headers" }, + { name = "djangorestframework" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "django", specifier = ">=6.1" }, + { name = "django-cors-headers", specifier = ">=4.7" }, + { name = "djangorestframework", extras = ["authtoken"], specifier = ">=3.18" }, + { name = "python-dotenv", specifier = ">=1.2.3" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +]