diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c9a6fd37ea168680978ecc71b7b181c1fffe224e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,52 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+.Python
+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
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# pyenv
+.python-version
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+
+# Editors
+*~
+DEADJOE
+\#*#
+
+# Database
+db.sqlite3
diff --git a/biscuit/apps/alsijil/forms.py b/biscuit/apps/alsijil/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b29a28f5a12f8806cbe50c4f1572e914cf5b8d
--- /dev/null
+++ b/biscuit/apps/alsijil/forms.py
@@ -0,0 +1,29 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from .models import LessonDocumentation, PersonalNote
+
+
+class LessonDocumentationForm(forms.ModelForm):
+    class Meta:
+        model = LessonDocumentation
+        fields = ['topic', 'homework']
+
+
+class PersonalNoteForm(forms.ModelForm):
+    class Meta:
+        model = PersonalNote
+        fields = ['absent', 'late', 'excused', 'remarks']
+
+    person_name = forms.CharField(disabled=True)
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        try:
+            self.fields['person_name'].initial = str(self.instance.person)
+        except:
+            pass
+
+
+PersonalNoteFormSet = forms.modelformset_factory(
+    PersonalNote, form=PersonalNoteForm, max_num=0, extra=0)
diff --git a/biscuit/apps/alsijil/migrations/0001_initial.py b/biscuit/apps/alsijil/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..183219e96b93fb4f186ec44dd0ad228c1ae0f33f
--- /dev/null
+++ b/biscuit/apps/alsijil/migrations/0001_initial.py
@@ -0,0 +1,49 @@
+# Generated by Django 2.2.4 on 2019-08-22 20:47
+
+import biscuit.core.util.core_helpers
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('chronos', '0004_auto_20190821_1550'),
+        ('core', '0014_remove_unique'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='PersonalNote',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('week', models.IntegerField()),
+                ('absent', models.BooleanField(default=False)),
+                ('late', models.IntegerField(default=0)),
+                ('excused', models.BooleanField(default=False)),
+                ('remarks', models.CharField(blank=True, max_length=200)),
+                ('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chronos.LessonPeriod')),
+                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Person')),
+                ('school', models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School')),
+            ],
+            options={
+                'unique_together': {('school', 'lesson_period', 'week', 'person')},
+            },
+        ),
+        migrations.CreateModel(
+            name='LessonDocumentation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('week', models.IntegerField()),
+                ('topic', models.CharField(blank=True, max_length=200, verbose_name='Lesson topic')),
+                ('homework', models.CharField(blank=True, max_length=200, verbose_name='Homework')),
+                ('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chronos.LessonPeriod')),
+                ('school', models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School')),
+            ],
+            options={
+                'unique_together': {('school', 'lesson_period', 'week')},
+            },
+        ),
+    ]
diff --git a/biscuit/apps/alsijil/models.py b/biscuit/apps/alsijil/models.py
index fba3ece6792d56d518a4241b97b145d3bb0adae9..5e9094d756d45c8c5b4ab71fb400dc1fcbd94fe1 100644
--- a/biscuit/apps/alsijil/models.py
+++ b/biscuit/apps/alsijil/models.py
@@ -1,7 +1,6 @@
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
-from biscuit.chronos.util import current_week
 from biscuit.core.mixins import SchoolRelated
 
 
@@ -23,10 +22,10 @@ class PersonalNote(SchoolRelated):
 
 class LessonDocumentation(SchoolRelated):
     week = models.IntegerField()
-    lesson_period = models.ForeigbKey('chronos.LessonPeriod', models.CASCADE)
+    lesson_period = models.ForeignKey('chronos.LessonPeriod', models.CASCADE)
 
-    topic = models.CharField(verbose_name=_('Lesson topic'), max_length=200)
-    homework = models.CharField(verbose_name=_('Homework'), max_length=200)
+    topic = models.CharField(verbose_name=_('Lesson topic'), max_length=200, blank=True)
+    homework = models.CharField(verbose_name=_('Homework'), max_length=200, blank=True)
 
     class Meta:
         unique_together = [['school', 'lesson_period', 'week']]
diff --git a/biscuit/apps/alsijil/templates/alsijil/lesson.html b/biscuit/apps/alsijil/templates/alsijil/lesson.html
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d8f3d3f8171dc9c0ab3fb19037bad20adad90c9c 100644
--- a/biscuit/apps/alsijil/templates/alsijil/lesson.html
+++ b/biscuit/apps/alsijil/templates/alsijil/lesson.html
@@ -0,0 +1,72 @@
+{# -*- engine:django -*- #}
+{% extends "core/base.html" %}
+{% load bootstrap4 i18n %}
+
+{% if lesson_period %}
+ {% block page_title %}
+  {% blocktrans %}Lesson{% endblocktrans %}
+
+  -
+
+  {% for group in lesson_period.get_groups.all %}
+   <span>{{ group.short_name }}</span>,
+  {% endfor %}
+
+  {{ lesson_period.get_subject.name }},
+
+  {% for teacher in lesson_period.get_teachers.all %}
+   {{ teacher.short_name }}
+  {% endfor %}
+ {% endblock %}
+
+ {% block content %}
+  <div class="card">
+   <div class="card-header bg-light text-dark">
+    {% blocktrans %}Lesson documentation{% endblocktrans %}
+   </div>
+   <div class="card-body">
+    <form method="post">
+     {% csrf_token %}
+     {% bootstrap_form lesson_documentation_form %}
+     <input type="hidden" name="action" value="lesson_documentation" />
+     <input type="submit" value="Update" />
+    </form>
+   </div>
+  </div>
+
+  <div class="card">
+   <div class="card-header bg-light text-dark">
+    {% blocktrans %}Personal notes{% endblocktrans %}
+   </div>
+   <div class="card-body">
+    <form method="post">
+     {% csrf_token %}
+     {{ personal_note_formset.management_form }}
+
+     <table class="table table-striped table-bordered table-hover table-condensed table-responsive w-100 d-block d-md-table">
+      <tr>
+       <th>{% blocktrans %}Person{% endblocktrans %}</th>
+       <th>{% blocktrans %}Absent{% endblocktrans %}</th>
+       <th>{% blocktrans %}Tardiness{% endblocktrans %}</th>
+       <th>{% blocktrans %}Excused{% endblocktrans %}</th>
+       <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
+      </tr>
+      {% for form in personal_note_formset %}
+       {% form.id %}
+       <tr>
+        <td>{{ form.person_name }}</td>
+        <td>{{ form.absent }}</td>
+        <td>{{ form.late }}</td>
+        <td>{{ form.excused }}</td>
+        <td>{{ form.remarks }}</td>
+       </tr>
+      {% endfor %}
+     </table>
+
+     <input type="hidden" name="action" value="personal_notes" />
+     <input type="submit" value="Update" />
+    </form>
+   </div>
+  </div>
+ {% endblock %}
+{% endif %}
diff --git a/biscuit/apps/alsijil/views.py b/biscuit/apps/alsijil/views.py
index 04cc7aeb455bdc0109000b23e370eccd98ce5399..4c6870ba2273d4c0cae12eeb0fb87432566e62da 100644
--- a/biscuit/apps/alsijil/views.py
+++ b/biscuit/apps/alsijil/views.py
@@ -8,15 +8,20 @@ from django.utils.translation import ugettext as _
 from biscuit.apps.chronos.models import LessonPeriod
 from biscuit.apps.chronos.util import current_lesson_periods, current_week
 
+from .forms import LessonDocumentationForm, PersonalNoteFormSet
+from .models import LessonDocumentation, PersonalNote
+
 
 @login_required
 def lesson(request: HttpRequest, week: Optional[int] = None, period_id: Optional[int] = None) -> HttpResponse:
     context = {}
 
     if week and period_id:
+        # Get a specific lesson period if provided in URL
         lesson_period = LessonPeriod.objects.get(pk=period_id)
         wanted_week = week
     else:
+        # Determine current lesson by current date and time
         lesson_period = current_lesson_periods().filter(
             lesson__teachers=request.user.person).first()
         wanted_week = current_week()
@@ -24,4 +29,32 @@ def lesson(request: HttpRequest, week: Optional[int] = None, period_id: Optional
     context['lesson_period'] = lesson_period
     context['week'] = wanted_week
 
+    if lesson_period:
+        # Create or get lesson documentation object; can be empty when first opening lesson
+        lesson_documentation, created = LessonDocumentation.objects.get_or_create(lesson_period=lesson_period, week=wanted_week)
+        lesson_documentation_form = LessonDocumentationForm(request.POST or None, instance=lesson_documentation, prefix='leson_documentation')
+
+        # Create all missing personal notes about members of all groups in lesson
+        for group in lesson_period.lesson.groups.all():
+            for person in group.members.all():
+                note, created = PersonalNote.objects.get_or_create(person=person, lesson_period=lesson_period,
+                                                                   week=wanted_week)
+
+        # Create a formset that holds all personal notes for all persons in this lesson
+        persons_qs = PersonalNote.objects.filter(lesson_period=lesson_period, week=wanted_week)
+        personal_note_formset = PersonalNoteFormSet(request.POST or None, queryset=persons_qs, prefix='personal_notes')
+
+        if request.method == 'POST':
+            if request.POST.get('action', None) == 'lesson_documentation':
+                # Save the lesson documentation
+                if lesson_documentation_form.is_valid():
+                    lesson_documentation_form.save()
+            elif request.POST.get('action', None) == 'personal_notes':
+                # Save all personal notes
+                if personal_note_formset.is_valid():
+                    personal_note_formset.save()
+
+        context['lesson_documentation_form'] = lesson_documentation_form
+        context['personal_note_formset'] = personal_note_formset
+
     return render(request, 'alsijil/lesson.html', context)