diff --git a/.flaskenv b/.flaskenv
index 557b84410e90c35e4130f92151117f37dbf8678a..0544671576aca4281abc3bba9a35eaacc3e4330f 100644
--- a/.flaskenv
+++ b/.flaskenv
@@ -1,2 +1,2 @@
 FLASK_APP=server.py:app
-FLASK_ENV=development
+FLASK_DEBUG=True
diff --git a/migrations/versions/da846db78ff9_.py b/migrations/versions/da846db78ff9_.py
new file mode 100644
index 0000000000000000000000000000000000000000..a569337c48e509669c15edb7f012d44008f4cd4d
--- /dev/null
+++ b/migrations/versions/da846db78ff9_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: da846db78ff9
+Revises: 7834767242e8
+Create Date: 2022-10-29 19:24:42.934124
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'da846db78ff9'
+down_revision = '7834767242e8'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('protocoltypes', sa.Column('recurrence', sa.Integer(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('protocoltypes', 'recurrence')
+    # ### end Alembic commands ###
diff --git a/models/database.py b/models/database.py
index a7c5ebf7e249c382ad01ed00555d25d896d7e893..6d98e438eb843b9d6e789706878938375b6e32d4 100644
--- a/models/database.py
+++ b/models/database.py
@@ -78,6 +78,7 @@ class ProtocolType(DatabaseModel):
     restrict_networks = db.Column(db.Boolean)
     allowed_networks = db.Column(db.Text)
     latex_template = db.Column(db.Text)
+    recurrence = db.Column(db.Integer)
 
     protocols = relationship(
         "Protocol", backref=backref("protocoltype"),
diff --git a/requirements.txt b/requirements.txt
index 8979d5ca0f479ee0270fdcde1a7092db44656b40..57271353d070318274e6d64ff41ea36b5012be17 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,91 +1,99 @@
-alembic==1.6.5
-amqp==5.0.6
+alembic==1.8.1
+amqp==5.1.1
 appdirs==1.4.4
-APScheduler==3.7.0
+APScheduler==3.9.1
 argh==0.26.2
-bandit==1.7.0
+async-timeout==4.0.2
+bandit==1.7.4
 billiard==3.6.4.0
+blessed==1.19.1
 blessings==1.7
-blinker==1.4
-bpython==0.21
-caldav==0.8.0
-celery==5.1.2
-certifi==2021.5.30
-chardet==4.0.0
-charset-normalizer==2.0.3
-click==7.1.2
-click-didyoumean==0.0.3
+blinker==1.5
+bpython==0.23
+caldav==0.10.0
+celery==5.2.7
+certifi==2022.9.24
+chardet==5.0.0
+charset-normalizer==2.1.1
+click==8.1.3
+click-didyoumean==0.3.0
 click-plugins==1.1.1
 click-repl==0.2.0
-colorama==0.4.4
-coverage==5.5
-curtsies==0.3.5
-cwcwidth==0.1.4
-dnspython==1.16.0
+colorama==0.4.6
+coverage==6.5.0
+curtsies==0.4.1
+cwcwidth==0.1.8
+Deprecated==1.2.13
+dnspython==2.2.1
 enum-compat==0.0.3
 etherpad-lite==0.5
-eventlet==0.31.1
+eventlet==0.33.1
 feedgen==0.9.0
-Flask==2.0.1
-Flask-Migrate==3.0.1
-Flask-SQLAlchemy==2.5.1
-Flask-WTF==0.15.1
+Flask==2.2.2
+Flask-Migrate==3.1.0
+Flask-SQLAlchemy==3.0.2
+Flask-WTF==1.0.1
 fuzzywuzzy==0.18.0
-gitdb==4.0.7
+gitdb==4.0.9
 gitdb2==4.0.2
-GitPython==3.1.18
-greenlet==1.1.0
-icalendar==4.0.7
-idna==3.2
-itsdangerous==2.0.1
-Jinja2==3.0.1
-kombu==5.1.0
+GitPython==3.1.29
+greenlet==1.1.3.post0
+icalendar==5.0.1
+idna==3.4
+itsdangerous==2.1.2
+Jinja2==3.1.2
+kombu==5.2.4
 ldap3==2.9.1
-lxml==4.6.3
-Mako==1.1.4
-mando==0.7.0
-MarkupSafe==2.0.1
-mccabe==0.6.1
+Levenshtein==0.20.7
+lxml==4.9.1
+Mako==1.2.3
+mando==0.7.1
+MarkupSafe==2.1.1
+mccabe==0.7.0
 monotonic==1.6
 nose==1.3.7
-packaging==21.0
+packaging==21.3
 pathtools==0.1.2
-pbr==5.6.0
-prompt-toolkit==3.0.19
-psycopg2-binary==2.9.1
+pbr==5.11.0
+prompt-toolkit==3.0.31
+psycopg2-binary==2.9.5
 pyasn1==0.4.8
 pyasn1-modules==0.2.8
-pycodestyle==2.7.0
-Pygments==2.9.0
+pycodestyle==2.9.1
+Pygments==2.13.0
 pyldap==3.0.0.post1
-pyparsing==2.4.7
+pyparsing==3.0.9
 python-dateutil==2.8.2
-python-dotenv==0.19.0
+python-dotenv==0.21.0
 python-editor==1.0.4
-python-engineio==4.2.0
-python-ldap==3.3.1
-python-Levenshtein==0.12.2
-python-pam==1.8.4
-pytz==2021.1
-pyxdg==0.27
-PyYAML==5.4.1
+python-engineio==4.3.4
+python-ldap==3.4.3
+python-Levenshtein==0.20.7
+python-pam==2.0.2
+pytz==2022.5
+pytz-deprecation-shim==0.1.0.post0
+pyxdg==0.28
+PyYAML==6.0
+rapidfuzz==2.12.0
 raven==6.10.0
-redis==3.5.3
-regex==2021.7.6
-requests==2.26.0
+redis==4.3.4
+regex==2022.9.13
+requests==2.28.1
 six==1.16.0
-smmap==4.0.0
+smmap==5.0.0
 smmap2==3.0.1
-SQLAlchemy==1.4.22
-SQLAlchemy-Utils==0.37.8
-stevedore==3.3.0
+SQLAlchemy==1.4.42
+SQLAlchemy-Utils==0.38.3
+stevedore==4.1.0
 typing==3.7.4.3
-tzlocal==2.1
-urllib3==1.26.6
+tzdata==2022.5
+tzlocal==4.2
+urllib3==1.26.12
 uwsgidecorators==1.1.0
 vine==5.0.0
 vobject==0.9.6.1
-watchdog==2.1.3
+watchdog==2.1.9
 wcwidth==0.2.5
-Werkzeug==2.0.1
-WTForms==2.3.3
+Werkzeug==2.2.2
+wrapt==1.14.1
+WTForms==3.0.1
diff --git a/tasks.py b/tasks.py
index 5976b989bd2dd2dab85dc33e2e8f1d9ead76d407..d894769faa617cef4fe44395e9aaadc48960ec90 100644
--- a/tasks.py
+++ b/tasks.py
@@ -4,7 +4,7 @@ import os
 import subprocess
 import shutil
 import tempfile
-from datetime import datetime
+from datetime import datetime, timedelta
 import time
 import traceback
 from copy import copy
@@ -500,6 +500,11 @@ def parse_protocol_async_inner(protocol, ignore_old_date=False):
         if not protocol.protocoltype.get_protocols_on_date(new_protocol_date):
             Protocol.create_new_protocol(
                 protocol.protocoltype, new_protocol_date, new_protocol_time)
+    if not protocol_tags and protocol.protocoltype.recurrence:
+        new_protocol_date = protocol.date + timedelta(
+            days=protocol.protocoltype.recurrence)
+        if new_protocol_date > datetime.now().date():
+            Protocol.create_new_protocol(protocol.protocoltype, new_protocol_date)
 
     # TOPs
     old_tops = list(protocol.tops)
diff --git a/views/forms.py b/views/forms.py
index 77308a73c0dd27907139adcd074db434cc7bc1eb..551763351d47a69676f8140da6f7b72b64bc8fa3 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -1,8 +1,8 @@
 from flask_wtf import FlaskForm
 from wtforms import (
     StringField, PasswordField, BooleanField, IntegerField, SelectField,
-    FileField, DateTimeField, TextAreaField, Field, FormField, widgets)
-from wtforms.fields.html5 import DateField
+    FileField, DateTimeField, TextAreaField, Field, FormField, DateField,
+    widgets)
 from wtforms.validators import InputRequired, Optional
 
 import ipaddress
@@ -174,6 +174,7 @@ class ProtocolTypeForm(FlaskForm):
     wiki_only_public = BooleanField("Wiki ist öffentlich")
     printer = SelectField("Drucker", choices=[])
     calendar = SelectField("Kalender", choices=[])
+    recurrence = IntegerField("Turnus (in Tagen)", validators=[Optional()])
     restrict_networks = BooleanField("Netzwerke einschränken")
     allowed_networks = IPNetworkField("Erlaubte Netzwerke")
     latex_template = SelectField("LaTeX Vorlage", choices=[])
diff --git a/views/tables.py b/views/tables.py
index 1009957ad1dd7eed3367af1a123453385378ada3..9db9c096196a4a44bfa230bc4e05d2e4dcbef47d 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -237,6 +237,7 @@ class ProtocolTypeTable(SingleValueTable):
         calendar_headers = ["Kalender"]
         if not config.CALENDAR_ACTIVE:
             calendar_headers = []
+        recurrence_headers = ["Turnus"]
         network_headers = ["Netzwerke einschränken", "Erlaubte Netzwerke"]
         action_headers = ["Aktion"]
         feed_headers = []
@@ -249,8 +250,8 @@ class ProtocolTypeTable(SingleValueTable):
         return (
             general_headers + etherpad_headers + mail_headers
             + printing_headers + wiki_headers + calendar_headers
-            + network_headers + latex_template_headers + feed_headers
-            + action_headers)
+            + recurrence_headers + network_headers + latex_template_headers
+            + feed_headers + action_headers)
 
     def row(self):
         user = current_user()
@@ -297,6 +298,7 @@ class ProtocolTypeTable(SingleValueTable):
             if self.value.calendar is not None else ""]
         if not config.CALENDAR_ACTIVE:
             calendar_part = []
+        recurrence_part = [f"{self.value.recurrence} Tage" if self.value.recurrence is not None else ""]
         network_part = [Table.bool(self.value.restrict_networks)]
         if self.value.allowed_networks is not None:
             network_part.append(
@@ -344,8 +346,8 @@ class ProtocolTypeTable(SingleValueTable):
             action_part = [""]
         return (
             general_part + etherpad_part + mail_part + printing_part
-            + wiki_part + calendar_part + network_part + latex_template_part
-            + feed_part + action_part)
+            + wiki_part + calendar_part + recurrence_part + network_part
+            + latex_template_part + feed_part + action_part)
 
 
 class DefaultTOPsTable(Table):