diff --git a/management/management/commands/create_admin.py b/management/management/commands/create_admin.py
index 291ef6d6e3cc9a4f7fe29e84a816fe527934d5b7..605a8efa5a1b64cafd819fba925b7968015caf93 100644
--- a/management/management/commands/create_admin.py
+++ b/management/management/commands/create_admin.py
@@ -60,4 +60,4 @@ class Command(BaseCommand):
 
         manager.save()
         self.stdout.write(self.style.SUCCESS(
-            f'Successfully created management login with username {username}, email {email}, password: {password}'))
+            f'Successfully created management login with username: {username}, email: {email}'))
diff --git a/management/views.py b/management/views.py
index d2592d611e9f6017adfa1c56c8aeb119cb9ba223..cf0f04014d74a73625a5a68d2295064d5324abcb 100644
--- a/management/views.py
+++ b/management/views.py
@@ -34,6 +34,7 @@ from management.forms import (
     SessionSettingsForm
 )
 from vote.models import Election, Application, Voter
+from vote.selectors import open_elections, upcoming_elections, published_elections, closed_elections
 
 logger = logging.getLogger('management.view')
 
@@ -90,19 +91,13 @@ def index(request):
 def session_detail(request, pk=None):
     manager = request.user
     session = manager.sessions.get(id=pk)
-    elections = session.elections.order_by('pk')
-    existing_elections = bool(elections)
-    open_elections = [e for e in elections if e.is_open]
-    upcoming_elections = [e for e in elections if not e.started]
-    published_elections = [e for e in elections if e.closed and not e.result_unpublished]
-    closed_elections = [e for e in elections if e.closed and e.result_unpublished]
     context = {
         'session': session,
-        'existing_elections': existing_elections,
-        'open_elections': open_elections,
-        'upcoming_elections': upcoming_elections,
-        'published_elections': published_elections,
-        'closed_elections': closed_elections,
+        'existing_elections': bool(session.elections),
+        'open_elections': open_elections(session),
+        'upcoming_elections': upcoming_elections(session),
+        'published_elections': published_elections(session),
+        'closed_elections': closed_elections(session),
         'voters': session.participants.all()
     }
     return render(request, template_name='management/session.html', context=context)
diff --git a/vote/forms.py b/vote/forms.py
index 1c3f878265bb8895c94c4ca8df69e73c176f99de..7e0b986ae837d2c822f51ffcde78e7fcafe98fab 100644
--- a/vote/forms.py
+++ b/vote/forms.py
@@ -84,14 +84,14 @@ class VoteForm(forms.Form):
         if self.election.max_votes_yes is not None:
             self.max_votes_yes = self.election.max_votes_yes
         else:
-            self.max_votes_yes = self.election.applications.count()
+            self.max_votes_yes = self.election.applications.all().count()
 
         # dynamically construct form fields
-        for application in self.election.applications:
+        for application in self.election.applications.all():
             self.fields[f'{application.pk}'] = VoteField(application=application,
                                                          disable_abstention=self.election.disable_abstention)
 
-        self.num_applications = len(self.election.applications)
+        self.num_applications = self.election.applications.all().count()
 
     def clean(self):
         super().clean()
diff --git a/vote/migrations/0028_auto_20210804_2335.py b/vote/migrations/0028_auto_20210804_2335.py
new file mode 100644
index 0000000000000000000000000000000000000000..5bdd978aa265c923bd3839fc0398aebdd64b5ca2
--- /dev/null
+++ b/vote/migrations/0028_auto_20210804_2335.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.1.13 on 2021-08-04 21:35
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('vote', '0027_change_field_type_results_published'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='application',
+            name='election',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='vote.election'),
+        ),
+        migrations.AlterField(
+            model_name='application',
+            name='voter',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='vote.voter'),
+        ),
+    ]
diff --git a/vote/models.py b/vote/models.py
index 9730ae32f1a75f529727e35e698a4df1e703d9f3..db30e085ef601182f77ddebbdbdfa08cac946d3f 100644
--- a/vote/models.py
+++ b/vote/models.py
@@ -107,24 +107,24 @@ class Election(models.Model):
     @property
     def started(self):
         if self.start_date is not None:
-            return timezone.now() > self.start_date
+            return timezone.now() >= self.start_date
 
         return False
 
     @property
     def closed(self):
         if self.end_date:
-            return self.end_date < timezone.now()
+            return self.end_date <= timezone.now()
 
         return False
 
     @property
     def is_open(self):
         if self.start_date and self.end_date:
-            return self.start_date < timezone.now() < self.end_date
+            return self.start_date <= timezone.now() < self.end_date
 
         if self.start_date:
-            return self.start_date < timezone.now()
+            return self.start_date <= timezone.now()
 
         return False
 
@@ -135,10 +135,6 @@ class Election(models.Model):
 
         return True
 
-    @property
-    def applications(self):
-        return Application.objects.filter(election=self)
-
     @property
     def election_summary(self):
         if not self.closed:
@@ -163,9 +159,9 @@ class Election(models.Model):
         return self.open_votes.count()
 
     def number_votes_cast(self):
-        if self.applications.count() == 0:
+        if self.applications.all().count() == 0:
             return 0
-        return int(self.votes.count() / self.applications.count())
+        return int(self.votes.count() / self.applications.all().count())
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         super().save(force_insert, force_update, using, update_fields)
@@ -290,9 +286,12 @@ class Voter(models.Model):
     def is_active(self):
         return self.has_usable_password()
 
-    def can_vote(self, election):
+    def can_vote(self, election: Election):
         return election.is_open and OpenVote.objects.filter(voter_id=self.voter_id, election_id=election.id).exists()
 
+    def has_applied(self, election: Election):
+        return self.applications.filter(election=election).exists()
+
     @property
     def is_staff(self):
         return False
@@ -458,10 +457,10 @@ def avatar_file_name(instance, filename):
 class Application(models.Model):
     text = models.TextField(max_length=250, blank=True)
     avatar = models.ImageField(upload_to=avatar_file_name, null=True, blank=True)
-    election = models.ForeignKey(Election, related_name='application', on_delete=models.CASCADE)
+    election = models.ForeignKey(Election, related_name='applications', on_delete=models.CASCADE)
     display_name = models.CharField(max_length=256)
     email = models.EmailField(null=True, blank=True)
-    voter = models.ForeignKey(Voter, related_name="application", null=True, blank=True, on_delete=models.CASCADE)
+    voter = models.ForeignKey(Voter, related_name="applications", null=True, blank=True, on_delete=models.CASCADE)
 
     _old_avatar = None
 
diff --git a/vote/selectors.py b/vote/selectors.py
new file mode 100644
index 0000000000000000000000000000000000000000..e40ccc93d6d83631f048bdb9f74a2ba38d2c4b18
--- /dev/null
+++ b/vote/selectors.py
@@ -0,0 +1,31 @@
+from django.db.models import Q
+from django.utils import timezone
+
+from vote.models import Election, Session
+
+
+def upcoming_elections(session: Session):
+    return Election.objects.filter(session=session).filter(
+        Q(start_date__gt=timezone.now()) | Q(start_date__isnull=True)
+    ).order_by('start_date')
+
+
+def open_elections(session: Session):
+    return Election.objects.filter(session=session).filter(
+        Q(start_date__isnull=False, end_date__isnull=False, start_date__lte=timezone.now(), end_date__gt=timezone.now())
+        | Q(start_date__isnull=False, end_date__isnull=True, start_date__lte=timezone.now())
+    ).order_by('-start_date')
+
+
+def _closed_elections(session: Session):
+    return Election.objects.filter(session=session).filter(
+        Q(end_date__lte=timezone.now(), end_date__isnull=False)
+    ).order_by('-start_date')
+
+
+def published_elections(session: Session):
+    return _closed_elections(session).filter(result_unpublished=False)
+
+
+def closed_elections(session: Session):
+    return _closed_elections(session).filter(result_unpublished=True)
diff --git a/vote/templates/vote/index_election_item.html b/vote/templates/vote/index_election_item.html
index 6413fa3d6d54ed60209680b06d79add496f5786d..7a82c6d497115e6b0bd1871be6592592df0f5c4f 100644
--- a/vote/templates/vote/index_election_item.html
+++ b/vote/templates/vote/index_election_item.html
@@ -79,7 +79,7 @@
           {% endif %}
           <div class="mt-3">
               <div class="row row-cols-1 row-cols-md-2 vote-list">
-                  {% for application in election.applications|shuffle %}
+                  {% for application in election.applications.all|shuffle %}
                       <div class="col mb-2">
                           <div class="applicant">
                               {% if application.avatar %}
diff --git a/vote/tests.py b/vote/tests.py
index a0fe09335bd61463eaae03e3e80876478104e78c..272439f950bb2369ac5fe9cb8d4b128ecc30e043 100644
--- a/vote/tests.py
+++ b/vote/tests.py
@@ -1,6 +1,10 @@
+from datetime import timedelta
+
 from django.test import TestCase
+from django.utils import timezone
 
-from vote.models import Enc32, Voter, Session
+from vote.models import Election, Enc32, Voter, Session
+from vote.selectors import closed_elections, open_elections, published_elections, upcoming_elections
 
 
 class Enc32TestCase(TestCase):
@@ -21,6 +25,58 @@ class VoterTestCase(TestCase):
             self.assertEqual(raw_password, ret_password)
 
 
+class ElectionSelectorsTest(TestCase):
+    def test_election_selectors(self) -> None:
+        now = timezone.now()  # but why does freezegun not work in the query sets
+        before = now - timedelta(minutes=50)
+        bbefore = now - timedelta(minutes=100)
+        after = now + timedelta(minutes=50)
+
+        session = Session.objects.create(title="TEST")
+        # upcoming elections
+        all_upcoming = set()
+        all_upcoming.add(Election.objects.create(session=session))
+        all_upcoming.add(Election.objects.create(session=session, start_date=after))
+        # open elections
+        all_opened = set()
+        all_opened.add(Election.objects.create(session=session, start_date=now))
+        all_opened.add(Election.objects.create(session=session, start_date=before, end_date=after))
+        # published elections
+        all_published = set()
+        all_published.add(Election.objects.create(session=session, start_date=bbefore, end_date=before,
+                                                  result_unpublished=False))
+        all_published.add(Election.objects.create(session=session, start_date=before, end_date=now,
+                                                  result_unpublished=False))
+        # closed (not published) elections
+        all_closed = set()
+        all_closed.add(Election.objects.create(session=session, start_date=bbefore, end_date=before))
+        all_closed.add(Election.objects.create(session=session, start_date=before, end_date=now))
+
+        # test upcoming
+        upcoming = upcoming_elections(session)
+        self.assertEqual(all_upcoming, set(upcoming))
+        for e in upcoming:
+            self.assertTrue(not e.started and not e.closed and not e.is_open)
+
+        # test open
+        opened = open_elections(session)
+        self.assertEqual(all_opened, set(opened))
+        for e in opened:
+            self.assertTrue(e.started and not e.closed and e.is_open)
+
+        # test published
+        published = published_elections(session)
+        self.assertEqual(all_published, set(published))
+        for e in published:
+            self.assertTrue(e.started and e.closed and not e.is_open and not e.result_unpublished)
+
+        # test closed
+        closed = closed_elections(session)
+        self.assertEqual(all_closed, set(closed))
+        for e in closed:
+            self.assertTrue(e.started and e.closed and not e.is_open and e.result_unpublished)
+
+
 def gen_data():
     session = Session.objects.create(
         title='Test session'
diff --git a/vote/views.py b/vote/views.py
index 04bc74d6604c7d1d9bd4b1c4bced1b6cd0ecbc1a..315d9b96dc5fc52e6cfaf86173f90f989192f68a 100644
--- a/vote/views.py
+++ b/vote/views.py
@@ -11,6 +11,7 @@ from ratelimit.decorators import ratelimit
 from vote.authentication import voter_login_required
 from vote.forms import AccessCodeAuthenticationForm, VoteForm, ApplicationUploadFormUser
 from vote.models import Election, Voter, Session
+from vote.selectors import open_elections, upcoming_elections, published_elections, closed_elections
 
 
 class LoginView(auth_views.LoginView):
@@ -57,32 +58,23 @@ def code_login(request, access_code=None):
 
 @voter_login_required
 def index(request):
-    voter = request.user
-    elections = [
-        (e, voter.can_vote(e), voter.application.filter(election=e).exists())
-        for e in voter.session.elections.order_by('pk')
-    ]
-
-    def date_asc(e):
-        date = e[0].start_date
-        return date.timestamp() if date else sys.maxsize
-
-    def date_desc(e):
-        date = e[0].start_date
-        return -date.timestamp() if date else -sys.maxsize
-
-    open_elections = sorted([e for e in elections if e[0].is_open], key=date_desc)
-    upcoming_elections = sorted([e for e in elections if not e[0].started], key=date_asc)
-    published_elections = sorted([e for e in elections if e[0].closed and not e[0].result_unpublished], key=date_desc)
-    closed_elections = sorted([e for e in elections if e[0].closed and e[0].result_unpublished], key=date_desc)
+    voter: Voter = request.user
+    session = voter.session
+
+    def list_elections(elections):
+        return [
+            (e, voter.can_vote(e), voter.has_applied(e))
+            for e in elections
+        ]
+
     context = {
-        'title': voter.session.title,
-        'meeting_link': voter.session.meeting_link,
+        'title': session.title,
+        'meeting_link': session.meeting_link,
         'voter': voter,
-        'open_elections': open_elections,
-        'upcoming_elections': upcoming_elections,
-        'published_elections': published_elections,
-        'closed_elections': closed_elections,
+        'open_elections': list_elections(open_elections(session)),
+        'upcoming_elections': list_elections(upcoming_elections(session)),
+        'published_elections': list_elections(published_elections(session)),
+        'closed_elections': list_elections(closed_elections(session)),
     }
 
     # overview
@@ -91,7 +83,7 @@ def index(request):
 
 @voter_login_required
 def vote(request, election_id):
-    voter = request.user
+    voter: Voter = request.user
     try:
         election = voter.session.elections.get(pk=election_id)
     except Election.DoesNotExist:
@@ -99,9 +91,9 @@ def vote(request, election_id):
 
     can_vote = voter.can_vote(election)
     if election.max_votes_yes is not None:
-        max_votes_yes = min(election.max_votes_yes, election.applications.count())
+        max_votes_yes = min(election.max_votes_yes, election.applications.all().count())
     else:
-        max_votes_yes = election.applications.count()
+        max_votes_yes = election.applications.all().count()
 
     context = {
         'title': election.title,
@@ -132,7 +124,7 @@ def apply(request, election_id):
                                                       ' currently not accepted')
         return redirect('vote:index')
 
-    application = voter.application.filter(election__id=election_id)
+    application = voter.applications.filter(election__id=election_id)
     instance = None
     if application.exists():
         instance = application.first()
@@ -158,7 +150,7 @@ def apply(request, election_id):
 def delete_own_application(request, election_id):
     voter = request.user
     election = get_object_or_404(voter.session.elections, pk=election_id)
-    application = voter.application.filter(election__id=election_id)
+    application = voter.applications.filter(election__id=election_id)
     if not election.can_apply:
         messages.add_message(request, messages.ERROR, 'Applications can currently not be deleted')
         return redirect('vote:index')
@@ -176,26 +168,13 @@ def help_page(request):
 
 def spectator(request, uuid):
     session = get_object_or_404(Session.objects, spectator_token=uuid)
-    elections = session.elections.all()
-
-    def date_asc(e):
-        date = e.start_date
-        return date.timestamp() if date else sys.maxsize
-
-    def date_desc(e):
-        date = e.start_date
-        return -date.timestamp() if date else -sys.maxsize
 
-    open_elections = sorted([e for e in elections if e.is_open], key=date_desc)
-    upcoming_elections = sorted([e for e in elections if not e.started], key=date_asc)
-    published_elections = sorted([e for e in elections if e.closed and int(e.result_published)], key=date_desc)
-    closed_elections = sorted([e for e in elections if e.closed and not int(e.result_published)], key=date_desc)
     context = {
         'title': session.title,
         'meeting_link': session.meeting_link,
-        'open_elections': open_elections,
-        'upcoming_elections': upcoming_elections,
-        'published_elections': published_elections,
-        'closed_elections': closed_elections,
+        'open_elections': open_elections(session),
+        'upcoming_elections': upcoming_elections(session),
+        'published_elections': published_elections(session),
+        'closed_elections': closed_elections(session),
     }
     return render(request, template_name='vote/spectator.html', context=context)