diff --git a/tabcmd/commands/site/edit_site_command.py b/tabcmd/commands/site/edit_site_command.py index 8b92a8ae..543cc634 100644 --- a/tabcmd/commands/site/edit_site_command.py +++ b/tabcmd/commands/site/edit_site_command.py @@ -8,6 +8,17 @@ from tabcmd.execution.logger_config import log +def _str_to_bool(value): + """Coerce a 'true'/'false' string (from argparse choices) to a real bool. + + TSC SiteItem boolean setters are guarded by @property_is_boolean and reject + strings, so we must convert before assignment. + """ + if value is None: + return None + return str(value).lower() == "true" + + class EditSiteCommand(Server): """ Command to change the name of a site or its web folder name. Users can also use this command to allow or deny @@ -24,6 +35,7 @@ def define_args(edit_site_parser): args_group.add_argument("--site-name", default=None, dest="new_site_name", help=_("editsite.options.site-name")) set_common_site_args(args_group) set_site_status_arg(args_group) + set_edit_site_only_args(args_group) @classmethod def run_command(cls, args): @@ -41,6 +53,50 @@ def run_command(cls, args): site_item.storage_quota = args.storage_quota if args.status: site_item.state = args.status + + # New flags for W-tabcmd-437. Every attribute is only touched when the + # corresponding argparse dest is not None, so an unpassed flag leaves + # the server-side setting unchanged. + if getattr(args, "guest_access_enabled", None) is not None: + site_item.guest_access_enabled = _str_to_bool(args.guest_access_enabled) + + if getattr(args, "cache_warmup_enabled", None) is not None: + site_item.cache_warmup_enabled = args.cache_warmup_enabled + + if getattr(args, "subscription_email", None) is not None: + # Matches Classic: set the value AND toggle the *_enabled flag. + # An empty string clears the value and disables the feature. + # NOTE: TSC RequestFactory currently lowercases this value (bug to + # file upstream); user@Domain.com will be sent as user@domain.com. + site_item.custom_subscription_email = args.subscription_email + site_item.custom_subscription_email_enabled = bool(args.subscription_email) + + if getattr(args, "subscription_footer", None) is not None: + # Same two-attribute wiring as subscription_email. TSC also + # lowercases this string; tracked as an upstream TSC bug. + site_item.custom_subscription_footer = args.subscription_footer + site_item.custom_subscription_footer_enabled = bool(args.subscription_footer) + + if getattr(args, "web_extraction_enabled", None) is not None: + site_item.web_extraction_enabled = _str_to_bool(args.web_extraction_enabled) + + if getattr(args, "allow_subscriptions", None) is not None: + # Inverted: REST API exposes this as disableSubscriptions. + site_item.disable_subscriptions = not args.allow_subscriptions + + if getattr(args, "allow_web_authoring", None) is not None: + site_item.authoring_enabled = args.allow_web_authoring + + if getattr(args, "allow_mobile_snapshots", None) is not None: + site_item.sheet_image_enabled = args.allow_mobile_snapshots + + if getattr(args, "use_default_time_zone", None): + # Only set use_default_time_zone; do NOT set time_zone -- argparse + # already enforces mutual exclusion between the two flags. + site_item.use_default_time_zone = True + elif getattr(args, "time_zone", None) is not None: + site_item.time_zone = args.time_zone + try: logger.info(_("editsite.status").format(site_item.name)) server.sites.update(site_item) diff --git a/tabcmd/execution/global_options.py b/tabcmd/execution/global_options.py index 1a24133c..282155ec 100644 --- a/tabcmd/execution/global_options.py +++ b/tabcmd/execution/global_options.py @@ -181,6 +181,128 @@ def set_site_status_arg(parser): return parser +# only in edit-site: flags that map onto TSC SiteItem attributes but are not +# currently wired in create_site_command. Kept out of set_common_site_args so +# createsite --help does not list options its run_command ignores. See #437. +def set_edit_site_only_args(parser): + # guest access: Classic passes true/false as a string value + parser.add_argument( + "-g", + "--guest-access-enabled", + choices=["true", "false"], + help=_("editsite.options.guest_access_enabled"), + ) + + # cache warmup: paired positive/negative boolean, default None so an + # unpassed flag is a no-op (leaves the server setting unchanged) + cache_warmup_group = parser.add_mutually_exclusive_group() + cache_warmup_group.add_argument( + "--cache-warmup", + dest="cache_warmup_enabled", + action="store_true", + default=None, + help=_("editsite.options.cache_warmup"), + ) + cache_warmup_group.add_argument( + "--no-cache-warmup", + dest="cache_warmup_enabled", + action="store_false", + default=None, + help=_("editsite.options.no_cache_warmup"), + ) + + # subscription email: also flips custom_subscription_email_enabled + parser.add_argument( + "-e", + "--subscription-email", + default=None, + help=_("editsite.options.subscription_email"), + ) + + # subscription footer: also flips custom_subscription_footer_enabled. + # -f matches Classic tabcmd editsite parity; editsite does not use -f for --filename. + parser.add_argument( + "-f", + "--subscription-footer", + default=None, + help=_("editsite.options.subscription_footer"), + ) + + # web extraction: string value, coerced to bool at wire time + parser.add_argument( + "--web-extraction-enabled", + choices=["true", "false"], + help=_("editsite.options.web_extraction_enabled"), + ) + + # allow subscriptions: inverted onto disable_subscriptions in run_command + allow_subs_group = parser.add_mutually_exclusive_group() + allow_subs_group.add_argument( + "--allow-subscriptions", + dest="allow_subscriptions", + action="store_true", + default=None, + help=_("editsite.options.allow_subscriptions"), + ) + allow_subs_group.add_argument( + "--no-allow-subscriptions", + dest="allow_subscriptions", + action="store_false", + default=None, + help=_("editsite.options.no_allow_subscriptions"), + ) + + # allow web authoring: direct mapping onto authoring_enabled + authoring_group = parser.add_mutually_exclusive_group() + authoring_group.add_argument( + "--allow-web-authoring", + dest="allow_web_authoring", + action="store_true", + default=None, + help=_("editsite.options.allow_web_authoring"), + ) + authoring_group.add_argument( + "--no-allow-web-authoring", + dest="allow_web_authoring", + action="store_false", + default=None, + help=_("editsite.options.no_allow_web_authoring"), + ) + + # allow mobile snapshots: maps onto sheet_image_enabled (NOT + # mobile_biometrics_enabled, which is a separate biometric-auth feature) + mobile_group = parser.add_mutually_exclusive_group() + mobile_group.add_argument( + "--allow-mobile-snapshots", + dest="allow_mobile_snapshots", + action="store_true", + default=None, + help=_("editsite.options.allow_mobile_snapshots"), + ) + mobile_group.add_argument( + "--no-allow-mobile-snapshots", + dest="allow_mobile_snapshots", + action="store_false", + default=None, + help=_("editsite.options.no_allow_mobile_snapshots"), + ) + + # time zone: string value, mutually exclusive with --use-default-time-zone + tz_group = parser.add_mutually_exclusive_group() + tz_group.add_argument( + "--time-zone", + default=None, + help=_("editsite.options.time_zone"), + ) + tz_group.add_argument( + "--use-default-time-zone", + action="store_true", + default=None, + help=_("editsite.options.use_default_time_zone"), + ) + return parser + + # mismatched arguments: createsite says --url, editsite says --site-id # just let both commands use either of them def set_site_id_args(parser): diff --git a/tabcmd/locales/en/tabcmd_messages_en.properties b/tabcmd/locales/en/tabcmd_messages_en.properties index 7eda4bff..580cf5d2 100644 --- a/tabcmd/locales/en/tabcmd_messages_en.properties +++ b/tabcmd/locales/en/tabcmd_messages_en.properties @@ -44,6 +44,20 @@ editsite.options.site-name=Display name of the site editsite.options.status=Change availability of site. Must be either ''{0}'' or ''{1}'' editsite.options.extract_encryption_mode=Extract encryption mode: disabled, enabled, or enforced. If not set, then it is not changed on the server. editsite.options.run_now_enabled=Allow [or deny] Run Now option for this site. Default is set to allow Run Now +editsite.options.guest_access_enabled=Allow or deny guest access to the site. Value must be ''true'' or ''false'' +editsite.options.cache_warmup=Enable cache warmup for the site +editsite.options.no_cache_warmup=Disable cache warmup for the site +editsite.options.subscription_email=Custom "from" email address used for subscription emails. Pass an empty string to clear +editsite.options.subscription_footer=Custom footer text appended to subscription emails. Pass an empty string to clear +editsite.options.web_extraction_enabled=Allow or deny web data extraction on the site. Value must be ''true'' or ''false'' +editsite.options.allow_subscriptions=Allow subscriptions on the site +editsite.options.no_allow_subscriptions=Disable subscriptions on the site +editsite.options.allow_web_authoring=Allow web authoring on the site +editsite.options.no_allow_web_authoring=Disable web authoring on the site +editsite.options.allow_mobile_snapshots=Allow mobile app snapshots (sheet images) on the site +editsite.options.no_allow_mobile_snapshots=Disable mobile app snapshots (sheet images) on the site +editsite.options.time_zone=Time zone for the site (IANA identifier, e.g. America/Los_Angeles). Mutually exclusive with --use-default-time-zone +editsite.options.use_default_time_zone=Use the server default time zone for the site. Mutually exclusive with --time-zone editsite.short_description=Edit a site editsite.status=Edit site ''{0}'' on the server... encryptextracts.short_description=Encrypt extracts on a site diff --git a/tests/parsers/test_parser_edit_site.py b/tests/parsers/test_parser_edit_site.py index 98121e70..33c74bbb 100644 --- a/tests/parsers/test_parser_edit_site.py +++ b/tests/parsers/test_parser_edit_site.py @@ -59,3 +59,142 @@ def test_edit_site_parser_optional_arguments_archive(self): assert args.site_id == "1234", args assert args.status == "ACTIVE", args assert args.run_now_enabled == "true", args + + # -------- New flags added for issue #437 -------- + + def test_edit_site_parser_guest_access_enabled_true(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--guest-access-enabled", "true"]) + assert args.guest_access_enabled == "true", args + + def test_edit_site_parser_guest_access_enabled_short_flag(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "-g", "false"]) + assert args.guest_access_enabled == "false", args + + def test_edit_site_parser_guest_access_enabled_rejects_invalid(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args([commandname, "site-to-edit", "--guest-access-enabled", "yes"]) + + def test_edit_site_parser_cache_warmup_enabled(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--cache-warmup"]) + assert args.cache_warmup_enabled is True, args + + def test_edit_site_parser_cache_warmup_disabled(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--no-cache-warmup"]) + assert args.cache_warmup_enabled is False, args + + def test_edit_site_parser_cache_warmup_default_is_none(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit"]) + assert args.cache_warmup_enabled is None, args + + def test_edit_site_parser_cache_warmup_mutually_exclusive(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args([commandname, "site-to-edit", "--cache-warmup", "--no-cache-warmup"]) + + def test_edit_site_parser_subscription_email(self): + args = self.parser_under_test.parse_args( + [commandname, "site-to-edit", "--subscription-email", "alerts@example.com"] + ) + assert args.subscription_email == "alerts@example.com", args + + def test_edit_site_parser_subscription_email_short_flag(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "-e", "hi@example.com"]) + assert args.subscription_email == "hi@example.com", args + + def test_edit_site_parser_subscription_footer(self): + args = self.parser_under_test.parse_args( + [commandname, "site-to-edit", "--subscription-footer", "Contact IT for help"] + ) + assert args.subscription_footer == "Contact IT for help", args + + def test_edit_site_parser_subscription_footer_short_flag(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "-f", "Contact IT"]) + assert args.subscription_footer == "Contact IT", args + + def test_edit_site_parser_web_extraction_enabled(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--web-extraction-enabled", "true"]) + assert args.web_extraction_enabled == "true", args + + def test_edit_site_parser_web_extraction_enabled_rejects_invalid(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args([commandname, "site-to-edit", "--web-extraction-enabled", "yes"]) + + def test_edit_site_parser_allow_subscriptions(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--allow-subscriptions"]) + assert args.allow_subscriptions is True, args + + def test_edit_site_parser_no_allow_subscriptions(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--no-allow-subscriptions"]) + assert args.allow_subscriptions is False, args + + def test_edit_site_parser_allow_subscriptions_default_is_none(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit"]) + assert args.allow_subscriptions is None, args + + def test_edit_site_parser_allow_subscriptions_mutually_exclusive(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args( + [ + commandname, + "site-to-edit", + "--allow-subscriptions", + "--no-allow-subscriptions", + ] + ) + + def test_edit_site_parser_allow_web_authoring(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--allow-web-authoring"]) + assert args.allow_web_authoring is True, args + + def test_edit_site_parser_no_allow_web_authoring(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--no-allow-web-authoring"]) + assert args.allow_web_authoring is False, args + + def test_edit_site_parser_allow_web_authoring_mutually_exclusive(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args( + [ + commandname, + "site-to-edit", + "--allow-web-authoring", + "--no-allow-web-authoring", + ] + ) + + def test_edit_site_parser_allow_mobile_snapshots(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--allow-mobile-snapshots"]) + assert args.allow_mobile_snapshots is True, args + + def test_edit_site_parser_no_allow_mobile_snapshots(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--no-allow-mobile-snapshots"]) + assert args.allow_mobile_snapshots is False, args + + def test_edit_site_parser_allow_mobile_snapshots_mutually_exclusive(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args( + [ + commandname, + "site-to-edit", + "--allow-mobile-snapshots", + "--no-allow-mobile-snapshots", + ] + ) + + def test_edit_site_parser_time_zone(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--time-zone", "America/Los_Angeles"]) + assert args.time_zone == "America/Los_Angeles", args + + def test_edit_site_parser_use_default_time_zone(self): + args = self.parser_under_test.parse_args([commandname, "site-to-edit", "--use-default-time-zone"]) + assert args.use_default_time_zone is True, args + + def test_edit_site_parser_time_zone_mutually_exclusive(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args( + [ + commandname, + "site-to-edit", + "--time-zone", + "UTC", + "--use-default-time-zone", + ] + )