From ac8617883469efc7e8d390d34c9583732d017192 Mon Sep 17 00:00:00 2001 From: Tasos Katsoulas Date: Thu, 29 Jan 2026 16:59:22 +0200 Subject: [PATCH] Cleanup ZD topics and obsolete code --- kitsune/customercare/__init__.py | 252 ------------------ kitsune/customercare/tests/test_utils.py | 77 +++--- kitsune/customercare/utils.py | 46 ++-- kitsune/products/admin.py | 24 +- .../0029_populate_zendesk_configs.py | 217 ++++++++++++++- .../0033_remove_product_codename.py | 17 ++ kitsune/products/models.py | 4 +- 7 files changed, 327 insertions(+), 310 deletions(-) create mode 100644 kitsune/products/migrations/0033_remove_product_codename.py diff --git a/kitsune/customercare/__init__.py b/kitsune/customercare/__init__.py index 7cacfbdb433..e69de29bb2d 100644 --- a/kitsune/customercare/__init__.py +++ b/kitsune/customercare/__init__.py @@ -1,252 +0,0 @@ -from typing import TypedDict - - -class TagsDict(TypedDict): - legacy: str - tiers: list[str] - automation: str | None - segmentation: str | None - - -class CategoryDict(TypedDict): - slug: str - topic: str - tags: TagsDict - - -BASE_CATEGORIES: dict[str, CategoryDict] = { - "payments": { - "slug": "payments", - "topic": "I need help with a billing or subscription question", - "tags": { - "legacy": "payments", - "tiers": ["t1-billing-and-subscriptions"], - "automation": None, - "segmentation": None, - }, - }, - "accounts_signin": { - "slug": "account-signin", - "topic": "I can't sign in to my Mozilla account or subscription", - "tags": { - "legacy": "accounts", - "tiers": ["t1-passwords-and-sign-in", "t2-sign-in", "t3-sign-in-failure"], - "automation": "ssa-sign-in-failure-automation", - "segmentation": None, - }, - }, - "general": { - "slug": "general", - "topic": "I want to share feedback or suggest a feature", - "tags": { - "legacy": "general", - "tiers": ["general"], - "automation": None, - "segmentation": None, - }, - }, - "not_listed": { - "slug": "not-listed", - "topic": "My issue isn't listed here", - "tags": { - "legacy": "not_listed", - "tiers": ["not_listed"], - "automation": None, - "segmentation": None, - }, - }, -} - -ZENDESK_CATEGORIES = { - "mozilla-vpn": [ - { - "slug": "vpn-connection-issues", - "topic": "I can't connect to Mozilla VPN", - "tags": { - "legacy": "technical", - "tiers": [ - "t1-performance-and-connectivity", - "t2-connectivity", - "t3-connection-failure", - ], - "automation": "ssa-connection-issues-automation", - "segmentation": None, - }, - }, - { - "slug": "vpn-installation-updates", - "topic": "I need help installing or updating Mozilla VPN", - "tags": { - "legacy": "technical", - "tiers": ["t1-installation-and-updates"], - "automation": None, - "segmentation": None, - }, - }, - { - "slug": "vpn-server-selection", - "topic": "I can't choose a VPN location", - "tags": { - "legacy": "technical", - "tiers": [ - "t1-performance-and-connectivity", - "t2-connectivity", - "t3-cant-select-server", - ], - "automation": None, - "segmentation": None, - }, - }, - *BASE_CATEGORIES.values(), - ], - "relay": [ - { - "slug": "relay-email-forwarding", - "topic": "I'm not receiving emails to my Relay mask", - "tags": { - "legacy": "technical", - "tiers": ["t1-privacy-and-security", "t2-masking", "t3-email-masking"], - "automation": None, - "segmentation": "seg-relay-no-fwd-deliver", - }, - }, - { - "slug": "relay-domain-change", - "topic": "I want to change my Relay email domain", - "tags": { - "legacy": "technical", - "tiers": ["t1-privacy-and-security", "t2-masking", "t3-email-masking"], - "automation": None, - "segmentation": "seg-relay-chg-domain", - }, - }, - *BASE_CATEGORIES.values(), - ], - "pocket": [ - BASE_CATEGORIES["not_listed"], - BASE_CATEGORIES["accounts_signin"], - ], - "mozilla-account": [ - { - "slug": "mozilla-account-sync", - "topic": "I need help with Firefox Sync", - "tags": { - "legacy": "accounts", - "tiers": ["t1-backup-recovery-and-sync"], - "automation": "ssa-sync-data-automation", - "segmentation": None, - }, - }, - { - "slug": "mozilla-account-delete", - "topic": "I want to delete my Mozilla account", - "tags": { - "legacy": "accounts", - "tiers": ["t1-accounts", "t2-account-management"], - "automation": None, - "segmentation": "seg-acct-delete", - }, - }, - *BASE_CATEGORIES.values(), - ], - "monitor": [ - { - "slug": "monitor-data-removal", - "topic": "My data removal is taking too long", - "tags": { - "legacy": "technical", - "tiers": ["t1-privacy-and-security", "t2-data-removal", "t3-data-brokers"], - "automation": None, - "segmentation": "seg-mntor-slow-remove", - }, - }, - { - "slug": "monitor-wrong-results", - "topic": "I'm seeing results that don't belong to me", - "tags": { - "legacy": "technical", - "tiers": [ - "t1-privacy-and-security", - "t2-data-removal", - "t3-privacy-protection-scan", - ], - "automation": None, - "segmentation": "seg-mntor-wrong-scan-result", - }, - }, - *BASE_CATEGORIES.values(), - ], - "mdn-plus": [ - *BASE_CATEGORIES.values(), - ], -} - -ZENDESK_CATEGORIES_LOGINLESS = { - "mozilla-account": [ - { - "slug": "fxa-2fa-lockout", - "topic": "My security code isn't working or is lost", - "tags": { - "legacy": "accounts", - "tiers": [ - "t1-passwords-and-sign-in", - "t2-two-factor-authentication", - "t3-two-factor-lockout", - ], - "automation": "ssa-2fa-automation", - "segmentation": None, - }, - }, - { - "slug": "fxa-emailverify-lockout", - "topic": "I can't recover my account using email", - "tags": { - "legacy": "accounts", - "tiers": ["t1-passwords-and-sign-in", "t2-sign-in", "t3-email-verify-lockout"], - "automation": "ssa-pwrdreset-automation", - "segmentation": None, - }, - }, - { - "slug": "fxa-reset-password", - "topic": "I forgot my password", - "tags": { - "legacy": "accounts", - "tiers": ["t1-passwords-and-sign-in", "t2-reset-passwords"], - "automation": "ssa-emailverify-automation", - "segmentation": None, - }, - }, - { - "slug": "fxa-remove3rdprtylogin", - "topic": "I'm having issues signing in with my Google or Apple ID", - "tags": { - "legacy": "accounts", - "tiers": ["t1-passwords-and-sign-in", "t2-sign-in", "t3-3rd-party-sign-in"], - "automation": None, - "segmentation": None, - }, - }, - ] -} - - -ZENDESK_LEGACY_MAPPING: dict[str, set] = { - "accounts": { - "t1-accounts", - "t1-passwords-and-sign-in", - }, - "technical": { - "t1-accessibility", - "t1-backup-recovery-and-sync", - "t1-browse", - "t1-download-and-save", - "t1-email-and-messaging", - "t1-installation-and-updates", - "t1-performance-and-connectivity", - "t1-privacy-and-security", - "t1-search-tag-and-share", - "t1-settings", - }, - "payment": {"t1-billing-and-subscriptions"}, -} diff --git a/kitsune/customercare/tests/test_utils.py b/kitsune/customercare/tests/test_utils.py index 111e7262024..afda5e9246e 100644 --- a/kitsune/customercare/tests/test_utils.py +++ b/kitsune/customercare/tests/test_utils.py @@ -8,7 +8,14 @@ generate_classification_tags, send_support_ticket_to_zendesk, ) -from kitsune.products.tests import ProductFactory, TopicFactory +from kitsune.products.tests import ( + ProductFactory, + ProductSupportConfigFactory, + TopicFactory, + ZendeskConfigFactory, + ZendeskTopicConfigurationFactory, + ZendeskTopicFactory, +) from kitsune.sumo.tests import TestCase @@ -86,7 +93,7 @@ def test_single_tier_topic(self): tags = generate_classification_tags(submission, result) - self.assertEqual(tags, ["t1-settings", "technical"]) + self.assertEqual(tags, ["t1-settings", "general"]) def test_two_tier_topic(self): """Test generating tags for a tier 2 topic.""" @@ -100,7 +107,7 @@ def test_two_tier_topic(self): tags = generate_classification_tags(submission, result) - self.assertEqual(tags, ["t1-settings", "t2-notifications", "technical"]) + self.assertEqual(tags, ["t1-settings", "t2-notifications", "general"]) def test_three_tier_topic(self): """Test generating tags for a tier 3 topic.""" @@ -119,27 +126,32 @@ def test_three_tier_topic(self): tags = generate_classification_tags(submission, result) self.assertEqual( - tags, ["t1-settings", "t2-addons-extensions-and-themes", "t3-extensions", "technical"] + tags, ["t1-settings", "t2-addons-extensions-and-themes", "t3-extensions", "general"] ) - @patch("kitsune.customercare.utils.ZENDESK_CATEGORIES") - def test_automation_tag_included_when_matched(self, mock_categories): + def test_automation_tag_included_when_matched(self): """Test that automation tag is included when tier tags match a category.""" - mock_categories.get.return_value = [ - { - "slug": "accounts-signin", - "tags": { - "tiers": ["t1-passwords-and-sign-in", "t2-sign-in"], - "automation": "ssa-sign-in-failure-automation", - }, - } - ] - tier1 = TopicFactory(title="Passwords and sign in", parent=None, is_archived=False) tier1.products.add(self.product) tier2 = TopicFactory(title="Sign in", parent=tier1, is_archived=False) tier2.products.add(self.product) + # Create ZendeskConfig and ZendeskTopic with matching tier tags + zendesk_config = ZendeskConfigFactory(name="Test Config") + zendesk_topic = ZendeskTopicFactory( + slug="accounts-signin", + topic="I can't sign in", + tier_tags=["t1-passwords-and-sign-in", "t2-sign-in"], + automation_tag="ssa-sign-in-failure-automation", + legacy_tag="accounts", + ) + ZendeskTopicConfigurationFactory( + zendesk_config=zendesk_config, zendesk_topic=zendesk_topic + ) + ProductSupportConfigFactory( + product=self.product, zendesk_config=zendesk_config, is_active=True + ) + submission = Mock(product=self.product) result = {"topic_result": {"topic": "Sign in"}} @@ -150,28 +162,33 @@ def test_automation_tag_included_when_matched(self, mock_categories): self.assertIn("t2-sign-in", tags) self.assertIn("accounts", tags) - @patch("kitsune.customercare.utils.ZENDESK_CATEGORIES") - def test_no_automation_tag_when_not_matched(self, mock_categories): + def test_no_automation_tag_when_not_matched(self): """Test that no automation tag is included when tier tags don't match.""" - mock_categories.get.return_value = [ - { - "slug": "different-category", - "tags": { - "tiers": ["t1-different"], - "automation": "some-automation", - }, - } - ] - tier1 = TopicFactory(title="Billing and subscriptions", parent=None, is_archived=False) tier1.products.add(self.product) + # Create ZendeskConfig with a ZendeskTopic that has different tier tags + zendesk_config = ZendeskConfigFactory(name="Test Config") + zendesk_topic = ZendeskTopicFactory( + slug="different-category", + topic="Different topic", + tier_tags=["t1-different"], + automation_tag="some-automation", + legacy_tag="", + ) + ZendeskTopicConfigurationFactory( + zendesk_config=zendesk_config, zendesk_topic=zendesk_topic + ) + ProductSupportConfigFactory( + product=self.product, zendesk_config=zendesk_config, is_active=True + ) + submission = Mock(product=self.product) result = {"topic_result": {"topic": "Billing and subscriptions"}} tags = generate_classification_tags(submission, result) - self.assertEqual(tags, ["t1-billing-and-subscriptions", "payment"]) + self.assertEqual(tags, ["t1-billing-and-subscriptions", "general"]) self.assertNotIn("some-automation", tags) def test_product_reassignment_includes_other_tag(self): @@ -189,7 +206,7 @@ def test_product_reassignment_includes_other_tag(self): self.assertIn("other", tags) self.assertIn("t1-settings", tags) - self.assertIn("technical", tags) + self.assertIn("general", tags) def test_archived_topic_returns_undefined(self): """Test that archived topics are not found and return ['undefined', 'general'].""" diff --git a/kitsune/customercare/utils.py b/kitsune/customercare/utils.py index 7037a01871f..1ffc3a4a236 100644 --- a/kitsune/customercare/utils.py +++ b/kitsune/customercare/utils.py @@ -1,10 +1,9 @@ import re -from typing import Any, cast +from typing import Any import waffle from zenpy.lib.exception import APIException -from kitsune.customercare import ZENDESK_CATEGORIES, ZENDESK_LEGACY_MAPPING from kitsune.customercare.forms import ZENDESK_PRODUCT_SLUGS from kitsune.customercare.models import SupportTicket from kitsune.customercare.zendesk import ZendeskClient @@ -30,7 +29,6 @@ def generate_classification_tags(submission: SupportTicket, result: dict[str, An Returns tier tags (t1-, t2-, t3-), legacy tags, and automation tags based on the classified topic. If product was reassigned, includes "other" tag. """ - product_slug = submission.product.slug topic_result = result.get("topic_result", {}) product_result = result.get("product_result", {}) @@ -45,7 +43,7 @@ def generate_classification_tags(submission: SupportTicket, result: dict[str, An # Find topic in database and build tier tags topic = Topic.objects.filter( - title=topic_title, products__slug=product_slug, is_archived=False + title=topic_title, products=submission.product, is_archived=False ).first() # If no topic or "Undefined" or topic not found in DB @@ -68,22 +66,32 @@ def generate_classification_tags(submission: SupportTicket, result: dict[str, An tags.extend(tier_tags) - # Find matching automation tags - categories = cast(list, ZENDESK_CATEGORIES.get(product_slug, [])) - for category in categories: - category_tiers = category.get("tags", {}).get("tiers", []) - if set(tier_tags) == set(category_tiers): - automation = category.get("tags", {}).get("automation") - if automation: - tags.append(automation) - break - - # Find matching legacy tag or fallback to "general" legacy tag. - for legacy_tag, topic_tags in ZENDESK_LEGACY_MAPPING.items(): - if set(tier_tags) & topic_tags: - tags.append(legacy_tag) - break + # Find matching ZendeskTopic from database + # Get the ZendeskConfig for this product through ProductSupportConfig + support_config = submission.product.support_configs.filter( + is_active=True, zendesk_config__isnull=False + ).first() + + if support_config and support_config.zendesk_config: + # Find ZendeskTopic with matching tier_tags + zendesk_topic = support_config.zendesk_config.topics.filter( + tier_tags=tier_tags + ).first() + + if zendesk_topic: + # Add automation tag if present + if zendesk_topic.automation_tag: + tags.append(zendesk_topic.automation_tag) + # Add legacy tag if present, otherwise fallback to "general" + if zendesk_topic.legacy_tag: + tags.append(zendesk_topic.legacy_tag) + else: + tags.append("general") + else: + # No matching ZendeskTopic found, use "general" as legacy tag + tags.append("general") else: + # No ZendeskConfig for this product, use "general" as legacy tag tags.append("general") except Exception: diff --git a/kitsune/products/admin.py b/kitsune/products/admin.py index b1475f5b9fe..561ca56f557 100644 --- a/kitsune/products/admin.py +++ b/kitsune/products/admin.py @@ -70,7 +70,6 @@ class ProductAdmin(admin.ModelAdmin): "slug", "display_order", "visible", - "codename", "is_archived", "pinned_article_config", ) @@ -181,6 +180,7 @@ class ZendeskTopicConfigurationInline(admin.TabularInline): Inline editor for topic-to-config associations. Shows which topics are included in this config with their settings. """ + model = ZendeskTopicConfiguration extra = 1 autocomplete_fields = ("zendesk_topic",) @@ -200,7 +200,13 @@ def config_count(self, obj): class ZendeskConfigAdmin(admin.ModelAdmin): - list_display = ("name", "ticket_form_id", "enable_os_field", "skip_spam_moderation", "topic_count") + list_display = ( + "name", + "ticket_form_id", + "enable_os_field", + "skip_spam_moderation", + "topic_count", + ) list_display_links = ("name",) list_editable = ("enable_os_field", "skip_spam_moderation") list_filter = ("enable_os_field", "skip_spam_moderation") @@ -309,19 +315,27 @@ def zendesk_topics_display(self, obj): if not obj.zendesk_config: return "—" - topic_configs = obj.zendesk_config.topic_configurations.all().select_related("zendesk_topic") + topic_configs = obj.zendesk_config.topic_configurations.all().select_related( + "zendesk_topic" + ) if not topic_configs: return format_html("No topics configured") topic_items = [] for topic_config in topic_configs: topic = topic_config.zendesk_topic - loginless_badge = " (loginless only)" if topic_config.loginless_only else "" + loginless_badge = ( + " (loginless only)" + if topic_config.loginless_only + else "" + ) topic_items.append( f"
  • {topic.slug}: {topic.topic}{loginless_badge}
  • " ) - return format_html("", "".join(topic_items)) + return format_html( + "", "".join(topic_items) + ) class ZendeskTopicConfigurationAdmin(admin.ModelAdmin): diff --git a/kitsune/products/migrations/0029_populate_zendesk_configs.py b/kitsune/products/migrations/0029_populate_zendesk_configs.py index d962336b112..675a04b2172 100644 --- a/kitsune/products/migrations/0029_populate_zendesk_configs.py +++ b/kitsune/products/migrations/0029_populate_zendesk_configs.py @@ -12,7 +12,222 @@ def populate_zendesk_configs(apps, schema_editor): in this environment. Creates ProductSupportConfig for all products with either AAQConfig or ZendeskConfig. """ - from kitsune.customercare import ZENDESK_CATEGORIES, ZENDESK_CATEGORIES_LOGINLESS + # Constants embedded in migration for self-containment + BASE_CATEGORIES = { + "payments": { + "slug": "payments", + "topic": "I need help with a billing or subscription question", + "tags": { + "legacy": "payments", + "tiers": ["t1-billing-and-subscriptions"], + "automation": None, + "segmentation": None, + }, + }, + "accounts_signin": { + "slug": "account-signin", + "topic": "I can't sign in to my Mozilla account or subscription", + "tags": { + "legacy": "accounts", + "tiers": ["t1-passwords-and-sign-in", "t2-sign-in", "t3-sign-in-failure"], + "automation": "ssa-sign-in-failure-automation", + "segmentation": None, + }, + }, + "general": { + "slug": "general", + "topic": "I want to share feedback or suggest a feature", + "tags": { + "legacy": "general", + "tiers": ["general"], + "automation": None, + "segmentation": None, + }, + }, + "not_listed": { + "slug": "not-listed", + "topic": "My issue isn't listed here", + "tags": { + "legacy": "not_listed", + "tiers": ["not_listed"], + "automation": None, + "segmentation": None, + }, + }, + } + + ZENDESK_CATEGORIES = { + "mozilla-vpn": [ + { + "slug": "vpn-connection-issues", + "topic": "I can't connect to Mozilla VPN", + "tags": { + "legacy": "technical", + "tiers": [ + "t1-performance-and-connectivity", + "t2-connectivity", + "t3-connection-failure", + ], + "automation": "ssa-connection-issues-automation", + "segmentation": None, + }, + }, + { + "slug": "vpn-installation-updates", + "topic": "I need help installing or updating Mozilla VPN", + "tags": { + "legacy": "technical", + "tiers": ["t1-installation-and-updates"], + "automation": None, + "segmentation": None, + }, + }, + { + "slug": "vpn-server-selection", + "topic": "I can't choose a VPN location", + "tags": { + "legacy": "technical", + "tiers": [ + "t1-performance-and-connectivity", + "t2-connectivity", + "t3-cant-select-server", + ], + "automation": None, + "segmentation": None, + }, + }, + *BASE_CATEGORIES.values(), + ], + "relay": [ + { + "slug": "relay-email-forwarding", + "topic": "I'm not receiving emails to my Relay mask", + "tags": { + "legacy": "technical", + "tiers": ["t1-privacy-and-security", "t2-masking", "t3-email-masking"], + "automation": None, + "segmentation": "seg-relay-no-fwd-deliver", + }, + }, + { + "slug": "relay-domain-change", + "topic": "I want to change my Relay email domain", + "tags": { + "legacy": "technical", + "tiers": ["t1-privacy-and-security", "t2-masking", "t3-email-masking"], + "automation": None, + "segmentation": "seg-relay-chg-domain", + }, + }, + *BASE_CATEGORIES.values(), + ], + "pocket": [ + BASE_CATEGORIES["not_listed"], + BASE_CATEGORIES["accounts_signin"], + ], + "mozilla-account": [ + { + "slug": "mozilla-account-sync", + "topic": "I need help with Firefox Sync", + "tags": { + "legacy": "accounts", + "tiers": ["t1-backup-recovery-and-sync"], + "automation": "ssa-sync-data-automation", + "segmentation": None, + }, + }, + { + "slug": "mozilla-account-delete", + "topic": "I want to delete my Mozilla account", + "tags": { + "legacy": "accounts", + "tiers": ["t1-accounts", "t2-account-management"], + "automation": None, + "segmentation": "seg-acct-delete", + }, + }, + *BASE_CATEGORIES.values(), + ], + "monitor": [ + { + "slug": "monitor-data-removal", + "topic": "My data removal is taking too long", + "tags": { + "legacy": "technical", + "tiers": ["t1-privacy-and-security", "t2-data-removal", "t3-data-brokers"], + "automation": None, + "segmentation": "seg-mntor-slow-remove", + }, + }, + { + "slug": "monitor-wrong-results", + "topic": "I'm seeing results that don't belong to me", + "tags": { + "legacy": "technical", + "tiers": [ + "t1-privacy-and-security", + "t2-data-removal", + "t3-privacy-protection-scan", + ], + "automation": None, + "segmentation": "seg-mntor-wrong-scan-result", + }, + }, + *BASE_CATEGORIES.values(), + ], + "mdn-plus": [ + *BASE_CATEGORIES.values(), + ], + } + + ZENDESK_CATEGORIES_LOGINLESS = { + "mozilla-account": [ + { + "slug": "fxa-2fa-lockout", + "topic": "My security code isn't working or is lost", + "tags": { + "legacy": "accounts", + "tiers": [ + "t1-passwords-and-sign-in", + "t2-two-factor-authentication", + "t3-two-factor-lockout", + ], + "automation": "ssa-2fa-automation", + "segmentation": None, + }, + }, + { + "slug": "fxa-emailverify-lockout", + "topic": "I can't recover my account using email", + "tags": { + "legacy": "accounts", + "tiers": ["t1-passwords-and-sign-in", "t2-sign-in", "t3-email-verify-lockout"], + "automation": "ssa-pwrdreset-automation", + "segmentation": None, + }, + }, + { + "slug": "fxa-reset-password", + "topic": "I forgot my password", + "tags": { + "legacy": "accounts", + "tiers": ["t1-passwords-and-sign-in", "t2-reset-passwords"], + "automation": "ssa-emailverify-automation", + "segmentation": None, + }, + }, + { + "slug": "fxa-remove3rdprtylogin", + "topic": "I'm having issues signing in with my Google or Apple ID", + "tags": { + "legacy": "accounts", + "tiers": ["t1-passwords-and-sign-in", "t2-sign-in", "t3-3rd-party-sign-in"], + "automation": None, + "segmentation": None, + }, + }, + ] + } Product = apps.get_model("products", "Product") ZendeskConfig = apps.get_model("products", "ZendeskConfig") diff --git a/kitsune/products/migrations/0033_remove_product_codename.py b/kitsune/products/migrations/0033_remove_product_codename.py new file mode 100644 index 00000000000..7296d11965a --- /dev/null +++ b/kitsune/products/migrations/0033_remove_product_codename.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.25 on 2026-01-29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0032_zendeskconfig_skip_spam_moderation'), + ] + + operations = [ + migrations.RemoveField( + model_name='product', + name='codename', + ), + ] diff --git a/kitsune/products/models.py b/kitsune/products/models.py index 60794e2da06..a721bc583a3 100644 --- a/kitsune/products/models.py +++ b/kitsune/products/models.py @@ -38,7 +38,6 @@ class Meta: class Product(BaseProductTopic): - codename = models.CharField(max_length=255, blank=True, default="") slug = models.SlugField() image = ImagePlusField( upload_to=settings.PRODUCT_IMAGE_PATH, @@ -377,8 +376,7 @@ class ZendeskConfig(ModelBase): default=False, help_text="Show operating system selector in the support form" ) skip_spam_moderation = models.BooleanField( - default=False, - help_text="Skip AI-based spam checking for this product" + default=False, help_text="Skip AI-based spam checking for this product" ) topics = models.ManyToManyField( "ZendeskTopic",