diff --git a/readthedocs/oauth/admin.py b/readthedocs/oauth/admin.py index cf59aeaefb7..9d27cb1a957 100644 --- a/readthedocs/oauth/admin.py +++ b/readthedocs/oauth/admin.py @@ -1,6 +1,8 @@ """Admin configuration for the OAuth app.""" +from django import urls from django.contrib import admin +from django.utils.html import format_html from .models import GitHubAppInstallation from .models import RemoteOrganization @@ -25,6 +27,7 @@ class RemoteRepositoryAdmin(admin.ModelAdmin): readonly_fields = ( "created", "modified", + "remote_repository_relations", ) raw_id_fields = ("organization", "github_app_installation") list_select_related = ("organization",) @@ -37,6 +40,8 @@ class RemoteRepositoryAdmin(admin.ModelAdmin): "name", "full_name", "remote_id", + "projects__slug", + "projects__name", ) list_display = ( "id", @@ -48,6 +53,29 @@ class RemoteRepositoryAdmin(admin.ModelAdmin): "get_vcs_display", ) + @admin.display(description="Remote repository relations") + def remote_repository_relations(self, obj): + """Link to relation objects filtered to this remote repository.""" + if not obj.pk: + return "-" + + url = urls.reverse( + "admin:{}_{}_changelist".format( + RemoteRepositoryRelation._meta.app_label, + RemoteRepositoryRelation._meta.model_name, + ), + ) + relation_count = obj.remote_repository_relations.count() + relation_label = "relation" if relation_count == 1 else "relations" + return format_html( + '{} remote repository {}', + url, + "remote_repository__id__exact", + obj.pk, + relation_count, + relation_label, + ) + @admin.register(RemoteOrganization) class RemoteOrganizationAdmin(admin.ModelAdmin): @@ -100,6 +128,13 @@ class RemoteRepositoryRelationAdmin(admin.ModelAdmin): "remote_repository__vcs_provider", "admin", ) + search_fields = ( + "remote_repository__name", + "remote_repository__full_name", + "remote_repository__remote_id", + "remote_repository__projects__slug", + "remote_repository__projects__name", + ) def vcs_provider(self, obj): """Get the display name for the VCS provider.""" diff --git a/readthedocs/oauth/tests/test_admin.py b/readthedocs/oauth/tests/test_admin.py new file mode 100644 index 00000000000..19d9a37c087 --- /dev/null +++ b/readthedocs/oauth/tests/test_admin.py @@ -0,0 +1,116 @@ +import django_dynamic_fixture as fixture +from allauth.socialaccount.models import SocialAccount +from django import urls +from django.contrib.auth.models import User +from django.test import TestCase + +from readthedocs.oauth.models import RemoteRepository +from readthedocs.oauth.models import RemoteRepositoryRelation +from readthedocs.projects.models import Project + + +class OAuthAdminTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.admin = fixture.get(User, is_staff=True, is_superuser=True) + cls.user = fixture.get(User, username="owner") + cls.account = fixture.get( + SocialAccount, + user=cls.user, + provider="github", + uid="owner-account", + ) + cls.remote_repository = fixture.get( + RemoteRepository, + name="zephyrdocs", + full_name="organization/zephyrdocs", + remote_id="100", + html_url=None, + ) + cls.other_remote_repository = fixture.get( + RemoteRepository, + name="telescope", + full_name="organization/telescope", + remote_id="101", + html_url=None, + ) + cls.project = fixture.get( + Project, + slug="manualhub", + name="Manualhub", + remote_repository=cls.remote_repository, + main_language_project=None, + ) + cls.other_project = fixture.get( + Project, + slug="telescopeapi", + name="Telescopeapi", + remote_repository=cls.other_remote_repository, + main_language_project=None, + ) + cls.remote_repository_relation = fixture.get( + RemoteRepositoryRelation, + remote_repository=cls.remote_repository, + user=cls.user, + account=cls.account, + admin=True, + ) + cls.other_user = fixture.get(User, username="other-owner") + cls.other_account = fixture.get( + SocialAccount, + user=cls.other_user, + provider="github", + uid="other-account", + ) + cls.other_remote_repository_relation = fixture.get( + RemoteRepositoryRelation, + remote_repository=cls.other_remote_repository, + user=cls.other_user, + account=cls.other_account, + admin=False, + ) + + def setUp(self): + self.client.force_login(self.admin) + + def test_remote_repository_admin_searches_by_project_slug(self): + response = self.client.get( + urls.reverse("admin:oauth_remoterepository_changelist"), + {"q": self.project.slug}, + ) + + self.assertContains(response, self.remote_repository.full_name) + self.assertNotContains(response, self.other_remote_repository.full_name) + + def test_remote_repository_change_view_links_to_filtered_relations(self): + response = self.client.get( + urls.reverse( + "admin:oauth_remoterepository_change", + args=[self.remote_repository.pk], + ), + ) + + changelist_url = urls.reverse("admin:oauth_remoterepositoryrelation_changelist") + self.assertContains( + response, + f'{changelist_url}?remote_repository__id__exact={self.remote_repository.pk}', + ) + self.assertContains(response, "1 remote repository relation") + + def test_remote_repository_relation_admin_searches_by_project_name(self): + response = self.client.get( + urls.reverse("admin:oauth_remoterepositoryrelation_changelist"), + {"q": self.project.name}, + ) + + self.assertContains(response, self.remote_repository.full_name) + self.assertNotContains(response, self.other_remote_repository.full_name) + + def test_remote_repository_relation_admin_searches_by_remote_repository(self): + response = self.client.get( + urls.reverse("admin:oauth_remoterepositoryrelation_changelist"), + {"q": self.remote_repository.name}, + ) + + self.assertContains(response, self.remote_repository.full_name) + self.assertNotContains(response, self.other_remote_repository.full_name)