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""" 

14 

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 

18 

19 

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. 

25 

26 Classes decorated by this decorator can provide two additional methods: 

27 

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. 

35 

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 

42 

43 def pre_confirm_default(self): 

44 self.reply('A confirmation mail has been sent to ' + self.user_email) 

45 return True 

46 

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() 

58 

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()) 

66 

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 

76 

77 return wrapper 

78 

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 

86 

87 return klass 

88 

89 

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 } 

101 

102 REGEX_LIST = ( 

103 r'\s+(?P<confirmation_key>\S+)$', 

104 ) 

105 

106 def __init__(self, confirmation_key): 

107 super(ConfirmCommand, self).__init__() 

108 self.confirmation_key = confirmation_key 

109 

110 def get_command_text(self): 

111 return Command.get_command_text(self, self.confirmation_key) 

112 

113 def handle(self): 

114 from distro_tracker.mail.control.commands import CommandFactory, \ 

115 CommandProcessor 

116 

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) 

125 

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()) 

133 

134 command_confirmation.delete()