diff --git a/db/db.ddl b/db/db.ddl
index fd3ce6c2c8f86c9b736993a36e2911491ddd23f6..b0808cd7762301d5a76e6b583a5ae94d6eb58f50 100644
--- a/db/db.ddl
+++ b/db/db.ddl
@@ -89,6 +89,12 @@ CREATE TYPE score_mode AS ENUM (
 	'mo'				-- jednoznačné pořadí podle pravidel MO
 );
 
+CREATE TYPE enroll_mode AS ENUM (	-- režim přihlašování účastníků
+	'manual',			-- přihlašuje organizátor
+	'register',			-- účastník se registruje
+	'confirm'			-- účastník se registruje, ale org ho musí potvrdit
+);
+
 CREATE TABLE rounds (
 	round_id	serial		PRIMARY KEY,
 	master_round_id	int		DEFAULT NULL REFERENCES rounds(round_id),
@@ -109,6 +115,8 @@ CREATE TABLE rounds (
 	score_successful_limit	int	DEFAULT NULL,			-- bodový limit na označení za úspěšného řešitele
 	points_step	numeric(2,1)	NOT NULL DEFAULT 1,		-- s jakou přesností jsou přidělovány body (celé aneb 1, 0.5, 0.1)
 	has_messages	boolean		NOT NULL DEFAULT false,		-- má zprávičky
+	enroll_mode	enroll_mode	NOT NULL DEFAULT 'manual',	-- režim přihlašování (pro vyšší kola vždy 'manual')
+	enroll_advert	varchar(255)	NOT NULL DEFAULT '',		-- popis v přihlašovacím formuláři
 	UNIQUE (year, category, seq, part)
 );
 
@@ -133,17 +141,19 @@ CREATE TABLE participants (
 	school		int		NOT NULL REFERENCES places(place_id),
 	birth_year	int		NOT NULL,
 	grade		varchar(20)	NOT NULL,			-- třída ve tvaru "X/Y"
+	registered_on	timestamp with time zone	DEFAULT NULL,	-- kdy se účastník přihlásil (NULL, pokud ho přihlásil organizátor)
 	PRIMARY KEY (user_id, year)
 );
 
 -- Účast v soutěžním kole
 
 CREATE TYPE part_state AS ENUM (
-	'registered',		-- sám se přihlásil
-	'invited',		-- pozván
-	'refused',		-- odmítl účast
-	'present',		-- soutěžil
-	'absent',		-- bez omluvy nedorazil
+	'registered',		-- sám se přihlásil, čeká na potvrzení organizátorem
+	-- 'invited',		-- pozván (už nepoužíváme)
+	'active',		-- soutěží (přihlášku zadal/potvrdil organizátor)
+	'refused',		-- organizátor odmítl přihlášku
+	-- 'present',		-- soutěžil (už nepoužíváme)
+	'absent',		-- nedorazil
 	'disqualified'		-- diskvalifikovaný
 );
 
diff --git a/db/upgrade-20210712.sql b/db/upgrade-20210712.sql
new file mode 100644
index 0000000000000000000000000000000000000000..737c4b6675d4fb8e418f7370b74f5f2ca05cd6ce
--- /dev/null
+++ b/db/upgrade-20210712.sql
@@ -0,0 +1,16 @@
+SET ROLE 'mo_osmo';
+
+CREATE TYPE enroll_mode AS ENUM (	-- režim přihlašování účastníků
+	'manual',			-- přihlašuje organizátor
+	'register',			-- účastník se registruje
+	'confirm'			-- účastník se registruje, ale org ho musí potvrdit
+);
+
+ALTER TABLE rounds ADD COLUMN enroll_mode enroll_mode NOT NULL DEFAULT 'manual';
+ALTER TABLE rounds ADD COLUMN enroll_advert varchar(255) NOT NULL DEFAULT '';
+
+ALTER TYPE part_state ADD VALUE 'active' AFTER 'invited';
+
+ALTER TABLE participants ADD COLUMN registered_on timestamp with time zone DEFAULT NULL;
+
+UPDATE participations SET state='active' WHERE state IN ('registered', 'invited');
diff --git a/mo/db.py b/mo/db.py
index c1de5014635fe58d8c326daed91d22dcad52c7cc..7ef564605aa14b81a86e640e4ae2c1d1762c4dfe 100644
--- a/mo/db.py
+++ b/mo/db.py
@@ -203,6 +203,22 @@ round_score_mode_names = {
 }
 
 
+class RoundEnrollMode(MOEnum):
+    manual = auto()
+    register = auto()
+    confirm = auto()
+
+    def friendly_name(self) -> str:
+        return round_enroll_mode_names[self]
+
+
+round_enroll_mode_names = {
+    RoundEnrollMode.manual: "Jen organizátoři",
+    RoundEnrollMode.register: "Účastníci sami",
+    RoundEnrollMode.confirm: "Potvrzení organizátorem",
+}
+
+
 # V DB jako numeric(2,1), používá se tak snadněji, než enum
 round_points_step_names = {
     decimal.Decimal('1'): "Celé body",
@@ -237,6 +253,8 @@ class Round(Base):
     score_successful_limit = Column(Numeric)
     points_step = Column(Numeric, nullable=False)
     has_messages = Column(Boolean, nullable=False, server_default=text("false"))
+    enroll_mode = Column(Enum(RoundEnrollMode, name='enroll_mode'), nullable=False, server_default=text("'basic'::enroll_mode"))
+    enroll_advert = Column(String(255), nullable=False, server_default=text("''::text"))
 
     master = relationship('Round', primaryjoin='Round.master_round_id == Round.round_id', remote_side='Round.round_id', post_update=True)
 
@@ -403,6 +421,7 @@ class Participant(Base):
     school = Column(Integer, ForeignKey('places.place_id'), nullable=False)
     birth_year = Column(Integer, nullable=False)
     grade = Column(String(20), nullable=False)
+    registered_on = Column(DateTime(True))
 
     user = relationship('User')
     school_place = relationship('Place', primaryjoin='Participant.school == Place.place_id')
@@ -410,9 +429,8 @@ class Participant(Base):
 
 class PartState(MOEnum):
     registered = auto()
-    invited = auto()
+    active = auto()
     refused = auto()
-    present = auto()
     absent = auto()
     disqualified = auto()
 
@@ -422,9 +440,8 @@ class PartState(MOEnum):
 
 part_state_names = {
     PartState.registered: 'přihlášený',
-    PartState.invited: 'pozvaný',
+    PartState.active: 'soutěží',
     PartState.refused: 'odmítnutý',
-    PartState.present: 'přítomný',
     PartState.absent: 'nepřítomný',
     PartState.disqualified: 'diskvalifikovaný',
 }