From 53c8a8190648c6d4a909457cd33ec269bebac5ce Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sat, 13 Jun 2020 16:44:05 +0200
Subject: [PATCH] Rename school year to school term and fix permissions

---
 aleksis/core/forms.py                         | 14 ++--
 aleksis/core/managers.py                      | 48 +++++++-------
 aleksis/core/menus.py                         |  6 +-
 aleksis/core/migrations/0002_school_term.py   | 64 +++++++++++++++++++
 aleksis/core/migrations/0002_school_year.py   | 31 ---------
 .../core/migrations/0003_group_school_year.py | 43 -------------
 aleksis/core/mixins.py                        | 24 +++----
 aleksis/core/models.py                        | 34 +++++-----
 aleksis/core/rules.py                         | 40 +++++-------
 aleksis/core/tables.py                        |  8 +--
 .../{school_year => school_term}/create.html  |  4 +-
 .../{school_year => school_term}/edit.html    |  4 +-
 .../{school_year => school_term}/list.html    |  8 +--
 aleksis/core/urls.py                          |  6 +-
 aleksis/core/views.py                         | 50 +++++++--------
 15 files changed, 183 insertions(+), 201 deletions(-)
 create mode 100644 aleksis/core/migrations/0002_school_term.py
 delete mode 100644 aleksis/core/migrations/0002_school_year.py
 delete mode 100644 aleksis/core/migrations/0003_group_school_year.py
 rename aleksis/core/templates/core/{school_year => school_term}/create.html (71%)
 rename aleksis/core/templates/core/{school_year => school_term}/edit.html (71%)
 rename aleksis/core/templates/core/{school_year => school_term}/list.html (64%)

diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index 91a593b7b..82aa7d4bc 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -9,8 +9,8 @@ from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
 from dynamic_preferences.forms import PreferenceForm
 from material import Fieldset, Layout, Row
 
-from .mixins import ExtensibleForm, SchoolYearRelatedExtensibleForm
-from .models import AdditionalField, Announcement, Group, GroupType, Person, SchoolYear
+from .mixins import ExtensibleForm, SchoolTermRelatedExtensibleForm
+from .models import AdditionalField, Announcement, Group, GroupType, Person, SchoolTerm
 from .registries import (
     group_preferences_registry,
     person_preferences_registry,
@@ -121,11 +121,11 @@ class EditPersonForm(ExtensibleForm):
         return PersonAccountForm.clean(self)
 
 
-class EditGroupForm(SchoolYearRelatedExtensibleForm):
+class EditGroupForm(SchoolTermRelatedExtensibleForm):
     """Form to edit an existing group in the frontend."""
 
     layout = Layout(
-        Fieldset(_("School year"), "school_year"),
+        Fieldset(_("School term"), "school_term"),
         Fieldset(_("Common data"), "name", "short_name", "group_type"),
         Fieldset(_("Persons"), "members", "owners", "parent_groups"),
         Fieldset(_("Additional data"), "additional_fields"),
@@ -208,7 +208,7 @@ class AnnouncementForm(ExtensibleForm):
 
         super().__init__(*args, **kwargs)
 
-        self.fields["groups"].queryset = Group.objects.for_current_school_year_or_all()
+        self.fields["groups"].queryset = Group.objects.for_current_school_term_or_all()
 
     def clean(self):
         data = super().clean()
@@ -305,11 +305,11 @@ class EditGroupTypeForm(forms.ModelForm):
         exclude = []
 
 
-class SchoolYearForm(ExtensibleForm):
+class SchoolTermForm(ExtensibleForm):
     """Form for managing school years."""
 
     layout = Layout("name", Row("date_start", "date_end"))
 
     class Meta:
-        model = SchoolYear
+        model = SchoolTerm
         exclude = []
diff --git a/aleksis/core/managers.py b/aleksis/core/managers.py
index 6b791a5d1..a08710780 100644
--- a/aleksis/core/managers.py
+++ b/aleksis/core/managers.py
@@ -32,50 +32,50 @@ class DateRangeQuerySetMixin:
         return self.within_dates(day, day)
 
 
-class SchoolYearQuerySet(QuerySet, DateRangeQuerySetMixin):
-    """Custom query set for school years."""
+class SchoolTermQuerySet(QuerySet, DateRangeQuerySetMixin):
+    """Custom query set for school terms."""
 
 
-class SchoolYearRelatedQuerySet(QuerySet):
-    """Custom query set for all models related to school years."""
+class SchoolTermRelatedQuerySet(QuerySet):
+    """Custom query set for all models related to school terms."""
 
-    def within_dates(self, start: date, end: date) -> "SchoolYearRelatedQuerySet":
+    def within_dates(self, start: date, end: date) -> "SchoolTermRelatedQuerySet":
         """Filter for all objects within a date range."""
-        return self.filter(school_year__date_start__lte=end, school_year__date_end__gte=start)
+        return self.filter(school_term__date_start__lte=end, school_term__date_end__gte=start)
 
-    def in_week(self, wanted_week: CalendarWeek) -> "SchoolYearRelatedQuerySet":
+    def in_week(self, wanted_week: CalendarWeek) -> "SchoolTermRelatedQuerySet":
         """Filter for all objects within a calendar week."""
         return self.within_dates(wanted_week[0], wanted_week[6])
 
-    def on_day(self, day: date) -> "SchoolYearRelatedQuerySet":
+    def on_day(self, day: date) -> "SchoolTermRelatedQuerySet":
         """Filter for all objects on a certain day."""
         return self.within_dates(day, day)
 
-    def for_school_year(self, school_year: "SchoolYear") -> "SchoolYearRelatedQuerySet":
-        return self.filter(school_year=school_year)
+    def for_school_term(self, school_term: "SchoolTerm") -> "SchoolTermRelatedQuerySet":
+        return self.filter(school_term=school_term)
 
-    def for_current_school_year_or_all(self) -> "SchoolYearRelatedQuerySet":
-        """Get all objects related to current school year.
+    def for_current_school_term_or_all(self) -> "SchoolTermRelatedQuerySet":
+        """Get all objects related to current school term.
 
-        If there is no current school year, it will return all objects.
+        If there is no current school term, it will return all objects.
         """
-        from aleksis.core.models import SchoolYear
+        from aleksis.core.models import SchoolTerm
 
-        current_school_year = SchoolYear.current
-        if current_school_year:
-            return self.for_school_year(current_school_year)
+        current_school_term = SchoolTerm.current
+        if current_school_term:
+            return self.for_school_term(current_school_term)
         else:
             return self
 
-    def for_current_school_year_or_none(self) -> Union["SchoolYearRelatedQuerySet", None]:
-        """Get all objects related to current school year.
+    def for_current_school_term_or_none(self) -> Union["SchoolTermRelatedQuerySet", None]:
+        """Get all objects related to current school term.
 
-        If there is no current school year, it will return `None`.
+        If there is no current school term, it will return `None`.
         """
-        from aleksis.core.models import SchoolYear
+        from aleksis.core.models import SchoolTerm
 
-        current_school_year = SchoolYear.current
-        if current_school_year:
-            return self.for_school_year(current_school_year)
+        current_school_term = SchoolTerm.current
+        if current_school_term:
+            return self.for_school_term(current_school_term)
         else:
             return None
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index a4c199bde..2e71c3573 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -83,13 +83,13 @@ MENUS = {
                     ],
                 },
                 {
-                    "name": _("School years"),
-                    "url": "school_years",
+                    "name": _("School terms"),
+                    "url": "school_terms",
                     "icon": "date_range",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
-                            "core.view_schoolyear",
+                            "core.view_schoolterm",
                         ),
                     ],
                 },
diff --git a/aleksis/core/migrations/0002_school_term.py b/aleksis/core/migrations/0002_school_term.py
new file mode 100644
index 000000000..456f78ebf
--- /dev/null
+++ b/aleksis/core/migrations/0002_school_term.py
@@ -0,0 +1,64 @@
+# Generated by Django 3.0.7 on 2020-06-13 14:34
+
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+        ('core', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SchoolTerm',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
+                ('date_start', models.DateField(verbose_name='Start date')),
+                ('date_end', models.DateField(verbose_name='End date')),
+            ],
+            options={
+                'verbose_name': 'School term',
+                'verbose_name_plural': 'School terms',
+            },
+        ),
+        migrations.AlterModelManagers(
+            name='group',
+            managers=[
+            ],
+        ),
+        migrations.AlterField(
+            model_name='group',
+            name='name',
+            field=models.CharField(max_length=255, verbose_name='Long name'),
+        ),
+        migrations.AlterField(
+            model_name='group',
+            name='short_name',
+            field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Short name'),
+        ),
+        migrations.AddField(
+            model_name='group',
+            name='school_term',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
+                                    related_name='+', to='core.SchoolTerm', verbose_name='Linked school term'),
+        ),
+        migrations.AddConstraint(
+            model_name='group',
+            constraint=models.UniqueConstraint(fields=('school_term', 'name'), name='unique_school_term_name'),
+        ),
+        migrations.AddConstraint(
+            model_name='group',
+            constraint=models.UniqueConstraint(fields=('school_term', 'short_name'), name='unique_school_term_short_name'),
+        ),
+        migrations.AddField(
+            model_name='schoolterm',
+            name='site',
+            field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
+        ),
+    ]
diff --git a/aleksis/core/migrations/0002_school_year.py b/aleksis/core/migrations/0002_school_year.py
deleted file mode 100644
index ec66b7ab4..000000000
--- a/aleksis/core/migrations/0002_school_year.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Generated by Django 3.0.6 on 2020-06-04 09:50
-
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('sites', '0002_alter_domain_unique'),
-        ('core', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='SchoolYear',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
-                ('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
-                ('date_start', models.DateField(verbose_name='Start date')),
-                ('date_end', models.DateField(verbose_name='End date')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
-            ],
-            options={
-                'verbose_name': 'School year',
-                'verbose_name_plural': 'School years',
-            },
-        ),
-    ]
diff --git a/aleksis/core/migrations/0003_group_school_year.py b/aleksis/core/migrations/0003_group_school_year.py
deleted file mode 100644
index e2e319592..000000000
--- a/aleksis/core/migrations/0003_group_school_year.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Generated by Django 3.0.6 on 2020-06-04 15:12
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('core', '0002_school_year'),
-    ]
-
-    operations = [
-        migrations.AlterModelManagers(
-            name='group',
-            managers=[
-            ],
-        ),
-        migrations.AddField(
-            model_name='group',
-            name='school_year',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='core.SchoolYear', verbose_name='Linked school year'),
-        ),
-        migrations.AlterField(
-            model_name='group',
-            name='name',
-            field=models.CharField(max_length=255, verbose_name='Long name'),
-        ),
-        migrations.AlterField(
-            model_name='group',
-            name='short_name',
-            field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Short name'),
-        ),
-        migrations.AddConstraint(
-            model_name='group',
-            constraint=models.UniqueConstraint(fields=('school_year', 'name'), name='unique_school_year_name'),
-        ),
-        migrations.AddConstraint(
-            model_name='group',
-            constraint=models.UniqueConstraint(fields=('school_year', 'short_name'),
-                                               name='unique_school_year_short_name'),
-        ),
-    ]
diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py
index 9ca87f71c..8bd018b43 100644
--- a/aleksis/core/mixins.py
+++ b/aleksis/core/mixins.py
@@ -25,7 +25,7 @@ from jsonstore.fields import JSONField, JSONFieldMixin
 from material.base import Layout, LayoutNode
 from rules.contrib.admin import ObjectPermissionsModelAdmin
 
-from aleksis.core.managers import CurrentSiteManagerWithoutMigrations, SchoolYearRelatedQuerySet
+from aleksis.core.managers import CurrentSiteManagerWithoutMigrations, SchoolTermRelatedQuerySet
 
 
 class _ExtensibleModelBase(models.base.ModelBase):
@@ -307,16 +307,16 @@ class AdvancedEditView(UpdateView, SuccessMessageMixin):
     pass
 
 
-class SchoolYearRelatedExtensibleModel(ExtensibleModel):
-    """Add relation to school year."""
+class SchoolTermRelatedExtensibleModel(ExtensibleModel):
+    """Add relation to school term."""
 
-    objects = CurrentSiteManagerWithoutMigrations.from_queryset(SchoolYearRelatedQuerySet)()
+    objects = CurrentSiteManagerWithoutMigrations.from_queryset(SchoolTermRelatedQuerySet)()
 
-    school_year = models.ForeignKey(
-        "core.SchoolYear",
+    school_term = models.ForeignKey(
+        "core.SchoolTerm",
         on_delete=models.CASCADE,
         related_name="+",
-        verbose_name=_("Linked school year"),
+        verbose_name=_("Linked school term"),
         blank=True,
         null=True,
     )
@@ -325,18 +325,18 @@ class SchoolYearRelatedExtensibleModel(ExtensibleModel):
         abstract = True
 
 
-class SchoolYearRelatedExtensibleForm(ExtensibleForm):
-    """Extensible form for school year related data.
+class SchoolTermRelatedExtensibleForm(ExtensibleForm):
+    """Extensible form for school term related data.
 
     .. warning::
-        This doesn't automatically include the field `school_year` in `fields` or `layout`,
+        This doesn't automatically include the field `school_term` in `fields` or `layout`,
         it just sets an initial value.
     """
 
     def __init__(self, *args, **kwargs):
-        from aleksis.core.models import SchoolYear  # noqa
+        from aleksis.core.models import SchoolTerm  # noqa
 
         if "instance" not in kwargs:
-            kwargs["initial"] = {"school_year": SchoolYear.current}
+            kwargs["initial"] = {"school_term": SchoolTerm.current}
 
         super().__init__(*args, **kwargs)
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 48b23a4b0..723d47e70 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -24,8 +24,8 @@ from image_cropping import ImageCropField, ImageRatioField
 from phonenumber_field.modelfields import PhoneNumberField
 from polymorphic.models import PolymorphicModel
 
-from .managers import CurrentSiteManagerWithoutMigrations, SchoolYearQuerySet
-from .mixins import ExtensibleModel, PureDjangoModel, SchoolYearRelatedExtensibleModel
+from .managers import CurrentSiteManagerWithoutMigrations, SchoolTermQuerySet
+from .mixins import ExtensibleModel, PureDjangoModel, SchoolTermRelatedExtensibleModel
 from .tasks import send_notification
 from .util.core_helpers import get_site_preferences, now_tomorrow
 from .util.model_helpers import ICONS
@@ -46,13 +46,13 @@ FIELD_CHOICES = (
 )
 
 
-class SchoolYear(ExtensibleModel):
-    """School year model.
+class SchoolTerm(ExtensibleModel):
+    """School term model.
 
-    This is used to manage start and end times of a school year and link data to it.
+    This is used to manage start and end times of a school term and link data to it.
     """
 
-    objects = CurrentSiteManagerWithoutMigrations.from_queryset(SchoolYearQuerySet)()
+    objects = CurrentSiteManagerWithoutMigrations.from_queryset(SchoolTermQuerySet)()
 
     name = models.CharField(verbose_name=_("Name"), max_length=255, unique=True)
 
@@ -65,7 +65,7 @@ class SchoolYear(ExtensibleModel):
             day = timezone.now().date()
         try:
             return cls.objects.on_day(day).first()
-        except SchoolYear.DoesNotExist:
+        except SchoolTerm.DoesNotExist:
             return None
 
     @classproperty
@@ -73,24 +73,24 @@ class SchoolYear(ExtensibleModel):
         return cls.get_current()
 
     def clean(self):
-        """Ensure there is only one school year at each point of time."""
+        """Ensure there is only one school term at each point of time."""
         if self.date_end < self.date_start:
             raise ValidationError(_("The start date must be earlier than the end date."))
 
-        qs = SchoolYear.objects.within_dates(self.date_start, self.date_end)
+        qs = SchoolTerm.objects.within_dates(self.date_start, self.date_end)
         if self.pk:
             qs.exclude(pk=self.pk)
         if qs.exists():
             raise ValidationError(
-                _("There is already a school year for this time or a part of this time.")
+                _("There is already a school term for this time or a part of this time.")
             )
 
     def __str__(self):
         return self.name
 
     class Meta:
-        verbose_name = _("School year")
-        verbose_name_plural = _("School years")
+        verbose_name = _("School term")
+        verbose_name_plural = _("School terms")
 
 
 class Person(ExtensibleModel):
@@ -305,7 +305,7 @@ class AdditionalField(ExtensibleModel):
         verbose_name_plural = _("Addtitional fields for groups")
 
 
-class Group(SchoolYearRelatedExtensibleModel):
+class Group(SchoolTermRelatedExtensibleModel):
     """Group model.
 
     Any kind of group of persons in a school, including, but not limited
@@ -318,9 +318,9 @@ class Group(SchoolYearRelatedExtensibleModel):
         verbose_name_plural = _("Groups")
         permissions = (("assign_child_groups_to_groups", _("Can assign child groups to groups")),)
         constraints = [
-            models.UniqueConstraint(fields=["school_year", "name"], name="unique_school_year_name"),
+            models.UniqueConstraint(fields=["school_term", "name"], name="unique_school_term_name"),
             models.UniqueConstraint(
-                fields=["school_year", "short_name"], name="unique_school_year_short_name"
+                fields=["school_term", "short_name"], name="unique_school_term_short_name"
             ),
         ]
 
@@ -369,8 +369,8 @@ class Group(SchoolYearRelatedExtensibleModel):
         return list(self.members.all()) + list(self.owners.all())
 
     def __str__(self) -> str:
-        if self.school_year:
-            return f"{self.name} ({self.short_name}) ({self.school_year})"
+        if self.school_term:
+            return f"{self.name} ({self.short_name}) ({self.school_term})"
         else:
             return f"{self.name} ({self.short_name})"
 
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 90eb52386..e854c3077 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -98,14 +98,6 @@ add_perm("core.assign_child_groups_to_groups", assign_child_groups_to_groups_pre
 edit_school_information_predicate = has_person & has_global_perm("core.change_school")
 add_perm("core.edit_school_information", edit_school_information_predicate)
 
-# Edit school term
-edit_schoolterm_predicate = has_person & has_global_perm("core.change_schoolterm")
-add_perm("core.edit_schoolterm", edit_schoolterm_predicate)
-
-# Manage school
-manage_school_predicate = edit_school_information_predicate | edit_schoolterm_predicate
-add_perm("core.manage_school", manage_school_predicate)
-
 # Manage data
 manage_data_predicate = has_person & has_global_perm("core.manage_data")
 add_perm("core.manage_data", manage_data_predicate)
@@ -154,16 +146,6 @@ add_perm(
     ),
 )
 
-# View admin menu
-view_admin_menu_predicate = has_person & (
-    manage_data_predicate
-    | manage_school_predicate
-    | impersonate_predicate
-    | view_system_status_predicate
-    | view_announcements_predicate
-)
-add_perm("core.view_admin_menu", view_admin_menu_predicate)
-
 # View person personal details
 view_personal_details_predicate = has_person & (
     has_global_perm("core.view_personal_details")
@@ -248,11 +230,21 @@ view_group_type_predicate = has_person & (
 add_perm("core.view_grouptype", view_group_type_predicate)
 
 # School years
-view_school_year_predicate = has_person & has_global_perm("core.view_schoolyear")
-add_perm("core.view_schoolyear", view_school_year_predicate)
+view_school_term_predicate = has_person & has_global_perm("core.view_schoolterm")
+add_perm("core.view_schoolterm", view_school_term_predicate)
+
+create_school_term_predicate = has_person & has_global_perm("core.add_schoolterm")
+add_perm("core.create_schoolterm", create_school_term_predicate)
 
-create_school_year_predicate = has_person & has_global_perm("core.add_schoolyear")
-add_perm("core.create_schoolyear", create_school_year_predicate)
+edit_school_term_predicate = has_person & has_global_perm("core.change_schoolterm")
+add_perm("core.edit_schoolterm", edit_school_term_predicate)
 
-edit_school_year_predicate = has_person & has_global_perm("core.change_schoolyear")
-add_perm("core.edit_schoolyear", edit_school_year_predicate)
+# View admin menu
+view_admin_menu_predicate = has_person & (
+    manage_data_predicate
+    | view_school_term_predicate
+    | impersonate_predicate
+    | view_system_status_predicate
+    | view_announcements_predicate
+)
+add_perm("core.view_admin_menu", view_admin_menu_predicate)
diff --git a/aleksis/core/tables.py b/aleksis/core/tables.py
index ca100daae..f562b61a1 100644
--- a/aleksis/core/tables.py
+++ b/aleksis/core/tables.py
@@ -4,17 +4,17 @@ import django_tables2 as tables
 from django_tables2.utils import A
 
 
-class SchoolYearTable(tables.Table):
+class SchoolTermTable(tables.Table):
     """Table to list persons."""
 
     class Meta:
         attrs = {"class": "responsive-table highlight"}
 
-    name = tables.LinkColumn("edit_school_year", args=[A("id")])
+    name = tables.LinkColumn("edit_school_term", args=[A("id")])
     date_start = tables.Column()
     date_end = tables.Column()
     edit = tables.LinkColumn(
-        "edit_school_year",
+        "edit_school_term",
         args=[A("id")],
         text=_("Edit"),
         attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}},
@@ -40,7 +40,7 @@ class GroupsTable(tables.Table):
 
     name = tables.LinkColumn("group_by_id", args=[A("id")])
     short_name = tables.LinkColumn("group_by_id", args=[A("id")])
-    school_year = tables.Column()
+    school_term = tables.Column()
 
 
 class AdditionalFieldsTable(tables.Table):
diff --git a/aleksis/core/templates/core/school_year/create.html b/aleksis/core/templates/core/school_term/create.html
similarity index 71%
rename from aleksis/core/templates/core/school_year/create.html
rename to aleksis/core/templates/core/school_term/create.html
index 65ad9e7ef..06fea51ec 100644
--- a/aleksis/core/templates/core/school_year/create.html
+++ b/aleksis/core/templates/core/school_term/create.html
@@ -3,8 +3,8 @@
 {% extends "core/base.html" %}
 {% load material_form i18n %}
 
-{% block browser_title %}{% blocktrans %}Manage school year{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Manage school year{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Create school term{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Create school term{% endblocktrans %}{% endblock %}
 
 {% block content %}
 
diff --git a/aleksis/core/templates/core/school_year/edit.html b/aleksis/core/templates/core/school_term/edit.html
similarity index 71%
rename from aleksis/core/templates/core/school_year/edit.html
rename to aleksis/core/templates/core/school_term/edit.html
index 123a033a2..ea0d0d379 100644
--- a/aleksis/core/templates/core/school_year/edit.html
+++ b/aleksis/core/templates/core/school_term/edit.html
@@ -3,8 +3,8 @@
 {% extends "core/base.html" %}
 {% load material_form i18n %}
 
-{% block browser_title %}{% blocktrans %}Edit school year{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Edit school year{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit school term{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Edit school term{% endblocktrans %}{% endblock %}
 
 {% block content %}
 
diff --git a/aleksis/core/templates/core/school_year/list.html b/aleksis/core/templates/core/school_term/list.html
similarity index 64%
rename from aleksis/core/templates/core/school_year/list.html
rename to aleksis/core/templates/core/school_term/list.html
index 5a432e910..14aa2f0a6 100644
--- a/aleksis/core/templates/core/school_year/list.html
+++ b/aleksis/core/templates/core/school_term/list.html
@@ -5,13 +5,13 @@
 {% load i18n %}
 {% load render_table from django_tables2 %}
 
-{% block browser_title %}{% blocktrans %}School years{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}School years{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}School terms{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}School terms{% endblocktrans %}{% endblock %}
 
 {% block content %}
-  <a class="btn green waves-effect waves-light" href="{% url 'create_school_year' %}">
+  <a class="btn green waves-effect waves-light" href="{% url 'create_school_term' %}">
     <i class="material-icons left">add</i>
-    {% trans "Create school year" %}
+    {% trans "Create school term" %}
   </a>
 
   {% render_table table %}
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 53b74b203..cfd0db9a0 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -21,9 +21,9 @@ urlpatterns = [
     path("status/", views.system_status, name="system_status"),
     path("", include(tf_urls)),
     path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
-    path("school_years/", views.SchoolYearListView.as_view(), name="school_years"),
-    path("school_years/create/", views.SchoolYearCreateView.as_view(), name="create_school_year"),
-    path("school_years/<int:pk>/", views.SchoolYearEditView.as_view(), name="edit_school_year"),
+    path("school_terms/", views.SchoolTermListView.as_view(), name="school_terms"),
+    path("school_terms/create/", views.SchoolTermCreateView.as_view(), name="create_school_term"),
+    path("school_terms/<int:pk>/", views.SchoolTermEditView.as_view(), name="edit_school_term"),
     path("persons", views.persons, name="persons"),
     path("persons/accounts", views.persons_accounts, name="persons_accounts"),
     path("person", views.person, name="person"),
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index c99505073..975fd32fd 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -28,7 +28,7 @@ from .forms import (
     GroupPreferenceForm,
     PersonPreferenceForm,
     PersonsAccountsFormSet,
-    SchoolYearForm,
+    SchoolTermForm,
     SitePreferenceForm,
 )
 from .mixins import AdvancedCreateView, AdvancedEditView
@@ -40,14 +40,14 @@ from .models import (
     GroupType,
     Notification,
     Person,
-    SchoolYear,
+    SchoolTerm,
 )
 from .registries import (
     group_preferences_registry,
     person_preferences_registry,
     site_preferences_registry,
 )
-from .tables import AdditionalFieldsTable, GroupsTable, GroupTypesTable, PersonsTable, SchoolYearTable
+from .tables import AdditionalFieldsTable, GroupsTable, GroupTypesTable, PersonsTable, SchoolTermTable
 from .util import messages
 from .util.apps import AppConfig
 from .util.core_helpers import objectgetter_optional
@@ -94,35 +94,35 @@ def about(request: HttpRequest) -> HttpResponse:
     return render(request, "core/about.html", context)
 
 
-class SchoolYearListView(SingleTableView, PermissionRequiredMixin):
-    """Table of all school years."""
+class SchoolTermListView(SingleTableView, PermissionRequiredMixin):
+    """Table of all school terms."""
 
-    model = SchoolYear
-    table_class = SchoolYearTable
-    permission_required = "core.view_schoolyear"
-    template_name = "core/school_year/list.html"
+    model = SchoolTerm
+    table_class = SchoolTermTable
+    permission_required = "core.view_schoolterm"
+    template_name = "core/school_term/list.html"
 
 
-class SchoolYearCreateView(AdvancedCreateView, PermissionRequiredMixin):
-    """Create view for school years."""
+class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
+    """Create view for school terms."""
 
-    model = SchoolYear
-    form_class = SchoolYearForm
-    permission_required = "core.add_schoolyear"
-    template_name = "core/school_year/create.html"
-    success_url = reverse_lazy("school_years")
-    success_message = _("The school year has been created.")
+    model = SchoolTerm
+    form_class = SchoolTermForm
+    permission_required = "core.add_schoolterm"
+    template_name = "core/school_term/create.html"
+    success_url = reverse_lazy("school_terms")
+    success_message = _("The school term has been created.")
 
 
-class SchoolYearEditView(AdvancedEditView, PermissionRequiredMixin):
-    """Edit view for school years."""
+class SchoolTermEditView(AdvancedEditView, PermissionRequiredMixin):
+    """Edit view for school terms."""
 
-    model = SchoolYear
-    form_class = SchoolYearForm
-    permission_required = "core.edit_schoolyear"
-    template_name = "core/school_year/edit.html"
-    success_url = reverse_lazy("school_years")
-    success_message = _("The school year has been saved.")
+    model = SchoolTerm
+    form_class = SchoolTermForm
+    permission_required = "core.edit_schoolterm"
+    template_name = "core/school_term/edit.html"
+    success_url = reverse_lazy("school_terms")
+    success_message = _("The school term has been saved.")
 
 
 @permission_required("core.view_persons")
-- 
GitLab