diff --git a/zerver/actions/users.py b/zerver/actions/users.py index 8badc9f7b4..df1cd44be6 100644 --- a/zerver/actions/users.py +++ b/zerver/actions/users.py @@ -41,8 +41,8 @@ from zerver.models import ( Subscription, UserGroupMembership, UserProfile, - get_bot_services, ) +from zerver.models.bots import get_bot_services from zerver.models.realms import get_fake_email_domain from zerver.models.users import ( active_non_guest_user_ids, diff --git a/zerver/lib/outgoing_webhook.py b/zerver/lib/outgoing_webhook.py index b52c6f3629..d1a7597e96 100644 --- a/zerver/lib/outgoing_webhook.py +++ b/zerver/lib/outgoing_webhook.py @@ -19,7 +19,8 @@ from zerver.lib.outgoing_http import OutgoingSession from zerver.lib.queue import retry_event from zerver.lib.topic import get_topic_from_message_info from zerver.lib.url_encoding import near_message_url -from zerver.models import GENERIC_INTERFACE, SLACK_INTERFACE, Realm, Service, UserProfile +from zerver.models import Realm, Service, UserProfile +from zerver.models.bots import GENERIC_INTERFACE, SLACK_INTERFACE from zerver.models.clients import get_client from zerver.models.users import get_user_profile_by_id diff --git a/zerver/models/__init__.py b/zerver/models/__init__.py index a05103cac5..2898b0bcd8 100644 --- a/zerver/models/__init__.py +++ b/zerver/models/__init__.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Tuple, TypeVar, Union +from typing import List, Tuple, TypeVar, Union from django.db import models from django.db.backends.base.base import BaseDatabaseWrapper @@ -13,6 +13,9 @@ from zerver.lib.cache import ( realm_alert_words_automaton_cache_key, realm_alert_words_cache_key, ) +from zerver.models.bots import BotConfigData as BotConfigData +from zerver.models.bots import BotStorageData as BotStorageData +from zerver.models.bots import Service as Service from zerver.models.clients import Client as Client from zerver.models.custom_profile_fields import CustomProfileField as CustomProfileField from zerver.models.custom_profile_fields import CustomProfileFieldValue as CustomProfileFieldValue @@ -130,83 +133,6 @@ def query_for_ids( return query -# Interfaces for services -# They provide additional functionality like parsing message to obtain query URL, data to be sent to URL, -# and parsing the response. -GENERIC_INTERFACE = "GenericService" -SLACK_INTERFACE = "SlackOutgoingWebhookService" - - -# A Service corresponds to either an outgoing webhook bot or an embedded bot. -# The type of Service is determined by the bot_type field of the referenced -# UserProfile. -# -# If the Service is an outgoing webhook bot: -# - name is any human-readable identifier for the Service -# - base_url is the address of the third-party site -# - token is used for authentication with the third-party site -# -# If the Service is an embedded bot: -# - name is the canonical name for the type of bot (e.g. 'xkcd' for an instance -# of the xkcd bot); multiple embedded bots can have the same name, but all -# embedded bots with the same name will run the same code -# - base_url and token are currently unused -class Service(models.Model): - name = models.CharField(max_length=UserProfile.MAX_NAME_LENGTH) - # Bot user corresponding to the Service. The bot_type of this user - # determines the type of service. If non-bot services are added later, - # user_profile can also represent the owner of the Service. - user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) - base_url = models.TextField() - token = models.TextField() - # Interface / API version of the service. - interface = models.PositiveSmallIntegerField(default=1) - - # Valid interfaces are {generic, zulip_bot_service, slack} - GENERIC = 1 - SLACK = 2 - - ALLOWED_INTERFACE_TYPES = [ - GENERIC, - SLACK, - ] - # N.B. If we used Django's choice=... we would get this for free (kinda) - _interfaces: Dict[int, str] = { - GENERIC: GENERIC_INTERFACE, - SLACK: SLACK_INTERFACE, - } - - def interface_name(self) -> str: - # Raises KeyError if invalid - return self._interfaces[self.interface] - - -def get_bot_services(user_profile_id: int) -> List[Service]: - return list(Service.objects.filter(user_profile_id=user_profile_id)) - - -def get_service_profile(user_profile_id: int, service_name: str) -> Service: - return Service.objects.get(user_profile_id=user_profile_id, name=service_name) - - -class BotStorageData(models.Model): - bot_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) - key = models.TextField(db_index=True) - value = models.TextField() - - class Meta: - unique_together = ("bot_profile", "key") - - -class BotConfigData(models.Model): - bot_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) - key = models.TextField(db_index=True) - value = models.TextField() - - class Meta: - unique_together = ("bot_profile", "key") - - class AlertWord(models.Model): # Realm isn't necessary, but it's a nice denormalization. Users # never move to another realm, so it's static, and having Realm diff --git a/zerver/models/bots.py b/zerver/models/bots.py new file mode 100644 index 0000000000..803d1d6c91 --- /dev/null +++ b/zerver/models/bots.py @@ -0,0 +1,82 @@ +from typing import Dict, List + +from django.db import models +from django.db.models import CASCADE + +from zerver.models.users import UserProfile + +# Interfaces for services +# They provide additional functionality like parsing message to obtain query URL, data to be sent to URL, +# and parsing the response. +GENERIC_INTERFACE = "GenericService" +SLACK_INTERFACE = "SlackOutgoingWebhookService" + + +# A Service corresponds to either an outgoing webhook bot or an embedded bot. +# The type of Service is determined by the bot_type field of the referenced +# UserProfile. +# +# If the Service is an outgoing webhook bot: +# - name is any human-readable identifier for the Service +# - base_url is the address of the third-party site +# - token is used for authentication with the third-party site +# +# If the Service is an embedded bot: +# - name is the canonical name for the type of bot (e.g. 'xkcd' for an instance +# of the xkcd bot); multiple embedded bots can have the same name, but all +# embedded bots with the same name will run the same code +# - base_url and token are currently unused +class Service(models.Model): + name = models.CharField(max_length=UserProfile.MAX_NAME_LENGTH) + # Bot user corresponding to the Service. The bot_type of this user + # determines the type of service. If non-bot services are added later, + # user_profile can also represent the owner of the Service. + user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) + base_url = models.TextField() + token = models.TextField() + # Interface / API version of the service. + interface = models.PositiveSmallIntegerField(default=1) + + # Valid interfaces are {generic, zulip_bot_service, slack} + GENERIC = 1 + SLACK = 2 + + ALLOWED_INTERFACE_TYPES = [ + GENERIC, + SLACK, + ] + # N.B. If we used Django's choice=... we would get this for free (kinda) + _interfaces: Dict[int, str] = { + GENERIC: GENERIC_INTERFACE, + SLACK: SLACK_INTERFACE, + } + + def interface_name(self) -> str: + # Raises KeyError if invalid + return self._interfaces[self.interface] + + +def get_bot_services(user_profile_id: int) -> List[Service]: + return list(Service.objects.filter(user_profile_id=user_profile_id)) + + +def get_service_profile(user_profile_id: int, service_name: str) -> Service: + return Service.objects.get(user_profile_id=user_profile_id, name=service_name) + + +class BotStorageData(models.Model): + bot_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) + key = models.TextField(db_index=True) + value = models.TextField() + + class Meta: + unique_together = ("bot_profile", "key") + + +class BotConfigData(models.Model): + bot_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) + key = models.TextField(db_index=True) + value = models.TextField() + + class Meta: + unique_together = ("bot_profile", "key") diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py index 44a31f168a..24692df53b 100644 --- a/zerver/tests/test_bots.py +++ b/zerver/tests/test_bots.py @@ -17,14 +17,8 @@ from zerver.lib.bot_lib import get_bot_handler from zerver.lib.integrations import EMBEDDED_BOTS, WebhookIntegration from zerver.lib.test_classes import UploadSerializeMixin, ZulipTestCase from zerver.lib.test_helpers import avatar_disk_path, get_test_image_file -from zerver.models import ( - Realm, - RealmUserDefault, - Service, - Subscription, - UserProfile, - get_bot_services, -) +from zerver.models import Realm, RealmUserDefault, Service, Subscription, UserProfile +from zerver.models.bots import get_bot_services from zerver.models.realms import get_realm from zerver.models.streams import get_stream from zerver.models.users import get_user, is_cross_realm_bot_email diff --git a/zerver/tests/test_embedded_bot_system.py b/zerver/tests/test_embedded_bot_system.py index 94d814bca0..2277ff2ce3 100644 --- a/zerver/tests/test_embedded_bot_system.py +++ b/zerver/tests/test_embedded_bot_system.py @@ -6,7 +6,8 @@ from typing_extensions import override from zerver.lib.bot_lib import EmbeddedBotQuitError from zerver.lib.display_recipient import get_display_recipient from zerver.lib.test_classes import ZulipTestCase -from zerver.models import UserProfile, get_service_profile +from zerver.models import UserProfile +from zerver.models.bots import get_service_profile from zerver.models.realms import get_realm from zerver.models.users import get_user diff --git a/zerver/tests/test_outgoing_webhook_interfaces.py b/zerver/tests/test_outgoing_webhook_interfaces.py index 27b72d9595..bcf1675ae3 100644 --- a/zerver/tests/test_outgoing_webhook_interfaces.py +++ b/zerver/tests/test_outgoing_webhook_interfaces.py @@ -12,7 +12,8 @@ from zerver.lib.outgoing_webhook import get_service_interface_class, process_suc from zerver.lib.test_classes import ZulipTestCase from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.topic import TOPIC_NAME -from zerver.models import SLACK_INTERFACE, Message +from zerver.models import Message +from zerver.models.bots import SLACK_INTERFACE from zerver.models.realms import get_realm from zerver.models.scheduled_jobs import NotificationTriggers from zerver.models.streams import get_stream diff --git a/zerver/worker/queue_processors.py b/zerver/worker/queue_processors.py index 97d59bb511..c6a6995720 100644 --- a/zerver/worker/queue_processors.py +++ b/zerver/worker/queue_processors.py @@ -104,8 +104,8 @@ from zerver.models import ( Stream, UserMessage, UserProfile, - get_bot_services, ) +from zerver.models.bots import get_bot_services from zerver.models.clients import get_client from zerver.models.prereg_users import filter_to_valid_prereg_users from zerver.models.users import get_system_bot, get_user_profile_by_id