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 (159)
Showing
with 914 additions and 772 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"
}
}
<!-- AlekSIS is developed on EduGit. GitHub only serves as
backup mirror and to help people find the project. If
possible, please submit your merge request on EduGit!
EduGit accepts logins with GitHub accounts.
-->
[ ] I have read the above and have no way to contribute on EduGit
[ ] I understand that GitHub's terms of service exclude young and
learning contributors, but still cannot contribute on EduGit
instead.
# 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/
......@@ -6,6 +6,78 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog`_,
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
~~~~~
* Importer failed sometimes on progressing absences.
* 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
---------------------
Fixed
~~~~~
* Guessing school ID could fail in `aleksis-admin migrate` under some
version conditions
`2.3.1`_ - 2022-08-13
---------------------
......@@ -14,6 +86,7 @@ Fixed
* Import failed sometimes if there were lessons with names.
* Import failed if there was a lesson without a teacher.
* Course group matching didn't work correctly with teachers.
`2.3`_ - 2022-06-25
-------------------
......@@ -218,17 +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
.. _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,16 +6,17 @@ 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()
except ValidityRange.DoesNotExist:
return
school_id = vr.school_id_untis
if school_id:
get_site_preferences()["untis_mysql__school_id"] = school_id
if not vr or not vr.school_id_untis:
return
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
comment = exam.text
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]
first = False
period = int(el[4])
period = time_periods_ref[weekday][period]
teacher_id = int(el[5])
room_id = int(el[6])
teacher = teachers_ref[teacher_id]
room = rooms_ref[room_id]
exams.append((period, teacher, room))
if not lesson or not subject:
logger.warning(f"Skip exam {import_ref} 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("exam {} deleted".format(e.id))
e.delete()