From 6ac6cf8edfebb6a24ff7c40b5f1f422c1518dc86 Mon Sep 17 00:00:00 2001 From: salsburj Date: Thu, 19 Mar 2020 23:27:13 -0400 Subject: [PATCH 1/7] bioshare1810 - add AttachmentsMixin --- attachments/__init__.py | 3 + attachments/migrations/mixins.py | 102 +++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 attachments/migrations/mixins.py diff --git a/attachments/__init__.py b/attachments/__init__.py index 73a19f9..ff17282 100644 --- a/attachments/__init__.py +++ b/attachments/__init__.py @@ -1,3 +1,6 @@ +from attachments.mixins import AttachmentsMixin + + __version_info__ = (3, 1, 5) __version__ = '.'.join(str(i) for i in __version_info__) diff --git a/attachments/migrations/mixins.py b/attachments/migrations/mixins.py new file mode 100644 index 0000000..40d7f98 --- /dev/null +++ b/attachments/migrations/mixins.py @@ -0,0 +1,102 @@ +from django.core.exceptions import ImproperlyConfigured + + +class AttachmentsMixin: + """ + This mixin is intended to be used with a form so that attachments functionality + can be tied in with the form itself. It handles all of the attaching and deleting. + + To use this mixin, first create a class that inherits from it so that the + ``get_session_error_message`` can be overridden. + Then, make it a superclass of an existing ``ModelForm``. + + Due to method resolution order, make this mixin the left-most superclass. + For example, ``class MyForm(MyAttachmentsMixin, forms.ModelForm)``. + + The ``__init__`` method does the following: + - Make ``session`` and ``attachments_field_name`` instance variables. + - Keep track of files that will be deleted. + + :param session: an already-created session + :type session: optional + :param attachments_field_name: the name of the field on the model that represents the attachments + :type attachments_field_name: str, optional + """ + def __init__(self, *args, session=None, attachments_field_name='attachments', **kwargs): + self.session = session + self.attachments_field_name = attachments_field_name + self.attached_files = [] + self.session_error = None + # This needs to be done since args may be (), (None, ), or (x, ...) where x is truthy. + if args and args[0] and args[0].get('delete-attachments'): + try: + # If this is QueryDict, must use getlist. + self.deletions = [int(pk) for pk in args[0].getlist('delete-attachments')] + except AttributeError: + # If this is any other mapping, must use get. + self.deletions = args[0].get('delete-attachments') + else: + self.deletions = [] + super().__init__(*args, **kwargs) + + def is_valid(self): + """ + :returns: ``True`` if both the form and session are valid. + """ + return all([super().is_valid(), self._session_is_valid()]) + + def _session_is_valid(self): + """ + :returns: ``True`` if the session is valid. + """ + if not self.session: + return True + self._clean_session() + return not self.session_error + + def _clean_session(self): + """Add a session error, if necessary.""" + if not self.session.is_valid(): + self.session_error = self.get_session_error_message() + return self.session_error + + def get_session_error_message(self): + """ + :returns: a string containing the session error message + """ + raise ImproperlyConfigured('Please implement this method so that a session error message will display.') + + def uploads_exist(self): + """ + :returns: ``True`` if uploads exist. + """ + return bool(self.session) and self.session.uploads.exists() + + def has_changed(self): + """ + :returns: ``True`` if the form or session has changed. + """ + return bool(self.deletions) or super().has_changed() or self.uploads_exist() + + def save(self, commit=True): + """ + Attach uploads from the attachment session to the ``ModelForm`` instance, delete the session, + and perform any deletions specified by the POST data. + """ + instance = super().save(commit=False) + + base_save_m2m = self.save_m2m + def save_m2m(): + base_save_m2m() + if self.uploads_exist(): + self.attached_files = self.session.attach(instance) + self.session.delete() + + getattr(instance, self.attachments_field_name).filter(pk__in=self.deletions).delete() + self.save_m2m = save_m2m + + if commit: + instance.save() + self.save_m2m() + + return instance From 9c08284fb359dc3d8d8219488d520c0495d89741 Mon Sep 17 00:00:00 2001 From: salsburj Date: Tue, 24 Mar 2020 10:47:57 -0400 Subject: [PATCH 2/7] bioshare1810 - fix accidental placement of file and make AttachmentsMixin available --- attachments/__init__.py | 9 ++++++--- attachments/{migrations => }/mixins.py | 0 2 files changed, 6 insertions(+), 3 deletions(-) rename attachments/{migrations => }/mixins.py (100%) diff --git a/attachments/__init__.py b/attachments/__init__.py index ff17282..3d37aa2 100644 --- a/attachments/__init__.py +++ b/attachments/__init__.py @@ -1,6 +1,3 @@ -from attachments.mixins import AttachmentsMixin - - __version_info__ = (3, 1, 5) __version__ = '.'.join(str(i) for i in __version_info__) @@ -9,3 +6,9 @@ def session(*args, **kwargs): # Expose utils.session without importing utils from __init__ at module level. from .utils import session as attachment_session return attachment_session(*args, **kwargs) + +def get_AttachmentsMixin(): + from attachments.mixins import AttachmentsMixin + return AttachmentsMixin + +AttachmentsMixin = get_AttachmentsMixin() diff --git a/attachments/migrations/mixins.py b/attachments/mixins.py similarity index 100% rename from attachments/migrations/mixins.py rename to attachments/mixins.py From b91909d8e4d429b930a04c3baf04f5ecdec22e23 Mon Sep 17 00:00:00 2001 From: salsburj Date: Thu, 13 Aug 2020 10:17:30 -0400 Subject: [PATCH 3/7] bioshare1810 - no need to have AttachmentsMixin in attachments.__init__.py --- attachments/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/attachments/__init__.py b/attachments/__init__.py index 3d37aa2..73a19f9 100644 --- a/attachments/__init__.py +++ b/attachments/__init__.py @@ -6,9 +6,3 @@ def session(*args, **kwargs): # Expose utils.session without importing utils from __init__ at module level. from .utils import session as attachment_session return attachment_session(*args, **kwargs) - -def get_AttachmentsMixin(): - from attachments.mixins import AttachmentsMixin - return AttachmentsMixin - -AttachmentsMixin = get_AttachmentsMixin() From 031f5f68944a63d634c39c7895d7bcd82dfb42ca Mon Sep 17 00:00:00 2001 From: salsburj Date: Thu, 13 Aug 2020 10:19:29 -0400 Subject: [PATCH 4/7] bioshare1810 - bump version --- attachments/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachments/__init__.py b/attachments/__init__.py index 74b4b20..d8b6696 100644 --- a/attachments/__init__.py +++ b/attachments/__init__.py @@ -1,4 +1,4 @@ -__version_info__ = (3, 3, 0) +__version_info__ = (3, 3, 1) __version__ = '.'.join(str(i) for i in __version_info__) From 8040315c6ec92d8b36408ff699b81644b2212cdc Mon Sep 17 00:00:00 2001 From: salsburj Date: Thu, 13 Aug 2020 10:31:48 -0400 Subject: [PATCH 5/7] bioshare1810 - changes include a new feature, not a hotfix, so fix version number --- attachments/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachments/__init__.py b/attachments/__init__.py index d8b6696..9d2fb30 100644 --- a/attachments/__init__.py +++ b/attachments/__init__.py @@ -1,4 +1,4 @@ -__version_info__ = (3, 3, 1) +__version_info__ = (3, 4, 0) __version__ = '.'.join(str(i) for i in __version_info__) From 865d9b7552a29480cd96aae9511ac9af88ce8bc3 Mon Sep 17 00:00:00 2001 From: salsburj Date: Fri, 18 Feb 2022 13:32:12 -0500 Subject: [PATCH 6/7] bioshare_1810 - update version --- attachments/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachments/__init__.py b/attachments/__init__.py index 7defd46..d30bb6b 100644 --- a/attachments/__init__.py +++ b/attachments/__init__.py @@ -1,4 +1,4 @@ -__version_info__ = (3, 4, 1) +__version_info__ = (3, 4, 2) __version__ = '.'.join(str(i) for i in __version_info__) From 32159b7f23c41f0437bf6f8696fc64705cf96a1e Mon Sep 17 00:00:00 2001 From: salsburj Date: Wed, 23 Feb 2022 14:06:26 -0500 Subject: [PATCH 7/7] bioshare_1810 - do super().has_changed() first in case there are side effects --- attachments/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachments/mixins.py b/attachments/mixins.py index 40d7f98..04cb702 100644 --- a/attachments/mixins.py +++ b/attachments/mixins.py @@ -76,7 +76,7 @@ def has_changed(self): """ :returns: ``True`` if the form or session has changed. """ - return bool(self.deletions) or super().has_changed() or self.uploads_exist() + return super().has_changed() or bool(self.deletions) or self.uploads_exist() def save(self, commit=True): """