1# Copyright 2013 The Distro Tracker Developers
2# See the COPYRIGHT file at the top-level directory of this distribution and
3# at https://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 https://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"""
11Implements classes and functions related to commands which require confirmation
12and confirming such commands.
13"""
15from distro_tracker.core.utils import get_or_none
16from distro_tracker.mail.control.commands.base import Command
17from distro_tracker.mail.models import CommandConfirmation
20def needs_confirmation(klass):
21 """
22 A class decorator to mark that a
23 :py:class:`Command <distro_tracker.mail.control.commands.base.Command>`
24 subclass requires confirmation before it is executed.
26 Classes decorated by this decorator can provide two additional methods:
28 - ``pre_confirm`` - for actions which should come before asking for
29 confirmation for the command. If this method does not return an
30 object which evalutes as a True Boolean, no confirmation is sent.
31 It should also make sure to add appropriate status messages to the
32 response.
33 If the method is not provided, then a default response indicating that
34 a confirmation is required is output.
36 - ``get_confirmation_message`` - Method which should return a string
37 containing an additional message to be included in the confirmation
38 email.
39 """
40 klass.needs_confirmation = True
41 klass.is_confirmed = False
43 def pre_confirm_default(self):
44 self.reply('A confirmation mail has been sent to ' + self.user_email)
45 return True
47 def decorate_call(func):
48 def wrapper(self):
49 # When the command is confirmed perform the default action
50 if self.is_confirmed:
51 return func(self)
52 # If the command is not confirmed, first try to run a pre_confirm
53 # method if it is provided.
54 should_confirm = True
55 pre_confirm = getattr(self, 'pre_confirm', None)
56 if pre_confirm: 56 ↛ 61line 56 didn't jump to line 61, because the condition on line 56 was never false
57 should_confirm = pre_confirm()
59 # Add the command and a custom confirmation message to the set of
60 # all commands requiring confirmation.
61 if should_confirm:
62 self.confirmation_set.add_command(
63 self.user_email,
64 self.get_command_text(),
65 self.get_confirmation_message())
67 # After that get the response to the command.
68 # The handle method becomes a no-op
69 handle = self.handle
70 self.handle = lambda: None # noqa
71 out = func(self)
72 # handle returned to the normal method.
73 self.handle = handle
74 # Finally return the response to the command
75 return out
77 return wrapper
79 klass.__call__ = decorate_call(klass.__call__)
80 # Add place-holders for the two optional methods if the class itself did
81 # not define them.
82 if not getattr(klass, 'get_confirmation_message', None):
83 klass.get_confirmation_message = lambda self: '' # noqa
84 if not getattr(klass, 'pre_confirm', None): 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true
85 klass.pre_confirm = pre_confirm_default
87 return klass
90class ConfirmCommand(Command):
91 """
92 The command used to confirm other commands which require confirmation.
93 """
94 META = {
95 'description': """confirm <confirmation-key>
96 Confirm a previously requested action, such as subscribing or
97 unsubscribing from a package.""",
98 'name': 'confirm',
99 'position': 3,
100 }
102 REGEX_LIST = (
103 r'\s+(?P<confirmation_key>\S+)$',
104 )
106 def __init__(self, confirmation_key):
107 super(ConfirmCommand, self).__init__()
108 self.confirmation_key = confirmation_key
110 def get_command_text(self):
111 return Command.get_command_text(self, self.confirmation_key)
113 def handle(self):
114 from distro_tracker.mail.control.commands import CommandFactory, \
115 CommandProcessor
117 command_confirmation = get_or_none(
118 CommandConfirmation,
119 confirmation_key=self.confirmation_key)
120 if not command_confirmation:
121 self.error('Confirmation failed: unknown key.')
122 return
123 lines = command_confirmation.commands.splitlines()
124 processor = CommandProcessor(CommandFactory({}), confirmed=True)
126 processor.process(lines)
127 if processor.is_success(): 127 ↛ 131line 127 didn't jump to line 131, because the condition on line 127 was never false
128 self.reply('Successfully confirmed commands:')
129 self.reply(processor.get_output())
130 else:
131 self.error('No commands confirmed.')
132 self.reply(processor.get_output())
134 command_confirmation.delete()