Coverage for wizards / lox_restore_wizard.py: 37%

81 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-28 01:16 +0000

1# -*- coding: utf-8 -*- 

2 

3from odoo import models, fields, api, _ 

4from odoo.exceptions import UserError, ValidationError 

5import logging 

6 

7_logger = logging.getLogger(__name__) 

8 

9 

10class LoxRestoreWizard(models.TransientModel): 

11 _name = 'lox.restore.wizard' 

12 _description = 'LOX Backup Restore Wizard' 

13 

14 CONFIRMATION_TEXT = 'RESTORE' 

15 

16 config_id = fields.Many2one( 

17 'lox.backup.config', 

18 string='Configuration', 

19 required=True, 

20 default=lambda self: self._default_config_id(), 

21 ) 

22 backup_uuid = fields.Char( 

23 string='Backup UUID', 

24 required=True, 

25 readonly=True, 

26 ) 

27 backup_name = fields.Char( 

28 string='Backup Name', 

29 readonly=True, 

30 ) 

31 backup_date = fields.Datetime( 

32 string='Backup Date', 

33 readonly=True, 

34 ) 

35 backup_size = fields.Char( 

36 string='Backup Size', 

37 readonly=True, 

38 ) 

39 backup_component = fields.Char( 

40 string='Component', 

41 readonly=True, 

42 ) 

43 backup_status = fields.Char( 

44 string='Status', 

45 readonly=True, 

46 ) 

47 

48 # Confirmation field 

49 confirmation_text = fields.Char( 

50 string='Confirmation', 

51 required=True, 

52 help='Type RESTORE to confirm the restoration', 

53 ) 

54 

55 # Display fields 

56 warning_message = fields.Html( 

57 string='Warning', 

58 compute='_compute_warning_message', 

59 ) 

60 

61 @api.model 

62 def _default_config_id(self): 

63 config = self.env['lox.backup.config'].search([ 

64 ('active', '=', True), 

65 ('source_registered', '=', True), 

66 ], limit=1) 

67 return config.id if config else False 

68 

69 @api.depends('backup_uuid') 

70 def _compute_warning_message(self): 

71 for record in self: 

72 record.warning_message = ''' 

73 <div style="background-color: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 16px; margin-bottom: 16px;"> 

74 <p style="font-weight: bold; color: #991b1b; margin: 0 0 8px 0;"> 

75 ⚠️ Warning: This action is irreversible! 

76 </p> 

77 <p style="color: #991b1b; margin: 0;"> 

78 This will overwrite your current Odoo data. All current database records, 

79 filestore attachments, and configurations will be replaced with the backup data. 

80 This action cannot be undone. 

81 </p> 

82 </div> 

83 ''' 

84 

85 @api.constrains('confirmation_text') 

86 def _check_confirmation_text(self): 

87 for record in self: 

88 if record.confirmation_text and record.confirmation_text.upper().strip() != self.CONFIRMATION_TEXT: 

89 raise ValidationError( 

90 _('Please type %s to confirm the restore.') % self.CONFIRMATION_TEXT 

91 ) 

92 

93 @api.model 

94 def create_for_backup(self, backup_uuid, backup_info=None): 

95 """Create a wizard pre-populated with backup info""" 

96 values = { 

97 'backup_uuid': backup_uuid, 

98 } 

99 

100 if backup_info: 

101 values['backup_name'] = backup_info.get('name', backup_uuid[:8] + '...') 

102 values['backup_date'] = backup_info.get('created_at') 

103 values['backup_component'] = backup_info.get('component', 'full') 

104 values['backup_status'] = backup_info.get('status', 'unknown') 

105 

106 # Format size 

107 size_bytes = backup_info.get('size', backup_info.get('size_bytes', 0)) 

108 if size_bytes: 

109 if size_bytes > 1024 * 1024 * 1024: 

110 values['backup_size'] = f"{size_bytes / (1024*1024*1024):.2f} GB" 

111 elif size_bytes > 1024 * 1024: 

112 values['backup_size'] = f"{size_bytes / (1024*1024):.2f} MB" 

113 elif size_bytes > 1024: 

114 values['backup_size'] = f"{size_bytes / 1024:.2f} KB" 

115 else: 

116 values['backup_size'] = f"{size_bytes} bytes" 

117 

118 return self.create(values) 

119 

120 def action_confirm_restore(self): 

121 """Request restore after confirmation""" 

122 self.ensure_one() 

123 

124 # Validate confirmation text 

125 if self.confirmation_text.upper().strip() != self.CONFIRMATION_TEXT: 

126 raise UserError( 

127 _('Please type %s to confirm the restore.') % self.CONFIRMATION_TEXT 

128 ) 

129 

130 config = self.config_id 

131 if not config.source_registered: 

132 raise UserError(_('Source is not registered. Please register first.')) 

133 

134 api = self.env['lox.api'].create_client(config) 

135 

136 try: 

137 # Request restore from LOX API 

138 result = api.request_restore(self.backup_uuid) 

139 

140 if result.get('success') or result.get('task_id'): 

141 # Log the restore request 

142 self.env['lox.backup.log'].create({ 

143 'config_id': config.id, 

144 'backup_uuid': self.backup_uuid, 

145 'component': 'restore', 

146 'status': 'pending', 

147 'started_at': fields.Datetime.now(), 

148 'notes': _('Restore requested for backup %s') % self.backup_uuid, 

149 }) 

150 

151 return { 

152 'type': 'ir.actions.client', 

153 'tag': 'display_notification', 

154 'params': { 

155 'title': _('Restore Requested'), 

156 'message': _( 

157 'Restore has been requested for backup %s. ' 

158 'You will be notified when the backup is ready for download and restoration.' 

159 ) % (self.backup_uuid[:8] + '...'), 

160 'type': 'success', 

161 'sticky': True, 

162 'next': {'type': 'ir.actions.act_window_close'}, 

163 } 

164 } 

165 else: 

166 raise UserError( 

167 _('Failed to request restore: %s') % result.get('error', 'Unknown error') 

168 ) 

169 

170 except UserError: 

171 raise 

172 except Exception as e: 

173 _logger.exception('Restore request failed') 

174 raise UserError(_('Restore request failed: %s') % str(e)) 

175 

176 def action_cancel(self): 

177 """Cancel the wizard""" 

178 return {'type': 'ir.actions.act_window_close'} 

179 

180 

181class LoxBackupLogRestore(models.Model): 

182 """Extend backup log to add restore action""" 

183 _inherit = 'lox.backup.log' 

184 

185 def action_request_restore(self): 

186 """Open restore confirmation wizard""" 

187 self.ensure_one() 

188 

189 if not self.backup_uuid: 

190 raise UserError(_('No backup UUID available for this log entry.')) 

191 

192 if self.status not in ['completed', 'validated']: 

193 raise UserError(_('Can only restore completed backups.')) 

194 

195 # Get backup info from API 

196 api = self.env['lox.api'].create_client(self.config_id) 

197 backup_info = api.get_backup(self.backup_uuid) 

198 

199 wizard = self.env['lox.restore.wizard'].create_for_backup( 

200 self.backup_uuid, 

201 backup_info.get('data', {}) if backup_info.get('success') else None 

202 ) 

203 

204 return { 

205 'name': _('Confirm Restore'), 

206 'type': 'ir.actions.act_window', 

207 'res_model': 'lox.restore.wizard', 

208 'res_id': wizard.id, 

209 'view_mode': 'form', 

210 'target': 'new', 

211 'context': {'dialog_size': 'medium'}, 

212 }