Skip to content
Commits on Source (63)
module.exports = {
extends: [
"eslint:recommended",
"plugin:vue/strongly-recommended",
// "plugin:prettier/recommended",
"plugin:@intlify/vue-i18n/recommended",
],
rules: {
"no-unused-vars": "warn",
"vue/no-unused-vars": "off",
"vue/multi-word-component-names": "off",
"@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]+$",
},
],
// 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",
},
};
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/docker/image.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/deploy/review.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/docker/image.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/deploy/review.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/
......@@ -6,6 +6,44 @@ 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`_.
`3.0`_ - 2023-05-14
-------------------
Changed
~~~~~~~
* Translations were updated.
`3.0b0`_ - 2023-02-16
---------------------
This version requires AlekSIS-Core 3.0. It is incompatible with any previous
version.
Removed
~~~~~~~
* `Room` model is now available in AlekSIS-Core 3.0
* Legacy menu integration for AlekSIS-Core pre-3.0
Added
~~~~~
* Support for SPA in AlekSIS-Core 3.0
Changed
~~~~~~~
* Improve rendering of substituted or cancelled items on daily lessons/supervisions pages.
Fixed
~~~~~
* The daily lessons page did not work correctly due to faulty pre-filtering of lessons.
* Substitution form teacher selections also included students in some cases
* Getting the max and min periods for events failed due to using always the current school term.
Typically, that caused problems when the schedule changed (more or less periods on a day).
`2.5`_ - 2022-11-12
-------------------
......@@ -344,3 +382,5 @@ Fixed
.. _2.4.1: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/2.4.1
.. _2.4.2: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/2.4.2
.. _2.5: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/2.5
.. _3.0b0: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0b0
.. _3.0: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0
......@@ -5,6 +5,8 @@ from django.utils.html import format_html
from guardian.admin import GuardedModelAdmin
from aleksis.core.models import Room
from .models import (
Absence,
AbsenceReason,
......@@ -16,7 +18,6 @@ from .models import (
Lesson,
LessonPeriod,
LessonSubstitution,
Room,
Subject,
Supervision,
SupervisionArea,
......
......@@ -8,9 +8,9 @@ from django_filters import BooleanFilter, FilterSet, ModelMultipleChoiceFilter
from django_select2.forms import ModelSelect2MultipleWidget
from material import Layout, Row
from aleksis.core.models import Group, Person, SchoolTerm
from aleksis.core.models import Group, Person, Room, SchoolTerm
from .models import Break, Room, Subject, SupervisionArea, TimePeriod
from .models import Break, Subject, SupervisionArea, TimePeriod
class MultipleModelMultipleChoiceFilter(ModelMultipleChoiceFilter):
......@@ -68,16 +68,7 @@ class LessonPeriodFilter(FilterSet):
)
lesson__teachers = MultipleModelMultipleChoiceFilter(
["lesson__teachers", "current_substitution__teachers"],
queryset=Person.objects.annotate(
lessons_count=Count(
"lessons_as_teacher",
filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current)
if SchoolTerm.current
else Q(),
)
)
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name"),
queryset=Person.objects.none(),
label=_("Teachers"),
widget=ModelSelect2MultipleWidget(
attrs={"data-minimum-input-length": 0, "class": "browser-default"},
......@@ -118,6 +109,18 @@ class LessonPeriodFilter(FilterSet):
weekday = kwargs.pop("weekday")
super().__init__(*args, **kwargs)
self.filters["period"].queryset = TimePeriod.objects.filter(weekday=weekday)
self.filters["lesson__teachers"].queryset = (
Person.objects.annotate(
lessons_count=Count(
"lessons_as_teacher",
filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current)
if SchoolTerm.current
else Q(),
)
)
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name")
)
self.form.layout = Layout(
Row("period", "lesson__groups", "room"),
Row("lesson__teachers", "lesson__subject", "substituted"),
......@@ -129,16 +132,7 @@ class SupervisionFilter(FilterSet):
area = ModelMultipleChoiceFilter(queryset=SupervisionArea.objects.all())
teacher = MultipleModelMultipleChoiceFilter(
["teacher", "current_substitution__teacher"],
queryset=Person.objects.annotate(
lessons_count=Count(
"lessons_as_teacher",
filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current)
if SchoolTerm.current
else Q(),
)
)
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name"),
queryset=Person.objects.none(),
label=_("Teacher"),
widget=ModelSelect2MultipleWidget(
attrs={"data-minimum-input-length": 0, "class": "browser-default"},
......@@ -166,6 +160,18 @@ class SupervisionFilter(FilterSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters["break_item"].queryset = Break.objects.filter(supervisions__in=self.queryset)
self.filters["teacher"].queryset = (
Person.objects.annotate(
lessons_count=Count(
"lessons_as_teacher",
filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current)
if SchoolTerm.current
else Q(),
)
)
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name")
)
self.form.layout = Layout(
Row("break_item", "area"),
Row("teacher", "substituted"),
......
......@@ -4,6 +4,7 @@ from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget
from material import Layout
from .models import AutomaticPlan, LessonSubstitution, SupervisionSubstitution
from .util.chronos_helpers import get_teachers
class LessonSubstitutionForm(forms.ModelForm):
......@@ -23,6 +24,11 @@ class LessonSubstitutionForm(forms.ModelForm):
),
}
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.fields["teachers"].queryset = get_teachers(request.user)
class SupervisionSubstitutionForm(forms.ModelForm):
"""Form to manage supervisions substitutions."""
......@@ -41,6 +47,11 @@ class SupervisionSubstitutionForm(forms.ModelForm):
),
}
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.fields["teacher"].queryset = get_teachers(request.user)
class AutomaticPlanForm(forms.ModelForm):
layout = Layout("slug", "name", "number_of_days", "show_header_box")
......
import { hasPersonValidator } from "aleksis.core/routeValidators";
export default {
meta: {
inMenu: true,
titleKey: "chronos.menu_title",
icon: "mdi-school-outline",
validators: [hasPersonValidator],
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
children: [
{
path: "",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.allTimetables",
meta: {
inMenu: true,
titleKey: "chronos.timetable.menu_title_all",
icon: "mdi-grid",
permission: "chronos.view_timetable_overview_rule",
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "timetable/my/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.myTimetable",
meta: {
inMenu: true,
titleKey: "chronos.timetable.menu_title_my",
icon: "mdi-account-outline",
permission: "chronos.view_my_timetable_rule",
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "timetable/my/:year/:month/:day/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.myTimetableByDate",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "timetable/:type_/:pk/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.timetable",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "timetable/:type_/:pk/:year/:week/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.timetableByWeek",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "timetable/:type_/:pk/print/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.timetablePrint",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "timetable/:type_/:pk/:regular/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.timetableRegular",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "lessons/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.lessonsDay",
meta: {
inMenu: true,
titleKey: "chronos.lessons.menu_title_daily",
icon: "mdi-calendar-outline",
permission: "chronos.view_lessons_day_rule",
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "lessons/:year/:month/:day/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.lessonsDayByDate",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "lessons/:id_/:week/substitution/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.editSubstitution",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "lessons/:id_/:week/substitution/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.deleteSubstitution",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "substitutions/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.substitutions",
meta: {
inMenu: true,
titleKey: "chronos.substitutions.menu_title",
icon: "mdi-update",
permission: "chronos.view_substitutions_rule",
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "substitutions/print/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.substitutionsPrint",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "substitutions/:year/:month/:day/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.substitutionsByDate",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "substitutions/:year/:month/:day/print/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.substitutionsPrintByDate",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "supervisions/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.supervisionsDay",
meta: {
inMenu: true,
titleKey: "chronos.supervisions.menu_title_daily",
icon: "mdi-calendar-outline",
permission: "chronos.view_supervisions_day_rule",
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "supervisions/:year/:month/:day/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.supervisionsDayByDate",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "supervisions/:id_/:week/substitution/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.editSupervisionSubstitution",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
{
path: "supervisions/:id_/:week/substitution/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "chronos.deleteSupervisionSubstitution",
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
},
],
};
{
"chronos": {
"menu_title": "Stundenpläne",
"timetable": {
"menu_title_all": "Alle Stundenpläne",
"menu_title_my": "Mein Stundenplan"
},
"lessons": {
"menu_title_daily": "Tagesstunden"
},
"substitutions": {
"menu_title": "Vertretungen"
},
"supervisions": {
"menu_title_daily": "Tagesvertretungen"
}
}
}
{
"chronos": {
"menu_title": "Timetables",
"timetable": {
"menu_title_all": "All timetables",
"menu_title_my": "My timetable"
},
"lessons": {
"menu_title_daily": "Daily lessons"
},
"substitutions": {
"menu_title": "Substitutions"
},
"supervisions": {
"menu_title_daily": "Daily supervisions"
}
}
}
{
"chronos": {
"timetable": {
"menu_title_all": "Все расписания",
"menu_title_my": "Моё расписание"
},
"supervisions": {
"menu_title_daily": "Ежедневные наблюдения"
},
"menu_title": "Расписания",
"lessons": {
"menu_title_daily": "Ежедневные уроки"
},
"substitutions": {
"menu_title": "Замены"
}
}
}
{
"chronos": {
"menu_title": "Розклади",
"timetable": {
"menu_title_all": "Усі розклади",
"menu_title_my": "Мій розклад"
},
"substitutions": {
"menu_title": "Заміни"
},
"supervisions": {
"menu_title_daily": "Щоденні спостереження"
},
"lessons": {
"menu_title_daily": "Щоденні уроки"
}
}
}
......@@ -8,10 +8,9 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-06-21 11:32+0200\n"
"PO-Revision-Date: 2022-06-22 19:59+0000\n"
"PO-Revision-Date: 2023-01-25 05:57+0000\n"
"Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
"Language-Team: German <https://translate.edugit.org/projects/aleksis/"
"aleksis-app-chronos/de/>\n"
"Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/de/>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
......@@ -165,7 +164,7 @@ msgstr "Fächer"
#: aleksis/apps/chronos/models.py:374
msgid "Can view room timetable"
msgstr "Kann Raumstundenpläne sehen"
msgstr "Kann Raum-Stundenplan sehen"
#: aleksis/apps/chronos/models.py:376 aleksis/apps/chronos/models.py:454
#: aleksis/apps/chronos/models.py:533 aleksis/apps/chronos/models.py:751
......
......@@ -8,17 +8,14 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-06-21 11:32+0200\n"
"PO-Revision-Date: 2022-06-22 19:59+0000\n"
"PO-Revision-Date: 2023-02-08 22:40+0000\n"
"Last-Translator: Serhii Horichenko <m@sgg.im>\n"
"Language-Team: Russian <https://translate.edugit.org/projects/aleksis/"
"aleksis-app-chronos/ru/>\n"
"Language-Team: Russian <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Generator: Weblate 4.12.1\n"
#: aleksis/apps/chronos/form_extensions.py:7
......@@ -167,7 +164,7 @@ msgstr "Предметы"
#: aleksis/apps/chronos/models.py:374
msgid "Can view room timetable"
msgstr "Может просматривать расписание комнаты"
msgstr "Может просмативать расписание комнаты"
#: aleksis/apps/chronos/models.py:376 aleksis/apps/chronos/models.py:454
#: aleksis/apps/chronos/models.py:533 aleksis/apps/chronos/models.py:751
......
# Generated by Django 3.2.16 on 2022-11-30 17:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0047_add_room_model'),
('chronos', '0012_add_supervision_global_permission'),
]
operations = [
migrations.AlterField(
model_name='absence',
name='room',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='core.room', verbose_name='Room'),
),
migrations.AlterField(
model_name='event',
name='rooms',
field=models.ManyToManyField(related_name='events', to='core.Room', verbose_name='Rooms'),
),
migrations.AlterField(
model_name='extralesson',
name='room',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='extra_lessons', to='core.room', verbose_name='Room'),
),
migrations.AlterField(
model_name='lessonperiod',
name='room',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='core.room', verbose_name='Room'),
),
migrations.AlterField(
model_name='lessonsubstitution',
name='room',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.room', verbose_name='Room'),
),
migrations.AlterField(
model_name='timeperiod',
name='weekday',
field=models.PositiveSmallIntegerField(choices=[(0, 'Montag'), (1, 'Dienstag'), (2, 'Mittwoch'), (3, 'Donnerstag'), (4, 'Freitag'), (5, 'Samstag'), (6, 'Sonntag')], verbose_name='Week day'),
),
migrations.DeleteModel(
name='Room',
),
]
......@@ -67,7 +67,7 @@ from aleksis.core.mixins import (
GlobalPermissionModel,
SchoolTermRelatedExtensibleModel,
)
from aleksis.core.models import DashboardWidget, Group, SchoolTerm
from aleksis.core.models import DashboardWidget, Group, Room, SchoolTerm
from aleksis.core.util.core_helpers import has_person
from aleksis.core.util.pdf import generate_pdf_from_template
......@@ -265,7 +265,6 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
@classproperty
@cache_memoize(3600)
def period_min(cls) -> int:
return (
cls.objects.for_current_or_all()
.aggregate(period__min=Coalesce(Min("period"), 1))
......@@ -360,28 +359,6 @@ class Subject(ExtensibleModel):
]
class Room(ExtensibleModel):
short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
name = models.CharField(verbose_name=_("Long name"), max_length=255)
def __str__(self) -> str:
return f"{self.name} ({self.short_name})"
def get_absolute_url(self) -> str:
return reverse("timetable", args=["room", self.id])
class Meta:
permissions = (("view_room_timetable", _("Can view room timetable")),)
ordering = ["name", "short_name"]
verbose_name = _("Room")
verbose_name_plural = _("Rooms")
constraints = [
models.UniqueConstraint(
fields=["site_id", "short_name"], name="unique_short_name_per_site_room"
),
]
class Lesson(ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
subject = models.ForeignKey(
"Subject",
......@@ -463,7 +440,9 @@ class LessonSubstitution(ExtensibleModel, TeacherPropertiesMixin, WeekRelatedMix
blank=True,
verbose_name=_("Teachers"),
)
room = models.ForeignKey("Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room"))
room = models.ForeignKey(
"core.Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room")
)
cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled?"))
cancelled_for_teachers = models.BooleanField(
......@@ -538,7 +517,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
)
room = models.ForeignKey(
"Room",
"core.Room",
models.CASCADE,
null=True,
related_name="lesson_periods",
......@@ -751,7 +730,7 @@ class Absence(SchoolTermRelatedExtensibleModel):
verbose_name=_("Group"),
)
room = models.ForeignKey(
"Room",
"core.Room",
on_delete=models.CASCADE,
related_name="absences",
null=True,
......@@ -1088,7 +1067,7 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
)
groups = models.ManyToManyField("core.Group", related_name="events", verbose_name=_("Groups"))
rooms = models.ManyToManyField("Room", related_name="events", verbose_name=_("Rooms"))
rooms = models.ManyToManyField("core.Room", related_name="events", verbose_name=_("Rooms"))
teachers = models.ManyToManyField(
"core.Person", related_name="events", verbose_name=_("Teachers")
)
......@@ -1099,6 +1078,20 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
else:
return _("Event {pk}").format(pk=self.pk)
def get_period_min(self, day) -> int:
return (
TimePeriod.objects.on_day(day)
.aggregate(period__min=Coalesce(Min("period"), 1))
.get("period__min")
)
def get_period_max(self, day) -> int:
return (
TimePeriod.objects.on_day(day)
.aggregate(period__max=Coalesce(Max("period"), 7))
.get("period__max")
)
@property
def raw_period_from_on_day(self) -> TimePeriod:
"""Get start period on the annotated day (as TimePeriod object).
......@@ -1107,7 +1100,7 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
"""
day = getattr(self, "_date", timezone.now().date())
if day != self.date_start:
return TimePeriod.from_period(TimePeriod.period_min, day)
return TimePeriod.from_period(self.get_period_min(day), day)
else:
return self.period_from
......@@ -1119,7 +1112,7 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
"""
day = getattr(self, "_date", timezone.now().date())
if day != self.date_end:
return TimePeriod.from_period(TimePeriod.period_max, day)
return TimePeriod.from_period(self.get_period_max(day), day)
else:
return self.period_to
......@@ -1219,7 +1212,7 @@ class ExtraLesson(
verbose_name=_("Teachers"),
)
room = models.ForeignKey(
"Room",
"core.Room",
models.CASCADE,
null=True,
related_name="extra_lessons",
......
......@@ -17,7 +17,7 @@ class UseParentGroups(BooleanPreference):
default = False
verbose_name = _("Use parent groups in timetable views")
help_text = _(
"If an lesson or substitution has only one group"
"If a lesson or substitution has only one group"
" and this group has parent groups,"
" show the parent groups instead of the original group."
)
......@@ -39,7 +39,7 @@ class ShortenGroupsLimit(IntegerPreference):
default = 4
verbose_name = _("Limit of groups for shortening of groups")
help_text = _(
"If an user activates shortening of groups,"
"If a user activates shortening of groups,"
"they will be collapsed if there are more groups than this limit."
)
......
from aleksis.core.util.search import Indexable, SearchIndex
from .models import Room
class RoomIndex(SearchIndex, Indexable):
"""Haystack index for searching rooms."""
model = Room
......@@ -2,161 +2,166 @@
/* Timetable */
/*+++++++++++*/
.smart-plan-badge {
margin: 5px 20px 5px 0;
margin: 5px 20px 5px 0;
}
li.active > a > .sidenav-badge {
background-color: whitesmoke !important;
color: #DA3D56 !important;
background-color: whitesmoke !important;
color: #da3d56 !important;
}
.timetable-plan .row, .timetable-plan .col {
display: flex;
padding: 0 .25rem;
.timetable-plan .row,
.timetable-plan .col {
display: flex;
padding: 0 0.25rem;
}
.timetable-plan .row {
margin-bottom: .25rem;
margin-bottom: 0.25rem;
}
.lesson-card, .timetable-title-card {
margin: 0;
display: flex;
flex-grow: 1;
min-height: 65px;
.lesson-card,
.timetable-title-card {
margin: 0;
display: flex;
flex-grow: 1;
min-height: 65px;
}
.supervision-card {
min-height: 35px;
min-height: 35px;
}
.lesson-card .card-content {
padding: 0;
text-align: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 0;
text-align: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.lesson-card .card-content > div {
padding: 3px;
flex: auto;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 3px;
flex: auto;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.timetable-title-card .card-content {
padding: 10px;
text-align: center;
width: 100%;
padding: 10px;
text-align: center;
width: 100%;
}
.timetable-mobile-title-card {
margin-top: 50px;
margin-bottom: .60rem;
display: flex;
flex-grow: 1;
margin-top: 50px;
margin-bottom: 0.6rem;
display: flex;
flex-grow: 1;
}
.timetable-mobile-title-card:first-child {
margin-top: -10px;
margin-bottom: .60rem;
margin-top: -10px;
margin-bottom: 0.6rem;
}
.timetable-mobile-title-card .card-content {
padding: 10px;
text-align: center;
width: 100%;
padding: 10px;
text-align: center;
width: 100%;
}
.timetable-mobile-title-card .card-content .card-title {
font-weight: bold;
font-weight: bold;
}
table.substitutions td, table.substitutions th {
padding: 10px 5px;
table.substitutions td,
table.substitutions th {
padding: 10px 5px;
}
.lesson-with-sub {
border: 3px solid red;
border-radius: 3px;
border: 3px solid red;
border-radius: 3px;
}
.lesson-with-sub .badge {
margin: 0;
margin: 0;
}
.lesson-with-event {
border: 3px solid #9c27b0;
border-radius: 3px;
border: 3px solid #9c27b0;
border-radius: 3px;
}
.lesson-card a, .substitutions a {
color: inherit;
.lesson-card a,
.substitutions a {
color: inherit;
}
.btn-timetable-quicklaunch {
margin: 1%;
width: 30%;
margin: 1%;
width: 30%;
}
.print-body table.substitutions td, .print-body table.substitutions th {
padding: 0;
.print-body table.substitutions td,
.print-body table.substitutions th {
padding: 0;
}
.print-body span.badge.new {
font-size: 0.9rem;
line-height: 20px;
height: 20px;
margin: 2px;
letter-spacing: 0.3pt;
font-size: 0.9rem;
line-height: 20px;
height: 20px;
margin: 2px;
letter-spacing: 0.3pt;
}
.black-text-a a {
color: black;
color: black;
}
.holiday-badge {
float: left !important;
position: relative;
margin-left: 0 !important;
left: 50%;
transform: translate(-50%);
width: auto;
height: auto !important;
min-height: 26px;
margin-top: 5px;
float: left !important;
position: relative;
margin-left: 0 !important;
left: 50%;
transform: translate(-50%);
width: auto;
height: auto !important;
min-height: 26px;
margin-top: 5px;
}
.holiday-card .card-content{
width: 100%;
.holiday-card .card-content {
width: 100%;
}
.holiday-card .holiday-badge {
margin-top: 0;
margin-top: 0;
}
@media print {
header {
width: 200mm;
top: 5mm;
}
.header-space {
height: 30mm;
}
.sheet {
padding: 5mm;
}
.print-layout-table, .print-layout-table > td {
width: 200mm;
max-width: 200mm;
min-width: 200mm;
}
header {
width: 200mm;
top: 5mm;
}
.header-space {
height: 30mm;
}
.sheet {
padding: 5mm;
}
.print-layout-table,
.print-layout-table > td {
width: 200mm;
max-width: 200mm;
min-width: 200mm;
}
}
.timetable-plan .row, .timetable-plan .col {
display: flex;
padding: 0rem;
.timetable-plan .row,
.timetable-plan .col {
display: flex;
padding: 0rem;
}
.timetable-plan .row {
margin-bottom: 0rem;
margin-bottom: 0rem;
}
.lesson-card, .timetable-title-card {
margin: 0;
display: flex;
flex-grow: 1;
min-height: 40px;
box-shadow: none;
border: 1px solid black;
margin-right: -1px;
margin-top: -1px;
border-radius: 0px;
font-size: 11px;
.lesson-card,
.timetable-title-card {
margin: 0;
display: flex;
flex-grow: 1;
min-height: 40px;
box-shadow: none;
border: 1px solid black;
margin-right: -1px;
margin-top: -1px;
border-radius: 0px;
font-size: 11px;
}
.lesson-card .card-content > div {
padding: 1px;
padding: 1px;
}
.card .card-title {
font-size: 18px;
font-size: 18px;
}
.timetable-title-card .card-content {
padding: 7px;
padding: 7px;
}