diff --git a/biscuit/apps/chronos/menus.py b/biscuit/apps/chronos/menus.py
index a2709a213571c5f1d4f7de894875485ca16421c6..109bbb85d9fe5d431d5ca2ebf40d6a85dcacae47 100644
--- a/biscuit/apps/chronos/menus.py
+++ b/biscuit/apps/chronos/menus.py
@@ -6,7 +6,7 @@ MENUS = {
             'name': _('Timetables'),
             'url': '#',
             'root': True,
-            'validators': ['menu_generator.validators.is_authenticated'],
+            'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'],
             'submenu': [
                 {
                     'name': _('Timetable'),
diff --git a/biscuit/apps/chronos/migrations/0005_school_related.py b/biscuit/apps/chronos/migrations/0005_school_related.py
new file mode 100644
index 0000000000000000000000000000000000000000..d30597a55d45f3f8998675d4a0853c7309efaeac
--- /dev/null
+++ b/biscuit/apps/chronos/migrations/0005_school_related.py
@@ -0,0 +1,78 @@
+# Generated by Django 2.2.4 on 2019-09-03 14:10
+
+import biscuit.apps.chronos.util
+import biscuit.core.util.core_helpers
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0004_auto_20190821_1550'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='lesson',
+            name='school',
+            field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
+        ),
+        migrations.AddField(
+            model_name='lessonperiod',
+            name='school',
+            field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
+        ),
+        migrations.AddField(
+            model_name='lessonsubstitution',
+            name='school',
+            field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='school',
+            field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
+        ),
+        migrations.AddField(
+            model_name='subject',
+            name='school',
+            field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
+        ),
+        migrations.AddField(
+            model_name='timeperiod',
+            name='school',
+            field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
+        ),
+        migrations.AlterField(
+            model_name='room',
+            name='name',
+            field=models.CharField(max_length=30, verbose_name='Long name'),
+        ),
+        migrations.AlterField(
+            model_name='room',
+            name='short_name',
+            field=models.CharField(max_length=10, verbose_name='Short name, e.g. room number'),
+        ),
+        migrations.AlterField(
+            model_name='subject',
+            name='abbrev',
+            field=models.CharField(max_length=10, verbose_name='Abbreviation of subject in timetable'),
+        ),
+        migrations.AlterField(
+            model_name='subject',
+            name='name',
+            field=models.CharField(max_length=30, verbose_name='Long name of subject'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='room',
+            unique_together={('school', 'name'), ('school', 'short_name')},
+        ),
+        migrations.AlterUniqueTogether(
+            name='subject',
+            unique_together={('school', 'abbrev'), ('school', 'name')},
+        ),
+        migrations.AlterUniqueTogether(
+            name='timeperiod',
+            unique_together={('school', 'weekday', 'period')},
+        ),
+    ]
diff --git a/biscuit/apps/chronos/models.py b/biscuit/apps/chronos/models.py
index 0d4eec2df40dc0d63c1761b13605d34cdba8f26a..0242663d63070a238b4f3b253a321d051a14c637 100644
--- a/biscuit/apps/chronos/models.py
+++ b/biscuit/apps/chronos/models.py
@@ -5,10 +5,12 @@ from django.core import validators
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
+from biscuit.core.mixins import SchoolRelated
+
 from .util import current_week
 
 
-class TimePeriod(models.Model):
+class TimePeriod(SchoolRelated):
     WEEKDAY_CHOICES = [
         (0, _('Sunday')),
         (1, _('Monday')),
@@ -39,15 +41,15 @@ class TimePeriod(models.Model):
         return periods
 
     class Meta:
-        unique_together = [['weekday', 'period']]
+        unique_together = [['school', 'weekday', 'period']]
         ordering = ['weekday', 'period']
 
 
-class Subject(models.Model):
+class Subject(SchoolRelated):
     abbrev = models.CharField(verbose_name=_(
-        'Abbreviation of subject in timetable'), max_length=10, unique=True)
+        'Abbreviation of subject in timetable'), max_length=10)
     name = models.CharField(verbose_name=_(
-        'Long name of subject'), max_length=30, unique=True)
+        'Long name of subject'), max_length=30)
 
     colour_fg = models.CharField(verbose_name=_('Foreground colour in timetable'), blank=True, validators=[
                                  validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7)
@@ -59,22 +61,24 @@ class Subject(models.Model):
 
     class Meta:
         ordering = ['name', 'abbrev']
+        unique_together = [['school', 'abbrev'], ['school', 'name']]
 
 
-class Room(models.Model):
+class Room(SchoolRelated):
     short_name = models.CharField(verbose_name=_(
-        'Short name, e.g. room number'), max_length=10, unique=True)
+        'Short name, e.g. room number'), max_length=10)
     name = models.CharField(verbose_name=_('Long name'),
-                            max_length=30, unique=True)
+                            max_length=30)
 
     def __str__(self) -> str:
         return '%s (%s)' % (self.name, self.short_name)
 
     class Meta:
         ordering = ['name', 'short_name']
+        unique_together = [['school', 'short_name'], ['school', 'name']]
 
 
-class Lesson(models.Model):
+class Lesson(SchoolRelated):
     subject = models.ForeignKey(
         'Subject', on_delete=models.CASCADE, related_name='lessons')
     teachers = models.ManyToManyField('core.Person', related_name='lessons')
@@ -99,7 +103,7 @@ class Lesson(models.Model):
         ordering = ['date_start']
 
 
-class LessonSubstitution(models.Model):
+class LessonSubstitution(SchoolRelated):
     week = models.IntegerField(verbose_name=_('Week'),
                                default=current_week)
 
@@ -118,14 +122,14 @@ class LessonSubstitution(models.Model):
                     'lesson_period__period__weekday', 'lesson_period__period__period']
 
 
-class LessonPeriod(models.Model):
+class LessonPeriod(SchoolRelated):
     lesson = models.ForeignKey('Lesson', models.CASCADE, related_name='lesson_periods')
     period = models.ForeignKey('TimePeriod', models.CASCADE, related_name='lesson_periods')
 
     room = models.ForeignKey('Room', models.CASCADE, null=True, related_name='lesson_periods')
 
     def get_substitution(self, week: Optional[int] = None) -> LessonSubstitution:
-        wanted_week = week or current_week()
+        wanted_week = week or getattr(self, '_week', None) or current_week()
         return self.substitutions.filter(week=wanted_week).first()
 
     def get_subject(self) -> Optional[Subject]:
diff --git a/biscuit/apps/chronos/templates/chronos/tt_week.html b/biscuit/apps/chronos/templates/chronos/tt_week.html
index 8cbf5cc9a36fa872ed641f09057b7037611e047a..973eb483ce7751baa89e805f462927fc7453712b 100644
--- a/biscuit/apps/chronos/templates/chronos/tt_week.html
+++ b/biscuit/apps/chronos/templates/chronos/tt_week.html
@@ -11,8 +11,21 @@
 {% block page_title %}{% blocktrans %}Timetable{% endblocktrans %}{% endblock %}
 
 {% block content %}
+<div class="d-flex justify-content-between">
+ <div>
+  <h1>{{ day }}</h1>
+ </div>
+ <div class="btn-group" role="group" aria-label="Day actions">
+  <a href="{% url 'timetable_by_week' week_prev %}" class="btn btn-secondary">
+   {% fa 'arrow-left' %}
+  </a>
+  <a href="{% url 'timetable_by_week' week_next %}" class="btn btn-secondary">
+   {% fa 'arrow-right' %}
+  </a>
+ </div>
+</div>
+
  <form method="get">
-  {% csrf_token %}
   <ul id="timetable_select_form">
    {{ select_form.as_ul }}
   </ul>
diff --git a/biscuit/apps/chronos/urls.py b/biscuit/apps/chronos/urls.py
index 87e879b7a84ada0d14688c8e9c2f89ff0619f5cd..e3a9751a63ef7207abe49fcfb5277dde07143fb9 100644
--- a/biscuit/apps/chronos/urls.py
+++ b/biscuit/apps/chronos/urls.py
@@ -5,6 +5,7 @@ from . import views
 
 urlpatterns = [
     path('timetable', views.timetable, name='timetable'),
+    path('timetable/<int:week>', views.timetable, name='timetable_by_week'),
     path('lessons', views.lessons_day, name='lessons_day'),
     path('lessons/<when>', views.lessons_day, name='lessons_day_by_date'),
     path('lessons/<int:id_>/<int:week>/substition', views.edit_substitution, name='edit_substitution')
diff --git a/biscuit/apps/chronos/views.py b/biscuit/apps/chronos/views.py
index 079f295c1891da45e62eddaf687b0139f1c070c6..6fa61d1227b1203e3337aa3c9ad48000ace91ff4 100644
--- a/biscuit/apps/chronos/views.py
+++ b/biscuit/apps/chronos/views.py
@@ -1,13 +1,15 @@
-from collections import OrderedDict
 from datetime import date, datetime, timedelta
-
+from collections import OrderedDict
 from typing import Optional
+
 from django.contrib.auth.decorators import login_required
-from django.db.models import Max, Min
+from django.db.models import Max, Min, Q
 from django.http import HttpRequest, HttpResponse
 from django.shortcuts import get_object_or_404, redirect, render
+from django.views.decorators.cache import cache_page
 from django.urls import reverse
 from django.utils.translation import ugettext as _
+
 from django_tables2 import RequestConfig
 
 from biscuit.core.decorators import admin_required
@@ -20,16 +22,22 @@ from .tables import LessonsTable
 
 
 @login_required
-def timetable(request: HttpRequest) -> HttpResponse:
+@cache_page(60 * 60 * 12)
+def timetable(request: HttpRequest, week: Optional[int] = None) -> HttpResponse:
     context = {}
 
-    lesson_periods = LessonPeriod.objects.all()
+    wanted_week = week or current_week()
+
+    lesson_periods = LessonPeriod.objects.filter(
+        lesson__date_start__gte=week_days(wanted_week)[0],
+        lesson__date_end__lte=week_days(wanted_week)[-1]
+    ).extra(select={'_week': wanted_week})
 
     if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None):
         # Incrementally filter lesson periods by GET parameters
         if 'group' in request.GET and request.GET['group']:
             lesson_periods = lesson_periods.filter(
-                lesson__groups__pk=int(request.GET['group']))
+                Q(lesson__groups__pk=int(request.GET['group'])) | Q(lesson__groups__parent_groups__pk=int(request.GET['group'])))
         if 'teacher' in request.GET and request.GET['teacher']:
             lesson_periods = lesson_periods.filter(
                 lesson__teachers__pk=int(request.GET['teacher']))
@@ -78,7 +86,9 @@ def timetable(request: HttpRequest) -> HttpResponse:
     context['lesson_periods'] = OrderedDict(sorted(per_day.items()))
     context['periods'] = TimePeriod.get_times_dict()
     context['weekdays'] = dict(TimePeriod.WEEKDAY_CHOICES)
-    context['current_week'] = current_week()
+    context['week'] = wanted_week
+    context['week_prev'] = wanted_week - 1
+    context['week_next'] = wanted_week + 1
     context['select_form'] = select_form
 
     return render(request, 'chronos/tt_week.html', context)