Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Untis
  • sunweaver/AlekSIS-App-Untis
  • cocguPpenda/AlekSIS-App-Untis
  • 0inraMfauri/AlekSIS-App-Untis
4 results
Show changes
Commits on Source (137)
Showing
with 906 additions and 791 deletions
module.exports = {
root: true,
overrides: [
{
files: ["*.js", "*.vue"],
// parser: "vue-eslint-parser",
//processor: "@graphql-eslint/graphql",
extends: [
"eslint:recommended",
"plugin:vue/strongly-recommended",
"plugin:@intlify/vue-i18n/recommended",
],
rules: {
"no-unused-vars": "warn",
"vue/no-unused-vars": "off",
"vue/multi-word-component-names": "off",
"vue/attribute-hyphenation": "error",
"vue/v-slot-style": "error",
"@intlify/vue-i18n/key-format-style": [
"error",
"snake_case",
{
splitByDots: false,
},
],
// "@intlify/vue-i18n/no-unused-keys": ["warn", {}],
"@intlify/vue-i18n/no-raw-text": [
"error",
{
ignoreNodes: ["v-icon"],
ignorePattern: "^[-–—·#:()\\[\\]&\\.\\s]+$",
},
],
"@intlify/vue-i18n/no-deprecated-tc": "off",
// Fixes for prettier (avoid eslint-config-prettier)
// The following rules can be used in some cases. See the README for more
// information. (These are marked with `0` instead of `"off"` so that a
// script can distinguish them.)
curly: 0,
"lines-around-comment": 0,
"max-len": 0,
"no-confusing-arrow": 0,
"no-mixed-operators": 0,
"no-tabs": 0,
"no-unexpected-multiline": 0,
quotes: 0,
"@typescript-eslint/quotes": 0,
"babel/quotes": 0,
"vue/html-self-closing": 0,
"vue/max-len": 0,
// The rest are rules that you never need to enable when using Prettier.
"array-bracket-newline": "off",
"array-bracket-spacing": "off",
"array-element-newline": "off",
"arrow-parens": "off",
"arrow-spacing": "off",
"block-spacing": "off",
"brace-style": "off",
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"computed-property-spacing": "off",
"dot-location": "off",
"eol-last": "off",
"func-call-spacing": "off",
"function-call-argument-newline": "off",
"function-paren-newline": "off",
"generator-star": "off",
"generator-star-spacing": "off",
"implicit-arrow-linebreak": "off",
indent: "off",
"jsx-quotes": "off",
"key-spacing": "off",
"keyword-spacing": "off",
"linebreak-style": "off",
"multiline-ternary": "off",
"newline-per-chained-call": "off",
"new-parens": "off",
"no-arrow-condition": "off",
"no-comma-dangle": "off",
"no-extra-parens": "off",
"no-extra-semi": "off",
"no-floating-decimal": "off",
"no-mixed-spaces-and-tabs": "off",
"no-multi-spaces": "off",
"no-multiple-empty-lines": "off",
"no-reserved-keys": "off",
"no-space-before-semi": "off",
"no-trailing-spaces": "off",
"no-whitespace-before-property": "off",
"no-wrap-func": "off",
"nonblock-statement-body-position": "off",
"object-curly-newline": "off",
"object-curly-spacing": "off",
"object-property-newline": "off",
"one-var-declaration-per-line": "off",
"operator-linebreak": "off",
"padded-blocks": "off",
"quote-props": "off",
"rest-spread-spacing": "off",
semi: "off",
"semi-spacing": "off",
"semi-style": "off",
"space-after-function-name": "off",
"space-after-keywords": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-before-function-parentheses": "off",
"space-before-keywords": "off",
"space-in-brackets": "off",
"space-in-parens": "off",
"space-infix-ops": "off",
"space-return-throw-case": "off",
"space-unary-ops": "off",
"space-unary-word-ops": "off",
"switch-colon-spacing": "off",
"template-curly-spacing": "off",
"template-tag-spacing": "off",
"unicode-bom": "off",
"wrap-iife": "off",
"wrap-regex": "off",
"yield-star-spacing": "off",
"@babel/object-curly-spacing": "off",
"@babel/semi": "off",
"@typescript-eslint/brace-style": "off",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/comma-spacing": "off",
"@typescript-eslint/func-call-spacing": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/keyword-spacing": "off",
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/no-extra-parens": "off",
"@typescript-eslint/no-extra-semi": "off",
"@typescript-eslint/object-curly-spacing": "off",
"@typescript-eslint/semi": "off",
"@typescript-eslint/space-before-blocks": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/space-infix-ops": "off",
"@typescript-eslint/type-annotation-spacing": "off",
"babel/object-curly-spacing": "off",
"babel/semi": "off",
"flowtype/boolean-style": "off",
"flowtype/delimiter-dangle": "off",
"flowtype/generic-spacing": "off",
"flowtype/object-type-curly-spacing": "off",
"flowtype/object-type-delimiter": "off",
"flowtype/quotes": "off",
"flowtype/semi": "off",
"flowtype/space-after-type-colon": "off",
"flowtype/space-before-generic-bracket": "off",
"flowtype/space-before-type-colon": "off",
"flowtype/union-intersection-spacing": "off",
"react/jsx-child-element-spacing": "off",
"react/jsx-closing-bracket-location": "off",
"react/jsx-closing-tag-location": "off",
"react/jsx-curly-newline": "off",
"react/jsx-curly-spacing": "off",
"react/jsx-equals-spacing": "off",
"react/jsx-first-prop-new-line": "off",
"react/jsx-indent": "off",
"react/jsx-indent-props": "off",
"react/jsx-max-props-per-line": "off",
"react/jsx-newline": "off",
"react/jsx-one-expression-per-line": "off",
"react/jsx-props-no-multi-spaces": "off",
"react/jsx-tag-spacing": "off",
"react/jsx-wrap-multilines": "off",
"standard/array-bracket-even-spacing": "off",
"standard/computed-property-even-spacing": "off",
"standard/object-curly-even-spacing": "off",
"unicorn/empty-brace-spaces": "off",
"unicorn/no-nested-ternary": "off",
"unicorn/number-literal-case": "off",
"vue/array-bracket-newline": "off",
"vue/array-bracket-spacing": "off",
"vue/arrow-spacing": "off",
"vue/block-spacing": "off",
"vue/block-tag-newline": "off",
"vue/brace-style": "off",
"vue/comma-dangle": "off",
"vue/comma-spacing": "off",
"vue/comma-style": "off",
"vue/dot-location": "off",
"vue/func-call-spacing": "off",
"vue/html-closing-bracket-newline": "off",
"vue/html-closing-bracket-spacing": "off",
"vue/html-end-tags": "off",
"vue/html-indent": "off",
"vue/html-quotes": "off",
"vue/key-spacing": "off",
"vue/keyword-spacing": "off",
"vue/max-attributes-per-line": "off",
"vue/multiline-html-element-content-newline": "off",
"vue/multiline-ternary": "off",
"vue/mustache-interpolation-spacing": "off",
"vue/no-extra-parens": "off",
"vue/no-multi-spaces": "off",
"vue/no-spaces-around-equal-signs-in-attribute": "off",
"vue/object-curly-newline": "off",
"vue/object-curly-spacing": "off",
"vue/object-property-newline": "off",
"vue/operator-linebreak": "off",
"vue/quote-props": "off",
"vue/script-indent": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/space-in-parens": "off",
"vue/space-infix-ops": "off",
"vue/space-unary-ops": "off",
"vue/template-curly-spacing": "off",
},
settings: {
"vue-i18n": {
localeDir: "./aleksis/core/frontend/messages/*.{json}",
messageSyntaxVersion: "^8.0.0",
},
},
env: {
es2021: true,
},
parserOptions: {
ecmaVersion: "latest",
},
},
{
files: ["*.graphql"],
parser: "@graphql-eslint/eslint-plugin",
plugins: ["@graphql-eslint"],
extends: "plugin:@graphql-eslint/operations-recommended",
parserOptions: {
graphQLConfig: {
schema: "./schema.json",
documents: "../aleksis/**/*/frontend/**/*.graphql",
},
},
rules: {
"@graphql-eslint/no-anonymous-operations": "error",
"@graphql-eslint/no-duplicate-fields": "error",
"@graphql-eslint/naming-convention": [
"error",
{
OperationDefinition: {
style: "camelCase",
forbiddenPrefixes: ["Query", "Mutation", "Subscription", "Get"],
forbiddenSuffixes: ["Query", "Mutation", "Subscription"],
},
},
],
},
},
],
};
{
"name": "aleksis-builddeps",
"version": "1.0.0",
"dependencies": {
"@graphql-eslint/eslint-plugin": "^4.3.0",
"@intlify/eslint-plugin-vue-i18n": "^3.0.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-vue": "^9.7.0",
"graphql": "^16.10.0",
"prettier": "^3.4.0",
"stylelint": "^15.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^34.0.0"
}
}
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.py[cod]
__pycache__/
# Distribution / packaging
*.egg
*.egg-info/
.Python
.eggs/
.installed.cfg
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-log.txt
# Translations
*.mo
......@@ -39,22 +39,60 @@ local_settings.py
# Environments
.env
.venv
ENV/
env/
venv/
ENV/
# Editors
*~
DEADJOE
\#*#
# IntelliJ
.idea
.idea/
# VSCode
.vscode/
.history/
*.code-workspace
# Database
db.sqlite3
# Sphinx
docs/_build/
# Test
.tox/
# TeX
*.aux
# Generated files
/cache
/node_modules
.dev-js/node_modules
/static/
/whoosh_index/
.vite
.dev-js/.yarn
.dev-js/.pnp.cjs
.dev-js/.pnp.loader.mjs
.dev-js/.yarnrc.yml
.dev-js/schema.json
# Lock files
poetry.lock
package-lock.json
yarn.lock
.dev-js/yarn.lock
# Tests
.coverage
.mypy_cache/
.tox/
htmlcov/
# Data
maintenance_mode_state.txt
media/
aleksis/core/static/style.css
include:
- project: "AlekSIS/official/AlekSIS"
file: /ci/general.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/prepare/lock.yml
# - project: "AlekSIS/official/AlekSIS"
# file: /ci/test/test.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test/lint.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test/security.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/build/dist.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/build/docs.yml
- project: "AlekSIS/official/AlekSIS"
file: "/ci/deploy/trigger_dist.yml"
- project: "AlekSIS/official/AlekSIS"
file: /ci/publish/pypi.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/deploy/pages.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/general.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/prepare/lock.yml
# - project: "AlekSIS/official/AlekSIS"
# file: /ci/test/test.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test/lint.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test/security.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/build/dist.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/build/docs.yml
- project: "AlekSIS/official/AlekSIS"
file: "/ci/deploy/trigger_dist.yml"
- project: "AlekSIS/official/AlekSIS"
file: /ci/publish/pypi.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/deploy/pages.yml
# Byte-compiled / optimized / DLL files
*$py.class
*.py[cod]
__pycache__/
# Distribution / packaging
*.egg
*.egg-info/
.Python
.eggs/
.installed.cfg
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
# Installer logs
pip-delete-this-directory.txt
pip-log.txt
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# pyenv
.python-version
# Environments
.env
.venv
ENV/
env/
venv/
# Editors
*~
DEADJOE
\#*#
# IntelliJ
.idea
.idea/
# Database
db.sqlite3
# Sphinx
docs/_build/
# TeX
*.aux
# Generated files
/node_modules/
/static/
/whoosh_index/
poetry.lock
.coverage
.mypy_cache/
.tox/
htmlcov/
maintenance_mode_state.txt
media/
package-lock.json
yarn.lock
# VSCode
.vscode/
.history/
*.code-workspace
/cache
# Add HTML files to avoid problems with unsupported Django templates
*.html
# Do not check/reformat generated files
aleksis/core/util/licenses.json
.vite/
.pnp.cjs
.pnp.loader.mjs
.git/
......@@ -9,6 +9,54 @@ and this project adheres to `Semantic Versioning`_.
Unreleased
----------
Breaking Changes
~~~~~~~~~~~~~~~~
The Untis importer was updated to work with Chronos 4.x. This now
requires also to import data into AlekSIS-App-Cursus,
AlekSIS-App-Kolego, and AlekSIS-App-Lesrooster.
Added
~~~~~
* Set group types for class and course groups during import.
Removed
~~~~~~~
* Remove support for Untis exams.
`3.0.1`_ - 2023-09-25
---------------------
Fixed
~~~~~
* Migrations failed in some cases due to programming mistake.
`3.0`_ - 2023-05-15
-------------------
Nothing changed.
`3.0b0`_ - 2023-02-27
---------------------
Removed
~~~~~~~
* Legacy menu integration for AlekSIS-Core pre-3.0
Added
~~~~~
* Add SPA support for AlekSIS-Core 3.0
Changed
~~~~~~~
* Use Room model from core
Fixed
~~~~~
......@@ -16,6 +64,11 @@ Fixed
* Exam import failed sometimes when data provided through Untis were incomplete.
* Importer now automatically fixes intersections of terms with previous terms.
Removed
~~~~~~~
* Remove unused data management menu entry.
`2.3.2`_ - 2022-09-01
---------------------
......@@ -238,18 +291,21 @@ Fixed
.. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
.. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
.. _1.0a1: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/1.0a1
.. _2.0a2: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.0a2
.. _2.0b0: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.0b0
.. _2.0rc1: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.0rc1
.. _2.0rc2: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.0rc2
.. _2.0rc3: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.0rc3
.. _2.0: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.0
.. _2.1: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.1
.. _2.1.1: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.1.1
.. _2.1.2: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.1.2
.. _2.1.3: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.1.3
.. _2.2: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.2
.. _2.3: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.3
.. _2.3.1: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.3.1
.. _2.3.2: https://edugit.org/Teckids/AlekSIS/AlekSIS-App-Untis/-/tags/2.3.2
.. _1.0a1: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/1.0a1
.. _2.0a2: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.0a2
.. _2.0b0: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.0b0
.. _2.0rc1: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.0rc1
.. _2.0rc2: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.0rc2
.. _2.0rc3: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.0rc3
.. _2.0: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.0
.. _2.1: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.1
.. _2.1.1: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.1.1
.. _2.1.2: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.1.2
.. _2.1.3: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.1.3
.. _2.2: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.2
.. _2.3: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.3
.. _2.3.1: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.3.1
.. _2.3.2: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/2.3.2
.. _3.0b0: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/3.0b0
.. _3.0: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/3.0
.. _3.0.1: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis/-/tags/3.0.1
......@@ -12,7 +12,7 @@ class CourseGroupNotFoundAndCreated(DataCheck):
"The Untis import created a new course group because no matching group has been found."
)
solve_options = {IgnoreSolveOption.name: IgnoreSolveOption}
solve_options = {IgnoreSolveOption._class_name: IgnoreSolveOption}
@classmethod
def run_check_data(cls):
......@@ -29,7 +29,7 @@ class CourseGroupNotFoundAndNotCreated(DataCheck):
"for a lesson because no matching group has been found."
)
solve_options = {IgnoreSolveOption.name: IgnoreSolveOption}
solve_options = {IgnoreSolveOption._class_name: IgnoreSolveOption}
@classmethod
def run_check_data(cls):
......
......@@ -8,31 +8,40 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-06-21 11:41+0200\n"
"PO-Revision-Date: 2022-04-29 14:23+0000\n"
"PO-Revision-Date: 2022-08-03 16:40+0000\n"
"Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
"Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis-app-untis/de/>\n"
"Language-Team: German <https://translate.edugit.org/projects/aleksis/"
"aleksis-app-untis/de/>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.8\n"
"X-Generator: Weblate 4.12.1\n"
#: aleksis/apps/untis/data_checks.py:9
msgid "Course groups created by the Untis import because no matching group has been found."
msgstr ""
"Durch Untis-Import erstellte Kursgruppen, weil keine passende Gruppe "
"gefunden wurde."
#: aleksis/apps/untis/data_checks.py:12
msgid "The Untis import created a new course group because no matching group has been found."
msgstr ""
"Der Untis-Import hat eine neue Kursgruppe erstellt, weil keine passende "
"Gruppe gefunden wurde."
#: aleksis/apps/untis/data_checks.py:25
msgid "Course group not set by the Untis import because no matching group has been found."
msgstr ""
"Kursgruppe nicht durch Untis-Import festgelegt, weil keine passende Gruppe "
"gefunden wurde."
#: aleksis/apps/untis/data_checks.py:28
msgid "The Untis import didn't set a course group for a lesson because no matching group has been found."
msgstr ""
"Der Untis-Import hat keine Kursgruppe für eine Stunde festgelegt, weil keine "
"passende Gruppe gefunden wurde."
#: aleksis/apps/untis/menus.py:6
msgid "Link subjects to groups (for Untis MySQL import)"
......@@ -74,10 +83,8 @@ msgid "Number of lesson element in Untis"
msgstr "Nummer des Unterrichtselement in Untis"
#: aleksis/apps/untis/model_extensions.py:57
#, fuzzy
#| msgid "Untis import reference"
msgid "Untis absence reference"
msgstr "Untis-Importreferenz"
msgstr "Untis-Absenz-Referenz"
#: aleksis/apps/untis/model_extensions.py:69
#: aleksis/apps/untis/model_extensions.py:72
......@@ -96,7 +103,7 @@ msgstr "Untis: MySQL"
#: aleksis/apps/untis/preferences.py:16
msgid "School ID in Untis database"
msgstr ""
msgstr "Schul-ID in der Untis-Datenbank"
#: aleksis/apps/untis/preferences.py:24
msgid "Update values of existing subjects"
......@@ -139,26 +146,22 @@ msgid "Use course groups"
msgstr "Kursgruppen nutzen"
#: aleksis/apps/untis/preferences.py:97
#, fuzzy
#| msgid "Build or search course groups for every course instead of setting classes as groups."
msgid "Search course groups for every course instead of setting classes as groups."
msgstr "Baut oder findet Kursgruppen für jeden Kurs anstatt die Klassengruppen zu setzen."
msgstr ""
"Sucht Kursgruppen für jeden Kurs, anstatt Klassen als Gruppen zu setzen."
#: aleksis/apps/untis/preferences.py:105
#, fuzzy
#| msgid "Update name of existing groups"
msgid "Create non-existing course groups"
msgstr "Namen von existierenden Gruppen aktualisieren"
msgstr "Nicht existierende Kursgruppen erstellen"
#: aleksis/apps/untis/preferences.py:106
#, fuzzy
#| msgid "Works only if 'Use course groups' is activated."
msgid "Only used if 'Use course groups' is enabled."
msgstr "Funktioniert nur, wenn 'Kursgruppen benutzen' aktiviert ist."
msgstr "Wird nur benutzt, wenn 'Kursgruppen nutzen' aktiviert ist."
#: aleksis/apps/untis/preferences.py:114
msgid "Register a data problem if a course group has been not found."
msgstr ""
"Ein Datenproblem registrieren, wenn eine Kursgruppe nicht gefunden wurde."
#: aleksis/apps/untis/preferences.py:122
msgid "Match course groups by a subset of parent groups if no 100% match is found"
......@@ -174,7 +177,7 @@ msgstr "Unvollständige Vertretungen ignorieren"
#: aleksis/apps/untis/util/mysql/importers/exams.py:123
msgid "Exam"
msgstr ""
msgstr "Klausur"
#: aleksis/apps/untis/util/mysql/importers/lessons.py:46
msgid "Import lesson {}"
......
from datetime import date, timedelta
from django.core.management.base import BaseCommand
from django.utils import timezone
from calendarweek import CalendarWeek
from tqdm import tqdm
from aleksis.apps.chronos.models import (
Absence,
Event,
Exam,
ExtraLesson,
Holiday,
LessonSubstitution,
ValidityRange,
)
from aleksis.core.models import SchoolTerm
class Command(BaseCommand):
help = "Move all dates to current school year" # noqa
def add_arguments(self, parser):
parser.add_argument("--dry", action="store_true")
def translate_date_start_end(self, name: str, qs):
self.stdout.write(name.upper())
for instance in tqdm(qs):
date_start = instance.date_start
date_end = instance.date_end
date_start_new = date_start + self.time_delta
date_end_new = date_end + self.time_delta
self.stdout.write(f"{date_start}{date_start_new}; {date_end}{date_end_new}")
instance.date_start = date_start_new
instance.date_end = date_end_new
if not self.dry:
instance.save()
def translate_date(self, name: str, qs):
self.stdout.write(name.upper())
for instance in tqdm(qs):
date_old = instance.date
date_new = date_old + self.time_delta
self.stdout.write(f"{date_old}{date_new}")
instance.date = date_new
if not self.dry:
instance.save()
def translate_week_year(self, name: str, qs):
self.stdout.write(name.upper())
for instance in tqdm(qs):
date_old = instance.date
date_new = instance.date + self.time_delta
week_new = CalendarWeek.from_date(date_new)
self.stdout.write(f"{date_old}, {instance.week}{date_new}, {week_new.week}")
instance.year = date_new.year
instance.week = week_new.week
if not self.dry:
instance.save()
def handle(self, *args, **options):
self.dry = options["dry"]
school_terms = SchoolTerm.objects.order_by("-date_end")
if not school_terms.exists():
raise RuntimeError("No school term available.")
school_term = school_terms.first()
self.stdout.write(f"Used school term: {school_term}")
date_start = school_term.date_start
date_end = school_term.date_end
current_date = timezone.now().date()
current_year = current_date.year
if date_start.month <= current_date.month and date_start.day < current_date.day:
current_year -= 1
date_end = date(year=current_year, day=date_start.day, month=date_start.month)
self.stdout.write(f"{current_year}, {date_end}")
days = (date_end - date_start).days
self.weeks = round(days / 7)
self.time_delta = timedelta(days=self.weeks * 7)
self.stdout.write(f"{days}, {self.weeks}")
self.translate_date_start_end("SCHOOL TERM", [school_term])
self.translate_date_start_end(
"VALIDITY RANGES", ValidityRange.objects.filter(school_term=school_term)
)
if not self.weeks:
self.stdout.write(self.style.ERROR("There are no data that have to be moved."))
return
self.translate_week_year(
"SUBSTITUTIONS",
LessonSubstitution.objects.filter(
lesson_period__lesson__validity__school_term=school_term
),
)
self.translate_week_year(
"EXTRA LESSONS", ExtraLesson.objects.filter(school_term=school_term)
)
self.translate_date_start_end("ABSENCES", Absence.objects.filter(school_term=school_term))
self.translate_date("EXAMS", Exam.objects.filter(school_term=school_term))
self.translate_date_start_end(
"HOLIDAYS", Holiday.objects.within_dates(date_start, date_end)
)
self.translate_date_start_end("EVENTS", Event.objects.filter(school_term=school_term))
......@@ -25,6 +25,6 @@ class Command(BaseCommand):
def handle(self, *args, **options):
command = COMMANDS_BY_NAME[options["command"]]
background = options["background"]
school_id = options.get("school_id", None)
version = options.get("plan_version", None)
command.run(background=background, version=version)
school_id = options.get("school_id")
version = options.get("plan_version")
command.run(background=background, school_id=school_id, version=version)
from django.utils.translation import gettext_lazy as _
MENUS = {
"DATA_MANAGEMENT_MENU": [
{
"name": _("Link subjects to groups (for Untis MySQL import)"),
"url": "untis_groups_subjects",
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"untis.assign_subjects_to_groups_rule",
),
],
},
]
}
......@@ -6,7 +6,7 @@ from aleksis.core.util.core_helpers import get_site_preferences
def guess_school_id(apps, schema_editor):
db_alias = schema_editor.connection.alias
from aleksis.apps.chronos.models import ValidityRange
from aleksis.apps.lesrooster.models import ValidityRange
try:
vr = ValidityRange.objects.using(db_alias).first()
......@@ -16,7 +16,7 @@ def guess_school_id(apps, schema_editor):
if not vr or not vr.school_id_untis:
return
get_site_preferences()["untis_mysql__school_id"] = school_id
get_site_preferences()["untis_mysql__school_id"] = vr.school_id_untis
class Migration(migrations.Migration):
......
from django.utils.translation import gettext as _
from jsonstore import IntegerField
from aleksis.apps.chronos import models as chronos_models
from aleksis.core import models as core_models
core_models.SchoolTerm.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
core_models.Person.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
core_models.Group.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
# Chronos models
chronos_models.ValidityRange.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.ValidityRange.field(
school_year_untis=IntegerField(verbose_name=_("Untis school year ID"), null=True, blank=True)
)
chronos_models.ValidityRange.field(
school_id_untis=IntegerField(verbose_name=_("Untis school id"), null=True, blank=True)
)
chronos_models.ValidityRange.field(
version_id_untis=IntegerField(verbose_name=_("Untis version id"), null=True, blank=True)
)
chronos_models.Subject.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.Room.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.SupervisionArea.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.Lesson.field(
lesson_id_untis=IntegerField(verbose_name=_("Lesson id in Untis"), null=True, blank=True)
)
chronos_models.Lesson.field(
element_id_untis=IntegerField(
verbose_name=_("Number of lesson element in Untis"), null=True, blank=True
)
)
chronos_models.LessonPeriod.field(
element_id_untis=IntegerField(
verbose_name=_("Number of lesson element in Untis"), null=True, blank=True
)
)
chronos_models.LessonSubstitution.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.LessonSubstitution.field(
absence_ref_untis=IntegerField(verbose_name=_("Untis absence reference"), null=True, blank=True)
)
chronos_models.SupervisionSubstitution.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.AbsenceReason.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.Absence.field(
import_ref_untis=IntegerField(verbose_name=_("Untis import reference"), null=True, blank=True)
)
chronos_models.Event.field(
import_ref_untis=IntegerField(verbose_name=_("UNTIS import reference"), null=True, blank=True)
)
chronos_models.Holiday.field(
import_ref_untis=IntegerField(verbose_name=_("UNTIS import reference"), null=True, blank=True)
)
chronos_models.ExtraLesson.field(
import_ref_untis=IntegerField(verbose_name=_("UNTIS import reference"), null=True, blank=True)
)
chronos_models.Exam.field(
import_ref_untis=IntegerField(verbose_name=_("UNTIS import reference"), null=True, blank=True)
)
from django.utils.translation import gettext_lazy as _
from dynamic_preferences.preferences import Section
from dynamic_preferences.types import BooleanPreference, IntegerPreference
from dynamic_preferences.types import BooleanPreference, IntegerPreference, ModelChoicePreference
from aleksis.core.models import GroupType
from aleksis.core.registries import site_preferences_registry
untis_mysql = Section("untis_mysql", verbose_name=_("Untis: MySQL"))
......@@ -72,6 +73,20 @@ class OverwriteGroupOwners(BooleanPreference):
default = True
@site_preferences_registry.register
class GroupTypeClassGroups(ModelChoicePreference):
section = untis_mysql
name = "group_type_class_groups"
required = False
default = None
model = GroupType
verbose_name = _("Group type for class groups")
help_text = _("If you leave it empty, no group type will be used.")
def get_queryset(self):
return GroupType.objects.managed_and_unmanaged()
@site_preferences_registry.register
class UpdateRoomsName(BooleanPreference):
section = untis_mysql
......@@ -106,6 +121,20 @@ class CreateCourseGroups(BooleanPreference):
help_text = _("Only used if 'Use course groups' is enabled.")
@site_preferences_registry.register
class GroupTypeCourseGroups(ModelChoicePreference):
section = untis_mysql
name = "group_type_course_groups"
required = False
default = None
model = GroupType
verbose_name = _("Group type for course groups")
help_text = _("If you leave it empty, no group type will be used.")
def get_queryset(self):
return GroupType.objects.managed_and_unmanaged()
@site_preferences_registry.register
class DataCheckNotFoundCourseGroups(BooleanPreference):
section = untis_mysql
......
......@@ -4,12 +4,22 @@ from aleksis.apps.untis.util.mysql.util import (
untis_split_third,
)
test_lesson_element = """49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;,49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;"""
test_lesson_element = (
'49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, Fokus Physik"~'
'"Nawi"~~n~~;11;12;13;14;15;16;17;,49~0~302~7r;~0;~0~0~175608~~'
'~~~"Freiraum: Naturwissenschaften, Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;'
)
test_lesson_element_partial = """49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;"""
test_lesson_element_partial = (
'49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften,'
+ ' Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;'
)
test_lesson_element_partial_partial = ";11;12;13;14;15;16;17;"
test_lesson_element_second = """46~0~92~45;~45;~12~11~0~~B~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~21700,45~0~92~45;~45;~0~0~19000~~~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~0"""
test_lesson_element_second = (
'46~0~92~45;~45;~12~11~0~~B~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~'
+ '21700,45~0~92~45;~45;~0~0~19000~~~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~0'
)
def test_untis_split_first():
......@@ -22,19 +32,20 @@ def test_untis_split_first():
assert len(untis_split_first(test_lesson_element)) == 2
assert untis_split_first(test_lesson_element) == [
"""49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;""",
"""49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;""",
'49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, '
+ 'Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;',
'49~0~302~7r;~0;~0~0~175608~~~~~"Freiraum: Naturwissenschaften, '
+ 'Fokus Physik"~"Nawi"~~n~~;11;12;13;14;15;16;17;',
]
assert len(untis_split_first(test_lesson_element_second)) == 2
assert untis_split_first(test_lesson_element_second) == [
"""46~0~92~45;~45;~12~11~0~~B~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~21700""",
"""45~0~92~45;~45;~0~0~19000~~~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~0""",
'46~0~92~45;~45;~12~11~0~~B~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~21700',
'45~0~92~45;~45;~0~0~19000~~~~~~~~n~~;18;~"Wp_Eb"~~34~0~67900~0~0~~0',
]
def test_untis_split_second():
assert untis_split_second("") == []
assert untis_split_second("a~b~c") == ["a", "b", "c"]
......@@ -46,7 +57,7 @@ def test_untis_split_second():
assert untis_split_second("1~~3", int, remove_empty=False) == [1, None, 3]
assert untis_split_second("1~~3", int) == [1, 3]
assert untis_split_second(""""asdf"~"asdf"~"asdf""") == ["asdf", "asdf", "asdf"]
assert untis_split_second('"asdf"~"asdf"~"asdf') == ["asdf", "asdf", "asdf"]
assert len(untis_split_second(test_lesson_element_partial, remove_empty=False)) == 18
assert untis_split_second(test_lesson_element_partial, remove_empty=False) == [
......@@ -83,7 +94,7 @@ def test_untis_split_third():
assert untis_split_third("1;;3", int, remove_empty=False) == [1, None, 3]
assert untis_split_third("1;;3", int) == [1, 3]
assert untis_split_third(""""asdf";"asdf";"asdf""") == ["asdf", "asdf", "asdf"]
assert untis_split_third('"asdf";"asdf";"asdf') == ["asdf", "asdf", "asdf"]
assert len(untis_split_third(test_lesson_element_partial_partial)) == 7
assert untis_split_third(test_lesson_element_partial_partial) == [
......
import logging
from datetime import timedelta
from enum import Enum
from calendarweek import CalendarWeek
from django.db.models import Count
from django.utils.translation import gettext as _
from reversion import create_revision, set_comment
from tqdm import tqdm
from aleksis.apps.chronos import models as chronos_models
from aleksis.apps.chronos.models import TimePeriod, ValidityRange
from aleksis.apps.chronos.models import LessonEvent
from aleksis.apps.kolego import models as kolego_models
from aleksis.apps.lesrooster.models import ValidityRange
from aleksis.core.models import Group
from .... import models as mysql_models
from ..util import (
......@@ -17,10 +21,11 @@ from ..util import (
move_weekday_to_range,
run_default_filter,
untis_date_to_date,
update_or_create_lesson_event,
)
logger = logging.getLogger(__name__)
unknown_reason, _ = chronos_models.AbsenceReason.objects.get_or_create(short_name="?")
unknown_reason, __ = kolego_models.AbsenceReason.objects.get_or_create(short_name="?")
class AbsenceType(Enum):
......@@ -32,7 +37,7 @@ class AbsenceType(Enum):
def import_absences(
validity_range: ValidityRange,
absence_reasons_ref,
time_periods_ref,
slots_ref,
teachers_ref,
classes_ref,
rooms_ref,
......@@ -55,7 +60,7 @@ def import_absences(
for absence in tqdm(absences, desc="Import absences", **TQDM_DEFAULTS):
import_ref = absence.absence_id
logger.info("Import absence {}".format(import_ref))
logger.info(f"Import absence {import_ref}")
if absence.absence_reason_id == 0:
reason = unknown_reason
......@@ -72,116 +77,112 @@ def import_absences(
weekday_to = date_to.weekday()
# Check min/max weekdays
weekday_from = move_weekday_to_range(time_periods_ref, weekday_from)
weekday_to = move_weekday_to_range(time_periods_ref, weekday_to)
weekday_from = move_weekday_to_range(slots_ref, weekday_from)
weekday_to = move_weekday_to_range(slots_ref, weekday_to)
# Check min/max periods
first_period = get_first_period(time_periods_ref, weekday_from)
last_period = get_last_period(time_periods_ref, weekday_from)
first_period = get_first_period(slots_ref, weekday_from)
last_period = get_last_period(slots_ref, weekday_from)
if period_from == 0:
period_from = first_period
if period_to == 0:
period_to = last_period
time_period_from = time_periods_ref[weekday_from][period_from]
time_period_to = time_periods_ref[weekday_to][period_to]
comment = absence.text
slot_from = slots_ref[weekday_from][period_from]
slot_to = slots_ref[weekday_to][period_to]
comment = absence.text if absence.text else ""
datetime_start = slot_from.get_datetime_start(date_from)
datetime_end = slot_to.get_datetime_end(date_to)
group = None
teacher = None
room = None
if type_ == AbsenceType.GROUP.value:
group = classes_ref[absence.ida]
group = classes_ref[absence.ida] # noqa
elif type_ == AbsenceType.TEACHER.value:
teacher = teachers_ref[absence.ida]
elif type_ == AbsenceType.ROOM.value:
room = rooms_ref[absence.ida]
new_absence, created = chronos_models.Absence.objects.get_or_create(
import_ref_untis=import_ref,
defaults={
"reason": reason,
"group": group,
"teacher": teacher,
"room": room,
"date_start": date_from,
"date_end": date_to,
"period_from": time_period_from,
"period_to": time_period_to,
"comment": absence.text,
"school_term": validity_range.school_term,
},
)
if created:
logger.info(" New absence created")
if (
new_absence.reason != reason
or new_absence.group != group
or new_absence.teacher != teacher
or new_absence.room != room
or new_absence.date_start != date_from
or new_absence.date_end != date_to
or new_absence.period_from != time_period_from
or new_absence.period_to != time_period_to
or new_absence.comment != comment
or new_absence.school_term != validity_range.school_term
):
room = rooms_ref[absence.ida] # noqa
if teacher:
try:
created = False
new_absence = kolego_models.Absence.objects.get(
extended_data__import_ref_untis=import_ref
)
except kolego_models.Absence.DoesNotExist:
created = True
new_absence = kolego_models.Absence()
new_absence.reason = reason
new_absence.group = group
new_absence.teacher = teacher
new_absence.room = room
new_absence.date_start = date_from
new_absence.date_end = date_to
new_absence.period_from = time_period_from
new_absence.period_to = time_period_to
new_absence.person = teacher
new_absence.datetime_start = datetime_start
new_absence.datetime_end = datetime_end
new_absence.comment = comment
new_absence.school_term = validity_range.school_term
new_absence.save()
logger.info(" Absence updated")
new_absence.extended_data["import_ref_untis"] = import_ref
new_absence.save(skip_overlap_handling=True)
existing_absences.append(import_ref)
ref[import_ref] = new_absence
if created:
logger.info(" New absence created")
existing_absences.append(import_ref)
ref[import_ref] = new_absence
# Cancel all lessons of this group
if group:
# If a group is absent, all lessons of this group are cancelled.
current_date = date_from
while current_date <= date_to:
current_period_from = (
period_from if current_date == date_from else TimePeriod.period_min
)
current_period_to = period_to if current_date == date_to else TimePeriod.period_max
lesson_periods = (
chronos_models.LessonPeriod.objects.filter_group(group)
.on_day(current_date)
.filter(
period__period__gte=current_period_from,
period__period__lte=current_period_to,
)
# Search parent groups with match
group_qs = Group.objects.annotate(parent_groups_count=Count("parent_groups")).filter(
parent_groups_count=1, parent_groups=group
)
# Search lesson events with exact groups or parent groups match
qs = LessonEvent.objects.filter(
amends__isnull=True, extended_data__event_untis__isnull=True
).filter(
pk__in=LessonEvent.objects.annotate(groups_count=Count("groups"))
.filter(groups_count=1, groups=group)
.values_list("id", flat=True)
.union(LessonEvent.objects.filter(groups__in=group_qs).values_list("id", flat=True))
)
affected_events = LessonEvent.get_single_events(
datetime_start, datetime_end, request=None, with_reference_object=True, queryset=qs
)
for affected_event in affected_events:
ref_object = affected_event["REFERENCE_OBJECT"]
amending_event = update_or_create_lesson_event(
amends=ref_object,
datetime_start=affected_event["DTSTART"].dt,
datetime_end=affected_event["DTEND"].dt,
defaults=dict(
cancelled=True,
current_change=True,
slot_number_start=ref_object.slot_number_start,
slot_number_end=ref_object.slot_number_end,
),
)
amending_event.extended_data["cancelled_by_absence_untis"] = import_ref
amending_event.save()
for lesson_period in lesson_periods:
week = CalendarWeek.from_date(current_date)
sub, __ = chronos_models.LessonSubstitution.objects.get_or_create(
lesson_period=lesson_period,
week=week.week,
year=week.year,
defaults=dict(cancelled=True, absence_ref_untis=import_ref),
)
created_substitutions.append(sub)
current_date += timedelta(days=1)
logging.info(
f" Cancel lesson event {ref_object.id} from {affected_event['DTSTART'].dt} "
f"to {affected_event['DTEND'].dt} with event {amending_event.id}"
)
# Delete all no longer existing absences
for a in chronos_models.Absence.objects.filter(
date_start__lte=validity_range.date_end, date_end__gte=validity_range.date_start
for a in kolego_models.Absence.objects.filter(
extended_data__import_ref_untis__isnull=False,
datetime_start__date__lte=validity_range.date_end,
datetime_end__date__gte=validity_range.date_start,
):
if a.import_ref_untis and a.import_ref_untis not in existing_absences:
logger.info("Absence {} deleted".format(a.id))
a.delete()
if a.extended_data["import_ref_untis"] not in existing_absences:
logger.info(f"Absence {a.id} deleted")
with create_revision():
set_comment(_("Deleted by Untis import"))
a.delete()
return ref, created_substitutions
import logging
from django.utils.translation import gettext as _
from reversion import create_revision, set_comment
from tqdm import tqdm
from aleksis.apps.chronos import models as chronos_models
from aleksis.apps.chronos.models import ValidityRange
from aleksis.apps.chronos.models import LessonEvent, SupervisionEvent
from aleksis.apps.lesrooster.models import ValidityRange
from .... import models as mysql_models
from ..util import (
......@@ -23,7 +26,7 @@ logger = logging.getLogger(__name__)
def import_events(
validity_range: ValidityRange,
time_periods_ref,
slots_ref,
teachers_ref,
classes_ref,
rooms_ref,
......@@ -44,33 +47,36 @@ def import_events(
for event in tqdm(events, desc="Import events", **TQDM_DEFAULTS):
import_ref = event.event_id
logger.info("Import event {}".format(import_ref))
# Build values
comment = event.text
date_from = untis_date_to_date(event.datefrom)
date_to = untis_date_to_date(event.dateto)
period_from = event.lessonfrom
period_to = event.lessonto
weekday_from = date_from.weekday()
weekday_to = date_to.weekday()
comment = event.text or ""
datetime_start = untis_date_to_date(event.datefrom)
datetime_end = untis_date_to_date(event.dateto)
slot_start = event.lessonfrom
slot_end = event.lessonto
weekday_from = datetime_start.weekday()
weekday_to = datetime_end.weekday()
logger.info(
f"Import event {import_ref} ({datetime_start}-{datetime_end}, "
f"{slot_start}-{slot_end}, {comment})"
)
# Check min/max weekdays
weekday_from = move_weekday_to_range(time_periods_ref, weekday_from)
weekday_to = move_weekday_to_range(time_periods_ref, weekday_to)
weekday_from = move_weekday_to_range(slots_ref, weekday_from)
weekday_to = move_weekday_to_range(slots_ref, weekday_to)
# Check min/max periods
first_period = get_first_period(time_periods_ref, weekday_from)
last_period = get_last_period(time_periods_ref, weekday_from)
first_slot = get_first_period(slots_ref, weekday_from)
last_slot = get_last_period(slots_ref, weekday_from)
if period_from == 0:
period_from = first_period
if period_to == 0:
period_to = last_period
if slot_start == 0:
slot_start = first_slot
if slot_end == 0:
slot_end = last_slot
time_period_from = time_periods_ref[weekday_from][period_from]
time_period_to = time_periods_ref[weekday_to][period_to]
slot_start = slots_ref[weekday_from][slot_start]
slot_end = slots_ref[weekday_to][slot_end]
groups = []
teachers = []
......@@ -96,39 +102,31 @@ def import_events(
room = rooms_ref[int(el[3])]
rooms.append(room)
new_event, created = chronos_models.Event.objects.get_or_create(
import_ref_untis=import_ref,
datetime_start = slot_start.get_datetime_start(datetime_start)
datetime_end = slot_end.get_datetime_end(datetime_end)
new_event, created = LessonEvent.objects.filter(
datetime_start__date__lte=validity_range.date_end,
datetime_end__date__gte=validity_range.date_start,
).update_or_create(
extended_data__event_untis=import_ref,
defaults={
"date_start": date_from,
"date_end": date_to,
"period_from": time_period_from,
"period_to": time_period_to,
"datetime_start": datetime_start,
"datetime_end": datetime_end,
"title": comment,
"school_term": validity_range.school_term,
"slot_number_start": slot_start.period,
"slot_number_end": slot_end.period,
"cancelled": False,
"current_change": True,
},
)
new_event.extended_data["event_untis"] = import_ref
new_event.save()
if created:
logger.info(" New event created")
# Sync simple fields
if (
new_event.date_start != date_from
or new_event.date_end != date_to
or new_event.period_from != time_period_from
or new_event.period_to != time_period_to
or new_event.title != comment
or new_event.school_term != validity_range.school_term
):
new_event.date_start = date_from
new_event.date_end = date_to
new_event.period_from = time_period_from
new_event.period_to = time_period_to
new_event.title = comment
new_event.school_term = validity_range.school_term
new_event.save()
logger.info(" Time range and title updated")
# Sync m2m-fields
new_event.groups.set(groups)
new_event.teachers.set(teachers)
......@@ -137,10 +135,14 @@ def import_events(
existing_events.append(import_ref)
ref[import_ref] = new_event
# Delete all no longer existing events
for e in chronos_models.Event.objects.within_dates(
validity_range.date_start, validity_range.date_end
):
if e.import_ref_untis and e.import_ref_untis not in existing_events:
logger.info("Event {} deleted".format(e.id))
# Delete all no longer existing events
for e in LessonEvent.objects.filter(
extended_data__event_untis__isnull=False,
datetime_start__date__lte=validity_range.date_end,
datetime_end__date__gte=validity_range.date_start,
).not_instance_of(SupervisionEvent):
if e.extended_data["event_untis"] not in existing_events:
logger.info(f"Event {e.id} deleted")
with create_revision():
set_comment(_("Deleted by Untis import"))
e.delete()
import logging
from django.utils.translation import gettext as _
from calendarweek import CalendarWeek
from tqdm import tqdm
from aleksis.apps.chronos import models as chronos_models
from aleksis.apps.chronos.models import ExtraLesson, Lesson, ValidityRange
from .... import models as mysql_models
from ..util import (
TQDM_DEFAULTS,
connect_untis_fields,
date_to_untis_date,
get_first_period,
get_last_period,
move_weekday_to_range,
run_default_filter,
untis_date_to_date,
untis_split_second,
)
logger = logging.getLogger(__name__)
def import_exams(
validity_range: ValidityRange,
time_periods_ref,
subjects_ref,
teachers_ref,
rooms_ref,
):
ref = {}
# Get absences
exams = (
run_default_filter(validity_range, mysql_models.Exam.objects, filter_term=False)
.filter(
date__lte=date_to_untis_date(validity_range.date_end),
date__gte=date_to_untis_date(validity_range.date_start),
)
.order_by("exam_id")
)
existing_exams = []
for exam in tqdm(exams, desc="Import exams", **TQDM_DEFAULTS):
import_ref = exam.exam_id
logger.info("Import exam {}".format(import_ref))
# Build values
title = exam.name or ""
comment = exam.text or ""
day = untis_date_to_date(exam.date)
period_from = exam.lessonfrom
period_to = exam.lessonto
weekday = day.weekday()
week = CalendarWeek.from_date(day)
# Check min/max weekdays
weekday = move_weekday_to_range(time_periods_ref, weekday)
# Check min/max periods
first_period = get_first_period(time_periods_ref, weekday)
last_period = get_last_period(time_periods_ref, weekday)
if period_from == 0:
period_from = first_period
if period_to == 0:
period_to = last_period
time_period_from = time_periods_ref[weekday][period_from]
time_period_to = time_periods_ref[weekday][period_to]
# Get groups, teachers and rooms
raw_exams = connect_untis_fields(exam, "examelement", 10)
first = True
lesson = None
subject = None
exams = []
for raw_exam in raw_exams:
el = untis_split_second(raw_exam, remove_empty=False)
if first:
lesson_id = int(el[0])
subject_id = int(el[1])
lesson = Lesson.objects.get(validity=validity_range, lesson_id_untis=lesson_id)
subject = subjects_ref[subject_id] if subject_id in subjects_ref else None
first = False
period = int(el[4])
if not period:
logger.warning(" Skip incomplete exam element")
continue
period = time_periods_ref[weekday][period]
teacher_id = int(el[5])
room_id = int(el[6])
teacher = teachers_ref[teacher_id] if teacher_id in teachers_ref else None
room = rooms_ref[room_id] if room_id in rooms_ref else None
exams.append((period, teacher, room))
if not lesson or not subject or not exams:
logger.warning(" Skip exam due to missing data.")
continue
new_exam, created = chronos_models.Exam.objects.update_or_create(
import_ref_untis=import_ref,
defaults={
"date": day,
"lesson": lesson,
"period_from": time_period_from,
"period_to": time_period_to,
"title": title,
"comment": comment,
"school_term": validity_range.school_term,
},
)
if created:
logger.info(" New exam created")
extra_lesson_pks = []
for exam in exams:
period, teacher, room = exam
comment = new_exam.title or _("Exam")
extra_lesson, __ = ExtraLesson.objects.get_or_create(
exam=new_exam,
period=period,
defaults={
"room": room,
"week": week.week,
"year": week.year,
"comment": comment,
"subject": subject,
},
)
if (
extra_lesson.room != room
or extra_lesson.week != week.week
or extra_lesson.year != week.year
or extra_lesson.comment != comment
or extra_lesson.subject != subject
):
extra_lesson.room = room
extra_lesson.week = week.week
extra_lesson.year = week.year
extra_lesson.comment = comment
extra_lesson.subject = subject
extra_lesson.save()
extra_lesson.groups.set(lesson.groups.all())
extra_lesson.teachers.set([teacher])
extra_lesson_pks.append(extra_lesson.pk)
# Delete no-longer necessary extra lessons
ExtraLesson.objects.filter(exam=new_exam).exclude(pk__in=extra_lesson_pks).delete()
existing_exams.append(import_ref)
ref[import_ref] = new_exam
# Delete all no longer existing exams
for e in chronos_models.Exam.objects.within_dates(
validity_range.date_start, validity_range.date_end
):
if e.import_ref_untis and e.import_ref_untis not in existing_exams:
logger.info(f" Exam {e.id} deleted")
e.delete()
import logging
from typing import Dict
from tqdm import tqdm
from aleksis.apps.chronos import models as chronos_models
from aleksis.apps.chronos.models import ValidityRange
from aleksis.apps.lesrooster import models as lesrooster_models
from aleksis.core import models as core_models
from .... import models as mysql_models
from ..util import TQDM_DEFAULTS, run_default_filter, untis_date_to_date
......@@ -12,7 +11,9 @@ from ..util import TQDM_DEFAULTS, run_default_filter, untis_date_to_date
logger = logging.getLogger(__name__)
def import_holidays(validity_range: ValidityRange) -> Dict[int, chronos_models.Holiday]:
def import_holidays(
validity_range: lesrooster_models.ValidityRange,
) -> dict[int, core_models.Holiday]:
"""Import holidays."""
ref = {}
......@@ -23,46 +24,32 @@ def import_holidays(validity_range: ValidityRange) -> Dict[int, chronos_models.H
import_ref = holiday.holiday_id
# Check if needed data are provided
if not holiday.name:
raise RuntimeError(
"Holiday ID {}: Cannot import holiday without short name.".format(import_ref)
)
if not holiday.name and not holiday.longname:
raise RuntimeError(f"Holiday ID {import_ref}: Cannot import holiday without name.")
title = holiday.name[:50]
comments = holiday.longname
title = holiday.name or holiday.longname
date_start = untis_date_to_date(holiday.datefrom)
date_end = untis_date_to_date(holiday.dateto)
logger.info("Import holiday {} …".format(title))
logger.info(f"Import holiday {title}")
# Get or create holiday
new_holiday, created = chronos_models.Holiday.objects.get_or_create(
import_ref_untis=import_ref,
new_holiday, created = core_models.Holiday.objects.update_or_create(
extended_data__import_ref_untis=import_ref,
defaults={
"title": title,
"comments": comments,
"holiday_name": title,
"date_start": date_start,
"date_end": date_end,
},
)
new_holiday.extended_data["import_ref_untis"] = import_ref
new_holiday.save()
if created:
logger.info(" New holiday created")
if (
new_holiday.title != title
or new_holiday.comments != comments
or new_holiday.date_start != date_start
or new_holiday.date_end != date_end
):
new_holiday.title = title
new_holiday.comments = comments
new_holiday.date_start = date_start
new_holiday.date_end = date_end
new_holiday.save()
logger.info(" Holiday updated")
ref[import_ref] = new_holiday
return ref