├── lms ├── __init__.py ├── models │ ├── __init__.py │ ├── courses.py │ ├── exercises.py │ ├── errors.py │ └── notes.py ├── utils │ ├── __init__.py │ ├── log.py │ ├── courses.py │ ├── debug.py │ ├── colors.py │ ├── consts.py │ ├── files.py │ └── hashing.py ├── lmstests │ ├── __init__.py │ ├── public │ │ ├── general │ │ │ ├── __init__.py │ │ │ └── tasks.py │ │ ├── unittests │ │ │ ├── __init__.py │ │ │ ├── image │ │ │ │ ├── requirements.txt │ │ │ │ └── Dockerfile │ │ │ └── tasks.py │ │ ├── identical_tests │ │ │ └── __init__.py │ │ ├── linters │ │ │ ├── __init__.py │ │ │ └── tasks.py │ │ ├── config │ │ │ ├── __init__.py │ │ │ └── celery.py │ │ └── __init__.py │ └── sandbox │ │ ├── config │ │ ├── __init__.py │ │ └── celery.py │ │ ├── __init__.py │ │ └── linters │ │ ├── __init__.py │ │ └── tasks.py ├── lmsweb │ ├── tools │ │ ├── __init__.py │ │ └── validators.py │ ├── routes.py │ └── forms │ │ └── reset_password.py ├── babel.cfg ├── static │ ├── avatar.jpg │ ├── favicon.ico │ ├── icons │ │ ├── icons.ico │ │ ├── icon_hi.jpg │ │ ├── apple-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-icon-precomposed.png │ │ ├── vivid │ │ │ ├── folder.svg │ │ │ ├── image.svg │ │ │ ├── blank.svg │ │ │ ├── tif.svg │ │ │ ├── c.svg │ │ │ ├── m.svg │ │ │ ├── tiff.svg │ │ │ ├── txt.svg │ │ │ ├── tex.svg │ │ │ ├── h.svg │ │ │ ├── me.svg │ │ │ ├── fla.svg │ │ │ ├── nef.svg │ │ │ ├── rtf.svg │ │ │ ├── nix.svg │ │ │ ├── dll.svg │ │ │ ├── yml.svg │ │ │ ├── jpe.svg │ │ │ ├── tfignore.svg │ │ │ ├── eslintignore.svg │ │ │ ├── gitignore.svg │ │ │ ├── npmignore.svg │ │ │ ├── vscodeignore.svg │ │ │ ├── coffeelintignore.svg │ │ │ ├── in.svg │ │ │ ├── gif.svg │ │ │ ├── xlt.svg │ │ │ ├── md.svg │ │ │ ├── ai.svg │ │ │ ├── heic.svg │ │ │ ├── iff.svg │ │ │ ├── mm.svg │ │ │ ├── bak.svg │ │ │ ├── flv.svg │ │ │ ├── log.svg │ │ │ ├── hs.svg │ │ │ ├── ait.svg │ │ │ ├── tga.svg │ │ │ ├── el.svg │ │ │ ├── key.svg │ │ │ ├── lua.svg │ │ │ ├── cgi.svg │ │ │ ├── yaml.svg │ │ │ ├── aif.svg │ │ │ ├── hsl.svg │ │ │ ├── aze.svg │ │ │ ├── lex.svg │ │ │ ├── ts.svg │ │ │ ├── xltx.svg │ │ │ ├── au.svg │ │ │ ├── jpg.svg │ │ │ ├── f4v.svg │ │ │ ├── xlm.svg │ │ │ ├── aa.svg │ │ │ ├── psp.svg │ │ │ ├── z.svg │ │ │ ├── aiff.svg │ │ │ ├── dng.svg │ │ │ ├── png.svg │ │ │ ├── bat.svg │ │ │ ├── pfx.svg │ │ │ ├── po.svg │ │ │ ├── xls.svg │ │ │ ├── ps.svg │ │ │ ├── jpeg.svg │ │ │ ├── ac.svg │ │ │ ├── avi.svg │ │ │ ├── dtd.svg │ │ │ ├── raw.svg │ │ │ ├── twig.svg │ │ │ ├── xltm.svg │ │ │ ├── tar.svg │ │ │ ├── tsv.svg │ │ │ ├── idx.svg │ │ │ ├── 7z.svg │ │ │ ├── bmp.svg │ │ │ ├── ra.svg │ │ │ ├── p12.svg │ │ │ ├── pot.svg │ │ │ ├── cpp.svg │ │ │ ├── bpg.svg │ │ │ ├── eps.svg │ │ │ ├── gitattributes.svg │ │ │ ├── p7b.svg │ │ │ ├── pcd.svg │ │ │ ├── cr2.svg │ │ │ ├── deb.svg │ │ │ ├── mo.svg │ │ │ ├── mid.svg │ │ │ ├── indd.svg │ │ │ ├── pkg.svg │ │ │ ├── ttf.svg │ │ │ ├── qt.svg │ │ │ ├── caf.svg │ │ │ ├── sh.svg │ │ │ ├── cmd.svg │ │ │ ├── midi.svg │ │ │ ├── wmf.svg │ │ │ ├── aifc.svg │ │ │ ├── webp.svg │ │ │ ├── zip.svg │ │ │ ├── xz.svg │ │ │ ├── pgp.svg │ │ │ ├── download.svg │ │ │ ├── msi.svg │ │ │ ├── xlsx.svg │ │ │ ├── enc.svg │ │ │ ├── pst.svg │ │ │ ├── ru.svg │ │ │ ├── crdownload.svg │ │ │ ├── mkv.svg │ │ │ ├── pem.svg │ │ │ ├── wav.svg │ │ │ ├── mpe.svg │ │ │ ├── ott.svg │ │ │ ├── com.svg │ │ │ ├── aac.svg │ │ │ ├── flac.svg │ │ │ ├── m4a.svg │ │ │ ├── mpa.svg │ │ │ ├── xpi.svg │ │ │ ├── aup.svg │ │ │ ├── csv.svg │ │ │ ├── htm.svg │ │ │ ├── ksh.svg │ │ │ ├── ppt.svg │ │ │ ├── rm.svg │ │ │ ├── asc.svg │ │ │ ├── dot.svg │ │ │ ├── asf.svg │ │ │ ├── rpm.svg │ │ │ ├── swf.svg │ │ │ ├── m4v.svg │ │ │ ├── ost.svg │ │ │ ├── nes.svg │ │ │ ├── vb.svg │ │ │ ├── xlsm.svg │ │ │ ├── crt.svg │ │ │ ├── gz.svg │ │ │ ├── svg.svg │ │ │ ├── cfm.svg │ │ │ ├── html.svg │ │ │ ├── kup.svg │ │ │ ├── msu.svg │ │ │ ├── wsf.svg │ │ │ ├── zsh.svg │ │ │ ├── ash.svg │ │ │ ├── cer.svg │ │ │ ├── mp4.svg │ │ │ ├── pub.svg │ │ │ ├── xrb.svg │ │ │ ├── asx.svg │ │ │ ├── gpg.svg │ │ │ ├── lisp.svg │ │ │ ├── mp2.svg │ │ │ ├── ifo.svg │ │ │ ├── lock.svg │ │ │ ├── mod.svg │ │ │ ├── mov.svg │ │ │ ├── odt.svg │ │ │ ├── wma.svg │ │ │ ├── rdf.svg │ │ │ ├── rom.svg │ │ │ ├── xml.svg │ │ │ ├── amr.svg │ │ │ ├── cfml.svg │ │ │ └── swift.svg │ │ └── browserconfig.xml │ └── shared.js ├── templates │ ├── admin │ │ └── index.html │ ├── strings.html │ ├── _formhelpers.html │ ├── public-courses.html │ └── banned.html ├── lmsdb │ └── __init__.py ├── app.py └── extractors │ └── __init__.py ├── tests ├── __init__.py ├── samples │ ├── __init__.py │ ├── Upload 1.py │ ├── Upload 3141.py │ ├── noexercise.py │ ├── code1.py │ ├── bird.jpg │ ├── Upload 3.jpg │ ├── Upload_1.zip │ ├── zipbomb.zip │ ├── Upload_123.zip │ ├── download_test.zip │ ├── zipfiletest.zip │ ├── code2.py │ ├── not_working_test_code.py │ ├── student_test_code.py │ ├── config_copy.py │ └── config.py.example ├── test_bootstrap.py ├── test_generic_linter.py ├── test_html_escaping.py └── test_config_migrator.py ├── .stylelintrc.json ├── pytest.ini ├── dev_requirements.txt ├── devops ├── rabbitmq.conf ├── secrets.env.template ├── lms.debug.yml ├── build.sh ├── i18n.sh ├── start.sh └── upgrade.sh ├── .flake8 ├── .eslintrc.js └── package.json /lms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmsweb/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/general/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/unittests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/identical_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/Upload 1.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | return 1 3 | -------------------------------------------------------------------------------- /tests/samples/Upload 3141.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | return 1 3 | -------------------------------------------------------------------------------- /tests/samples/noexercise.py: -------------------------------------------------------------------------------- 1 | def get_id(): 2 | return 0 3 | -------------------------------------------------------------------------------- /lms/babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | [jinja2: **/templates/**.html] 3 | -------------------------------------------------------------------------------- /tests/samples/code1.py: -------------------------------------------------------------------------------- 1 | # Upload 3141 2 | 3 | """Normal exercise""" 4 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | env = 3 | LOCAL_SETUP=true 4 | norecursedirs = node_modules/* 5 | -------------------------------------------------------------------------------- /lms/static/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/avatar.jpg -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | debugpy==1.8.1 2 | ipdb==0.13.13 3 | pytest-cov==4.1.0 4 | pytest-env==1.1.3 5 | -------------------------------------------------------------------------------- /lms/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/favicon.ico -------------------------------------------------------------------------------- /tests/samples/bird.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/bird.jpg -------------------------------------------------------------------------------- /lms/static/icons/icons.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/icons.ico -------------------------------------------------------------------------------- /tests/samples/Upload 3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/Upload 3.jpg -------------------------------------------------------------------------------- /tests/samples/Upload_1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/Upload_1.zip -------------------------------------------------------------------------------- /tests/samples/zipbomb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/zipbomb.zip -------------------------------------------------------------------------------- /lms/static/icons/icon_hi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/icon_hi.jpg -------------------------------------------------------------------------------- /tests/samples/Upload_123.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/Upload_123.zip -------------------------------------------------------------------------------- /lms/static/icons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/apple-icon.png -------------------------------------------------------------------------------- /tests/samples/download_test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/download_test.zip -------------------------------------------------------------------------------- /tests/samples/zipfiletest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/tests/samples/zipfiletest.zip -------------------------------------------------------------------------------- /devops/rabbitmq.conf: -------------------------------------------------------------------------------- 1 | default_user=rabbit-user 2 | default_pass=YgKlCvnYVzpTa3T9adG3NrMoUNe4Z5aZ 3 | default_vhost=/ 4 | -------------------------------------------------------------------------------- /lms/lmstests/public/linters/__init__.py: -------------------------------------------------------------------------------- 1 | from lms.lmstests.public.linters import tasks 2 | 3 | __all__ = ('tasks',) 4 | -------------------------------------------------------------------------------- /devops/secrets.env.template: -------------------------------------------------------------------------------- 1 | export DB_NAME=db-name 2 | export DB_USERNAME=db-username 3 | export DB_PASSWORD=db-password 4 | -------------------------------------------------------------------------------- /lms/static/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/favicon-16x16.png -------------------------------------------------------------------------------- /lms/static/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/favicon-32x32.png -------------------------------------------------------------------------------- /lms/static/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/favicon-96x96.png -------------------------------------------------------------------------------- /lms/static/icons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/android-icon-144x144.png -------------------------------------------------------------------------------- /lms/static/icons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/android-icon-192x192.png -------------------------------------------------------------------------------- /lms/static/icons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/android-icon-36x36.png -------------------------------------------------------------------------------- /lms/static/icons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/android-icon-48x48.png -------------------------------------------------------------------------------- /lms/static/icons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/android-icon-72x72.png -------------------------------------------------------------------------------- /lms/static/icons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/android-icon-96x96.png -------------------------------------------------------------------------------- /lms/static/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /lms/static/icons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PythonFreeCourse/lms/HEAD/lms/static/icons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /lms/static/shared.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | window.dispatchEvent(new Event('defined-window-variables')); 3 | }); 4 | -------------------------------------------------------------------------------- /lms/lmstests/public/config/__init__.py: -------------------------------------------------------------------------------- 1 | from lms.lmstests.public.config import celery 2 | 3 | celery_app = celery.app 4 | 5 | __all__ = ('celery_app',) 6 | -------------------------------------------------------------------------------- /lms/lmstests/sandbox/config/__init__.py: -------------------------------------------------------------------------------- 1 | from lms.lmstests.sandbox.config import celery 2 | 3 | celery_app = celery.app 4 | 5 | __all__ = ('celery_app',) 6 | -------------------------------------------------------------------------------- /lms/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/master.html' %} 2 | {% block head_css %} 3 | {{ super() }} 4 | {% endblock head_css %} 5 | {% block body %} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /tests/samples/code2.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Upload 3141 4 | 5 | Normal exercise 6 | להלהלה 7 | 8 | 9 | קצת מילים, @!%^%^& וסימנים מוזרים 10 | רווח בסוף yay 11 | 12 | 13 | -------------------------------------------------------------------------------- /lms/templates/strings.html: -------------------------------------------------------------------------------- 1 | /* Actually a JS hack */ 2 | const _language = { 3 | "Submit": "{{ _('Submit') }}", 4 | "Cancel": "{{ _('Cancel') }}", 5 | }; 6 | const _ = (s) => _language[s]; 7 | -------------------------------------------------------------------------------- /lms/lmsweb/routes.py: -------------------------------------------------------------------------------- 1 | SOLUTIONS = '/view' 2 | STATUS = '/status' 3 | SUBMISSIONS = '/submissions' 4 | DOWNLOADS = '/download' 5 | SHARED = '/shared' 6 | GIT = '/git//.git' 7 | -------------------------------------------------------------------------------- /lms/lmsdb/__init__.py: -------------------------------------------------------------------------------- 1 | from lms.lmsdb import database_config 2 | from lms.lmsdb import models 3 | 4 | database = database_config.get_db_instance() 5 | 6 | __all__ = [ 7 | 'database', 8 | 'models', 9 | ] 10 | -------------------------------------------------------------------------------- /lms/app.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from lms.lmsweb import webapp 4 | 5 | if __name__ == '__main__': 6 | APP_CONFIG: Dict[str, Any] = { 7 | 'port': 8080, 8 | } 9 | 10 | webapp.run(**APP_CONFIG) 11 | -------------------------------------------------------------------------------- /lms/lmstests/sandbox/__init__.py: -------------------------------------------------------------------------------- 1 | from lms.lmstests.sandbox.config import celery as celery_config 2 | from lms.lmstests.sandbox.linters import tasks as flake8_tasks 3 | 4 | celery_app = celery_config.app 5 | 6 | __all__ = ('flake8_tasks', 'celery_app') 7 | -------------------------------------------------------------------------------- /lms/utils/log.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | 3 | 4 | logger.add( 5 | 'logs.log', 6 | format='{time:YYYY-MM-DD at HH:mm:ss} | {name} | {message}', 7 | level='DEBUG', 8 | rotation='monthly', 9 | ) 10 | 11 | log = logger.bind() 12 | -------------------------------------------------------------------------------- /lms/extractors/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | for module in Path(__file__).parent.glob('[!_]*.py'): 4 | __import__(f'{__name__}.{module.stem}', locals(), globals()) 5 | del module # we don't want to expose the module due to this import 6 | 7 | del Path 8 | -------------------------------------------------------------------------------- /lms/lmstests/public/unittests/image/requirements.txt: -------------------------------------------------------------------------------- 1 | atomicwrites==1.4.1 2 | attrs==23.2.0 3 | importlib-metadata==7.0.1 4 | more-itertools==10.2.0 5 | packaging==23.2 6 | pluggy==1.4.0 7 | pyparsing==3.1.1 8 | pytest==8.0.1 9 | six==1.16.0 10 | wcwidth==0.2.13 11 | zipp==3.17.0 12 | -------------------------------------------------------------------------------- /lms/utils/courses.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | import string 3 | 4 | 5 | def generate_invite_code(length: int = 10) -> str: 6 | return ''.join( 7 | secrets.SystemRandom().choices( 8 | string.ascii_letters + string.digits, k=length, 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /tests/samples/not_working_test_code.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | 4 | class TestStudent: 5 | """python 0""" 6 | def __init__(self): 7 | raise NotImplementedError 8 | 9 | def test_check_foo_foo(self): 10 | """שם כזה מגניב""" 11 | assert foo() == 'foo' 12 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /devops/lms.debug.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | http: 5 | command: flask run --no-debugger --host 0.0.0.0 --port 8080 6 | environment: 7 | - DEBUGGER=True 8 | - FLASK_APP=app.py 9 | - FLASK_DEBUG=1 10 | - FLASK_ENV=development 11 | ports: 12 | - 5678:5678 13 | -------------------------------------------------------------------------------- /lms/static/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = 3 | lms/lmstests/sandbox/linters/defines.py:E501 4 | tests/test_exercise_unit_tests.py:Q001 5 | tests/test_html_linter.py:Q001,E501 6 | tests/test_css_linter.py:Q001,E501 7 | tests/test_extractor.py:W293 8 | tests/samples/code2.py:E999,W293,W391,E227,E225,E228 9 | ignore=I100,S101,I201,W503 10 | -------------------------------------------------------------------------------- /lms/models/courses.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | from lms.lmsdb.models import Course, User 4 | 5 | 6 | def get_students(course_id: int) -> NamedTuple: 7 | fields = [User.id, User.username, User.fullname, Course.name] 8 | course = Course.get_by_id(course_id) 9 | return course.get_students(fields=fields).namedtuples() 10 | -------------------------------------------------------------------------------- /lms/utils/debug.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import os 3 | 4 | 5 | def is_enabled() -> bool: 6 | debug_env = os.getenv('DEBUGGER') 7 | return debug_env in {'1', 'True'} 8 | 9 | 10 | def start() -> None: 11 | pid = multiprocessing.current_process().pid 12 | if pid is not None and pid > 1: 13 | import debugpy # type: ignore # NOQA 14 | 15 | debugpy.listen(('0.0.0.0', 5678)) # NOQA 16 | -------------------------------------------------------------------------------- /tests/test_bootstrap.py: -------------------------------------------------------------------------------- 1 | from lms.lmsdb import bootstrap 2 | from lms.lmsdb.models import User 3 | 4 | 5 | class TestBootstrapper: 6 | @staticmethod 7 | def test_has_column_named_true_positive(db): 8 | assert bootstrap.has_column_named(db, User, 'username') 9 | 10 | @staticmethod 11 | def test_has_column_named_true_negative(db): 12 | assert not bootstrap.has_column_named(db, User, 'moshiko@') 13 | -------------------------------------------------------------------------------- /devops/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | SCRIPT_FILE_PATH=$(readlink -f "${0}") 4 | SCRIPT_FOLDER=$(dirname "${SCRIPT_FILE_PATH}") 5 | MAIN_FOLDER="${SCRIPT_FOLDER}/.." 6 | 7 | echo "Using sudo to remove the old erlang cookie" 8 | ERLANG_COOKIE_FILE="${SCRIPT_FOLDER}/rabbitmq.cookie" 9 | sudo rm -rf "$ERLANG_COOKIE_FILE" 10 | 11 | echo "Running build on folder ${MAIN_FOLDER}" 12 | ( cd "${MAIN_FOLDER}" && docker build -t lms . ) 13 | -------------------------------------------------------------------------------- /lms/lmstests/sandbox/linters/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from lms.lmstests.sandbox.linters import base 4 | from lms.lmstests.sandbox.linters import tasks 5 | 6 | for module in Path(__file__).parent.glob('[!_]*.py'): 7 | __import__(f'{__name__}.{module.stem}', locals(), globals()) 8 | del module # we don't want to expose the module due to this import 9 | 10 | del Path 11 | 12 | __all__ = ('base', 'tasks') 13 | -------------------------------------------------------------------------------- /lms/lmstests/public/unittests/image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | COPY requirements.txt /tmp/requirements.txt 4 | RUN pip config --user set global.progress_bar off && \ 5 | pip3 install -r /tmp/requirements.txt 6 | 7 | RUN adduser --disabled-password --gecos '' app-user 8 | 9 | RUN mkdir -p /app_dir/lms 10 | RUN chown -R app-user:app-user /app_dir 11 | 12 | WORKDIR /app_dir/lms 13 | ENV PYTHONPATH /app_dir/:$PYTHONPATH 14 | -------------------------------------------------------------------------------- /tests/samples/student_test_code.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | 4 | class TestStudent: 5 | """python 0""" 6 | def test_check_foo_foo(self): 7 | """שם כזה מגניב""" 8 | assert foo() == 'foo' 9 | 10 | def test_check_bar_bar(self): 11 | """שם כזה מגניב 2""" 12 | assert foo('bar') == 'barbaron', 'איזה ברברון' 13 | 14 | def test_check_foo_bar_foo(self): 15 | """שם כזה מגניב 3""" 16 | assert foo() == 'foofoon' 17 | -------------------------------------------------------------------------------- /lms/utils/colors.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Optional 3 | 4 | from lms.utils.consts import COLORS 5 | 6 | 7 | HEX_COLOR = re.compile(r'#?(?P[a-f0-9]{6}|[a-f0-9]{3})') 8 | 9 | 10 | def get_hex_color(number: str) -> Optional[str]: 11 | if color := HEX_COLOR.match(number): 12 | return '#' + color.groupdict()['hex'] 13 | elif color := COLORS.get(number): 14 | return color 15 | raise ValueError('This is not a valid hex color') 16 | -------------------------------------------------------------------------------- /lms/models/exercises.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from lms.lmsdb.models import Exercise 4 | 5 | 6 | def get_basic_exercises_view(course_id: Optional[int]): 7 | fields = [ 8 | Exercise.id, Exercise.number, 9 | Exercise.subject.alias('name'), Exercise.is_archived, 10 | ] 11 | query = Exercise.select(*fields) 12 | if course_id is not None: 13 | query = query.where(Exercise.course == course_id) 14 | return query.namedtuples() 15 | -------------------------------------------------------------------------------- /tests/samples/config_copy.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | import os 4 | 5 | WTF_CSRF_ENABLED = False # On production, delete this line! 6 | 7 | SECRET_KEY = '' 8 | SERVER_ADDRESS = os.getenv('SERVER_ADDRESS', '127.0.0.1:80') 9 | 10 | FEATURE_FLAG_CHECK_IDENTICAL_CODE_ON = os.getenv( 11 | 'FEATURE_FLAG_CHECK_IDENTICAL_CODE_ON', False, 12 | ) 13 | 14 | 15 | 16 | USERS_CSV = 'users.csv' 17 | 18 | 19 | # Babel config 20 | LANGUAGES = { 21 | 'en': 'English', 22 | 'he': 'Hebrew', 23 | } 24 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/templates/_formhelpers.html: -------------------------------------------------------------------------------- 1 | {% macro render_field(field, cls) %} 2 |
3 | 4 | {{ field(class=cls, **kwargs) | safe }} 5 |
6 | {% if field.errors %} 7 | {% for error in field.errors %} 8 | {{ error }} 9 | {% endfor %} 10 | {% endif %} 11 |
12 |
13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /devops/i18n.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | original_dir="$(pwd)" 4 | 5 | cd "$(dirname "${0:-${BASH_SOURCE[0]}}")" || return 6 | cd .. 7 | 8 | 9 | if [ "${1}" = "update" ]; then 10 | echo "Updating translations" 11 | pybabel extract -F "lms/babel.cfg" -o "lms/lmsweb/translations/messages.pot" "lms" 12 | pybabel update -i "lms/lmsweb/translations/messages.pot" -d "lms/lmsweb/translations" 13 | fi 14 | 15 | echo "Compiling Flask Babel" 16 | pybabel compile -d "lms/lmsweb/translations" 17 | 18 | cd "$original_dir" || return 19 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/blank.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "ignorePatterns": [ 7 | "/lms/static/prism.js", 8 | "/lms/static/markdown.js", 9 | ], 10 | "globals": { 11 | "bootstrap": true, 12 | "Dropzone": true, 13 | "workbox": true, 14 | }, 15 | "extends": "eslint:recommended", 16 | "parserOptions": { 17 | "ecmaVersion": "latest", 18 | "sourceType": "module" 19 | }, 20 | rules: { 21 | 'no-param-reassign': [2, { props: false }], 22 | 'no-console': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tif.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/__init__.py: -------------------------------------------------------------------------------- 1 | from lms.lmstests.public.config import celery as celery_config 2 | from lms.lmstests.public.linters import tasks as flake8_tasks 3 | from lms.lmstests.public.identical_tests import tasks as identical_tests_tasks 4 | from lms.lmstests.public.unittests import tasks as unittests_tasks 5 | from lms.lmstests.public.general import tasks as general_tasks 6 | 7 | celery_app = celery_config.app 8 | 9 | 10 | __all__ = ( 11 | 'flake8_tasks', 12 | 'celery_app', 13 | 'identical_tests_tasks', 14 | 'general_tasks', 15 | 'unittests_tasks', 16 | ) 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lms", 3 | "version": "1.0.0", 4 | "description": "Learning Management System - Exercise management", 5 | "main": "lms/static/my.js", 6 | "repository": "https://github.com/PythonFreeCourse/lms", 7 | "author": "Yam Mesicka ", 8 | "license": "BSD-3-Clause", 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "eslint": "^7.10.0", 12 | "eslint-config-airbnb-base": "^14.2.0", 13 | "eslint-plugin-import": "^2.22.1", 14 | "stylelint": "^13.7.2", 15 | "stylelint-config-standard": "^20.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/c.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/m.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tiff.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/templates/public-courses.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_content %} 4 |
5 |
6 |

{{ _('Public Courses List') }}

7 |
8 | 15 |
16 |
17 |
18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/txt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/h.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/me.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/fla.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/nef.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/rtf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/nix.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/config.py.example: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | import os 4 | 5 | WTF_CSRF_ENABLED = False # On production, delete this line! 6 | 7 | SECRET_KEY = '' 8 | MAILGUN_API_KEY = os.getenv('MAILGUN_API_KEY') 9 | MAILGUN_DOMAIN = os.getenv('MAILGUN_DOMAIN', 'mail.pythonic.guru') 10 | SERVER_ADDRESS = os.getenv('SERVER_ADDRESS', '127.0.0.1:5000') 11 | 12 | FEATURE_FLAG_CHECK_IDENTICAL_CODE_ON = os.getenv( 13 | 'FEATURE_FLAG_CHECK_IDENTICAL_CODE_ON', False, 14 | ) 15 | 16 | 17 | MAIL_WELCOME_MESSAGE = 'welcome-email' 18 | USERS_CSV = 'users.csv' 19 | ERRORS_CSV = 'errors.csv' 20 | 21 | 22 | # Babel config 23 | LANGUAGES = { 24 | 'he': 'Hebrew', 25 | 'en': 'English', 26 | } 27 | LOCALE = 'en' 28 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/dll.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/yml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/jpe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tfignore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/eslintignore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/gitignore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/npmignore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/vscodeignore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/coffeelintignore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/in.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/gif.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xlt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/utils/consts.py: -------------------------------------------------------------------------------- 1 | RTL_LANGUAGES = { 2 | 'he', 'ar', 'arc', 'dv', 'fa', 'ha', 3 | 'khw', 'ks', 'ku', 'ps', 'ur', 'yi', 4 | } 5 | 6 | 7 | COLORS = { 8 | 'primary': '#0d6efd', 'blue': '#0d6efd', 'secondary': '#6c757d', 9 | 'success': '#198754', 'green': '#198754', 'danger': '#dc3545', 10 | 'red': '#dc3545', 'warning': '#ffc107', 'yellow': '#ffc107', 11 | 'info': '#0dcaf0', 'cyan': '#0dcaf0', 'gray': '#adb5bd', 12 | 'dark': '#000', 'black': '#000', 'white': '#fff', 13 | 'teal': '#20c997', 'orange': '#fd7e14', 'pink': '#d63384', 14 | 'purple': '#6f42c1', 'indigo': '#6610f2', 'light': '#f8f9fa', 15 | } 16 | DEFAULT_ASSESSMENT_BUTTON_COLOR = '#0d6efd' # primary 17 | DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR = '#fff' # white 18 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/md.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_generic_linter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from lms.lmsdb import models 4 | from lms.lmstests.public.linters import tasks 5 | 6 | 7 | class TestGenericLinter: 8 | @staticmethod 9 | def test_run_linters_expect_unknown_solution( 10 | solution: models.Solution, caplog: pytest.LogCaptureFixture, 11 | ): 12 | tasks.run_linter_on_solution(solution.id) 13 | assert 'does not exist' not in caplog.text 14 | assert 'failed to check' not in caplog.text 15 | 16 | nonexist_solution = 123456789 17 | with pytest.raises(models.Solution.DoesNotExist): 18 | tasks.run_linter_on_solution(nonexist_solution) 19 | assert 'does not exist' in caplog.text 20 | assert 'failed to check' not in caplog.text 21 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/heic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/iff.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/templates/banned.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_content %} 4 |
5 |
6 |
7 | 10 |

השעייה

11 |

12 | {{ _('Your account has been suspended by the manager.') }}
13 | {{ _('For more details please contact the management team.') }} 14 |

15 |
16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/bak.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/flv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/hs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ait.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tga.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/el.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/key.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/lua.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cgi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/yaml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aif.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/hsl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aze.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/lex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/utils/files.py: -------------------------------------------------------------------------------- 1 | import mimetypes 2 | 3 | 4 | LANGUAGE_EXTENSIONS_TO_NAMES = { 5 | 'bat': 'batch', 6 | 'css': 'css', 7 | 'h': 'c', 8 | 'htm': 'html', 9 | 'html': 'html', 10 | 'j2': 'jinja2', 11 | 'js': 'javascript', 12 | 'md': 'markup', 13 | 'ps1': 'powershell', 14 | 'psm1': 'powershell', 15 | 'py': 'python', 16 | 'rb': 'ruby', 17 | 'sh': 'bash', 18 | 'sql': 'sql', 19 | 'tex': 'latex', 20 | 'yml': 'yaml', 21 | } 22 | 23 | ALLOWED_EXTENSIONS = set(LANGUAGE_EXTENSIONS_TO_NAMES) 24 | 25 | ALLOWED_IMAGES_EXTENSIONS = {'png', 'jpeg', 'jpg', 'svg', 'tiff', 'bmp', 'ico'} 26 | 27 | 28 | def get_language_name_by_extension(ext: str) -> str: 29 | return LANGUAGE_EXTENSIONS_TO_NAMES.get(ext, ext) 30 | 31 | 32 | def get_mime_type_by_extention(ext: str) -> str: 33 | mimetypes.init() 34 | return mimetypes.types_map[ext] 35 | -------------------------------------------------------------------------------- /lms/utils/hashing.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from typing import IO, Union 3 | 4 | from werkzeug.datastructures import FileStorage 5 | 6 | 7 | def by_content( 8 | file_content: Union[bytes, str], *args, **kwargs, 9 | ) -> str: 10 | if not isinstance(file_content, bytes): 11 | file_content = file_content.encode('utf-8') 12 | 13 | hashed_code = hashlib.blake2b(digest_size=20) 14 | hashed_code.update(file_content) 15 | return hashed_code.hexdigest() 16 | 17 | 18 | def by_file( 19 | file: Union[FileStorage, IO], *args, **kwargs, 20 | ) -> str: 21 | saved_location = file.tell() 22 | file.seek(0) 23 | 24 | file_content = file.read() 25 | if not isinstance(file_content, bytes): 26 | file_content = file_content.encode('utf-8') 27 | 28 | file_hash = by_content(file_content) 29 | file.seek(saved_location) 30 | 31 | return file_hash 32 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xltx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/au.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/jpg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/general/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from celery.utils.log import get_task_logger 4 | 5 | from lms.lmsdb import models 6 | from lms.lmstests.public.config.celery import app 7 | 8 | 9 | _logger: logging.Logger = get_task_logger(__name__) 10 | _logger.setLevel(logging.INFO) 11 | 12 | 13 | @app.task 14 | def reset_solution_state_if_needed(solution_pk: str) -> None: 15 | _logger.info('reset_solution_state_if_needed: solution %s', solution_pk) 16 | 17 | try: 18 | solution = models.Solution.get_by_id(solution_pk) 19 | except models.Solution.DoesNotExist: 20 | _logger.exception('Solution %s does not exist', solution_pk) 21 | raise 22 | 23 | if solution.state == models.Solution.STATES.IN_CHECKING.name: 24 | _logger.info('Reset solution %s to CREATED state', solution_pk) 25 | solution.set_state(models.Solution.STATES.CREATED) 26 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/f4v.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xlm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /devops/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f secrets.env ]; then 4 | echo "Using prod secrets" 5 | source secrets.env 6 | else 7 | echo "Using default dev variables" 8 | fi 9 | 10 | SCRIPT_FILE_PATH=$(readlink -f "${0}") 11 | SCRIPT_FOLDER=$(dirname "${SCRIPT_FILE_PATH}") 12 | ERLANG_COOKIE_FILE="${SCRIPT_FOLDER}/rabbitmq.cookie" 13 | 14 | echo erlang_cookie=AAVyo5djdSMGIZXiwEQs3JeVaBx5l14z > "$ERLANG_COOKIE_FILE" 15 | chmod 600 "$ERLANG_COOKIE_FILE" 16 | 17 | docker network create lms 18 | 19 | if [[ "${DEBUGGER}" == "1" || "${DEBUGGER}" = "True" ]]; then 20 | docker-compose -p lms -f "${SCRIPT_FOLDER}/lms.yml" -f "${SCRIPT_FOLDER}/lms.debug.yml" down 21 | docker-compose -p lms -f "${SCRIPT_FOLDER}/lms.yml" -f "${SCRIPT_FOLDER}/lms.debug.yml" up -d 22 | else 23 | docker-compose -p lms -f "${SCRIPT_FOLDER}/lms.yml" down 24 | docker-compose -p lms -f "${SCRIPT_FOLDER}/lms.yml" up -d 25 | fi 26 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aa.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/psp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/z.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aiff.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/dng.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/png.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/bat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pfx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/po.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xls.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ps.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/jpeg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/avi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/dtd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/raw.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/twig.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xltm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/unittests/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from celery.utils.log import get_task_logger 4 | 5 | from lms.lmsdb import models 6 | from lms.lmstests.public.config.celery import app 7 | from lms.lmstests.public.unittests import services 8 | 9 | 10 | _logger: logging.Logger = get_task_logger(__name__) 11 | _logger.setLevel(logging.INFO) 12 | 13 | 14 | @app.task 15 | def run_tests_for_solution(solution_id: str, executor_name=None): 16 | _logger.info('Start run_tests_for_solution %s', solution_id) 17 | checker = services.UnitTestChecker(_logger, solution_id, executor_name) 18 | try: 19 | checker.initialize() 20 | except models.Solution.DoesNotExist: 21 | _logger.exception('The solution %s does not exists', solution_id) 22 | raise 23 | except models.ExerciseTest.DoesNotExist: 24 | _logger.exception('Missing tests for solution %s', solution_id) 25 | raise 26 | 27 | checker.run_check() 28 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/tsv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/idx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/7z.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmsweb/forms/reset_password.py: -------------------------------------------------------------------------------- 1 | from lms.lmsweb.tools.validators import EmailNotExists 2 | from flask_babel import gettext as _ # type: ignore 3 | from flask_wtf import FlaskForm 4 | from wtforms import StringField 5 | from wtforms.fields.simple import PasswordField 6 | from wtforms.validators import Email, EqualTo, InputRequired, Length 7 | 8 | 9 | class ResetPassForm(FlaskForm): 10 | email = StringField( 11 | 'Email', validators=[ 12 | InputRequired(), Email(message=_('Invalid email')), 13 | EmailNotExists, 14 | ], 15 | ) 16 | 17 | 18 | class RecoverPassForm(FlaskForm): 19 | password = PasswordField( 20 | 'Password', validators=[InputRequired(), Length(min=8)], id='password', 21 | ) 22 | confirm = PasswordField( 23 | 'Password Confirmation', validators=[ 24 | InputRequired(), 25 | EqualTo('password', message=_('The passwords are not identical')), 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/bmp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/linters/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from celery.exceptions import OperationalError, TaskError 4 | from celery.utils.log import get_task_logger 5 | 6 | from lms.lmsdb import models 7 | from lms.lmstests.public.config.celery import app 8 | from lms.lmstests.public.linters import services 9 | 10 | 11 | _logger: logging.Logger = get_task_logger(__name__) 12 | _logger.setLevel(logging.INFO) 13 | 14 | 15 | @app.task 16 | def run_linter_on_solution(solution_pk: str) -> None: 17 | _logger.info('Start running check solution %s', solution_pk) 18 | checker = services.LinterChecker(solution_pk, _logger) 19 | 20 | try: 21 | checker.initialize() 22 | except models.Solution.DoesNotExist: 23 | _logger.exception('Solution %s does not exist', solution_pk) 24 | raise 25 | 26 | try: 27 | checker.run_check() 28 | except (TaskError, OperationalError): 29 | _logger.exception('Failed to check solution %s', solution_pk) 30 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ra.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /devops/upgrade.sh: -------------------------------------------------------------------------------- 1 | upgrade () { 2 | sudo chown -R $USER:$USER /opt/lms /opt/notebooks-tests 3 | cd /opt/notebooks-tests || { echo "cd failed"; exit 127; } 4 | git reset --hard 5 | git pull origin 6 | git checkout master 7 | 8 | cd /opt/lms || { echo "cd failed"; exit 127; } 9 | git reset --hard 10 | git pull origin 11 | git checkout master 12 | 13 | USED=$(df . | awk 'NR==2{print $4}') 14 | MINIMUM_MB=200 15 | MINIMUM_KB=$((MINIMUM_MB * 1024)) 16 | if [ ${USED} -le ${MINIMUM_KB} ]; then 17 | echo "There are less than ${MINIMUM_MB}MB space left in your disk. Exiting code."; return; 18 | fi 19 | 20 | sudo systemctl stop nginx 21 | docker exec -t lms_db_1 pg_dump -c -U lmsweb lms > /home/$USER/dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql 22 | cd devops || { echo "cd failed"; exit 127; } 23 | source ./build.sh 24 | source ./start.sh 25 | sudo systemctl restart lms 26 | sudo systemctl start nginx 27 | source ./i18n.sh 28 | source ./bootstrap.sh 29 | } 30 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/p12.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pot.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cpp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/bpg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/eps.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/gitattributes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/p7b.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pcd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cr2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/deb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_html_escaping.py: -------------------------------------------------------------------------------- 1 | from flask import json 2 | 3 | from lms.lmsdb.models import Solution, User 4 | from tests import conftest 5 | 6 | 7 | USER_COMMENT_BEFORE_ESCAPING = '

Welcome "LMS"

' 8 | USER_COMMENT_AFTER_ESCAPING = ( 9 | '<html><body><p>Welcome "LMS"' 10 | '</p></body></html>' 11 | ) 12 | 13 | 14 | class TestHtmlEscaping: 15 | @staticmethod 16 | def test_comment_text_escaping(student_user: User, solution: Solution): 17 | client = conftest.get_logged_user(student_user.username) 18 | 19 | comment_response = client.post('/comments', data=json.dumps({ 20 | 'fileId': solution.files[0].id, 'act': 'create', 'kind': 'text', 21 | 'comment': USER_COMMENT_BEFORE_ESCAPING, 'line': 1, 22 | }), content_type='application/json') 23 | assert comment_response.status_code == 200 24 | assert solution.comments[0].comment.text == USER_COMMENT_AFTER_ESCAPING 25 | -------------------------------------------------------------------------------- /lms/lmstests/sandbox/config/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | CELERY_RABBITMQ_ERLANG_COOKIE = os.getenv('CELERY_RABBITMQ_ERLANG_COOKIE') 6 | CELERY_RABBITMQ_DEFAULT_USER = os.getenv('CELERY_RABBITMQ_DEFAULT_USER') 7 | CELERY_RABBITMQ_DEFAULT_PASS = os.getenv('CELERY_RABBITMQ_DEFAULT_PASS') 8 | CELERY_CHECKS_SANDBOX_VHOST = os.getenv('CELERY_CHECKS_SANDBOX_VHOST') 9 | CELERY_RABBITMQ_HOST = os.getenv('CELERY_RABBITMQ_HOST') 10 | CELERY_RABBITMQ_PORT = os.getenv('CELERY_RABBITMQ_PORT', 222) 11 | 12 | broker_url = (f'amqp://{CELERY_RABBITMQ_DEFAULT_USER}:' 13 | f'{CELERY_RABBITMQ_DEFAULT_PASS}@' 14 | f'{CELERY_RABBITMQ_HOST}:{CELERY_RABBITMQ_PORT}/' 15 | f'{CELERY_CHECKS_SANDBOX_VHOST}') 16 | app = Celery('lmstests-sandbox', broker=broker_url, backend='rpc://') 17 | 18 | app.conf.update( 19 | task_serializer='json', 20 | accept_content=['json'], # Ignore other content 21 | result_serializer='json', 22 | enable_utc=True, 23 | ) 24 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_config_migrator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import shutil 4 | 5 | from lms.utils import config_migrator 6 | from tests import conftest 7 | 8 | 9 | CONFIG_EXAMPLE_FILE = pathlib.Path(conftest.SAMPLES_DIR) / 'config.py.example' 10 | CONFIG_COPY_FILE = pathlib.Path(conftest.SAMPLES_DIR) / 'config_copy.py' 11 | CONFIG_FILE = pathlib.Path(conftest.SAMPLES_DIR) / 'config.py' 12 | 13 | 14 | class TestConfigMigrator: 15 | @staticmethod 16 | def setup_method(): 17 | shutil.copyfile(str(CONFIG_COPY_FILE), str(CONFIG_FILE)) 18 | 19 | @staticmethod 20 | def teardown_method(): 21 | os.remove(str(CONFIG_FILE)) 22 | 23 | @staticmethod 24 | def test_config_migration(): 25 | config_migrator.migrate(CONFIG_FILE, CONFIG_EXAMPLE_FILE) 26 | get_assignments = config_migrator.get_config_assignments 27 | new = get_assignments(CONFIG_FILE).keys() 28 | old = get_assignments(CONFIG_EXAMPLE_FILE).keys() 29 | assert len(old - new) == 0 30 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/indd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pkg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ttf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/qt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/caf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/sh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cmd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/midi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/wmf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aifc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/webp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xz.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pgp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/msi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xlsx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/enc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pst.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ru.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/public/config/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | CELERY_RABBITMQ_ERLANG_COOKIE = os.getenv('CELERY_RABBITMQ_ERLANG_COOKIE') 6 | CELERY_RABBITMQ_DEFAULT_USER = os.getenv('CELERY_RABBITMQ_DEFAULT_USER') 7 | CELERY_RABBITMQ_DEFAULT_PASS = os.getenv('CELERY_RABBITMQ_DEFAULT_PASS') 8 | CELERY_CHECKS_PUBLIC_VHOST = os.getenv('CELERY_CHECKS_PUBLIC_VHOST') 9 | CELERY_RABBITMQ_HOST = os.getenv('CELERY_RABBITMQ_HOST') 10 | CELERY_RABBITMQ_PORT = os.getenv('CELERY_RABBITMQ_PORT', 222) 11 | 12 | public_broker_url = (f'amqp://{CELERY_RABBITMQ_DEFAULT_USER}:' 13 | f'{CELERY_RABBITMQ_DEFAULT_PASS}@' 14 | f'{CELERY_RABBITMQ_HOST}:{CELERY_RABBITMQ_PORT}/' 15 | f'{CELERY_CHECKS_PUBLIC_VHOST}') 16 | app = Celery('lmstests-public', broker=public_broker_url) 17 | 18 | app.conf.update( 19 | task_serializer='json', 20 | accept_content=['json'], # Ignore other content 21 | result_serializer='json', 22 | enable_utc=True, 23 | task_always_eager=bool(os.getenv('FLASK_DEBUG')), 24 | ) 25 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/crdownload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mkv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pem.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/wav.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/models/errors.py: -------------------------------------------------------------------------------- 1 | from flask import abort, jsonify 2 | 3 | 4 | class LmsError(Exception): 5 | pass 6 | 7 | 8 | class AlreadyExists(LmsError): # Usually a 409 HTTP Error 9 | pass 10 | 11 | 12 | class BadUploadFile(LmsError): 13 | pass 14 | 15 | 16 | class FileSizeError(LmsError): 17 | pass 18 | 19 | 20 | class UnhashedPasswordError(LmsError): 21 | pass 22 | 23 | 24 | class UploadError(LmsError): 25 | pass 26 | 27 | 28 | class NotValidRequest(LmsError): # Error 400 29 | pass 30 | 31 | 32 | class UnauthorizedError(LmsError): # Error 401 33 | pass 34 | 35 | 36 | class ForbiddenPermission(LmsError): # Error 403 37 | pass 38 | 39 | 40 | class ResourceNotFound(LmsError): # Error 404 41 | pass 42 | 43 | 44 | class UnprocessableRequest(LmsError): # Error 422 45 | pass 46 | 47 | 48 | def fail(status_code: int, error_msg: str): 49 | data = { 50 | 'status': 'failed', 51 | 'msg': error_msg, 52 | } 53 | response = jsonify(data) 54 | response.status_code = status_code 55 | return abort(response) 56 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mpe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ott.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/com.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/flac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/m4a.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mpa.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xpi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/aup.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/csv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/htm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ksh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ppt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/rm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/asc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/dot.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/asf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/rpm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/swf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/models/notes.py: -------------------------------------------------------------------------------- 1 | from flask_login import current_user 2 | 3 | from lms.lmsdb.models import CommentText, Exercise, Note, User 4 | from lms.models.errors import ForbiddenPermission, UnprocessableRequest 5 | 6 | 7 | def delete(note_id: int): 8 | note_ = Note.get_or_none(Note.id == note_id) 9 | if note_.creator.id != current_user.id and note_.is_private: 10 | raise ForbiddenPermission( 11 | "You aren't allowed to access this page.", 403, 12 | ) 13 | if note_ is not None: 14 | note_.delete_instance() 15 | 16 | 17 | def create( 18 | user: User, note_text: str, note_exercise: str, privacy: str, 19 | ) -> None: 20 | if not note_text: 21 | raise UnprocessableRequest('Empty notes are not allowed.', 422) 22 | new_note_id = CommentText.create_comment(text=note_text).id 23 | 24 | Note.create( 25 | creator=User.get_or_none(User.id == current_user.id), 26 | user=user, 27 | note=new_note_id, 28 | exercise=Exercise.get_or_none(Exercise.subject == note_exercise), 29 | privacy=Note.get_privacy_level(int(privacy)), 30 | ) 31 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/m4v.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ost.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/nes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/vb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xlsm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/crt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/gz.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/svg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cfm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/html.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/kup.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/msu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/wsf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/zsh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mp4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmstests/sandbox/linters/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from celery.utils.log import get_task_logger 4 | 5 | from lms.lmstests.sandbox.config.celery import app 6 | from lms.lmstests.sandbox.linters import base 7 | 8 | 9 | _logger: logging.Logger = get_task_logger(__name__) 10 | _logger.setLevel(logging.INFO) 11 | 12 | 13 | @app.task 14 | def run_linters_in_sandbox(solution_file_id: str, code: str, file_suffix: str): 15 | _logger.info('Start running sandbox check solution %s', solution_file_id) 16 | get_linter = base.BaseLinter.get_match_linter 17 | 18 | try: 19 | checker = get_linter(_logger, code, file_suffix, solution_file_id) 20 | except NotImplementedError: 21 | _logger.info('All linters must implement BaseLinter core methods.') 22 | raise 23 | 24 | if checker is None: 25 | _logger.info('No suitable linter for file %s', solution_file_id) 26 | return [] 27 | 28 | checker.initialize() 29 | try: 30 | return checker.run_check() 31 | except Exception: # NOQA: B902 32 | _logger.exception("Can't check solution %s", solution_file_id) 33 | return [] 34 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/pub.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xrb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/asx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/gpg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/lisp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mp2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/lmsweb/tools/validators.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from flask_babel import gettext as _ # type: ignore 4 | from wtforms import StringField 5 | from wtforms.validators import ValidationError 6 | 7 | from lms.lmsdb.models import User 8 | 9 | if TYPE_CHECKING: 10 | from lms.lmsweb.forms.register import RegisterForm 11 | from lms.lmsweb.forms.reset_password import ResetPassForm 12 | 13 | 14 | def UniqueUsernameRequired(__: 'RegisterForm', field: StringField) -> None: 15 | username_exists = User.get_or_none(User.username == field.data) 16 | if username_exists: 17 | raise ValidationError(_('The username is already in use')) 18 | 19 | 20 | def UniqueEmailRequired(__: 'RegisterForm', field: StringField) -> None: 21 | email_exists = User.get_or_none(User.mail_address == field.data) 22 | if email_exists: 23 | raise ValidationError(_('The email is already in use')) 24 | 25 | 26 | def EmailNotExists(__: 'ResetPassForm', field: StringField) -> None: 27 | email_exists = User.get_or_none(User.mail_address == field.data) 28 | if not email_exists: 29 | raise ValidationError(_('Invalid email')) 30 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/ifo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mod.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/mov.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/odt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/wma.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/rdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/rom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/xml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/amr.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/cfml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lms/static/icons/vivid/swift.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------