Browse Source

added an initial version of a Django Backend

Dimitri Korsch 3 years ago
parent
commit
82a064a4bc

+ 2 - 0
backend/.gitignore

@@ -0,0 +1,2 @@
+secret.txt
+mysql.cnf

+ 22 - 0
backend/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', 'pycs_backend.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()

+ 6 - 0
backend/mysql.template.cnf

@@ -0,0 +1,6 @@
+[client]
+host = localhost
+port = 3306
+database = NAME
+user = USER
+password = PWD

+ 0 - 0
backend/pycs_api/__init__.py


+ 3 - 0
backend/pycs_api/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
backend/pycs_api/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class PycsApiConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'pycs_api'

+ 0 - 0
backend/pycs_api/migrations/__init__.py


+ 3 - 0
backend/pycs_api/models/__init__.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
backend/pycs_api/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 38 - 0
backend/pycs_api/urls.py

@@ -0,0 +1,38 @@
+"""pycs_backend URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/3.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.urls import path
+from django.urls import include
+from pycs_api import views
+
+urlpatterns = [
+    path('projects/',
+        views.ProjectView.as_view(), name="projects"),
+
+    path('projects/<int:project_id>/remove',
+        views.ProjectView.remove, name="remove_project"),
+
+    path('projects/<int:project_id>/name',
+        views.ProjectView.edit_name, name="edit_project_name"),
+
+    path('projects/<int:project_id>/description',
+        views.ProjectView.edit_description, name="edit_project_description"),
+
+    path('projects/<int:project_id>/external_storage',
+        views.ProjectView.run_external_storage, name="execute_external_storage"),
+
+    path('projects/<int:project_id>/label_provider',
+        views.ProjectView.run_label_provider, name="execute_label_provider"),
+]

+ 1 - 0
backend/pycs_api/views/__init__.py

@@ -0,0 +1 @@
+from pycs_api.views.project import ProjectView

+ 8 - 0
backend/pycs_api/views/base.py

@@ -0,0 +1,8 @@
+from django import http
+from django import views
+
+class JsonResponseView(views.View):
+
+    @classmethod
+    def respond(cls, obj = {}):
+        return http.JsonResponse(obj)

+ 57 - 0
backend/pycs_api/views/project.py

@@ -0,0 +1,57 @@
+
+from pycs_api.views.base import JsonResponseView
+from django.views.decorators.http import require_POST
+
+class ProjectView(JsonResponseView):
+
+    """
+        serves POST and GET for '/projects/'
+
+        staticmethods serve POST methods for
+            '/projects/<id>/name'
+            '/projects/<id>/description'
+            '/projects/<id>/remove'
+    """
+
+    http_method_names = ["get", "post"]
+
+
+    def get(self, request, *args, **kwargs):
+        """ lists projects """
+        return self.respond()
+
+    def post(self, request, *args, **kwargs):
+        """ creates a project """
+        return self.respond()
+
+
+    @require_POST
+    @classmethod
+    def remove(cls, request, project_id: int):
+        """ removes a project """
+        return cls.respond()
+
+    @require_POST
+    @classmethod
+    def edit_name(cls, request, project_id: int):
+        """ edit project's name """
+        return cls.respond()
+
+    @require_POST
+    @classmethod
+    def edit_description(cls, request, project_id: int):
+        """ edit project's description """
+        return cls.respond()
+
+    @require_POST
+    @classmethod
+    def run_external_storage(cls, request, project_id: int):
+        """ runs external storage routine """
+        return cls.respond()
+
+    @require_POST
+    @classmethod
+    def run_label_provider(cls, request, project_id: int):
+        """ runs label provider routine """
+        return cls.respond()
+

+ 0 - 0
backend/pycs_backend/__init__.py


+ 16 - 0
backend/pycs_backend/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for pycs_backend 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/3.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pycs_backend.settings')
+
+application = get_asgi_application()

+ 53 - 0
backend/pycs_backend/settings/__init__.py

@@ -0,0 +1,53 @@
+"""
+Django settings for pycs_backend project.
+
+Generated by 'django-admin startproject' using Django 3.2.9.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.2/ref/settings/
+"""
+
+from .base import *
+from .logger import *
+
+# Application definition
+
+INSTALLED_APPS = [
+    'pycs_socketio',
+    'pycs_api',
+
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    '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 = 'pycs_backend.urls'
+
+WSGI_APPLICATION = 'pycs_backend.wsgi.application'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+from .database import *
+from .security import *
+from .i18n import *
+from .media import *

+ 22 - 0
backend/pycs_backend/settings/base.py

@@ -0,0 +1,22 @@
+import os
+import base64
+
+from pathlib import Path
+
+APP_NAME = "pycs2"
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent.parent
+
+SOCKETIO_PORT = os.environ.get("PYCS_SOCKETIO_PORT", 5000)
+
+# SECURITY WARNING: don't run with debug turned on in production!
+
+DEBUG = os.environ.get("PYCS_DEBUG", True)
+
+if isinstance(DEBUG, str):
+    if DEBUG.isdigit():
+        DEBUG = bool(int(DEBUG))
+    else:
+        DEBUG = DEBUG == "True"
+

+ 14 - 0
backend/pycs_backend/settings/database.py

@@ -0,0 +1,14 @@
+from .base import BASE_DIR
+
+# Database
+# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql',
+        'OPTIONS': {
+            'read_default_file': str(BASE_DIR / 'mysql.cnf')
+        }
+    }
+}
+

+ 13 - 0
backend/pycs_backend/settings/i18n.py

@@ -0,0 +1,13 @@
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.2/topics/i18n/
+
+LANGUAGE_CODE = 'de-de'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True

+ 19 - 0
backend/pycs_backend/settings/logger.py

@@ -0,0 +1,19 @@
+import logging
+
+from .base import DEBUG
+from .base import BASE_DIR
+
+LOGFILE = None
+FMT = '%(levelname)s - [%(asctime)s] %(filename)s:%(lineno)d [%(funcName)s]: %(message)s'
+
+if DEBUG:
+    level = logging.DEBUG
+else:
+    level = logging.INFO
+
+level = logging.INFO
+logging.basicConfig(format=FMT, level=level, filename=LOGFILE, filemode="w")
+
+
+logging.info("Running in {} mode".format("DEBUG" if DEBUG else "PRODUCTION"))
+logging.info(f"{BASE_DIR=}")

+ 30 - 0
backend/pycs_backend/settings/media.py

@@ -0,0 +1,30 @@
+import os
+
+from .base import BASE_DIR
+
+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',
+            ],
+        },
+    },
+]
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/3.2/howto/static-files/
+
+STATIC_URL = '/static/'
+
+STATIC_DIR = os.environ.get("PYCS_STATIC_DIR", BASE_DIR / "static")
+
+STATICFILES_DIRS = [
+    STATIC_DIR,
+]

+ 47 - 0
backend/pycs_backend/settings/security.py

@@ -0,0 +1,47 @@
+import os
+
+from django.core.management.utils import get_random_secret_key
+from pathlib import Path
+from .base import BASE_DIR
+
+SECRET_KEY_FILE = Path(os.environ.get("PYCS_SECRET_KEYFILE", BASE_DIR / 'secret.txt'))
+
+if not SECRET_KEY_FILE.exists():
+    with open(SECRET_KEY_FILE, "w") as f:
+        f.write(get_random_secret_key())
+
+    os.chmod(SECRET_KEY_FILE, 0o600)
+
+SECRET_KEY = open(SECRET_KEY_FILE).read()
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
+
+ALLOWED_HOSTS = [
+    "https://ammod.inf-cv.uni-jena.de",
+    "https://deimos.inf-cv.uni-jena.de",
+    "http://localhost:5000",
+    "http://localhost:8080",
+    "localhost",
+]
+
+
+# Password validation
+# https://docs.djangoproject.com/en/3.2/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',
+    },
+]
+

+ 23 - 0
backend/pycs_backend/urls.py

@@ -0,0 +1,23 @@
+"""pycs_backend URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/3.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+from django.urls import include
+
+urlpatterns = [
+    path(r'', include('pycs_api.urls')),
+    path('admin/', admin.site.urls),
+]

+ 20 - 0
backend/pycs_backend/wsgi.py

@@ -0,0 +1,20 @@
+"""
+WSGI config for pycs_backend 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/3.2/howto/deployment/wsgi/
+"""
+
+import os
+import socketio
+
+from django.contrib.staticfiles.handlers import StaticFilesHandler
+from django.core.wsgi import get_wsgi_application
+from pycs_socketio import sio
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pycs_backend.settings')
+
+django_app = StaticFilesHandler(get_wsgi_application())
+application = socketio.WSGIApp(sio, django_app)

+ 15 - 0
backend/pycs_socketio/__init__.py

@@ -0,0 +1,15 @@
+"""
+example code from
+https://github.com/miguelgrinberg/python-socketio/tree/main/examples/server/wsgi
+"""
+
+import socketio
+
+from django.conf import settings
+
+async_mode = 'eventlet'
+
+sio = socketio.Server(
+    async_mode=async_mode,
+    cors_allowed_origins=settings.ALLOWED_HOSTS or ["*"]
+)

+ 6 - 0
backend/pycs_socketio/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class PycsSocketioConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'pycs_socketio'

+ 0 - 0
backend/pycs_socketio/management/__init__.py


+ 0 - 0
backend/pycs_socketio/management/commands/__init__.py


+ 50 - 0
backend/pycs_socketio/management/commands/runserver.py

@@ -0,0 +1,50 @@
+import logging
+
+from django.conf import settings
+from django.core.management.commands.runserver import Command as RunCommand
+from pycs_socketio import sio
+
+def run_eventlet():
+
+    import eventlet
+    import eventlet.wsgi
+    from pycs_backend.wsgi import application
+    listener = eventlet.listen(('', settings.SOCKETIO_PORT))
+    eventlet.wsgi.server(listener, application)
+
+def run_gevent():
+
+    # deploy with gevent
+    from gevent import pywsgi
+    from pycs_backend.wsgi import application
+
+    try:
+        from geventwebsocket.handler import WebSocketHandler
+
+        pywsgi.WSGIServer(
+            ('', settings.SOCKETIO_PORT), application,
+            handler_class=WebSocketHandler).serve_forever()
+    except ImportError:
+        pywsgi.WSGIServer(('', settings.SOCKETIO_PORT), application).serve_forever()
+
+
+class Command(RunCommand):
+    help = 'Run the Socket.IO server'
+
+    def handle(self, *args, **options):
+        if sio.async_mode == 'threading':
+            super(Command, self).handle(*args, **options)
+
+        elif sio.async_mode == 'eventlet':
+            run_eventlet()
+
+        elif sio.async_mode == 'gevent':
+            run_gevent()
+
+        elif sio.async_mode == 'gevent_uwsgi':
+            logging.warning('Start the application through the uwsgi server. Example:')
+            logging.warning('uwsgi --http :5000 --gevent 1000 --http-websockets '
+                  '--master --wsgi-file pycs_backend/wsgi.py --callable '
+                  'application')
+        else:
+            logging.error('Unknown async_mode: ' + sio.async_mode)

+ 12 - 0
backend/requirements.txt

@@ -0,0 +1,12 @@
+django~=3.2.9
+mysqlclient~=2.1.0
+
+dnspython==1.16.0
+enum-compat==0.0.3
+eventlet==0.25.2
+greenlet==0.4.16
+monotonic==1.5
+python-engineio
+python-socketio
+pytz==2020.1
+six==1.15.0