From 3409571a0d92a56fdf1f978448ff1aedb8857b05 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Tue, 19 Oct 2021 15:20:52 -0500 Subject: [PATCH 1/4] Organizations: Test access from anonymous users and users outside the organization Currently I'm checking the global option of `ALLOW_PRIVATE_REPOS` to give access to public views to users outside the org. We could introduce a privacy level for the organization if we want. --- readthedocs/organizations/managers.py | 18 +- readthedocs/organizations/querysets.py | 19 +- .../templatetags/organizations.py | 2 + .../organizations/tests/test_privacy_urls.py | 184 +++++++++++++++++- readthedocs/organizations/urls/private.py | 5 + readthedocs/organizations/urls/public.py | 5 - readthedocs/organizations/views/base.py | 10 +- readthedocs/organizations/views/private.py | 9 +- readthedocs/organizations/views/public.py | 6 - 9 files changed, 240 insertions(+), 18 deletions(-) diff --git a/readthedocs/organizations/managers.py b/readthedocs/organizations/managers.py index 9a654739cc8..5e0cabf55f5 100644 --- a/readthedocs/organizations/managers.py +++ b/readthedocs/organizations/managers.py @@ -1,5 +1,5 @@ """Organizations managers.""" - +from django.conf import settings from django.db import models from readthedocs.core.utils.extend import SettingsOverrideObject @@ -12,7 +12,12 @@ class TeamManagerBase(models.Manager): """Manager to control team's access.""" def teams_for_user(self, user, organization, admin, member): + """Get the teams where the user is an admin or member.""" teams = self.get_queryset().none() + + if not user.is_authenticated: + return teams + if admin: # Project Team Admin teams |= user.teams.filter(access=ADMIN_ACCESS) @@ -30,6 +35,17 @@ def teams_for_user(self, user, organization, admin, member): return teams.distinct() + def public(self, user): + """ + Return all teams the user has access to. + + If ``ALLOW_PRIVATE_REPOS`` is `False`, all teams are public by default. + Otherwise, we return only the teams where the user is a member. + """ + if not settings.ALLOW_PRIVATE_REPOS: + return self.get_queryset().all() + return self.member(user) + def admin(self, user, organization=None): return self.teams_for_user( user, diff --git a/readthedocs/organizations/querysets.py b/readthedocs/organizations/querysets.py index ddd2b692888..a7137a944b7 100644 --- a/readthedocs/organizations/querysets.py +++ b/readthedocs/organizations/querysets.py @@ -2,6 +2,7 @@ from datetime import timedelta +from django.conf import settings from django.db import models from django.db.models import Q from django.utils import timezone @@ -13,13 +14,29 @@ class BaseOrganizationQuerySet(models.QuerySet): """Organizations queryset.""" + def public(self, user): + """ + Return all organizations the user has access to. + + If ``ALLOW_PRIVATE_REPOS`` is `False`, all organizations are public by default. + Otherwise, we return only the organizations where the user is a member or owner. + """ + if not settings.ALLOW_PRIVATE_REPOS: + return self.all() + return self.for_user(user) + def for_user(self, user): - # Never list all for membership + """List all organizations where the user is a member or owner.""" + if not user.is_authenticated: + return self.none() return self.filter( Q(owners__in=[user]) | Q(teams__members__in=[user]), ).distinct() def for_admin_user(self, user): + """List all organizations where the user is an owner.""" + if not user.is_authenticated: + return self.none() return self.filter(owners__in=[user],).distinct() def created_days_ago(self, days, field='pub_date'): diff --git a/readthedocs/organizations/templatetags/organizations.py b/readthedocs/organizations/templatetags/organizations.py index 1e3d0822f33..1e4b55544ce 100644 --- a/readthedocs/organizations/templatetags/organizations.py +++ b/readthedocs/organizations/templatetags/organizations.py @@ -38,6 +38,8 @@ def org_owner(user, obj): # noqa Any model instance with a relationship with an organization, or an organization itself. """ + if not user.is_authenticated: + return False try: cls = type(obj) if cls is Organization: diff --git a/readthedocs/organizations/tests/test_privacy_urls.py b/readthedocs/organizations/tests/test_privacy_urls.py index e9109f33bf2..f0d06caec51 100644 --- a/readthedocs/organizations/tests/test_privacy_urls.py +++ b/readthedocs/organizations/tests/test_privacy_urls.py @@ -27,6 +27,8 @@ def setUp(self): } ) + self.another_user = get(User) + def get_url_path_ctx(self): return self.default_kwargs @@ -35,7 +37,7 @@ def get_url_path_ctx(self): class NoOrganizationsTest(OrganizationMixin, TestCase): """Organization views aren't available if organizations aren't allowed.""" - + default_status_code = 404 def login(self): @@ -74,3 +76,183 @@ def test_public_urls(self): def test_private_urls(self): from readthedocs.organizations.urls.private import urlpatterns self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=False, +) +class AnonymousUserWithPublicOrganizationsTest(OrganizationMixin, TestCase): + + """If organizations are public, an anonymous user can access the public views.""" + + response_data = { + # Places where we 302 on success. + '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + } + + def login(self): + pass + + def test_public_urls(self): + from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=True, +) +class AnonymousUserWithPrivateOrganizationsTest(OrganizationMixin, TestCase): + + """If organizations are private, an anonymous user can't access the public views.""" + + default_status_code = 404 + response_data = { + # Places where we 302 on success. + '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + } + + def login(self): + pass + + def test_public_urls(self): + from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=False, +) +class AnonymousUserWithPublicOrganizationsPrivateViewsTest(OrganizationMixin, TestCase): + + """If organizations are public, an anonymous user can't access the private views.""" + + # We get redirected to the login page. + default_status_code = 302 + + def login(self): + pass + + def test_private_urls(self): + from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=True, +) +class AnonymousUserWithPrivateOrganizationsPrivateViewsTest(OrganizationMixin, TestCase): + + """If organizations are private, an anonymous user can't access the private views.""" + + # We get redirected to the login page. + default_status_code = 302 + + def login(self): + pass + + def test_private_urls(self): + from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=False, +) +class AnotherUserWithPublicOrganizationsTest(OrganizationMixin, TestCase): + + """If organizations are public, an user outside the organization can access the public views.""" + + response_data = { + # Places where we 302 on success. + '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + } + + def login(self): + self.client.force_login(self.another_user) + + def test_public_urls(self): + from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=True, +) +class AnotherUserWithPrivateOrganizationsTest(OrganizationMixin, TestCase): + + """If organizations are private, an user outside the organization can't access the public views.""" + + default_status_code = 404 + response_data = { + # Places where we 302 on success. + '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + } + + def login(self): + self.client.force_login(self.another_user) + + def test_public_urls(self): + from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=False, +) +class AnotherUserWithPublicOrganizationsPrivateViewsTest(OrganizationMixin, TestCase): + + """If organizations are public, an user outside the organization can access the public views.""" + + default_status_code = 404 + response_data = { + # All users have access to these views. + '/organizations/': {'status_code': 200}, + '/organizations/create/': {'status_code': 200}, + '/organizations/verify-email/': {'status_code': 200}, + + # 405's where we should be POST'ing + '/organizations/{slug}/owners/{owner}/delete/': {'status_code': 405}, + '/organizations/{slug}/teams/{team}/members/{member}/revoke/': {'status_code': 405}, + } + + def login(self): + self.client.force_login(self.another_user) + + def test_private_urls(self): + from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) + + +@override_settings( + RTD_ALLOW_ORGANIZATIONS=True, + ALLOW_PRIVATE_REPOS=True, +) +class AnotherUserWithPrivateOrganizationsPrivateViewsTest(OrganizationMixin, TestCase): + + """If organizations are private, an user outside the organization can't access the private views.""" + + default_status_code = 404 + response_data = { + # All users have access to these views. + '/organizations/': {'status_code': 200}, + '/organizations/create/': {'status_code': 200}, + '/organizations/verify-email/': {'status_code': 200}, + + # 405's where we should be POST'ing + '/organizations/{slug}/owners/{owner}/delete/': {'status_code': 405}, + '/organizations/{slug}/teams/{team}/members/{member}/revoke/': {'status_code': 405}, + } + + def login(self): + self.client.force_login(self.another_user) + + def test_private_urls(self): + from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) diff --git a/readthedocs/organizations/urls/private.py b/readthedocs/organizations/urls/private.py index b3b77327c7e..cd29c91f1a6 100644 --- a/readthedocs/organizations/urls/private.py +++ b/readthedocs/organizations/urls/private.py @@ -14,6 +14,11 @@ views.CreateOrganizationSignup.as_view(), name='organization_create', ), + url( + r'^verify-email/$', + views.OrganizationTemplateView.as_view(template_name='organizations/verify_email.html'), + name='organization_verify_email', + ), url( r'^(?P[\w.-]+)/edit/$', views.EditOrganization.as_view(), diff --git a/readthedocs/organizations/urls/public.py b/readthedocs/organizations/urls/public.py index 8ee0e17f2f1..bba55f85ab0 100644 --- a/readthedocs/organizations/urls/public.py +++ b/readthedocs/organizations/urls/public.py @@ -4,11 +4,6 @@ from readthedocs.organizations.views import public as views urlpatterns = [ - url( - r'^verify-email/$', - views.OrganizationTemplateView.as_view(template_name='organizations/verify_email.html'), - name='organization_verify_email', - ), url( r'^(?P[\w.-]+)/$', views.DetailOrganization.as_view(), diff --git a/readthedocs/organizations/views/base.py b/readthedocs/organizations/views/base.py index 0595d9043ba..20656421604 100644 --- a/readthedocs/organizations/views/base.py +++ b/readthedocs/organizations/views/base.py @@ -72,7 +72,7 @@ def get_organization_queryset(self): """ if self.admin_only: return Organization.objects.for_admin_user(user=self.request.user) - return Organization.objects.for_user(user=self.request.user) + return Organization.objects.public(user=self.request.user) @lru_cache(maxsize=1) def get_organization(self): @@ -109,7 +109,11 @@ def get_team_queryset(self): This will either be team the user is a member of, or teams where the user is an owner of the organization. """ - return Team.objects.member(self.request.user).filter( + if self.admin_only: + queryset = Team.objects.member(self.request.user) + else: + queryset = Team.objects.public(self.request.user) + return queryset.filter( organization=self.get_organization(), ).order_by('name') @@ -141,7 +145,7 @@ class OrganizationView(CheckOrganizationsEnabled): def get_queryset(self): if self.admin_only: return Organization.objects.for_admin_user(user=self.request.user) - return Organization.objects.for_user(user=self.request.user) + return Organization.objects.public(user=self.request.user) def get_form(self, data=None, files=None, **kwargs): kwargs['user'] = self.request.user diff --git a/readthedocs/organizations/views/private.py b/readthedocs/organizations/views/private.py index 2e1a712ae03..a179aa548f9 100644 --- a/readthedocs/organizations/views/private.py +++ b/readthedocs/organizations/views/private.py @@ -3,6 +3,7 @@ from django.contrib import messages from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from django.views.generic.base import TemplateView from vanilla import CreateView, DeleteView, ListView, UpdateView from readthedocs.core.history import UpdateChangeReasonPostView @@ -13,6 +14,7 @@ ) from readthedocs.organizations.models import Organization from readthedocs.organizations.views.base import ( + CheckOrganizationsEnabled, OrganizationOwnerView, OrganizationTeamMemberView, OrganizationTeamView, @@ -20,6 +22,11 @@ ) +class OrganizationTemplateView(PrivateViewMixin, CheckOrganizationsEnabled, TemplateView): + + """Wrapper around `TemplateView` to check if organizations are enabled.""" + + # Organization views class CreateOrganizationSignup(PrivateViewMixin, OrganizationView, CreateView): @@ -52,9 +59,9 @@ def get_success_url(self): class ListOrganization(PrivateViewMixin, OrganizationView, ListView): template_name = 'organizations/organization_list.html' - admin_only = False def get_queryset(self): + """Overriden so we always list the organizations of the current user.""" return Organization.objects.for_user(user=self.request.user) diff --git a/readthedocs/organizations/views/public.py b/readthedocs/organizations/views/public.py index fd427a7f0a5..557b8adf4a6 100644 --- a/readthedocs/organizations/views/public.py +++ b/readthedocs/organizations/views/public.py @@ -5,7 +5,6 @@ from django.db.models import F from django.http import HttpResponseRedirect from django.urls import reverse, reverse_lazy -from django.views.generic.base import TemplateView from vanilla import DetailView, GenericModelView, ListView from readthedocs.core.permissions import AdminPermission @@ -22,11 +21,6 @@ log = logging.getLogger(__name__) -class OrganizationTemplateView(CheckOrganizationsEnabled, TemplateView): - - """Wrapper around `TemplateView` to check if organizations are enabled.""" - - # Organization class DetailOrganization(OrganizationView, DetailView): From 1847e48b21ac5cdc80c1ac249f84335b885fd4e5 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Mon, 23 May 2022 20:14:26 -0500 Subject: [PATCH 2/4] Fix tests --- readthedocs/organizations/tests/test_access.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/readthedocs/organizations/tests/test_access.py b/readthedocs/organizations/tests/test_access.py index 41ae4f5f715..b3348eb1b85 100644 --- a/readthedocs/organizations/tests/test_access.py +++ b/readthedocs/organizations/tests/test_access.py @@ -34,7 +34,7 @@ def assertResponse(self, path, method=None, data=None, **kwargs): response_attrs.update(kwargs) response_attrs.update(self.url_responses.get(path, {})) for (key, val) in list(response_attrs.items()): - self.assertEqual(getattr(response, key), val) + self.assertEqual(getattr(response, key), val, path) return response def setUp(self): @@ -188,7 +188,7 @@ def test_organization_teams(self): self.assertEqual(self.organization.teams.count(), 1) -@override_settings(RTD_ALLOW_ORGANIZATIONS=True) +@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=False) class OrganizationOwnerAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed org owner.""" @@ -200,7 +200,7 @@ def is_admin(self): return True -@override_settings(RTD_ALLOW_ORGANIZATIONS=True) +@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=False) class OrganizationMemberAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed org member.""" @@ -226,13 +226,16 @@ def is_admin(self): return False -@override_settings(RTD_ALLOW_ORGANIZATIONS=True) +@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=False) class OrganizationNonmemberAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed but non-org user.""" url_responses = { '/organizations/': {'status_code': 200}, + '/organizations/mozilla/': {'status_code': 200}, + '/organizations/mozilla/members/': {'status_code': 200}, + '/organizations/mozilla/teams/': {'status_code': 200}, } def assertResponse(self, path, method=None, data=None, **kwargs): From d60f7bb4afc861fd417943eee161e822ed76322e Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Mon, 23 May 2022 20:15:20 -0500 Subject: [PATCH 3/4] Black --- .../organizations/tests/test_access.py | 14 ++---- .../organizations/tests/test_privacy_urls.py | 46 ++++++++++++------- readthedocs/organizations/urls/private.py | 8 ++-- readthedocs/organizations/views/private.py | 6 ++- readthedocs/organizations/views/public.py | 1 - 5 files changed, 43 insertions(+), 32 deletions(-) diff --git a/readthedocs/organizations/tests/test_access.py b/readthedocs/organizations/tests/test_access.py index b3348eb1b85..6a965eee77e 100644 --- a/readthedocs/organizations/tests/test_access.py +++ b/readthedocs/organizations/tests/test_access.py @@ -2,11 +2,7 @@ from django.contrib.auth.models import User from django.test import TestCase, override_settings -from readthedocs.organizations.models import ( - Organization, - OrganizationOwner, - Team, -) +from readthedocs.organizations.models import Organization, OrganizationOwner, Team from readthedocs.projects.models import Project from readthedocs.rtd_tests.utils import create_user @@ -232,10 +228,10 @@ class OrganizationNonmemberAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed but non-org user.""" url_responses = { - '/organizations/': {'status_code': 200}, - '/organizations/mozilla/': {'status_code': 200}, - '/organizations/mozilla/members/': {'status_code': 200}, - '/organizations/mozilla/teams/': {'status_code': 200}, + "/organizations/": {"status_code": 200}, + "/organizations/mozilla/": {"status_code": 200}, + "/organizations/mozilla/members/": {"status_code": 200}, + "/organizations/mozilla/teams/": {"status_code": 200}, } def assertResponse(self, path, method=None, data=None, **kwargs): diff --git a/readthedocs/organizations/tests/test_privacy_urls.py b/readthedocs/organizations/tests/test_privacy_urls.py index f0d06caec51..c380338d4ed 100644 --- a/readthedocs/organizations/tests/test_privacy_urls.py +++ b/readthedocs/organizations/tests/test_privacy_urls.py @@ -88,7 +88,7 @@ class AnonymousUserWithPublicOrganizationsTest(OrganizationMixin, TestCase): response_data = { # Places where we 302 on success. - '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + "/organizations/invite/{hash}/redeem/": {"status_code": 302}, } def login(self): @@ -96,6 +96,7 @@ def login(self): def test_public_urls(self): from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) @@ -110,7 +111,7 @@ class AnonymousUserWithPrivateOrganizationsTest(OrganizationMixin, TestCase): default_status_code = 404 response_data = { # Places where we 302 on success. - '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + "/organizations/invite/{hash}/redeem/": {"status_code": 302}, } def login(self): @@ -118,6 +119,7 @@ def login(self): def test_public_urls(self): from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) @@ -137,6 +139,7 @@ def login(self): def test_private_urls(self): from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) @@ -144,7 +147,9 @@ def test_private_urls(self): RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=True, ) -class AnonymousUserWithPrivateOrganizationsPrivateViewsTest(OrganizationMixin, TestCase): +class AnonymousUserWithPrivateOrganizationsPrivateViewsTest( + OrganizationMixin, TestCase +): """If organizations are private, an anonymous user can't access the private views.""" @@ -156,6 +161,7 @@ def login(self): def test_private_urls(self): from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) @@ -169,7 +175,7 @@ class AnotherUserWithPublicOrganizationsTest(OrganizationMixin, TestCase): response_data = { # Places where we 302 on success. - '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + "/organizations/invite/{hash}/redeem/": {"status_code": 302}, } def login(self): @@ -177,6 +183,7 @@ def login(self): def test_public_urls(self): from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) @@ -191,7 +198,7 @@ class AnotherUserWithPrivateOrganizationsTest(OrganizationMixin, TestCase): default_status_code = 404 response_data = { # Places where we 302 on success. - '/organizations/invite/{hash}/redeem/': {'status_code': 302}, + "/organizations/invite/{hash}/redeem/": {"status_code": 302}, } def login(self): @@ -199,6 +206,7 @@ def login(self): def test_public_urls(self): from readthedocs.organizations.urls.public import urlpatterns + self._test_url(urlpatterns) @@ -213,13 +221,14 @@ class AnotherUserWithPublicOrganizationsPrivateViewsTest(OrganizationMixin, Test default_status_code = 404 response_data = { # All users have access to these views. - '/organizations/': {'status_code': 200}, - '/organizations/create/': {'status_code': 200}, - '/organizations/verify-email/': {'status_code': 200}, - + "/organizations/": {"status_code": 200}, + "/organizations/create/": {"status_code": 200}, + "/organizations/verify-email/": {"status_code": 200}, # 405's where we should be POST'ing - '/organizations/{slug}/owners/{owner}/delete/': {'status_code': 405}, - '/organizations/{slug}/teams/{team}/members/{member}/revoke/': {'status_code': 405}, + "/organizations/{slug}/owners/{owner}/delete/": {"status_code": 405}, + "/organizations/{slug}/teams/{team}/members/{member}/revoke/": { + "status_code": 405 + }, } def login(self): @@ -227,6 +236,7 @@ def login(self): def test_private_urls(self): from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) @@ -241,13 +251,14 @@ class AnotherUserWithPrivateOrganizationsPrivateViewsTest(OrganizationMixin, Tes default_status_code = 404 response_data = { # All users have access to these views. - '/organizations/': {'status_code': 200}, - '/organizations/create/': {'status_code': 200}, - '/organizations/verify-email/': {'status_code': 200}, - + "/organizations/": {"status_code": 200}, + "/organizations/create/": {"status_code": 200}, + "/organizations/verify-email/": {"status_code": 200}, # 405's where we should be POST'ing - '/organizations/{slug}/owners/{owner}/delete/': {'status_code': 405}, - '/organizations/{slug}/teams/{team}/members/{member}/revoke/': {'status_code': 405}, + "/organizations/{slug}/owners/{owner}/delete/": {"status_code": 405}, + "/organizations/{slug}/teams/{team}/members/{member}/revoke/": { + "status_code": 405 + }, } def login(self): @@ -255,4 +266,5 @@ def login(self): def test_private_urls(self): from readthedocs.organizations.urls.private import urlpatterns + self._test_url(urlpatterns) diff --git a/readthedocs/organizations/urls/private.py b/readthedocs/organizations/urls/private.py index bd02dc89d50..c55c5917bf5 100644 --- a/readthedocs/organizations/urls/private.py +++ b/readthedocs/organizations/urls/private.py @@ -15,9 +15,11 @@ name='organization_create', ), re_path( - r'^verify-email/$', - views.OrganizationTemplateView.as_view(template_name='organizations/verify_email.html'), - name='organization_verify_email', + r"^verify-email/$", + views.OrganizationTemplateView.as_view( + template_name="organizations/verify_email.html" + ), + name="organization_verify_email", ), re_path( r'^(?P[\w.-]+)/edit/$', diff --git a/readthedocs/organizations/views/private.py b/readthedocs/organizations/views/private.py index bacfae9ca73..d13b8d4a25d 100644 --- a/readthedocs/organizations/views/private.py +++ b/readthedocs/organizations/views/private.py @@ -4,9 +4,9 @@ from django.conf import settings from django.contrib import messages from django.urls import reverse_lazy -from django.views.generic.base import TemplateView from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from django.views.generic.base import TemplateView from vanilla import CreateView, DeleteView, ListView, UpdateView from readthedocs.audit.filters import OrganizationSecurityLogFilter @@ -30,7 +30,9 @@ from readthedocs.projects.utils import get_csv_file -class OrganizationTemplateView(PrivateViewMixin, CheckOrganizationsEnabled, TemplateView): +class OrganizationTemplateView( + PrivateViewMixin, CheckOrganizationsEnabled, TemplateView +): """Wrapper around `TemplateView` to check if organizations are enabled.""" diff --git a/readthedocs/organizations/views/public.py b/readthedocs/organizations/views/public.py index 03c649a256b..25ace5d5ae3 100644 --- a/readthedocs/organizations/views/public.py +++ b/readthedocs/organizations/views/public.py @@ -1,7 +1,6 @@ """Views that don't require login.""" # pylint: disable=too-many-ancestors import structlog - from django.db.models import F from django.http import HttpResponseRedirect from django.urls import reverse, reverse_lazy From 018711690169ce03fa17232328bb3529890e4614 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Tue, 24 May 2022 13:34:52 -0500 Subject: [PATCH 4/4] Fix tests --- readthedocs/organizations/tests/test_access.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/readthedocs/organizations/tests/test_access.py b/readthedocs/organizations/tests/test_access.py index 6a965eee77e..04ed971ef4b 100644 --- a/readthedocs/organizations/tests/test_access.py +++ b/readthedocs/organizations/tests/test_access.py @@ -184,7 +184,7 @@ def test_organization_teams(self): self.assertEqual(self.organization.teams.count(), 1) -@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=False) +@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=True) class OrganizationOwnerAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed org owner.""" @@ -196,7 +196,7 @@ def is_admin(self): return True -@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=False) +@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=True) class OrganizationMemberAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed org member.""" @@ -222,16 +222,13 @@ def is_admin(self): return False -@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=False) +@override_settings(RTD_ALLOW_ORGANIZATIONS=True, ALLOW_PRIVATE_REPOS=True) class OrganizationNonmemberAccess(OrganizationAccessMixin, TestCase): """Test organization paths with authed but non-org user.""" url_responses = { "/organizations/": {"status_code": 200}, - "/organizations/mozilla/": {"status_code": 200}, - "/organizations/mozilla/members/": {"status_code": 200}, - "/organizations/mozilla/teams/": {"status_code": 200}, } def assertResponse(self, path, method=None, data=None, **kwargs):