Repository: comic/grand-challenge.org

Issue:
make results hidden
Every page has permission, so you can set it to only registered users. But the Results page is an automatically created page, see e.g. https://lola19.grand-challenge.org/evaluation/results/

Is it possible to also make this page visible only to registered users. Would also be nice if you can make one setting to set all pages to visible to registered users only.


Discussion:
I second this request :)

Resolving patch:
diff --git a/app/grandchallenge/evaluation/migrations/0005_auto_20200904_1015.py b/app/grandchallenge/evaluation/migrations/0005_auto_20200904_1015.py
new file mode 100644
--- /dev/null
+++ b/app/grandchallenge/evaluation/migrations/0005_auto_20200904_1015.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.9 on 2020-09-04 10:15
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("evaluation", "0004_auto_20200901_1502"),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name="phase",
+            options={
+                "ordering": ("challenge", "submissions_open", "created"),
+                "permissions": (
+                    ("create_phase_submission", "Create Phase Submission"),
+                ),
+            },
+        ),
+    ]
diff --git a/app/grandchallenge/evaluation/models.py b/app/grandchallenge/evaluation/models.py
--- a/app/grandchallenge/evaluation/models.py
+++ b/app/grandchallenge/evaluation/models.py
@@ -363,6 +363,7 @@ class Meta:
             ("challenge", "slug"),
         )
         ordering = ("challenge", "submissions_open", "created")
+        permissions = (("create_phase_submission", "Create Phase Submission"),)
 
     def __str__(self):
         return f"{self.title} Evaluation for {self.challenge.short_name}"
@@ -389,6 +390,12 @@ def set_default_interfaces(self):
     def assign_permissions(self):
         assign_perm("view_phase", self.challenge.admins_group, self)
         assign_perm("change_phase", self.challenge.admins_group, self)
+        assign_perm(
+            "create_phase_submission", self.challenge.admins_group, self
+        )
+        assign_perm(
+            "create_phase_submission", self.challenge.participants_group, self
+        )
 
     def get_absolute_url(self):
         return reverse(
diff --git a/app/grandchallenge/evaluation/views/__init__.py b/app/grandchallenge/evaluation/views/__init__.py
--- a/app/grandchallenge/evaluation/views/__init__.py
+++ b/app/grandchallenge/evaluation/views/__init__.py
@@ -16,11 +16,12 @@
     RedirectView,
     UpdateView,
 )
-
-from grandchallenge.core.permissions.mixins import (
-    UserIsChallengeAdminMixin,
-    UserIsChallengeParticipantOrAdminMixin,
+from guardian.mixins import (
+    LoginRequiredMixin,
+    PermissionListMixin,
+    PermissionRequiredMixin as ObjectPermissionRequiredMixin,
 )
+
 from grandchallenge.core.views import Column, PaginatedTableListView
 from grandchallenge.evaluation.forms import (
     LegacySubmissionForm,
@@ -35,13 +36,21 @@
     Submission,
 )
 from grandchallenge.jqfileupload.widgets.uploader import StagedAjaxFile
-from grandchallenge.subdomains.utils import reverse
+from grandchallenge.subdomains.utils import reverse, reverse_lazy
 from grandchallenge.teams.models import Team
 
 
-class PhaseUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):
+class PhaseUpdate(
+    LoginRequiredMixin,
+    ObjectPermissionRequiredMixin,
+    SuccessMessageMixin,
+    UpdateView,
+):
     form_class = PhaseForm
     success_message = "Configuration successfully updated"
+    permission_required = "change_phase"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")
 
     def get_object(self, queryset=None):
         return Phase.objects.get(
@@ -58,9 +67,17 @@ def get_success_url(self):
         )
 
 
-class MethodCreate(UserIsChallengeAdminMixin, CreateView):
+class MethodCreate(
+    LoginRequiredMixin, ObjectPermissionRequiredMixin, CreateView
+):
     model = Method
     form_class = MethodForm
+    permission_required = "change_challenge"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")
+
+    def get_permission_object(self):
+        return self.request.challenge
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
@@ -78,16 +95,23 @@ def form_valid(self, form):
         return super().form_valid(form)
 
 
-class MethodList(UserIsChallengeAdminMixin, ListView):
+class MethodList(LoginRequiredMixin, PermissionListMixin, ListView):
     model = Method
+    permission_required = "view_method"
+    login_url = reverse_lazy("userena_signin")
 
     def get_queryset(self):
         queryset = super().get_queryset()
         return queryset.filter(phase__challenge=self.request.challenge)
 
 
-class MethodDetail(UserIsChallengeAdminMixin, DetailView):
+class MethodDetail(
+    LoginRequiredMixin, ObjectPermissionRequiredMixin, DetailView
+):
     model = Method
+    permission_required = "view_method"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")
 
 
 class SubmissionCreateBase(SuccessMessageMixin, CreateView):
@@ -216,13 +240,27 @@ def get_success_url(self):
 
 
 class SubmissionCreate(
-    UserIsChallengeParticipantOrAdminMixin, SubmissionCreateBase
+    LoginRequiredMixin, ObjectPermissionRequiredMixin, SubmissionCreateBase
 ):
     form_class = SubmissionForm
+    permission_required = "create_phase_submission"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")
+
+    def get_permission_object(self):
+        return self.phase
 
 
-class LegacySubmissionCreate(UserIsChallengeAdminMixin, SubmissionCreateBase):
+class LegacySubmissionCreate(
+    LoginRequiredMixin, ObjectPermissionRequiredMixin, SubmissionCreateBase
+):
     form_class = LegacySubmissionForm
+    permission_required = "change_challenge"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")
+
+    def get_permission_object(self):
+        return self.request.challenge
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
@@ -230,25 +268,23 @@ def get_form_kwargs(self):
         return kwargs
 
 
-class SubmissionList(UserIsChallengeParticipantOrAdminMixin, ListView):
+class SubmissionList(LoginRequiredMixin, PermissionListMixin, ListView):
     model = Submission
+    permission_required = "view_submission"
+    login_url = reverse_lazy("userena_signin")
 
     def get_queryset(self):
-        """Admins see everything, participants just their submissions."""
         queryset = super().get_queryset()
-        challenge = self.request.challenge
-        if challenge.is_admin(self.request.user):
-            return queryset.filter(phase__challenge=self.request.challenge)
-        else:
-            return queryset.filter(
-                Q(phase__challenge=self.request.challenge),
-                Q(creator__pk=self.request.user.pk),
-            )
+        return queryset.filter(phase__challenge=self.request.challenge)
 
 
-class SubmissionDetail(UserIsChallengeAdminMixin, DetailView):
-    # TODO - if participant: list only their submissions
+class SubmissionDetail(
+    LoginRequiredMixin, ObjectPermissionRequiredMixin, DetailView
+):
     model = Submission
+    permission_required = "view_submission"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")
 
 
 class TeamContextMixin:
@@ -276,20 +312,21 @@ def get_context_data(self, *args, **kwargs):
 
 
 class EvaluationList(
-    UserIsChallengeParticipantOrAdminMixin, TeamContextMixin, ListView
+    LoginRequiredMixin, PermissionListMixin, TeamContextMixin, ListView
 ):
     model = Evaluation
+    permission_required = "view_evaluation"
+    login_url = reverse_lazy("userena_signin")
 
     def get_queryset(self):
-        """Admins see everything, participants just their evaluations."""
-        challenge = self.request.challenge
-
         queryset = super().get_queryset()
-        queryset = queryset.select_related(
+        queryset = queryset.filter(
+            submission__phase__challenge=self.request.challenge
+        ).select_related(
             "submission__creator__user_profile", "submission__phase__challenge"
-        ).filter(submission__phase__challenge=challenge)
+        )
 
-        if challenge.is_admin(self.request.user):
+        if self.request.challenge.is_admin(self.request.user):
             return queryset
         else:
             return queryset.filter(
@@ -297,9 +334,10 @@ def get_queryset(self):
             )
 
 
-class EvaluationDetail(DetailView):
-    # TODO - if participant: list only their evaluations
+class EvaluationDetail(ObjectPermissionRequiredMixin, DetailView):
     model = Evaluation
+    permission_required = "view_evaluation"
+    raise_exception = True
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -331,11 +369,14 @@ def get_redirect_url(self, *args, **kwargs):
         )
 
 
-class LeaderboardDetail(TeamContextMixin, PaginatedTableListView):
+class LeaderboardDetail(
+    PermissionListMixin, TeamContextMixin, PaginatedTableListView
+):
     model = Evaluation
     template_name = "evaluation/leaderboard_detail.html"
     row_template = "evaluation/leaderboard_row.html"
     search_fields = ["pk", "submission__creator__username"]
+    permission_required = "view_evaluation"
 
     @cached_property
     def phase(self):
@@ -424,8 +465,8 @@ def get_context_data(self, *args, **kwargs):
         context.update({"phase": self.phase})
         return context
 
-    def get_unfiltered_queryset(self):
-        queryset = super().get_queryset()
+    def get_queryset(self, *args, **kwargs):
+        queryset = super().get_queryset(*args, **kwargs)
         queryset = self.filter_by_date(queryset=queryset)
         queryset = (
             queryset.select_related(
@@ -459,8 +500,14 @@ def filter_by_date(self, queryset):
 
 
 class EvaluationUpdate(
-    UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView
+    LoginRequiredMixin,
+    ObjectPermissionRequiredMixin,
+    SuccessMessageMixin,
+    UpdateView,
 ):
     model = Evaluation
     fields = ("published",)
     success_message = "Result successfully updated."
+    permission_required = "change_evaluation"
+    raise_exception = True
+    login_url = reverse_lazy("userena_signin")


Test patch:
diff --git a/app/tests/evaluation_tests/factories.py b/app/tests/evaluation_tests/factories.py
--- a/app/tests/evaluation_tests/factories.py
+++ b/app/tests/evaluation_tests/factories.py
@@ -23,6 +23,7 @@ class MethodFactory(factory.DjangoModelFactory):
     class Meta:
         model = Method
 
+    creator = factory.SubFactory(UserFactory)
     phase = factory.SubFactory(PhaseFactory)
     image = factory.django.FileField()
     image_sha256 = factory.sequence(lambda n: hash_sha256(f"image{n}"))
diff --git a/app/tests/evaluation_tests/test_config.py b/app/tests/evaluation_tests/test_config.py
--- a/app/tests/evaluation_tests/test_config.py
+++ b/app/tests/evaluation_tests/test_config.py
@@ -61,6 +61,7 @@ def test_setting_display_all_metrics(client, challenge_set):
         viewname="evaluation:detail",
         challenge=challenge_set.challenge,
         reverse_kwargs={"pk": e.pk},
+        user=challenge_set.challenge.creator,
     )
 
     assert response.status_code == 200
@@ -76,6 +77,7 @@ def test_setting_display_all_metrics(client, challenge_set):
         viewname="evaluation:detail",
         challenge=challenge_set.challenge,
         reverse_kwargs={"pk": e.pk},
+        user=challenge_set.challenge.creator,
     )
 
     assert response.status_code == 200
diff --git a/app/tests/evaluation_tests/test_permissions.py b/app/tests/evaluation_tests/test_permissions.py
--- a/app/tests/evaluation_tests/test_permissions.py
+++ b/app/tests/evaluation_tests/test_permissions.py
@@ -36,7 +36,12 @@ def test_phase_permissions(self):
         p: Phase = PhaseFactory()
 
         assert get_groups_with_set_perms(p) == {
-            p.challenge.admins_group: {"change_phase", "view_phase"}
+            p.challenge.admins_group: {
+                "change_phase",
+                "view_phase",
+                "create_phase_submission",
+            },
+            p.challenge.participants_group: {"create_phase_submission"},
         }
         assert get_users_with_perms(p, with_group_users=False).count() == 0
 
diff --git a/app/tests/evaluation_tests/test_views.py b/app/tests/evaluation_tests/test_views.py
--- a/app/tests/evaluation_tests/test_views.py
+++ b/app/tests/evaluation_tests/test_views.py
@@ -1,218 +1,266 @@
-from collections import namedtuple
 from datetime import timedelta
 
 import factory
 import pytest
 from django.db.models import signals
 from django.utils import timezone
+from guardian.shortcuts import assign_perm, remove_perm
 
+from grandchallenge.evaluation.models import Evaluation
 from tests.evaluation_tests.factories import (
     EvaluationFactory,
     MethodFactory,
+    PhaseFactory,
     SubmissionFactory,
 )
-from tests.utils import (
-    get_view_for_user,
-    validate_admin_only_view,
-    validate_admin_or_participant_view,
-    validate_open_view,
-)
-
-
-def submission_and_evaluation(*, phase, creator):
-    """Creates a submission and an evaluation for that submission."""
-    s = SubmissionFactory(phase=phase, creator=creator)
-    e = EvaluationFactory(submission=s)
-    return s, e
-
-
-def submissions_and_evaluations(two_challenge_sets):
-    """
-    Create (e)valuations and (s)ubmissions for each (p)articipant and
-    (c)hallenge.
-    """
-    SubmissionsAndEvaluations = namedtuple(
-        "SubmissionsAndEvaluations",
-        [
-            "p_s1",
-            "p_s2",
-            "p1_s1",
-            "p12_s1_c1",
-            "p12_s1_c2",
-            "e_p_s1",
-            "e_p_s2",
-            "e_p1_s1",
-            "e_p12_s1_c1",
-            "e_p12_s1_c2",
-        ],
-    )
-    # participant 0, submission 1, challenge 1, etc
-    p_s1, e_p_s1 = submission_and_evaluation(
-        phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
-        creator=two_challenge_sets.challenge_set_1.participant,
-    )
-    p_s2, e_p_s2 = submission_and_evaluation(
-        phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
-        creator=two_challenge_sets.challenge_set_1.participant,
-    )
-    p1_s1, e_p1_s1 = submission_and_evaluation(
-        phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
-        creator=two_challenge_sets.challenge_set_1.participant1,
-    )
-    # participant12, submission 1 to each challenge
-    p12_s1_c1, e_p12_s1_c1 = submission_and_evaluation(
-        phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
-        creator=two_challenge_sets.participant12,
-    )
-    p12_s1_c2, e_p12_s1_c2 = submission_and_evaluation(
-        phase=two_challenge_sets.challenge_set_2.challenge.phase_set.get(),
-        creator=two_challenge_sets.participant12,
-    )
-    return SubmissionsAndEvaluations(
-        p_s1,
-        p_s2,
-        p1_s1,
-        p12_s1_c1,
-        p12_s1_c2,
-        e_p_s1,
-        e_p_s2,
-        e_p1_s1,
-        e_p12_s1_c1,
-        e_p12_s1_c2,
-    )
+from tests.factories import ChallengeFactory, UserFactory
+from tests.utils import get_view_for_user
 
 
 @pytest.mark.django_db
-def test_method_list(client, two_challenge_sets):
-    validate_admin_only_view(
-        viewname="evaluation:method-list",
-        two_challenge_set=two_challenge_sets,
-        client=client,
-    )
+class TestLoginViews:
+    def test_login_redirect(self, client):
+        e = EvaluationFactory()
+
+        for view_name, kwargs in [
+            ("phase-update", {"slug": e.submission.phase.slug}),
+            ("method-create", {}),
+            ("method-list", {}),
+            ("method-detail", {"pk": e.method.pk}),
+            ("submission-create", {"slug": e.submission.phase.slug}),
+            ("submission-create-legacy", {"slug": e.submission.phase.slug}),
+            ("submission-list", {}),
+            ("submission-detail", {"pk": e.submission.pk}),
+            ("list", {}),
+            ("update", {"pk": e.pk}),
+        ]:
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e.submission.phase.challenge.short_name,
+                    **kwargs,
+                },
+                user=None,
+            )
+
+            assert response.status_code == 302
+            assert response.url.startswith(
+                f"https://testserver/users/signin/?next=http%3A//"
+                f"{e.submission.phase.challenge.short_name}.testserver/"
+            )
+
+    def test_open_views(self, client):
+        e = EvaluationFactory(submission__phase__challenge__hidden=False)
+
+        for view_name, kwargs in [
+            ("leaderboard", {"slug": e.submission.phase.slug}),
+            ("detail", {"pk": e.pk}),
+        ]:
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e.submission.phase.challenge.short_name,
+                    **kwargs,
+                },
+                user=None,
+            )
+
+            assert response.status_code == 200
 
 
 @pytest.mark.django_db
-def test_method_create(client, two_challenge_sets):
-    validate_admin_only_view(
-        viewname="evaluation:method-create",
-        two_challenge_set=two_challenge_sets,
-        client=client,
-    )
-
+class TestObjectPermissionRequiredViews:
+    def test_permission_required_views(self, client):
+        e = EvaluationFactory()
+        u = UserFactory()
+
+        for view_name, kwargs, permission, obj in [
+            (
+                "phase-update",
+                {"slug": e.submission.phase.slug},
+                "change_phase",
+                e.submission.phase,
+            ),
+            (
+                "method-create",
+                {},
+                "change_challenge",
+                e.submission.phase.challenge,
+            ),
+            ("method-detail", {"pk": e.method.pk}, "view_method", e.method),
+            (
+                "submission-create",
+                {"slug": e.submission.phase.slug},
+                "create_phase_submission",
+                e.submission.phase,
+            ),
+            (
+                "submission-create-legacy",
+                {"slug": e.submission.phase.slug},
+                "change_challenge",
+                e.submission.phase.challenge,
+            ),
+            (
+                "submission-detail",
+                {"pk": e.submission.pk},
+                "view_submission",
+                e.submission,
+            ),
+            ("update", {"pk": e.pk}, "change_evaluation", e),
+            ("detail", {"pk": e.pk}, "view_evaluation", e),
+        ]:
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e.submission.phase.challenge.short_name,
+                    **kwargs,
+                },
+                user=u,
+            )
+
+            assert response.status_code == 403
+
+            assign_perm(permission, u, obj)
+
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e.submission.phase.challenge.short_name,
+                    **kwargs,
+                },
+                user=u,
+            )
+
+            assert response.status_code == 200
+
+            remove_perm(permission, u, obj)
+
+    def test_permission_filtered_views(self, client):
+        u = UserFactory()
+
+        p = PhaseFactory()
+        m = MethodFactory(phase=p)
+        s = SubmissionFactory(phase=p, creator=u)
+        e = EvaluationFactory(
+            method=m, submission=s, rank=1, status=Evaluation.SUCCESS
+        )
 
-@pytest.mark.django_db
-def test_method_detail(client, two_challenge_sets):
-    method = MethodFactory(
-        phase__challenge=two_challenge_sets.challenge_set_1.challenge,
-        creator=two_challenge_sets.challenge_set_1.admin,
-    )
-    validate_admin_only_view(
-        viewname="evaluation:method-detail",
-        two_challenge_set=two_challenge_sets,
-        reverse_kwargs={"pk": method.pk},
-        client=client,
-    )
+        for view_name, kwargs, permission, obj in [
+            ("method-list", {}, "view_method", m),
+            ("submission-list", {}, "view_submission", s),
+            ("list", {}, "view_evaluation", e),
+            (
+                "leaderboard",
+                {"slug": e.submission.phase.slug},
+                "view_evaluation",
+                e,
+            ),
+        ]:
+            assign_perm(permission, u, obj)
+
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e.submission.phase.challenge.short_name,
+                    **kwargs,
+                },
+                user=u,
+            )
+
+            assert response.status_code == 200
+            assert obj in response.context[-1]["object_list"]
+
+            remove_perm(permission, u, obj)
+
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e.submission.phase.challenge.short_name,
+                    **kwargs,
+                },
+                user=u,
+            )
+
+            assert response.status_code == 200
+            assert obj not in response.context[-1]["object_list"]
 
 
 @pytest.mark.django_db
-@factory.django.mute_signals(signals.post_save)
-def test_submission_list(client, two_challenge_sets):
-    validate_admin_or_participant_view(
-        viewname="evaluation:submission-list",
-        two_challenge_set=two_challenge_sets,
-        client=client,
-    )
-    p_s1, p_s2, p1_s1, p12_s1_c1, p12_s1_c2, *_ = submissions_and_evaluations(
-        two_challenge_sets
-    )
-    # Participants should only be able to see their own submissions
-    response = get_view_for_user(
-        viewname="evaluation:submission-list",
-        challenge=two_challenge_sets.challenge_set_1.challenge,
-        client=client,
-        user=two_challenge_sets.challenge_set_1.participant,
-    )
-    assert str(p_s1.pk) in response.rendered_content
-    assert str(p_s2.pk) in response.rendered_content
-    assert str(p1_s1.pk) not in response.rendered_content
-    assert str(p12_s1_c1.pk) not in response.rendered_content
-    assert str(p12_s1_c2.pk) not in response.rendered_content
-    # Admins should be able to see all submissions
-    response = get_view_for_user(
-        viewname="evaluation:submission-list",
-        challenge=two_challenge_sets.challenge_set_1.challenge,
-        client=client,
-        user=two_challenge_sets.challenge_set_1.admin,
-    )
-    assert str(p_s1.pk) in response.rendered_content
-    assert str(p_s2.pk) in response.rendered_content
-    assert str(p1_s1.pk) in response.rendered_content
-    assert str(p12_s1_c1.pk) in response.rendered_content
-    assert str(p12_s1_c2.pk) not in response.rendered_content
-    # Only submissions relevant to this challenge should be listed
-    response = get_view_for_user(
-        viewname="evaluation:submission-list",
-        challenge=two_challenge_sets.challenge_set_1.challenge,
-        client=client,
-        user=two_challenge_sets.participant12,
-    )
-    assert str(p12_s1_c1.pk) in response.rendered_content
-    assert str(p12_s1_c2.pk) not in response.rendered_content
-    assert str(p_s1.pk) not in response.rendered_content
-    assert str(p_s2.pk) not in response.rendered_content
-    assert str(p1_s1.pk) not in response.rendered_content
+class TestViewFilters:
+    def test_challenge_filtered_views(self, client):
+        c1, c2 = ChallengeFactory.create_batch(2, hidden=False)
 
+        u = UserFactory()
 
-@pytest.mark.django_db
-def test_submission_create(client, two_challenge_sets):
-    validate_admin_or_participant_view(
-        viewname="evaluation:submission-create",
-        two_challenge_set=two_challenge_sets,
-        client=client,
-        reverse_kwargs={
-            "challenge_short_name": two_challenge_sets.challenge_set_1.challenge.short_name,
-            "slug": two_challenge_sets.challenge_set_1.challenge.phase_set.get().slug,
-        },
-    )
-
-    response = get_view_for_user(
-        viewname="evaluation:submission-create",
-        user=two_challenge_sets.challenge_set_1.participant,
-        client=client,
-        reverse_kwargs={
-            "challenge_short_name": two_challenge_sets.challenge_set_1.challenge.short_name,
-            "slug": two_challenge_sets.challenge_set_1.challenge.phase_set.get().slug,
-        },
-    )
-
-    assert response.status_code == 200
-    assert "Creator" not in response.rendered_content
-
+        e1 = EvaluationFactory(
+            method__phase__challenge=c1,
+            submission__phase__challenge=c1,
+            submission__creator=u,
+        )
+        e2 = EvaluationFactory(
+            method__phase__challenge=c2,
+            submission__phase__challenge=c2,
+            submission__creator=u,
+        )
 
-@pytest.mark.django_db
-def test_legacy_submission_create(client, two_challenge_sets):
-    validate_admin_only_view(
-        viewname="evaluation:submission-create-legacy",
-        two_challenge_set=two_challenge_sets,
-        client=client,
-        reverse_kwargs={
-            "challenge_short_name": two_challenge_sets.challenge_set_1.challenge.short_name,
-            "slug": two_challenge_sets.challenge_set_1.challenge.phase_set.get().slug,
-        },
-    )
+        assign_perm("view_method", u, e1.method)
+        assign_perm("view_method", u, e2.method)
+
+        for view_name, obj in [
+            ("method-list", e1.method),
+            ("submission-list", e1.submission),
+            ("list", e1),
+        ]:
+
+            response = get_view_for_user(
+                client=client,
+                viewname=f"evaluation:{view_name}",
+                reverse_kwargs={
+                    "challenge_short_name": e1.submission.phase.challenge.short_name,
+                },
+                user=u,
+            )
+
+            assert response.status_code == 200
+            assert {obj.pk} == {
+                o.pk for o in response.context[-1]["object_list"]
+            }
+
+    def test_phase_filtered_views(self, client):
+        c = ChallengeFactory(hidden=False)
+
+        p1, p2 = PhaseFactory.create_batch(2, challenge=c)
+
+        e1 = EvaluationFactory(
+            method__phase=p1,
+            submission__phase=p1,
+            rank=1,
+            status=Evaluation.SUCCESS,
+        )
+        _ = EvaluationFactory(
+            method__phase=p2,
+            submission__phase=p2,
+            rank=1,
+            status=Evaluation.SUCCESS,
+        )
 
-    response = get_view_for_user(
-        viewname="evaluation:submission-create-legacy",
-        user=two_challenge_sets.admin12,
-        client=client,
-        reverse_kwargs={
-            "challenge_short_name": two_challenge_sets.challenge_set_1.challenge.short_name,
-            "slug": two_challenge_sets.challenge_set_1.challenge.phase_set.get().slug,
-        },
-    )
+        response = get_view_for_user(
+            client=client,
+            viewname="evaluation:leaderboard",
+            reverse_kwargs={
+                "challenge_short_name": e1.submission.phase.challenge.short_name,
+                "slug": e1.submission.phase.slug,
+            },
+        )
 
-    assert response.status_code == 200
-    assert "Creator" in response.rendered_content
+        assert response.status_code == 200
+        assert {e1.pk} == {o.pk for o in response.context[-1]["object_list"]}
 
 
 @pytest.mark.django_db
@@ -251,36 +299,32 @@ def get_submission_view():
     assert "make 8 more" in get_submission_view().rendered_content
 
 
-@pytest.mark.django_db
-def test_submission_detail(client, two_challenge_sets):
-    submission = SubmissionFactory(
-        phase__challenge=two_challenge_sets.challenge_set_1.challenge,
-        creator=two_challenge_sets.challenge_set_1.participant,
-    )
-    validate_admin_only_view(
-        viewname="evaluation:submission-detail",
-        two_challenge_set=two_challenge_sets,
-        reverse_kwargs={"pk": submission.pk},
-        client=client,
-    )
-
-
 @pytest.mark.django_db
 @factory.django.mute_signals(signals.post_save)
 def test_evaluation_list(client, two_challenge_sets):
-    validate_admin_or_participant_view(
-        viewname="evaluation:list",
-        two_challenge_set=two_challenge_sets,
-        client=client,
+    # participant 0, submission 1, challenge 1, etc
+    e_p_s1 = EvaluationFactory(
+        submission__phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
+        submission__creator=two_challenge_sets.challenge_set_1.participant,
+    )
+    e_p_s2 = EvaluationFactory(
+        submission__phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
+        submission__creator=two_challenge_sets.challenge_set_1.participant,
+    )
+    e_p1_s1 = EvaluationFactory(
+        submission__phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
+        submission__creator=two_challenge_sets.challenge_set_1.participant1,
     )
-    (
-        *_,
-        e_p_s1,
-        e_p_s2,
-        e_p1_s1,
-        e_p12_s1_c1,
-        e_p12_s1_c2,
-    ) = submissions_and_evaluations(two_challenge_sets)
+    # participant12, submission 1 to each challenge
+    e_p12_s1_c1 = EvaluationFactory(
+        submission__phase=two_challenge_sets.challenge_set_1.challenge.phase_set.get(),
+        submission__creator=two_challenge_sets.participant12,
+    )
+    e_p12_s1_c2 = EvaluationFactory(
+        submission__phase=two_challenge_sets.challenge_set_2.challenge.phase_set.get(),
+        submission__creator=two_challenge_sets.participant12,
+    )
+
     # Participants should only be able to see their own evaluations
     response = get_view_for_user(
         viewname="evaluation:list",
@@ -293,6 +337,7 @@ def test_evaluation_list(client, two_challenge_sets):
     assert str(e_p1_s1.pk) not in response.rendered_content
     assert str(e_p12_s1_c1.pk) not in response.rendered_content
     assert str(e_p12_s1_c2.pk) not in response.rendered_content
+
     # Admins should be able to see all evaluations
     response = get_view_for_user(
         viewname="evaluation:list",
@@ -305,6 +350,7 @@ def test_evaluation_list(client, two_challenge_sets):
     assert str(e_p1_s1.pk) in response.rendered_content
     assert str(e_p12_s1_c1.pk) in response.rendered_content
     assert str(e_p12_s1_c2.pk) not in response.rendered_content
+
     # Only evaluations relevant to this challenge should be listed
     response = get_view_for_user(
         viewname="evaluation:list",
@@ -317,32 +363,3 @@ def test_evaluation_list(client, two_challenge_sets):
     assert str(e_p_s1.pk) not in response.rendered_content
     assert str(e_p_s2.pk) not in response.rendered_content
     assert str(e_p1_s1.pk) not in response.rendered_content
-
-
-@pytest.mark.django_db
-def test_leaderboard(client, eval_challenge_set):
-    validate_open_view(
-        viewname="evaluation:leaderboard",
-        challenge_set=eval_challenge_set.challenge_set,
-        client=client,
-        reverse_kwargs={
-            "slug": eval_challenge_set.challenge_set.challenge.phase_set.get().slug,
-            "challenge_short_name": eval_challenge_set.challenge_set.challenge.short_name,
-        },
-    )
-
-
-# TODO: test that private results cannot be seen
-@pytest.mark.django_db
-def test_evaluation_detail(client, eval_challenge_set):
-    submission = SubmissionFactory(
-        phase=eval_challenge_set.challenge_set.challenge.phase_set.get(),
-        creator=eval_challenge_set.challenge_set.participant,
-    )
-    e = EvaluationFactory(submission=submission)
-    validate_open_view(
-        viewname="evaluation:detail",
-        challenge_set=eval_challenge_set.challenge_set,
-        reverse_kwargs={"pk": e.pk},
-        client=client,
-    )