Commit d0b92f73 authored by Roberto Rosario's avatar Roberto Rosario
Browse files

Add support for copying object



Enable the feature for workflows.
Signed-off-by: Roberto Rosario's avatarRoberto Rosario <roberto.rosario@mayan-edms.com>
parent 22d0c002
Pipeline #158704702 skipped with stage
......@@ -137,6 +137,7 @@
- Allow passing environment entries to the Tesseract OCR backend.
- Improve main menu styling and JavaScript code. Improve hover highlighting
and maximize space.
- Add support for copying workflows.
3.4.9 (2020-05-26)
==================
......
......@@ -7,9 +7,123 @@ from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.classes import ModelPermission
from .links import link_object_copy
from .menus import menu_object
from .permissions import permission_object_copy
from .settings import setting_home_view
class ModelCopy:
_registry = {}
@staticmethod
def method_instance_copy(self, values=None):
model_copy = ModelCopy.get(model=self._meta.model)
model_copy.copy(instance=self, values=values)
@classmethod
def get(cls, model):
return cls._registry[model]
def __init__(self, model, register_permission=False):
self.fields_copy = []
self.fields_foreign_keys = []
self.fields_many_to_many = []
self.fields_reverse_related = []
self.fields_unique = []
self.model = model
self.__class__._registry[model] = self
model.add_to_class(
name='copy_instance', value=ModelCopy.method_instance_copy
)
menu_object.bind_links(
links=(link_object_copy,), sources=(model,)
)
if register_permission:
ModelPermission.register(
model=model, permissions=(permission_object_copy,)
)
def add_fields(self, field_names, field_value_gets=None):
self.field_value_gets = field_value_gets or {}
for field_name in field_names:
field = self.model._meta.get_field(field_name=field_name)
if isinstance(field, models.fields.reverse_related.ManyToOneRel):
self.fields_reverse_related.append(field_name)
elif isinstance(field, models.fields.related.ForeignKey):
self.fields_foreign_keys.append(field_name)
elif isinstance(field, models.fields.related.ManyToManyField):
self.fields_many_to_many.append(field_name)
else:
if field.unique:
self.fields_unique.append(field_name)
else:
self.fields_copy.append(field_name)
def copy(self, instance, values=None):
values = values or {}
new_instance = self.model()
# Base fields whose values are copied
for field in self.fields_copy:
value = values.get(field, getattr(instance, field))
setattr(new_instance, field, value)
# Base fields with unique values
for field in self.fields_unique:
base_value = getattr(instance, field)
counter = 1
while True:
value = '{}_{}'.format(base_value, counter)
if not self.model._meta.default_manager.filter(**{field: value}).exists():
break
counter = counter + 1
setattr(new_instance, field, value)
# Foreign keys
for field in self.fields_foreign_keys:
value = values.get(field, getattr(instance, field))
field_value_gets = self.field_value_gets.get(field, None)
if field_value_gets:
related_model = self.model._meta.get_field(field).related_model
final_filter = {}
context = {'instance': instance}
context.update(values)
for key, value in field_value_gets.items():
final_filter[key] = (value.format(**context))
value = related_model._meta.default_manager.get(**final_filter)
setattr(new_instance, field, value)
new_instance.save()
# Many to many fields added after instance creation
for field in self.fields_many_to_many:
getattr(new_instance, field).set(getattr(instance, field).all())
# Reverse related
for field in self.fields_reverse_related:
related_field = self.model._meta.get_field(field_name=field)
related_field_name = related_field.field.name
for related_instance in getattr(instance, field).all():
related_instance.copy_instance(
values={related_field_name: new_instance}
)
class MissingItem:
_registry = []
......
......@@ -21,6 +21,9 @@ icon_menu_about = Icon(
icon_menu_user = Icon(
driver_name='fontawesome', symbol='user-circle'
)
icon_object_copy = Icon(
driver_name='fontawesome', symbol='stamp'
)
icon_setup = Icon(
driver_name='fontawesome', symbol='cog'
)
......
from django.utils.translation import ugettext_lazy as _
from mayan.apps.navigation.classes import Link
from mayan.apps.navigation.utils import get_content_type_kwargs_factory
from .icons import (
icon_about, icon_book, icon_current_user_locale_profile_details,
icon_current_user_locale_profile_edit, icon_documentation,
icon_forum, icon_license, icon_setup, icon_source_code, icon_support,
icon_tools
icon_forum, icon_license, icon_object_copy, icon_setup, icon_source_code,
icon_support, icon_tools
)
link_about = Link(
......@@ -37,6 +38,10 @@ link_forum = Link(
link_license = Link(
icon_class=icon_license, text=_('License'), view='common:license_view'
)
link_object_copy = Link(
icon_class=icon_object_copy, kwargs=get_content_type_kwargs_factory(),
text=_('Copy'), view='common:object_copy'
)
link_setup = Link(
icon_class=icon_setup, text=_('Setup'), view='common:setup_list'
)
......
from django.utils.translation import ugettext_lazy as _
from mayan.apps.permissions import PermissionNamespace
namespace = PermissionNamespace(label=_('Common'), name='common')
permission_object_copy = namespace.add_permission(
label=_('Copy object'), name='object_copy'
)
......@@ -9,7 +9,7 @@ from .api_views import (
from .views import (
AboutView, CurrentUserLocaleProfileDetailsView,
CurrentUserLocaleProfileEditView, FaviconRedirectView, HomeView,
LicenseView, RootView, SetupListView, ToolsListView
LicenseView, ObjectCopyView, RootView, SetupListView, ToolsListView
)
urlpatterns_user_locale = [
......@@ -30,7 +30,11 @@ urlpatterns_misc = [
url(
regex=r'^jsi18n/(?P<packages>\S+?)/$', name='javascript_catalog',
view=JavaScriptCatalog.as_view()
)
),
url(
regex=r'^object/(?P<app_label>[-\w]+)/(?P<model_name>[-\w]+)/(?P<object_id>\d+)/copy/$',
name='object_copy', view=ObjectCopyView.as_view()
),
]
urlpatterns = [
......
from django.conf import settings
from django.contrib import messages
from django.templatetags.static import static
from django.urls import reverse_lazy
from django.utils import timezone, translation
from django.utils.translation import ugettext_lazy as _
from django.views.generic import RedirectView
from mayan.apps.views.generics import SingleObjectEditView, SimpleView
from mayan.apps.views.generics import (
ConfirmView, SingleObjectEditView, SimpleView
)
from mayan.apps.views.mixins import ExternalContentTypeObjectMixin
from .forms import (
LicenseForm, LocaleProfileForm, LocaleProfileForm_view,
)
from .icons import icon_setup
from .menus import menu_tools, menu_setup
from .permissions import permission_object_copy
from .settings import setting_home_view
......@@ -98,6 +103,23 @@ class LicenseView(SimpleView):
template_name = 'appearance/generic_form.html'
class ObjectCopyView(ExternalContentTypeObjectMixin, ConfirmView):
external_object_permission = permission_object_copy
def get_extra_context(self):
return {
'object': self.external_object,
'title': _('Copy the object: %s?' % self.external_object),
}
def view_action(self):
self.external_object.copy_instance()
messages.success(
message=_('Object copied successfully.'),
request=self.request
)
class RootView(SimpleView):
extra_context = {'home_view': setting_home_view.value}
template_name = 'appearance/root.html'
......
......@@ -6,7 +6,7 @@ from mayan.apps.acls.classes import ModelPermission
from mayan.apps.acls.links import link_acl_list
from mayan.apps.common.apps import MayanAppConfig
from mayan.apps.common.classes import (
ModelField, ModelProperty, ModelReverseField
ModelCopy, ModelField, ModelProperty, ModelReverseField
)
from mayan.apps.common.menus import (
menu_facet, menu_list_facet, menu_main, menu_object, menu_secondary,
......@@ -104,6 +104,53 @@ class DocumentStatesApp(MayanAppConfig):
WorkflowAction.load_modules()
ModelCopy(model=WorkflowState).add_fields(
field_names=(
'actions', 'workflow', 'label', 'initial', 'completion'
)
)
ModelCopy(model=WorkflowStateAction).add_fields(
field_names=(
'state', 'label', 'enabled', 'when', 'action_path',
'action_data', 'condition'
)
)
ModelCopy(model=WorkflowTransition).add_fields(
field_names=(
'workflow', 'label', 'origin_state', 'destination_state',
'condition', 'fields', 'trigger_events'
),
field_value_gets={
'origin_state': {
'workflow': '{workflow.pk}',
'label': '{instance.origin_state.label}'
},
'destination_state': {
'workflow': '{workflow.pk}',
'label': '{instance.destination_state.label}'
},
}
)
ModelCopy(
model=WorkflowTransitionTriggerEvent
).add_fields(
field_names=('transition', 'event_type')
)
ModelCopy(
model=WorkflowTransitionField
).add_fields(
field_names=(
'transition', 'field_type', 'name', 'label', 'help_text',
'required', 'widget', 'widget_kwargs',
)
)
ModelCopy(model=Workflow, register_permission=True).add_fields(
field_names=(
'internal_name', 'label', 'document_types', 'states',
'transitions'
),
)
ModelEventType.register(
event_types=(event_workflow_edited,), model=Workflow
)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment