From e1a133af6be8b864a08fa9315f3b4042ed243ab7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 3 Oct 2024 19:00:49 +0000 Subject: [PATCH 1/2] Add base classes for org level views --- temba/campaigns/tests.py | 2 +- temba/campaigns/views.py | 13 ++-- temba/contacts/views.py | 3 +- temba/flows/views.py | 11 ++-- temba/msgs/tests.py | 4 +- temba/msgs/views.py | 9 +-- temba/orgs/mixins.py | 14 ----- temba/orgs/views.py | 106 +-------------------------------- temba/orgs/views_base.py | 125 +++++++++++++++++++++++++++++++++++++++ temba/tickets/views.py | 3 +- temba/triggers/views.py | 10 ++-- 11 files changed, 156 insertions(+), 144 deletions(-) create mode 100644 temba/orgs/views_base.py diff --git a/temba/campaigns/tests.py b/temba/campaigns/tests.py index 0f13426a3f7..a0c93f19d37 100644 --- a/temba/campaigns/tests.py +++ b/temba/campaigns/tests.py @@ -1229,7 +1229,7 @@ def test_archive_and_activate(self): # can't archive campaign from other org response = self.client.post(reverse("campaigns.campaign_archive", args=[other_org_campaign.id])) - self.assertEqual(404, response.status_code) + self.assertEqual(302, response.status_code) # check object is unchanged other_org_campaign.refresh_from_db() diff --git a/temba/campaigns/views.py b/temba/campaigns/views.py index d4d049d59bc..3003cc1f872 100644 --- a/temba/campaigns/views.py +++ b/temba/campaigns/views.py @@ -1,4 +1,4 @@ -from smartmin.views import SmartCreateView, SmartCRUDL, SmartDeleteView, SmartListView, SmartReadView, SmartUpdateView +from smartmin.views import SmartCreateView, SmartCRUDL, SmartDeleteView, SmartReadView, SmartUpdateView from django import forms from django.contrib import messages @@ -11,8 +11,9 @@ from temba.contacts.models import ContactField, ContactGroup from temba.flows.models import Flow from temba.msgs.models import Msg -from temba.orgs.mixins import OrgFilterMixin, OrgObjPermsMixin, OrgPermsMixin -from temba.orgs.views import BaseMenuView, ModalMixin +from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.views import ModalMixin +from temba.orgs.views_base import BaseListView, BaseMenuView from temba.utils import languages from temba.utils.fields import CompletionTextarea, InputWidget, SelectWidget, TembaChoiceField from temba.utils.views import BulkActionMixin, ContentMenuMixin, SpaMixin @@ -161,7 +162,7 @@ def get_form_kwargs(self): kwargs["org"] = self.request.org return kwargs - class BaseList(SpaMixin, ContentMenuMixin, OrgFilterMixin, OrgPermsMixin, BulkActionMixin, SmartListView): + class BaseList(SpaMixin, ContentMenuMixin, BulkActionMixin, BaseListView): fields = ("name", "group") default_template = "campaigns/campaign_list.html" default_order = ("-modified_on",) @@ -205,7 +206,7 @@ def get_queryset(self, *args, **kwargs): qs = qs.filter(is_active=True, is_archived=True) return qs - class Archive(OrgFilterMixin, OrgPermsMixin, SmartUpdateView): + class Archive(OrgObjPermsMixin, SmartUpdateView): fields = () success_url = "uuid@campaigns.campaign_read" success_message = _("Campaign archived") @@ -214,7 +215,7 @@ def save(self, obj): obj.apply_action_archive(self.request.user, Campaign.objects.filter(id=obj.id)) return obj - class Activate(OrgFilterMixin, OrgPermsMixin, SmartUpdateView): + class Activate(OrgObjPermsMixin, SmartUpdateView): fields = () success_url = "uuid@campaigns.campaign_read" success_message = _("Campaign activated") diff --git a/temba/contacts/views.py b/temba/contacts/views.py index c804a8b6221..201fd9c3091 100644 --- a/temba/contacts/views.py +++ b/temba/contacts/views.py @@ -27,7 +27,8 @@ from temba.notifications.views import NotificationTargetMixin from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.models import User -from temba.orgs.views import BaseExportView, BaseMenuView, DependencyDeleteModal, DependencyUsagesModal, ModalMixin +from temba.orgs.views import BaseExportView, DependencyDeleteModal, DependencyUsagesModal, ModalMixin +from temba.orgs.views_base import BaseMenuView from temba.tickets.models import Ticket, Topic from temba.utils import json, on_transaction_commit from temba.utils.dates import datetime_to_timestamp, timestamp_to_datetime diff --git a/temba/flows/views.py b/temba/flows/views.py index 442df215132..71b63488190 100644 --- a/temba/flows/views.py +++ b/temba/flows/views.py @@ -34,9 +34,10 @@ from temba.flows.models import Flow, FlowRevision, FlowRun, FlowSession, FlowStart from temba.flows.tasks import update_session_wait_expires from temba.ivr.models import Call -from temba.orgs.mixins import OrgFilterMixin, OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.models import IntegrationType, Org -from temba.orgs.views import BaseExportView, BaseMenuView, DependencyDeleteModal, ModalMixin +from temba.orgs.views import BaseExportView, DependencyDeleteModal, ModalMixin +from temba.orgs.views_base import BaseListView, BaseMenuView from temba.triggers.models import Trigger from temba.utils import analytics, gettext, json, languages, on_transaction_commit from temba.utils.fields import ( @@ -665,7 +666,7 @@ def update_triggers(self, flow, user, new_keywords: list): match_type=Trigger.MATCH_FIRST_WORD, ) - class BaseList(SpaMixin, OrgFilterMixin, OrgPermsMixin, BulkActionMixin, ContentMenuMixin, SmartListView): + class BaseList(SpaMixin, BulkActionMixin, ContentMenuMixin, BaseListView): permission = "flows.flow_list" title = _("Flows") refresh = 10000 @@ -1846,7 +1847,7 @@ class FlowStartCRUDL(SmartCRUDL): model = FlowStart actions = ("list", "interrupt", "status") - class List(SpaMixin, OrgFilterMixin, OrgPermsMixin, SmartListView): + class List(SpaMixin, BaseListView): title = _("Flow Starts") ordering = ("-created_on",) select_related = ("flow", "created_by") @@ -1877,7 +1878,7 @@ def get_context_data(self, *args, **kwargs): return context - class Status(OrgPermsMixin, OrgFilterMixin, SmartListView): + class Status(BaseListView): permission = "flows.flowstart_list" def derive_queryset(self, **kwargs): diff --git a/temba/msgs/tests.py b/temba/msgs/tests.py index bbdd17dd24b..6760a4e8178 100644 --- a/temba/msgs/tests.py +++ b/temba/msgs/tests.py @@ -2612,8 +2612,8 @@ def test_status(self): ) status_url = f"{reverse('msgs.broadcast_status')}?id={broadcast.id}&status=P" - self.assertRequestDisallowed(status_url, [None, self.user, self.agent]) - response = self.assertReadFetch(status_url, [self.editor, self.admin]) + self.assertRequestDisallowed(status_url, [None, self.agent]) + response = self.assertReadFetch(status_url, [self.user, self.editor, self.admin]) # status returns json self.assertEqual("Pending", response.json()["results"][0]["status"]) diff --git a/temba/msgs/views.py b/temba/msgs/views.py index f8e6dee7af2..79282d55015 100644 --- a/temba/msgs/views.py +++ b/temba/msgs/views.py @@ -20,9 +20,10 @@ from temba import mailroom from temba.archives.models import Archive from temba.mailroom.client.types import Exclusions -from temba.orgs.mixins import OrgFilterMixin, OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.models import Org -from temba.orgs.views import BaseExportView, BaseMenuView, DependencyDeleteModal, DependencyUsagesModal, ModalMixin +from temba.orgs.views import BaseExportView, DependencyDeleteModal, DependencyUsagesModal, ModalMixin +from temba.orgs.views_base import BaseListView, BaseMenuView from temba.schedules.views import ScheduleFormMixin from temba.templates.models import Template, TemplateTranslation from temba.utils import json, languages @@ -704,8 +705,8 @@ def form_valid(self, form): return self.render_modal_response(form) - class Status(OrgPermsMixin, OrgFilterMixin, SmartListView): - permission = "msgs.broadcast_read" + class Status(BaseListView): + permission = "msgs.broadcast_list" def derive_queryset(self, **kwargs): qs = super().derive_queryset(**kwargs) diff --git a/temba/orgs/mixins.py b/temba/orgs/mixins.py index d87e5e7697f..2d45f104da4 100644 --- a/temba/orgs/mixins.py +++ b/temba/orgs/mixins.py @@ -81,20 +81,6 @@ def pre_process(self, request, *args, **kwargs): ) -class OrgFilterMixin: - """ - Simple mixin to filter a view's queryset by the request org - """ - - def derive_queryset(self, *args, **kwargs): - queryset = super().derive_queryset(*args, **kwargs) - - if not self.request.user.is_authenticated: - return queryset.none() # pragma: no cover - else: - return queryset.filter(org=self.request.org) - - class InferOrgMixin: """ Mixin for view whose object is the current org diff --git a/temba/orgs/views.py b/temba/orgs/views.py index 2bb0d89b996..064d6865b0a 100644 --- a/temba/orgs/views.py +++ b/temba/orgs/views.py @@ -38,7 +38,6 @@ from django.utils import timezone from django.utils.encoding import DjangoUnicodeDecodeError, force_str from django.utils.functional import cached_property -from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt @@ -87,6 +86,7 @@ User, UserSettings, ) +from .views_base import BaseMenuView # session key for storing a two-factor enabled user's id once we've checked their password TWO_FACTOR_USER_SESSION_KEY = "_two_factor_user_id" @@ -1069,110 +1069,6 @@ def form_valid(self, form): return super().form_valid(form) -class BaseMenuView(OrgPermsMixin, SmartTemplateView): - """ - Base view for the section menu views - """ - - def create_divider(self): - return {"type": "divider"} - - def create_space(self): # pragma: no cover - return {"type": "space"} - - def create_section(self, name, items=()): # pragma: no cover - return {"id": slugify(name), "name": name, "type": "section", "items": items} - - def create_list(self, name, href, type): - return {"id": name, "href": href, "type": type} - - def create_modax_button(self, name, href, icon=None, on_submit=None): # pragma: no cover - menu_item = {"id": slugify(name), "name": name, "type": "modax-button"} - if href: - if href[0] == "/": # pragma: no cover - menu_item["href"] = href - elif self.has_org_perm(href): - menu_item["href"] = reverse(href) - - if on_submit: - menu_item["on_submit"] = on_submit - - if icon: # pragma: no cover - menu_item["icon"] = icon - - if "href" not in menu_item: # pragma: no cover - return None - - return menu_item - - def create_menu_item( - self, - menu_id=None, - name=None, - icon=None, - avatar=None, - endpoint=None, - href=None, - count=None, - perm=None, - items=[], - inline=False, - bottom=False, - popup=False, - event=None, - posterize=False, - bubble=None, - mobile=False, - ): - if perm and not self.has_org_perm(perm): # pragma: no cover - return - - menu_item = {"name": name, "inline": inline} - menu_item["id"] = menu_id if menu_id else slugify(name) - menu_item["bottom"] = bottom - menu_item["popup"] = popup - menu_item["avatar"] = avatar - menu_item["posterize"] = posterize - menu_item["event"] = event - menu_item["mobile"] = mobile - - if bubble: - menu_item["bubble"] = bubble - - if icon: - menu_item["icon"] = icon - - if count is not None: - menu_item["count"] = count - - if endpoint: - if endpoint[0] == "/": # pragma: no cover - menu_item["endpoint"] = endpoint - elif perm or self.has_org_perm(endpoint): - menu_item["endpoint"] = reverse(endpoint) - - if href: - if href[0] == "/": - menu_item["href"] = href - elif perm or self.has_org_perm(href): - menu_item["href"] = reverse(href) - - if items: # pragma: no cover - menu_item["items"] = [item for item in items if item is not None] - - # only include the menu item if we have somewhere to go - if "href" not in menu_item and "endpoint" not in menu_item and not inline and not popup and not event: - return None - - return menu_item - - def get_menu(self): - return [item for item in self.derive_menu() if item is not None] - - def render_to_response(self, context, **response_kwargs): - return JsonResponse({"results": self.get_menu()}) - - class InvitationMixin: @cached_property def invitation(self, **kwargs): diff --git a/temba/orgs/views_base.py b/temba/orgs/views_base.py new file mode 100644 index 00000000000..e9b1ea63676 --- /dev/null +++ b/temba/orgs/views_base.py @@ -0,0 +1,125 @@ +from smartmin.views import SmartListView, SmartTemplateView + +from django.http import JsonResponse +from django.urls import reverse +from django.utils.text import slugify + +from .mixins import OrgPermsMixin + + +class BaseListView(OrgPermsMixin, SmartListView): + """ + Base list view for objects that belong to the current org + """ + + def derive_queryset(self, *args, **kwargs): + queryset = super().derive_queryset(*args, **kwargs) + + if not self.request.user.is_authenticated: + return queryset.none() # pragma: no cover + else: + return queryset.filter(org=self.request.org) + + +class BaseMenuView(OrgPermsMixin, SmartTemplateView): + """ + Base view for the section menus + """ + + def create_divider(self): + return {"type": "divider"} + + def create_space(self): # pragma: no cover + return {"type": "space"} + + def create_section(self, name, items=()): # pragma: no cover + return {"id": slugify(name), "name": name, "type": "section", "items": items} + + def create_list(self, name, href, type): + return {"id": name, "href": href, "type": type} + + def create_modax_button(self, name, href, icon=None, on_submit=None): # pragma: no cover + menu_item = {"id": slugify(name), "name": name, "type": "modax-button"} + if href: + if href[0] == "/": # pragma: no cover + menu_item["href"] = href + elif self.has_org_perm(href): + menu_item["href"] = reverse(href) + + if on_submit: + menu_item["on_submit"] = on_submit + + if icon: # pragma: no cover + menu_item["icon"] = icon + + if "href" not in menu_item: # pragma: no cover + return None + + return menu_item + + def create_menu_item( + self, + menu_id=None, + name=None, + icon=None, + avatar=None, + endpoint=None, + href=None, + count=None, + perm=None, + items=[], + inline=False, + bottom=False, + popup=False, + event=None, + posterize=False, + bubble=None, + mobile=False, + ): + if perm and not self.has_org_perm(perm): # pragma: no cover + return + + menu_item = {"name": name, "inline": inline} + menu_item["id"] = menu_id if menu_id else slugify(name) + menu_item["bottom"] = bottom + menu_item["popup"] = popup + menu_item["avatar"] = avatar + menu_item["posterize"] = posterize + menu_item["event"] = event + menu_item["mobile"] = mobile + + if bubble: + menu_item["bubble"] = bubble + + if icon: + menu_item["icon"] = icon + + if count is not None: + menu_item["count"] = count + + if endpoint: + if endpoint[0] == "/": # pragma: no cover + menu_item["endpoint"] = endpoint + elif perm or self.has_org_perm(endpoint): + menu_item["endpoint"] = reverse(endpoint) + + if href: + if href[0] == "/": + menu_item["href"] = href + elif perm or self.has_org_perm(href): + menu_item["href"] = reverse(href) + + if items: # pragma: no cover + menu_item["items"] = [item for item in items if item is not None] + + # only include the menu item if we have somewhere to go + if "href" not in menu_item and "endpoint" not in menu_item and not inline and not popup and not event: + return None + + return menu_item + + def get_menu(self): + return [item for item in self.derive_menu() if item is not None] + + def render_to_response(self, context, **response_kwargs): + return JsonResponse({"results": self.get_menu()}) diff --git a/temba/tickets/views.py b/temba/tickets/views.py index 43bc1aadbd4..c8fa04f1a9b 100644 --- a/temba/tickets/views.py +++ b/temba/tickets/views.py @@ -21,7 +21,8 @@ from temba.msgs.models import Msg from temba.notifications.views import NotificationTargetMixin from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin -from temba.orgs.views import BaseExportView, BaseMenuView, ModalMixin +from temba.orgs.views import BaseExportView, ModalMixin +from temba.orgs.views_base import BaseMenuView from temba.utils.dates import datetime_to_timestamp, timestamp_to_datetime from temba.utils.export import response_from_workbook from temba.utils.fields import InputWidget diff --git a/temba/triggers/views.py b/temba/triggers/views.py index 4cdcffccb7c..e1c6454e012 100644 --- a/temba/triggers/views.py +++ b/temba/triggers/views.py @@ -1,6 +1,6 @@ from enum import Enum -from smartmin.views import SmartCreateView, SmartCRUDL, SmartListView, SmartTemplateView, SmartUpdateView +from smartmin.views import SmartCreateView, SmartCRUDL, SmartTemplateView, SmartUpdateView from django import forms from django.db.models.functions import Upper @@ -15,8 +15,8 @@ from temba.flows.models import Flow from temba.formax import FormaxMixin from temba.msgs.views import ModalMixin -from temba.orgs.mixins import OrgFilterMixin, OrgObjPermsMixin, OrgPermsMixin -from temba.orgs.views import BaseMenuView +from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.views_base import BaseListView, BaseMenuView from temba.schedules.models import Schedule from temba.utils.fields import SelectMultipleWidget, SelectWidget, TembaChoiceField, TembaMultipleChoiceField from temba.utils.views import BulkActionMixin, ComponentFormMixin, ContentMenuMixin, SpaMixin @@ -243,7 +243,7 @@ def derive_menu(self): return menu - class Create(SpaMixin, FormaxMixin, OrgFilterMixin, OrgPermsMixin, SmartTemplateView): + class Create(SpaMixin, FormaxMixin, OrgPermsMixin, SmartTemplateView): title = _("New Trigger") menu_path = "/trigger/new-trigger" @@ -425,7 +425,7 @@ def form_valid(self, form): response["REDIRECT"] = self.get_success_url() return response - class BaseList(SpaMixin, OrgFilterMixin, OrgPermsMixin, BulkActionMixin, SmartListView): + class BaseList(SpaMixin, BulkActionMixin, BaseListView): """ Base class for list views """ From d1b8144944a825aaaeb34fd6dacca3015e48c171 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 3 Oct 2024 19:37:29 +0000 Subject: [PATCH 2/2] Reorg all view stuff in the orgs app into a views package --- temba/airtime/views.py | 2 +- temba/api/v2/views.py | 2 +- temba/archives/views.py | 2 +- temba/campaigns/views.py | 4 ++-- temba/channels/types/plivo/views.py | 2 +- temba/channels/types/twilio/views.py | 2 +- temba/channels/types/vonage/views.py | 2 +- temba/channels/types/whatsapp/views.py | 2 +- temba/channels/views.py | 2 +- temba/classifiers/views.py | 2 +- temba/contacts/views.py | 4 ++-- temba/dashboard/views.py | 2 +- temba/flows/views.py | 4 ++-- temba/globals/views.py | 2 +- temba/locations/views.py | 2 +- temba/msgs/views.py | 4 ++-- temba/notifications/views.py | 2 +- temba/orgs/views/__init__.py | 1 + temba/orgs/{views_base.py => views/base.py} | 0 temba/orgs/{ => views}/forms.py | 2 +- temba/orgs/{ => views}/mixins.py | 0 temba/orgs/{ => views}/views.py | 8 ++++---- temba/request_logs/views.py | 2 +- temba/templates/views.py | 2 +- temba/tickets/views.py | 4 ++-- temba/triggers/views.py | 4 ++-- temba/utils/whatsapp/views.py | 2 +- 27 files changed, 34 insertions(+), 33 deletions(-) create mode 100644 temba/orgs/views/__init__.py rename temba/orgs/{views_base.py => views/base.py} (100%) rename temba/orgs/{ => views}/forms.py (99%) rename temba/orgs/{ => views}/mixins.py (100%) rename temba/orgs/{ => views}/views.py (99%) diff --git a/temba/airtime/views.py b/temba/airtime/views.py index 2fd0eb8c6a5..fcceaf0996f 100644 --- a/temba/airtime/views.py +++ b/temba/airtime/views.py @@ -6,7 +6,7 @@ from temba.airtime.models import AirtimeTransfer from temba.contacts.models import URN, ContactURN -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.request_logs.models import HTTPLog from temba.utils.views import SpaMixin diff --git a/temba/api/v2/views.py b/temba/api/v2/views.py index e9a2ab45572..6f83ac54b56 100644 --- a/temba/api/v2/views.py +++ b/temba/api/v2/views.py @@ -21,8 +21,8 @@ from temba.globals.models import Global from temba.locations.models import AdminBoundary, BoundaryAlias from temba.msgs.models import Broadcast, BroadcastMsgCount, Label, LabelCount, Media, Msg, OptIn, SystemLabel -from temba.orgs.mixins import OrgPermsMixin from temba.orgs.models import OrgMembership, User +from temba.orgs.views.mixins import OrgPermsMixin from temba.tickets.models import Ticket, TicketCount, Topic from temba.utils import str_to_bool from temba.utils.uuid import is_uuid diff --git a/temba/archives/views.py b/temba/archives/views.py index cdb2c7cb25c..a4fb9adb285 100644 --- a/temba/archives/views.py +++ b/temba/archives/views.py @@ -4,7 +4,7 @@ from django.http import HttpResponseRedirect -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils.views import SpaMixin from .models import Archive diff --git a/temba/campaigns/views.py b/temba/campaigns/views.py index 3003cc1f872..6577293c720 100644 --- a/temba/campaigns/views.py +++ b/temba/campaigns/views.py @@ -11,9 +11,9 @@ from temba.contacts.models import ContactField, ContactGroup from temba.flows.models import Flow from temba.msgs.models import Msg -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import ModalMixin -from temba.orgs.views_base import BaseListView, BaseMenuView +from temba.orgs.views.base import BaseListView, BaseMenuView +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils import languages from temba.utils.fields import CompletionTextarea, InputWidget, SelectWidget, TembaChoiceField from temba.utils.views import BulkActionMixin, ContentMenuMixin, SpaMixin diff --git a/temba/channels/types/plivo/views.py b/temba/channels/types/plivo/views.py index f5ba611fd77..4c8cc70334e 100644 --- a/temba/channels/types/plivo/views.py +++ b/temba/channels/types/plivo/views.py @@ -13,7 +13,7 @@ from temba.channels.models import Channel from temba.channels.views import BaseClaimNumberMixin, ChannelTypeMixin, ClaimViewMixin -from temba.orgs.mixins import OrgPermsMixin +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils import countries from temba.utils.fields import SelectWidget from temba.utils.http import http_headers diff --git a/temba/channels/types/twilio/views.py b/temba/channels/types/twilio/views.py index beacd6857b2..1916ea3b2d9 100644 --- a/temba/channels/types/twilio/views.py +++ b/temba/channels/types/twilio/views.py @@ -13,7 +13,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from temba.orgs.mixins import OrgPermsMixin +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils import countries from temba.utils.fields import InputWidget, SelectWidget from temba.utils.timezones import timezone_to_country_code diff --git a/temba/channels/types/vonage/views.py b/temba/channels/types/vonage/views.py index c44771588fd..8ae2e5504a1 100644 --- a/temba/channels/types/vonage/views.py +++ b/temba/channels/types/vonage/views.py @@ -7,7 +7,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from temba.orgs.mixins import OrgPermsMixin +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils import countries from temba.utils.fields import InputWidget, SelectWidget from temba.utils.models import generate_uuid diff --git a/temba/channels/types/whatsapp/views.py b/temba/channels/types/whatsapp/views.py index 199ddb05ff7..95c3e76aa95 100644 --- a/temba/channels/types/whatsapp/views.py +++ b/temba/channels/types/whatsapp/views.py @@ -10,8 +10,8 @@ from django.utils.translation import gettext_lazy as _ from temba.channels.views import ChannelTypeMixin -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import ModalMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils.fields import InputWidget from temba.utils.text import truncate from temba.utils.views import ContentMenuMixin diff --git a/temba/channels/views.py b/temba/channels/views.py index e4710f40f33..bbb4e9e561c 100644 --- a/temba/channels/views.py +++ b/temba/channels/views.py @@ -35,8 +35,8 @@ from temba.ivr.models import Call from temba.msgs.models import Msg from temba.notifications.views import NotificationTargetMixin -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import DependencyDeleteModal, ModalMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils import countries from temba.utils.fields import SelectWidget from temba.utils.json import EpochEncoder diff --git a/temba/classifiers/views.py b/temba/classifiers/views.py index 2f178b9f65d..3adf0fa9903 100644 --- a/temba/classifiers/views.py +++ b/temba/classifiers/views.py @@ -5,8 +5,8 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import DependencyDeleteModal +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils.views import ComponentFormMixin, ContentMenuMixin, SpaMixin from .models import Classifier diff --git a/temba/contacts/views.py b/temba/contacts/views.py index 201fd9c3091..64e2d45e1dc 100644 --- a/temba/contacts/views.py +++ b/temba/contacts/views.py @@ -25,10 +25,10 @@ from temba.channels.models import Channel from temba.mailroom.events import Event from temba.notifications.views import NotificationTargetMixin -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.models import User from temba.orgs.views import BaseExportView, DependencyDeleteModal, DependencyUsagesModal, ModalMixin -from temba.orgs.views_base import BaseMenuView +from temba.orgs.views.base import BaseMenuView +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.tickets.models import Ticket, Topic from temba.utils import json, on_transaction_commit from temba.utils.dates import datetime_to_timestamp, timestamp_to_datetime diff --git a/temba/dashboard/views.py b/temba/dashboard/views.py index f374073fc3a..980e774ba04 100644 --- a/temba/dashboard/views.py +++ b/temba/dashboard/views.py @@ -9,8 +9,8 @@ from django.utils.translation import gettext_lazy as _ from temba.channels.models import Channel, ChannelCount -from temba.orgs.mixins import OrgPermsMixin from temba.orgs.models import Org +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils.views import SpaMixin flattened_colors = [ diff --git a/temba/flows/views.py b/temba/flows/views.py index 71b63488190..8dbe3030e09 100644 --- a/temba/flows/views.py +++ b/temba/flows/views.py @@ -34,10 +34,10 @@ from temba.flows.models import Flow, FlowRevision, FlowRun, FlowSession, FlowStart from temba.flows.tasks import update_session_wait_expires from temba.ivr.models import Call -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.models import IntegrationType, Org from temba.orgs.views import BaseExportView, DependencyDeleteModal, ModalMixin -from temba.orgs.views_base import BaseListView, BaseMenuView +from temba.orgs.views.base import BaseListView, BaseMenuView +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.triggers.models import Trigger from temba.utils import analytics, gettext, json, languages, on_transaction_commit from temba.utils.fields import ( diff --git a/temba/globals/views.py b/temba/globals/views.py index b6593735dca..1c8f81f7567 100644 --- a/temba/globals/views.py +++ b/temba/globals/views.py @@ -5,8 +5,8 @@ from django import forms from django.urls import reverse -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import DependencyDeleteModal, DependencyUsagesModal, ModalMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils.fields import InputWidget from temba.utils.views import ContentMenuMixin, SpaMixin diff --git a/temba/locations/views.py b/temba/locations/views.py index 1e82ad5a5bc..4c7615eaebc 100644 --- a/temba/locations/views.py +++ b/temba/locations/views.py @@ -8,7 +8,7 @@ from django.views.decorators.csrf import csrf_exempt from temba.locations.models import AdminBoundary, BoundaryAlias -from temba.orgs.mixins import OrgPermsMixin +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils import json from temba.utils.views import ContentMenuMixin, SpaMixin diff --git a/temba/msgs/views.py b/temba/msgs/views.py index 79282d55015..52faf827845 100644 --- a/temba/msgs/views.py +++ b/temba/msgs/views.py @@ -20,10 +20,10 @@ from temba import mailroom from temba.archives.models import Archive from temba.mailroom.client.types import Exclusions -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.models import Org from temba.orgs.views import BaseExportView, DependencyDeleteModal, DependencyUsagesModal, ModalMixin -from temba.orgs.views_base import BaseListView, BaseMenuView +from temba.orgs.views.base import BaseListView, BaseMenuView +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.schedules.views import ScheduleFormMixin from temba.templates.models import Template, TemplateTranslation from temba.utils import json, languages diff --git a/temba/notifications/views.py b/temba/notifications/views.py index 2358005115b..fa93c64a6f7 100644 --- a/temba/notifications/views.py +++ b/temba/notifications/views.py @@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _ -from temba.orgs.mixins import OrgPermsMixin +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils.views import SpaMixin from .mixins import NotificationTargetMixin diff --git a/temba/orgs/views/__init__.py b/temba/orgs/views/__init__.py new file mode 100644 index 00000000000..360d6f84458 --- /dev/null +++ b/temba/orgs/views/__init__.py @@ -0,0 +1 @@ +from .views import * # noqa diff --git a/temba/orgs/views_base.py b/temba/orgs/views/base.py similarity index 100% rename from temba/orgs/views_base.py rename to temba/orgs/views/base.py diff --git a/temba/orgs/forms.py b/temba/orgs/views/forms.py similarity index 99% rename from temba/orgs/forms.py rename to temba/orgs/views/forms.py index 10f35c9d601..1f07abc0ee4 100644 --- a/temba/orgs/forms.py +++ b/temba/orgs/views/forms.py @@ -10,7 +10,7 @@ from temba.utils.fields import InputWidget from temba.utils.timezones import TimeZoneFormField -from .models import Org, User +from ..models import Org, User class SignupForm(forms.ModelForm): diff --git a/temba/orgs/mixins.py b/temba/orgs/views/mixins.py similarity index 100% rename from temba/orgs/mixins.py rename to temba/orgs/views/mixins.py diff --git a/temba/orgs/views.py b/temba/orgs/views/views.py similarity index 99% rename from temba/orgs/views.py rename to temba/orgs/views/views.py index 064d6865b0a..aaac9fc3967 100644 --- a/temba/orgs/views.py +++ b/temba/orgs/views/views.py @@ -72,9 +72,7 @@ StaffOnlyMixin, ) -from .forms import SignupForm, SMTPForm -from .mixins import InferOrgMixin, OrgObjPermsMixin, OrgPermsMixin -from .models import ( +from ..models import ( BackupToken, DefinitionExport, Export, @@ -86,7 +84,9 @@ User, UserSettings, ) -from .views_base import BaseMenuView +from .base import BaseMenuView +from .forms import SignupForm, SMTPForm +from .mixins import InferOrgMixin, OrgObjPermsMixin, OrgPermsMixin # session key for storing a two-factor enabled user's id once we've checked their password TWO_FACTOR_USER_SESSION_KEY = "_two_factor_user_id" diff --git a/temba/request_logs/views.py b/temba/request_logs/views.py index 1c19f013bc0..59d0d9ce310 100644 --- a/temba/request_logs/views.py +++ b/temba/request_logs/views.py @@ -7,7 +7,7 @@ from temba.channels.models import Channel from temba.classifiers.models import Classifier -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils import str_to_bool from temba.utils.views import ContentMenuMixin, SpaMixin diff --git a/temba/templates/views.py b/temba/templates/views.py index 1cf1aa235b6..e8a92728b6a 100644 --- a/temba/templates/views.py +++ b/temba/templates/views.py @@ -1,7 +1,7 @@ from smartmin.views import SmartCRUDL, SmartListView, SmartReadView -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import DependencyUsagesModal +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils.views import SpaMixin from .models import Template, TemplateTranslation diff --git a/temba/tickets/views.py b/temba/tickets/views.py index c8fa04f1a9b..ece526d474d 100644 --- a/temba/tickets/views.py +++ b/temba/tickets/views.py @@ -20,9 +20,9 @@ from temba.msgs.models import Msg from temba.notifications.views import NotificationTargetMixin -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.orgs.views import BaseExportView, ModalMixin -from temba.orgs.views_base import BaseMenuView +from temba.orgs.views.base import BaseMenuView +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.utils.dates import datetime_to_timestamp, timestamp_to_datetime from temba.utils.export import response_from_workbook from temba.utils.fields import InputWidget diff --git a/temba/triggers/views.py b/temba/triggers/views.py index e1c6454e012..b14fbbbd620 100644 --- a/temba/triggers/views.py +++ b/temba/triggers/views.py @@ -15,8 +15,8 @@ from temba.flows.models import Flow from temba.formax import FormaxMixin from temba.msgs.views import ModalMixin -from temba.orgs.mixins import OrgObjPermsMixin, OrgPermsMixin -from temba.orgs.views_base import BaseListView, BaseMenuView +from temba.orgs.views.base import BaseListView, BaseMenuView +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin from temba.schedules.models import Schedule from temba.utils.fields import SelectMultipleWidget, SelectWidget, TembaChoiceField, TembaMultipleChoiceField from temba.utils.views import BulkActionMixin, ComponentFormMixin, ContentMenuMixin, SpaMixin diff --git a/temba/utils/whatsapp/views.py b/temba/utils/whatsapp/views.py index 36f43288dba..bae94373d46 100644 --- a/temba/utils/whatsapp/views.py +++ b/temba/utils/whatsapp/views.py @@ -4,7 +4,7 @@ from temba.channels.models import Channel from temba.channels.views import ChannelTypeMixin -from temba.orgs.mixins import OrgPermsMixin +from temba.orgs.views.mixins import OrgPermsMixin from temba.utils.views import PostOnlyMixin from .tasks import refresh_whatsapp_contacts