confirmation.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. # Copyright 2013 The Distro Tracker Developers
  2. # See the COPYRIGHT file at the top-level directory of this distribution and
  3. # at http://deb.li/DTAuthors
  4. #
  5. # This file is part of Distro Tracker. It is subject to the license terms
  6. # in the LICENSE file found in the top-level directory of this
  7. # distribution and at http://deb.li/DTLicense. No part of Distro Tracker,
  8. # including this file, may be copied, modified, propagated, or distributed
  9. # except according to the terms contained in the LICENSE file.
  10. """
  11. Implements classes and functions related to commands which require confirmation
  12. and confirming such commands.
  13. """
  14. from __future__ import unicode_literals
  15. from distro_tracker.core.utils import get_or_none
  16. from distro_tracker.mail.models import CommandConfirmation
  17. from distro_tracker.mail.control.commands.base import Command
  18. def needs_confirmation(klass):
  19. """
  20. A class decorator to mark that a
  21. :py:class:`Command <distro_tracker.mail.control.commands.base.Command>`
  22. subclass requires confirmation before it is executed.
  23. Classes decorated by this decorator can provide two additional methods:
  24. - ``pre_confirm`` - for actions which should come before asking for
  25. confirmation for the command. If this method does not return an
  26. object which evalutes as a True Boolean, no confirmation is sent.
  27. It should also make sure to add appropriate status messages to the
  28. response.
  29. If the method is not provided, then a default response indicating that
  30. a confirmation is required is output.
  31. - ``get_confirmation_message`` - Method which should return a string
  32. containing an additional message to be included in the confirmation
  33. email.
  34. """
  35. klass.needs_confirmation = True
  36. klass.is_confirmed = False
  37. def pre_confirm_default(self):
  38. self.reply('A confirmation mail has been sent to ' + self.user_email)
  39. return True
  40. def decorate_call(func):
  41. def wrapper(self):
  42. # When the command is confirmed perform the default action
  43. if self.is_confirmed:
  44. return func(self)
  45. # If the command is not confirmed, first try to run a pre_confirm
  46. # method if it is provided.
  47. should_confirm = True
  48. pre_confirm = getattr(self, 'pre_confirm', None)
  49. if pre_confirm:
  50. should_confirm = pre_confirm()
  51. # Add the command and a custom confirmation message to the set of
  52. # all commands requiring confirmation.
  53. if should_confirm:
  54. self.confirmation_set.add_command(
  55. self.user_email,
  56. self.get_command_text(),
  57. self.get_confirmation_message())
  58. # After that get the response to the command.
  59. # The handle method becomes a no-op
  60. handle = self.handle
  61. self.handle = lambda: None # noqa
  62. out = func(self)
  63. # handle returned to the normal method.
  64. self.handle = handle
  65. # Finally return the response to the command
  66. return out
  67. return wrapper
  68. klass.__call__ = decorate_call(klass.__call__)
  69. # Add place-holders for the two optional methods if the class itself did
  70. # not define them.
  71. if not getattr(klass, 'get_confirmation_message', None):
  72. klass.get_confirmation_message = lambda self: '' # noqa
  73. if not getattr(klass, 'pre_confirm', None):
  74. klass.pre_confirm = pre_confirm_default
  75. return klass
  76. class ConfirmCommand(Command):
  77. """
  78. The command used to confirm other commands which require confirmation.
  79. """
  80. META = {
  81. 'description': """confirm <confirmation-key>
  82. Confirm a previously requested action, such as subscribing or
  83. unsubscribing from a package.""",
  84. 'name': 'confirm',
  85. 'position': 3,
  86. }
  87. REGEX_LIST = (
  88. r'\s+(?P<confirmation_key>\S+)$',
  89. )
  90. def __init__(self, confirmation_key):
  91. super(ConfirmCommand, self).__init__()
  92. self.confirmation_key = confirmation_key
  93. def get_command_text(self):
  94. return Command.get_command_text(self, self.confirmation_key)
  95. def handle(self):
  96. from distro_tracker.mail.control.commands import CommandFactory, \
  97. CommandProcessor
  98. command_confirmation = get_or_none(
  99. CommandConfirmation,
  100. confirmation_key=self.confirmation_key)
  101. if not command_confirmation:
  102. self.error('Confirmation failed: unknown key.')
  103. return
  104. lines = command_confirmation.commands.splitlines()
  105. processor = CommandProcessor(CommandFactory({}), confirmed=True)
  106. processor.process(lines)
  107. if processor.is_success():
  108. self.reply('Successfully confirmed commands:')
  109. self.reply(processor.get_output())
  110. else:
  111. self.error('No commands confirmed.')
  112. self.reply(processor.get_output())
  113. command_confirmation.delete()