forked from Mapan/odoo17e
272 lines
11 KiB
Python
272 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from ast import literal_eval
|
|
|
|
from odoo import models, fields, api, exceptions
|
|
from odoo.tools.translate import _
|
|
from odoo.tools import consteq
|
|
|
|
from odoo.osv import expression
|
|
|
|
import uuid
|
|
|
|
|
|
class DocumentShare(models.Model):
|
|
_name = 'documents.share'
|
|
_inherit = ['mail.thread', 'mail.alias.mixin']
|
|
_description = 'Documents Share'
|
|
|
|
folder_id = fields.Many2one('documents.folder', string="Workspace", required=True, ondelete='cascade')
|
|
include_sub_folders = fields.Boolean()
|
|
name = fields.Char(string="Name")
|
|
|
|
access_token = fields.Char(required=True, default=lambda x: str(uuid.uuid4()), groups="documents.group_documents_user")
|
|
full_url = fields.Char(string="URL", compute='_compute_full_url')
|
|
links_count = fields.Integer(string="Number of Links", compute='_compute_links_count')
|
|
date_deadline = fields.Date(string="Valid Until")
|
|
state = fields.Selection([
|
|
('live', "Live"),
|
|
('expired', "Expired"),
|
|
], default='live', compute='_compute_state', string="Status")
|
|
can_upload = fields.Boolean(compute='_compute_can_upload')
|
|
|
|
type = fields.Selection([
|
|
('ids', "Document list"),
|
|
('domain', "Domain"),
|
|
], default='ids', string="Share type")
|
|
# type == 'ids'
|
|
document_ids = fields.Many2many('documents.document', string='Shared Documents')
|
|
# type == 'domain'
|
|
domain = fields.Char()
|
|
|
|
action = fields.Selection([
|
|
('download', "Download"),
|
|
('downloadupload', "Download and Upload"),
|
|
], default='download', string="Allows to", inverse="_inverse_action")
|
|
tag_ids = fields.Many2many('documents.tag', string="Shared Tags")
|
|
partner_id = fields.Many2one('res.partner', string="Contact")
|
|
owner_id = fields.Many2one('res.partner', string="Document Owner", default=lambda self: self.env.user.partner_id.id)
|
|
email_drop = fields.Boolean(compute='_compute_email_drop', string='Upload by Email', store=True, readonly=False)
|
|
|
|
# Activity
|
|
activity_option = fields.Boolean(string='Create a new activity')
|
|
activity_type_id = fields.Many2one('mail.activity.type', string="Activity type")
|
|
activity_summary = fields.Char('Summary')
|
|
activity_date_deadline_range = fields.Integer(string='Due Date In')
|
|
activity_date_deadline_range_type = fields.Selection([
|
|
('days', 'Days'),
|
|
('weeks', 'Weeks'),
|
|
('months', 'Months'),
|
|
], string='Due type', default='days')
|
|
activity_note = fields.Html(string="Note")
|
|
activity_user_id = fields.Many2one('res.users', string='Responsible')
|
|
|
|
_sql_constraints = [
|
|
('share_unique', 'unique (access_token)', "This access token already exists"),
|
|
]
|
|
|
|
def _compute_display_name(self):
|
|
for record in self:
|
|
record.display_name = record.name or "unnamed link"
|
|
|
|
def _get_documents_domain(self):
|
|
"""
|
|
Allows overriding the domain in customizations for modifying the search() domain
|
|
"""
|
|
if self.type == 'ids':
|
|
return []
|
|
domains_list = [[('folder_id', 'child_of' if self.include_sub_folders else '=', self.folder_id.id)]]
|
|
if self.tag_ids:
|
|
domains_list = [expression.AND([domains_list[0], [('tag_ids', 'in', self.tag_ids.ids)]])]
|
|
return domains_list
|
|
|
|
def _get_documents(self, document_ids=None):
|
|
"""
|
|
:param list[int] document_ids: limit to the list of documents to fetch.
|
|
:return: recordset of the documents that can be accessed by the create_uid based on the settings
|
|
of the share link.
|
|
"""
|
|
self.ensure_one()
|
|
limited_self = self.with_user(self.create_uid)
|
|
Documents = limited_self.env['documents.document']
|
|
|
|
search_ids = set()
|
|
domains = self._get_documents_domain()
|
|
|
|
if document_ids is not None:
|
|
if not document_ids:
|
|
return Documents
|
|
search_ids = set(document_ids)
|
|
|
|
if self.type == 'domain':
|
|
record_domain = []
|
|
if self.domain:
|
|
record_domain = literal_eval(self.domain)
|
|
domains.append(record_domain)
|
|
if self.action == 'download':
|
|
domains.append([('type', '!=', 'empty')])
|
|
else:
|
|
share_ids = limited_self.document_ids.ids
|
|
search_ids = search_ids.intersection(share_ids) if search_ids else share_ids
|
|
|
|
if search_ids or self.type != 'domain':
|
|
domains.append([('id', 'in', list(search_ids))])
|
|
|
|
search_domain = expression.AND(domains)
|
|
return Documents.search(search_domain)
|
|
|
|
def _get_writable_documents(self, documents):
|
|
"""
|
|
|
|
:param documents:
|
|
:return: the recordset of documents for which the create_uid has write access
|
|
False only if no write right.
|
|
"""
|
|
self.ensure_one()
|
|
try:
|
|
# checks the rights first in case of empty recordset
|
|
documents.with_user(self.create_uid).check_access_rights('write')
|
|
except exceptions.AccessError:
|
|
return False
|
|
return documents.with_user(self.create_uid)._filter_access_rules('write')
|
|
|
|
def _check_token(self, access_token):
|
|
if not access_token:
|
|
return False
|
|
try:
|
|
return consteq(access_token, self.access_token)
|
|
except:
|
|
return False
|
|
|
|
def _get_documents_and_check_access(self, access_token, document_ids=None, operation='write'):
|
|
"""
|
|
:param str access_token: the access_token to be checked with the share link access_token
|
|
:param list[int] document_ids: limit to the list of documents to fetch and check from the share link.
|
|
:param str operation: access right to check on documents (read/write).
|
|
:return: Recordset[documents.document]: all the accessible requested documents
|
|
False if it fails access checks: False always means "no access right", if there are no documents but
|
|
the rights are valid, it still returns an empty recordset.
|
|
"""
|
|
self.ensure_one()
|
|
if not self._check_token(access_token):
|
|
return False
|
|
if self.state == 'expired':
|
|
return False
|
|
documents = self._get_documents(document_ids)
|
|
if operation == 'write':
|
|
return self._get_writable_documents(documents)
|
|
else:
|
|
return documents
|
|
|
|
@api.depends('folder_id')
|
|
def _compute_can_upload(self):
|
|
for record in self:
|
|
folder = record.folder_id
|
|
folder_has_groups = folder.group_ids.ids or folder.read_group_ids.ids
|
|
in_write_group = set(folder.group_ids.ids) & set(record.create_uid.groups_id.ids)
|
|
record.can_upload = in_write_group or not folder_has_groups
|
|
|
|
@api.depends('date_deadline')
|
|
def _compute_state(self):
|
|
"""
|
|
changes the state based on the expiration date,
|
|
an expired share link cannot be used to upload or download files.
|
|
"""
|
|
for record in self:
|
|
record.state = 'live'
|
|
if record.date_deadline:
|
|
today = fields.Date.from_string(fields.Date.today())
|
|
exp_date = fields.Date.from_string(record.date_deadline)
|
|
diff_time = (exp_date - today).days
|
|
if diff_time <= 0:
|
|
record.state = 'expired'
|
|
|
|
@api.depends('action', 'alias_name')
|
|
def _compute_email_drop(self):
|
|
for record in self:
|
|
record.email_drop = record.action == 'downloadupload' and bool(record.alias_name)
|
|
|
|
@api.depends('access_token')
|
|
def _compute_full_url(self):
|
|
for record in self:
|
|
record.full_url = (f'{record.get_base_url()}/document/share/'
|
|
f'{record._origin.id or record.id}/{record.access_token}')
|
|
|
|
@api.depends('type', 'document_ids', 'domain', 'tag_ids')
|
|
def _compute_links_count(self):
|
|
domains = [record._get_documents_domain()[0] for record in self if record.type == "domain"]
|
|
documents_from_domain = self.env['documents.document'].search(expression.OR(domains))
|
|
|
|
for record in self:
|
|
documents = []
|
|
if record.type == "ids":
|
|
documents = record.document_ids
|
|
elif record.type == "domain":
|
|
documents = documents_from_domain.filtered_domain(record._get_documents_domain()[0])
|
|
record.links_count = sum(1 for document in documents if document.type == 'url')
|
|
|
|
def _inverse_action(self):
|
|
# Prevent the alias from existing if the option is removed
|
|
for record in self:
|
|
if record.action != 'downloadupload' and record.alias_name:
|
|
record.alias_name = False
|
|
|
|
def _alias_get_creation_values(self):
|
|
values = super(DocumentShare, self)._alias_get_creation_values()
|
|
values['alias_model_id'] = self.env['ir.model']._get('documents.document').id
|
|
if self.id:
|
|
values['alias_defaults'] = defaults = literal_eval(self.alias_defaults or "{}")
|
|
defaults.update({
|
|
'tag_ids': [(6, 0, self.tag_ids.ids)],
|
|
'folder_id': self.folder_id.id,
|
|
'partner_id': self.partner_id.id,
|
|
'create_share_id': self.id,
|
|
})
|
|
return values
|
|
|
|
def _get_share_popup(self, context, vals):
|
|
view_id = self.env.ref('documents.share_view_form_popup').id
|
|
return {
|
|
'context': context,
|
|
'res_model': 'documents.share',
|
|
'target': 'new',
|
|
'name': _('Share selected files') if vals.get('type') == 'ids' else _('Share selected workspace'),
|
|
'res_id': self.id if self else False,
|
|
'type': 'ir.actions.act_window',
|
|
'views': [[view_id, 'form']],
|
|
}
|
|
|
|
def send_share_by_mail(self, template_xmlid):
|
|
self.ensure_one()
|
|
request_template = self.env.ref(template_xmlid, raise_if_not_found=False)
|
|
if request_template:
|
|
self.message_mail_with_source(request_template)
|
|
|
|
@api.model
|
|
def open_share_popup(self, vals):
|
|
"""
|
|
returns a view.
|
|
:return: a form action that opens the share window to display the settings.
|
|
"""
|
|
new_context = dict(self.env.context)
|
|
# TOOD: since the share is created directly do we really need to set the context?
|
|
new_context.update({
|
|
'default_owner_id': self.env.user.partner_id.id,
|
|
'default_folder_id': vals.get('folder_id'),
|
|
'default_tag_ids': vals.get('tag_ids'),
|
|
'default_type': vals.get('type', 'domain'),
|
|
'default_domain': vals.get('domain') if vals.get('type', 'domain') == 'domain' else False,
|
|
'default_document_ids': vals.get('document_ids', False),
|
|
})
|
|
return self.create(vals)._get_share_popup(new_context, vals)
|
|
|
|
@api.model
|
|
def action_get_share_url(self, vals):
|
|
"""
|
|
Creates a new share directly and return it's url
|
|
"""
|
|
return self.create(vals).full_url
|
|
|
|
def action_delete_shares(self):
|
|
self.unlink()
|