""" open/dulcinea/lib/attachable.py Provides the Attachable mixin class that allows objects to to have file attachments. """ from qp.lib.stored_file import StoredFile from durus.persistent import PersistentObject from qp.lib.spec import spec, specify, add_getters_and_setters, require, string from qp.lib.spec import Specified, Mixin, datetime_with_tz, optional from qp.pub.common import site_now from qp.pub.user import User class Attachment (PersistentObject, Specified): """ Stores properties of an attachment relationship between an Attachable object and a StoredFile. """ file_is = spec( StoredFile, "the file attached") owner_is = spec( User, "the owner of this association") date_is = spec( datetime_with_tz, "date this attachment was created") filename_is = spec( string, "the filename to use when downloading the file") description_is = spec( (None, string), "a description of the file") hidden_is = optional( bool, "Should this attachment be restricted to users with manage access?") def __init__(self, file, owner): specify(self, file=file, owner=owner, date=site_now(), filename=file.get_filename(), description=file.get_description()) def get_hidden(self): return getattr(self, 'hidden', False) def open(self): return self.file.open() def get_file_id(self): return self.file.get_id() def get_size(self): return self.file.get_size() def get_mime_type(self): return self.file.get_mime_type() def has_manage_access(self, user): return (user and (user is self.owner or user.is_granted('attachment-manager') or user.is_admin())) def is_image(self): return self.get_mime_type().startswith('image/') add_getters_and_setters(Attachment) class Attachable (Mixin): attachments_is = spec( [Attachment], "attachments connected to this object") def __init__(self, attachments=None): specify(self, attachments=list(attachments or [])) def clear_attachments(self): specify(self, attachments=[]) def get_attachments(self): """() -> [Attachment] Retrieve a list of the attachments for this object. """ return self.attachments def get_attached_files(self): """() -> [StoredFile] Retrieve a list of the file objects attached to this object. """ return [a.get_file() for a in self.attachments] def add_attachment(self, attachment): require(attachment, Attachment) for existing_attachment in self.attachments: if existing_attachment.get_file_id() == attachment.get_file_id(): return self.attachments = self.attachments + [attachment] def move_attachment(self, attachment, index): assert attachment in self.attachments, "%r not in %r" % (attachment, self.attachments) new_attachments = [] for existing_index, existing_attachment in enumerate(self.attachments): if existing_attachment is not attachment: new_attachments.append(existing_attachment) new_attachments.insert(index, attachment) self.attachments = new_attachments def get_attachment(self, id): """(id : string) -> Attachment | None """ for attachment in self.attachments: if attachment.get_file_id() == id: return attachment for attachment in self.attachments: if attachment.get_filename() == id: return attachment return None def attach_file(self, stored_file, user): """(stored_file: StoredFile) Attach a file to this object. """ files = self.attachments[:] files.append(Attachment(stored_file, user)) self.attachments = files def detach_attachment(self, attachment): """(attachment: Attachment) Detach an attachment from this object. """ require(attachment, Attachment) files = self.attachments[:] files.remove(attachment) self.attachments = files def attachment_modified(self, attachment, user): """(attachment : Attachment, user : DulcineaUser) 'attachment' was modified by 'user'. Override this method to intercept modification activity for attachments """ pass def get_allowed_mime_types(self, user=None): """() -> [string] Override to restrict the types of files that can be attached For example: return [mime_type[0] for mime_type in MIME_TYPES] """ return []