From a987afd5d3c670b0ac39d84bab912fbacbbbb8ef Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 5 Oct 2023 15:51:36 -0400 Subject: [PATCH] bemade_document_versions: successfully uploading a new revision via the Kanban view. --- bemade_document_versions/__init__.py | 1 + bemade_document_versions/__manifest__.py | 6 +- .../data/document_revision_data.xml | 8 +- bemade_document_versions/models/__init__.py | 5 +- bemade_document_versions/models/document.py | 100 +++++++++--------- .../models/document_revision.py | 15 +++ .../models/document_revision_sequence.py | 11 +- bemade_document_versions/models/workflow.py | 28 +++++ .../security/ir.model.access.csv | 5 + .../views/document_views.xml | 12 +++ bemade_document_versions/wizard/__init__.py | 1 + .../wizard/document_revision_wizard.py | 63 +++++++++-- .../wizard/document_revision_wizard.xml | 32 ++++++ 13 files changed, 221 insertions(+), 66 deletions(-) create mode 100644 bemade_document_versions/models/workflow.py create mode 100644 bemade_document_versions/security/ir.model.access.csv create mode 100644 bemade_document_versions/views/document_views.xml create mode 100644 bemade_document_versions/wizard/__init__.py create mode 100644 bemade_document_versions/wizard/document_revision_wizard.xml diff --git a/bemade_document_versions/__init__.py b/bemade_document_versions/__init__.py index 899bcc9..a22f984 100644 --- a/bemade_document_versions/__init__.py +++ b/bemade_document_versions/__init__.py @@ -1,2 +1,3 @@ from . import models +from . import wizard diff --git a/bemade_document_versions/__manifest__.py b/bemade_document_versions/__manifest__.py index 20d23ae..80c37f6 100644 --- a/bemade_document_versions/__manifest__.py +++ b/bemade_document_versions/__manifest__.py @@ -25,7 +25,11 @@ 'website': 'https://www.bemade.org', 'license': 'OPL-1', 'depends': ['documents', 'mail'], - 'data': [], + 'data': ['security/ir.model.access.csv', + 'data/document_revision_data.xml', + 'views/document_views.xml', + 'wizard/document_revision_wizard.xml', + ], 'installable': True, 'auto_install': False, } diff --git a/bemade_document_versions/data/document_revision_data.xml b/bemade_document_versions/data/document_revision_data.xml index 1de5b39..611fb26 100644 --- a/bemade_document_versions/data/document_revision_data.xml +++ b/bemade_document_versions/data/document_revision_data.xml @@ -1,12 +1,18 @@ - + Default Document Revision Sequence document.revision standard DOC/ 2 + + 0 + Revise + + documents.revision + \ No newline at end of file diff --git a/bemade_document_versions/models/__init__.py b/bemade_document_versions/models/__init__.py index 69e0c5b..5648a48 100644 --- a/bemade_document_versions/models/__init__.py +++ b/bemade_document_versions/models/__init__.py @@ -1,3 +1,4 @@ -from . import document_revision from . import document -from . import document_sequence +from . import document_revision +from . import document_revision_sequence +from . import workflow diff --git a/bemade_document_versions/models/document.py b/bemade_document_versions/models/document.py index 0e6684c..8ee2940 100644 --- a/bemade_document_versions/models/document.py +++ b/bemade_document_versions/models/document.py @@ -3,68 +3,68 @@ from odoo.exceptions import ValidationError class Document(models.Model): - _inherit = ['documents.document'] + _inherit = 'documents.document' current_revision_id = fields.Many2one('documents.revision', 'Current Revision') revision_ids = fields.One2many('documents.revision', 'document_id') - revision_sequence = fields.Many2one('documents.revision.sequence', 'document_id', + revision_sequence = fields.Many2one('ir.sequence', 'Revision Sequence', ) - track_revisions = fields.Boolean(default=False) - # set copy=False for number_next - number_next = fields.Integer(string='Next Number', required=True, default=1, - copy=False, help="Next number of this sequence") - @api.constrains('revision_ids', 'revision_sequence', 'track_revisions') - def constrain_revisions(self): - for rec in self: - revision_fields = [rec.revision_ids, rec.revision_sequence, - rec.track_revisions] - if any(revision_fields) and not all(revision_fields): - raise ValidationError(_('A revision sequence must be selected to track' - ' revisions.')) - - def write(self, vals): - super().write(vals) - if self.track_revisions and 'track_revisions' in vals \ - and not vals['track_revisions']: - raise ValidationError(_('Revision tracking cannot be disabled after it has' - 'been turned on for a document.')) - self._check_revision_fields() - - @api.model_create_multi - def create(self, vals): - res = super().create(vals) - for rec in res: - rec._check_revision_fields() - return res - - def _check_revision_fields(self): - """ Helper method to check that all three fields `track_revisions`, - `revisions_sequence` and `revision_ids` are properly set if any one of them is set. - :return: None - """ - self.ensure_one() - revision_fields = [self.revision_ids, self.revision_sequence, - self.track_revisions] - if any(revision_fields) and not all(revision_fields): - self._set_up_revisions() - - def _set_up_revisions(self): + def set_up_revisions(self, initial_revision_name: str = None, + sequence_prefix: str = None, + sequence_suffix: str = None, sequence_padding: int = None): """ Helper method to set up revisions, no matter how we got into tracking them. - :return: None + :param initial_revision_name str: The name to give to the initial revision. By + default, this will be the next name from the sequence. + :param sequence_prefix: The prefix to use for the new revision sequence. + :param sequence_suffix: The suffix to use for the new revision sequence. + :return None: """ self.ensure_one() - self.track_revisions = True # may already be true, but no matter - self.revision_sequence = self.revision_sequence or self.env.ref( - 'bemade_document_versions.document_revision_sequence_default') - self.revision_ids = self.revision_ids or Command.set( - [self._create_first_revision()]) + self.revision_sequence = self.revision_sequence or self._create_rev_sequence( + sequence_prefix, sequence_suffix, sequence_padding) + self.revision_ids = self.revision_ids or [Command.set( + [self._create_first_revision(initial_revision_name).id])] - def _create_first_revision(self): + def _create_rev_sequence(self, sequence_prefix: str = None, + sequence_suffix: str = None, sequence_padding: int = None): + """ + Helper method to create a new sequence for a document that we are just starting + to track revisions for. + + :param sequence_prefix: The prefix to use for the sequence. + :param sequence_suffix: The suffix to use for the sequence. + :param sequence_padding: The padding to use for the sequence. + :return: The created ir.sequence record. + """ + return self.env['ir.sequence'].create({ + 'document_id': self.id, + 'name': f'sequence_doc{self.id}', + 'prefix': sequence_prefix, + 'suffix': sequence_suffix, + 'padding': sequence_padding, + 'implementation': 'standard', + }) + + def _create_first_revision(self, name: str = None): """ Creates a new documents.revision record to associate to this Document + + :param name: Optional name to use for the newly created revision. :return: a single, new documents.revision record """ self.ensure_one() - return self.env['documents.revision'].create({'document_id': self.id, }) + name = name or self.revision_sequence.get_next_char(0) + return self.env['documents.revision'].create({ + 'document_id': self.id, + 'name': name, + 'attachment_id': self.attachment_id.id, + }) + + def get_next_revision_name(self): + """ + :return str: The name of the next revision in the sequence or None if revision + tracking is not yet set up. + """ + return self.revision_sequence.get_next_char(self.revision_sequence._next()) diff --git a/bemade_document_versions/models/document_revision.py b/bemade_document_versions/models/document_revision.py index 70f333f..240d82f 100644 --- a/bemade_document_versions/models/document_revision.py +++ b/bemade_document_versions/models/document_revision.py @@ -4,7 +4,11 @@ from odoo import models, fields, _, api class DocumentRevision(models.Model): _name = "documents.revision" _description = "Document Revision" + _sql_constraints = [ + ('name_document_id_unique', 'unique (name,document_id)', + 'The revision name must be unique for each document.')] + name = fields.Char() document_id = fields.Many2one('documents.document', required=True) attachment_id = fields.Many2one('ir.attachment', required=True) previous_revision_id = fields.Many2one('documents.revision', 'Previous Revision') @@ -12,6 +16,16 @@ class DocumentRevision(models.Model): @api.model_create_multi def create(self, vals_list): + """ + Creates one or more new document revisions. Each revision created replaces the + ir.attachment tied to the document with the one provided in this revision. It + also supersedes the previous revision, updating the document's + current_revision_id and carrying a link to the previous revision in + previous_revision_id. + + :param vals_list: Dictionary or list of dictionaries + :return: + """ res = super().create(vals_list) for rec in res: # When we create a new revision, we need to replace the attachment linked @@ -21,3 +35,4 @@ class DocumentRevision(models.Model): # back to the previous revisions rec.previous_revision_id = rec.document_id.current_revision_id rec.document_id.current_revision_id = rec + return res diff --git a/bemade_document_versions/models/document_revision_sequence.py b/bemade_document_versions/models/document_revision_sequence.py index 167d645..dee825d 100644 --- a/bemade_document_versions/models/document_revision_sequence.py +++ b/bemade_document_versions/models/document_revision_sequence.py @@ -1,11 +1,12 @@ from odoo import models, fields, api, _ +from odoo.exceptions import UserError class DocumentRevisionSequence(models.Model): - """ Creates an independent sequence for each Document when the document is set to - track - """ - _name = 'documents.revision.sequence' _inherit = 'ir.sequence' + # Would prefer to inherit and make a new model, but the way ir.sequence is + # implemented has a lot of hard coded references to "ir.sequence" for all the + # database operations. - document_id = fields.Many2one('documents.document', 'Document', ondelete='cascade') \ No newline at end of file + document_id = fields.Many2one('documents.document', 'Document', + ondelete='cascade') diff --git a/bemade_document_versions/models/workflow.py b/bemade_document_versions/models/workflow.py new file mode 100644 index 0000000..7a3ced9 --- /dev/null +++ b/bemade_document_versions/models/workflow.py @@ -0,0 +1,28 @@ +from odoo import models, fields, _ +from odoo.exceptions import UserError + + +class WorkflowActionRuleRevision(models.Model): + _inherit = ['documents.workflow.rule'] + + create_model = fields.Selection(selection_add=[('documents.revision', "Revision")]) + + def create_record(self, documents=None): + rv = super().create_record(documents=documents) + if self.create_model == 'documents.revision': + if len(documents) != 1: + raise UserError(_('Document revisions must be added for one and ' + 'only one document at a time.')) + ctx = {'document_id': documents[0].id} + ctx.update(self._context) + return { + 'type': 'ir.actions.act_window', + 'res_model': 'documents.revision.wizard', + 'name': 'New Revision', + 'target': 'new', + 'context': ctx, + 'views': [(self.env.ref( + 'bemade_document_versions.document_revision_wizard_view_form').id, + 'form')], + 'view_mode': 'form', + } diff --git a/bemade_document_versions/security/ir.model.access.csv b/bemade_document_versions/security/ir.model.access.csv new file mode 100644 index 0000000..a9ae138 --- /dev/null +++ b/bemade_document_versions/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_document_revision_user,Document Revision Access,model_documents_revision,documents.group_documents_user,1,1,1,1 +access_document_revision_manager,Document Revision Manager Access,model_documents_revision,documents.group_documents_manager,1,1,1,1 +access_document_revision_wizard_user,Document Revision Wizard Access,model_documents_revision_wizard,documents.group_documents_user,1,1,1,1 +access_document_revision_wizard_manager,Document Revision Wizard Manager Access,model_documents_revision_wizard,documents.group_documents_manager,1,1,1,1 diff --git a/bemade_document_versions/views/document_views.xml b/bemade_document_versions/views/document_views.xml new file mode 100644 index 0000000..d8d4049 --- /dev/null +++ b/bemade_document_versions/views/document_views.xml @@ -0,0 +1,12 @@ + + + + Upload Document Revision + documents.revision.wizard + + action + list,form,kanban + form + new + + \ No newline at end of file diff --git a/bemade_document_versions/wizard/__init__.py b/bemade_document_versions/wizard/__init__.py new file mode 100644 index 0000000..e8b027a --- /dev/null +++ b/bemade_document_versions/wizard/__init__.py @@ -0,0 +1 @@ +from . import document_revision_wizard diff --git a/bemade_document_versions/wizard/document_revision_wizard.py b/bemade_document_versions/wizard/document_revision_wizard.py index 13a4995..e117594 100644 --- a/bemade_document_versions/wizard/document_revision_wizard.py +++ b/bemade_document_versions/wizard/document_revision_wizard.py @@ -1,4 +1,4 @@ -from odoo import models, fields, _ +from odoo import models, fields, _, api from odoo.exceptions import UserError @@ -7,13 +7,62 @@ class DocumentRevisionWizard(models.TransientModel): _description = 'Allows the creation of new document revisions' document_id = fields.Many2one('documents.document', 'Document') + document_name = fields.Char() file = fields.Binary('File to upload') - revision_name = fields.Char() + revision_name = fields.Char(required=True) + revision_sequence_prefix = fields.Char(string='Sequence Prefix') + revision_sequence_suffix = fields.Char(string='Sequence Suffix') + revision_sequence_padding = fields.Integer(string='Sequence Padding', + help="Number of digits to show in the" + " sequence number. A value of 2 will" + " generate sequence numbers like 01," + " 02, 03, etc.") def default_get(self, fields_list): ctx = self._context - if 'active_ids' in ctx and len(ctx.get('active_ids')) > 1: - raise UserError(_('You can only create revisions for one document at a time.')) - if 'active_id' not in ctx: - raise UserError(_('You must select a document for which you are creating a revision.')) - self.document_id = self.env['documents.document'].browse(ctx.get('active_id')) + vals = {} + if 'document_id' not in ctx: + raise UserError( + _('You must select a document for which you are creating a revision.')) + document = self.env['documents.document'].browse(ctx.get('document_id')) + vals['document_name'] = document.name + vals['document_id'] = document.id + sequence = document.revision_sequence or self.env.ref( + 'bemade_document_versions.document_revision_sequence_default') + if document.revision_sequence: + vals['revision_name'] = self.document_id.get_next_revision_name() + else: + vals['revision_name'] = sequence.get_next_char(0) + vals['revision_sequence_prefix'] = sequence.prefix + vals['revision_sequence_suffix'] = sequence.suffix + vals['revision_sequence_padding'] = sequence.padding + return vals + + def action_upload_revision(self): + for wizard in self: + if not wizard.file: + raise UserError(_('You must upload a file.')) + if not wizard.revision_name: + raise UserError(_('Revision name cannot be empty.')) + prev_attachment = wizard.document_id.attachment_id + attachment = self.env['ir.attachment'].with_context( + {'no_document': True}).create({ + 'name': prev_attachment.name, + 'datas': wizard.file, + 'res_model': prev_attachment.res_model, + 'res_id': prev_attachment.res_id, + 'company_id': prev_attachment.company_id.id, + 'public': prev_attachment.public, + }) + if not wizard.document_id.revision_sequence: + wizard.document_id.set_up_revisions(wizard.revision_name, + wizard.revision_sequence_prefix, + wizard.revision_sequence_suffix, + wizard.revision_sequence_padding) + else: + self.env['documents.revision'].create({ + 'document_id': wizard.document_id.id, + 'attachment_id': attachment.id, + 'name': wizard.revision_name, + 'attachment_id': attachment.id, + }) diff --git a/bemade_document_versions/wizard/document_revision_wizard.xml b/bemade_document_versions/wizard/document_revision_wizard.xml new file mode 100644 index 0000000..4021061 --- /dev/null +++ b/bemade_document_versions/wizard/document_revision_wizard.xml @@ -0,0 +1,32 @@ + + + + bemade_document_versions.document.revision.wizard.form + documents.revision.wizard + +
+
+ +
+ + + + + + + + + + + + + + +
+
+
+
\ No newline at end of file