Refactor the ModelAttribute class into two separate classes: ModelAttribute for executable model attributes and ModelField for actual ORM fields. Expose more document fields for use in smart links.
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -151,6 +151,10 @@
|
|||||||
templates that only refresh the menu when there are changes.
|
templates that only refresh the menu when there are changes.
|
||||||
Closes GitLab issue #511. Thanks to Daniel Carrico
|
Closes GitLab issue #511. Thanks to Daniel Carrico
|
||||||
@daniel1113 for the report.
|
@daniel1113 for the report.
|
||||||
|
- Refactor the ModelAttribute class into two separate classes:
|
||||||
|
ModelAttribute for executable model attributes and ModelField
|
||||||
|
for actual ORM fields.
|
||||||
|
- Expose more document fields for use in smart links.
|
||||||
|
|
||||||
3.0.3 (2018-08-17)
|
3.0.3 (2018-08-17)
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -367,6 +367,11 @@ classes beyond the provide line chart.
|
|||||||
templates that only refresh the menu when there are changes.
|
templates that only refresh the menu when there are changes.
|
||||||
Closes GitLab issue #511. Thanks to Daniel Carrico
|
Closes GitLab issue #511. Thanks to Daniel Carrico
|
||||||
@daniel1113 for the report.
|
@daniel1113 for the report.
|
||||||
|
- Refactor the ModelAttribute class into two separate classes:
|
||||||
|
ModelAttribute for executable model attributes and ModelField
|
||||||
|
for actual ORM fields.
|
||||||
|
- Expose more document fields for use in smart links.
|
||||||
|
|
||||||
|
|
||||||
Removals
|
Removals
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -178,19 +178,15 @@ class MissingItem(object):
|
|||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class ModelAttribute(object):
|
class ModelAttribute(object):
|
||||||
__registry = {}
|
_registry = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for(cls, model, type_names=None):
|
def get_for(cls, model):
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for type_name, attributes in cls.__registry[model].items():
|
return cls._registry[model]
|
||||||
if not type_names or type_name in type_names:
|
except KeyError:
|
||||||
result.extend(attributes)
|
|
||||||
|
|
||||||
return result
|
|
||||||
except IndexError:
|
|
||||||
# We were passed a model instance, try again using the model of
|
# We were passed a model instance, try again using the model of
|
||||||
# the instance
|
# the instance
|
||||||
|
|
||||||
@@ -198,30 +194,39 @@ class ModelAttribute(object):
|
|||||||
if model.__class__ == models.base.ModelBase:
|
if model.__class__ == models.base.ModelBase:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return cls.get_for[type(model)]
|
return cls.get_for(model=type(model))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_choices_for(cls, model, type_names=None):
|
def get_choices_for(cls, model):
|
||||||
return [
|
return [
|
||||||
(
|
(attribute.name, attribute) for attribute in cls.get_for(model)
|
||||||
attribute.name, attribute
|
|
||||||
) for attribute in cls.get_for(model, type_names)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def help_text_for(cls, model, type_names=None):
|
def get_help_text_for(cls, model, show_name=False):
|
||||||
result = []
|
result = []
|
||||||
for count, attribute in enumerate(cls.get_for(model, type_names), 1):
|
for count, attribute in enumerate(cls.get_for(model=model), 1):
|
||||||
result.append(
|
result.append(
|
||||||
'{}) {}'.format(
|
'{}) {}'.format(
|
||||||
count, force_text(attribute.get_display(show_name=True))
|
count, force_text(attribute.get_display(show_name=show_name))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return ' '.join(
|
return ' '.join(
|
||||||
[ugettext('Available attributes: \n'), ', \n'.join(result)]
|
[ugettext('Available attributes: \n'), '\n'.join(result)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, model, name, label=None, description=None):
|
||||||
|
self.model = model
|
||||||
|
self.label = label
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self._registry.setdefault(model, [])
|
||||||
|
self._registry[model].append(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.get_display()
|
||||||
|
|
||||||
def get_display(self, show_name=False):
|
def get_display(self, show_name=False):
|
||||||
if self.description:
|
if self.description:
|
||||||
return '{} - {}'.format(
|
return '{} - {}'.format(
|
||||||
@@ -230,29 +235,101 @@ class ModelAttribute(object):
|
|||||||
else:
|
else:
|
||||||
return force_text(self.name if show_name else self.label)
|
return force_text(self.name if show_name else self.label)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.get_display()
|
|
||||||
|
|
||||||
def __init__(self, model, name, label=None, description=None, type_name=None):
|
class ModelField(ModelAttribute):
|
||||||
self.model = model
|
"""Subclass to handle model database fields"""
|
||||||
self.label = label
|
_registry = {}
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
|
|
||||||
for field in model._meta.fields:
|
@classmethod
|
||||||
if field.name == name:
|
def get_help_text_for(cls, model, show_name=False):
|
||||||
self.label = field.verbose_name
|
result = []
|
||||||
self.description = field.help_text
|
for count, model_field in enumerate(cls.get_for(model=model), 1):
|
||||||
|
result.append(
|
||||||
|
'{}) {} - {}'.format(
|
||||||
|
count,
|
||||||
|
model_field.name if show_name else model_field.label,
|
||||||
|
model_field.description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.__registry.setdefault(model, {})
|
return ' '.join(
|
||||||
|
[ugettext('Available fields: \n'), '\n'.join(result)]
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(type_name, list):
|
def __init__(self, *args, **kwargs):
|
||||||
for single_type in type_name:
|
super(ModelField, self).__init__(*args, **kwargs)
|
||||||
self.__registry[model].setdefault(single_type, [])
|
self._final_model_verbose_name = None
|
||||||
self.__registry[model][single_type].append(self)
|
|
||||||
|
if not self.label:
|
||||||
|
self.label = self.get_field_attribute(
|
||||||
|
attribute='verbose_name'
|
||||||
|
)
|
||||||
|
if self.label != self._final_model_verbose_name:
|
||||||
|
self.label = '{} {}'.format(
|
||||||
|
self._final_model_verbose_name, self.label
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.description:
|
||||||
|
self.description = self.get_field_attribute(
|
||||||
|
attribute='help_text'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_field_attribute(self, attribute, model=None, field_name=None):
|
||||||
|
if not model:
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
if not field_name:
|
||||||
|
field_name = self.name
|
||||||
|
|
||||||
|
parts = field_name.split('__')
|
||||||
|
if len(parts) > 1:
|
||||||
|
return self.get_field_attribute(
|
||||||
|
model=model._meta.get_field(parts[0]).related_model,
|
||||||
|
field_name='__'.join(parts[1:]), attribute=attribute
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.__registry[model].setdefault(type_name, [])
|
self._final_model_verbose_name = model._meta.verbose_name
|
||||||
self.__registry[model][type_name].append(self)
|
return getattr(
|
||||||
|
model._meta.get_field(field_name=field_name),
|
||||||
|
attribute
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModelProperty(object):
|
||||||
|
_registry = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_for(cls, model):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for klass in cls._registry:
|
||||||
|
result.extend(klass.get_for(model=model))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_choices_for(cls, model):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for klass in cls._registry:
|
||||||
|
result.extend(klass.get_choices_for(model=model))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_help_text_for(cls, model, show_name=False):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for klass in cls._registry:
|
||||||
|
result.append(
|
||||||
|
klass.get_help_text_for(model=model, show_name=show_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls, klass):
|
||||||
|
cls._registry.append(klass)
|
||||||
|
|
||||||
|
|
||||||
class Package(object):
|
class Package(object):
|
||||||
@@ -321,3 +398,7 @@ class Template(object):
|
|||||||
self.html = result.content
|
self.html = result.content
|
||||||
self.hex_hash = hashlib.sha256(result.content).hexdigest()
|
self.hex_hash = hashlib.sha256(result.content).hexdigest()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
ModelProperty.register(ModelAttribute)
|
||||||
|
ModelProperty.register(ModelField)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from django.utils.encoding import force_text
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from acls.models import AccessControlList
|
from acls.models import AccessControlList
|
||||||
from common.classes import ModelAttribute
|
from common.classes import ModelProperty
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
|
|
||||||
from .models import Index, IndexTemplateNode
|
from .models import Index, IndexTemplateNode
|
||||||
@@ -40,8 +40,9 @@ class IndexTemplateNodeForm(forms.ModelForm):
|
|||||||
self.fields['expression'].help_text = ' '.join(
|
self.fields['expression'].help_text = ' '.join(
|
||||||
[
|
[
|
||||||
force_text(self.fields['expression'].help_text),
|
force_text(self.fields['expression'].help_text),
|
||||||
ModelAttribute.help_text_for(
|
'<br>',
|
||||||
Document, type_names=['indexing']
|
ModelProperty.get_help_text_for(
|
||||||
|
model=Document, show_name=True
|
||||||
).replace('\n', '<br>')
|
).replace('\n', '<br>')
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from common import (
|
|||||||
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
||||||
menu_tools
|
menu_tools
|
||||||
)
|
)
|
||||||
|
from common.classes import ModelField
|
||||||
from common.settings import settings_db_sync_task_delay
|
from common.settings import settings_db_sync_task_delay
|
||||||
from documents.search import document_search, document_page_search
|
from documents.search import document_search, document_page_search
|
||||||
from documents.signals import post_version_upload
|
from documents.signals import post_version_upload
|
||||||
@@ -97,6 +98,10 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
'submit_for_parsing', document_version_parsing_submit
|
'submit_for_parsing', document_version_parsing_submit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ModelField(
|
||||||
|
Document, name='versions__pages__content__content'
|
||||||
|
)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
permission_content_view, permission_parse_document
|
permission_content_view, permission_parse_document
|
||||||
@@ -110,6 +115,7 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
ModelPermission.register_inheritance(
|
ModelPermission.register_inheritance(
|
||||||
model=DocumentTypeSettings, related='document_type',
|
model=DocumentTypeSettings, related='document_type',
|
||||||
)
|
)
|
||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=DocumentVersionParseError, label=_('Document'),
|
source=DocumentVersionParseError, label=_('Document'),
|
||||||
func=lambda context: document_link(context['object'].document_version.document)
|
func=lambda context: document_link(context['object'].document_version.document)
|
||||||
|
|||||||
@@ -90,18 +90,19 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
WorkflowAction.initialize()
|
WorkflowAction.initialize()
|
||||||
|
|
||||||
ModelAttribute(
|
ModelAttribute(
|
||||||
Document, 'workflow.< workflow internal name >.get_current_state',
|
model=Document,
|
||||||
|
name='workflow.< workflow internal name >.get_current_state',
|
||||||
label=_('Current state of a workflow'), description=_(
|
label=_('Current state of a workflow'), description=_(
|
||||||
'Return the current state of the selected workflow'
|
'Return the current state of the selected workflow'
|
||||||
), type_name=['property', 'indexing']
|
)
|
||||||
)
|
)
|
||||||
ModelAttribute(
|
ModelAttribute(
|
||||||
Document,
|
model=Document,
|
||||||
'workflow.< workflow internal name >.get_current_state.completion',
|
name='workflow.< workflow internal name >.get_current_state.completion',
|
||||||
label=_('Current state of a workflow'), description=_(
|
label=_('Current state of a workflow'), description=_(
|
||||||
'Return the completion value of the current state of the '
|
'Return the completion value of the current state of the '
|
||||||
'selected workflow'
|
'selected workflow'
|
||||||
), type_name=['property', 'indexing']
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from common import (
|
|||||||
MayanAppConfig, MissingItem, menu_facet, menu_main, menu_object,
|
MayanAppConfig, MissingItem, menu_facet, menu_main, menu_object,
|
||||||
menu_secondary, menu_setup, menu_sidebar, menu_multi_item, menu_tools
|
menu_secondary, menu_setup, menu_sidebar, menu_multi_item, menu_tools
|
||||||
)
|
)
|
||||||
from common.classes import ModelAttribute
|
from common.classes import ModelAttribute, ModelField
|
||||||
from common.dashboards import dashboard_main
|
from common.dashboards import dashboard_main
|
||||||
from common.signals import post_initial_setup
|
from common.signals import post_initial_setup
|
||||||
from common.widgets import TwoStateWidget
|
from common.widgets import TwoStateWidget
|
||||||
@@ -142,14 +142,29 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
view='documents:document_type_list'
|
view='documents:document_type_list'
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelAttribute(
|
ModelField(Document, name='description')
|
||||||
Document, label=_('Label'), name='label', type_name='field'
|
ModelField(Document, name='date_added')
|
||||||
|
ModelField(Document, name='deleted_date_time')
|
||||||
|
ModelField(Document, name='document_type__label')
|
||||||
|
ModelField(Document, name='in_trash')
|
||||||
|
ModelField(Document, name='is_stub')
|
||||||
|
ModelField(Document, name='label')
|
||||||
|
ModelField(Document, name='language')
|
||||||
|
ModelField(Document, name='uuid')
|
||||||
|
ModelField(
|
||||||
|
Document, name='versions__checksum'
|
||||||
)
|
)
|
||||||
|
ModelField(
|
||||||
ModelAttribute(
|
Document, label=_('Versions comment'), name='versions__comment'
|
||||||
Document,
|
)
|
||||||
description=_('The MIME type of any of the versions of a document'),
|
ModelField(
|
||||||
label=_('MIME type'), name='versions__mimetype', type_name='field'
|
Document, label=_('Versions encoding'), name='versions__encoding'
|
||||||
|
)
|
||||||
|
ModelField(
|
||||||
|
Document, label=_('Versions mime type'), name='versions__mimetype'
|
||||||
|
)
|
||||||
|
ModelField(
|
||||||
|
Document, label=_('Versions timestamp'), name='versions__timestamp'
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelEventType.register(
|
ModelEventType.register(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django import forms
|
|||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.classes import ModelAttribute
|
from common.classes import ModelAttribute, ModelField, ModelProperty
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
|
|
||||||
from .models import SmartLink, SmartLinkCondition
|
from .models import SmartLink, SmartLinkCondition
|
||||||
@@ -16,8 +16,8 @@ class SmartLinkForm(forms.ModelForm):
|
|||||||
self.fields['dynamic_label'].help_text = ' '.join(
|
self.fields['dynamic_label'].help_text = ' '.join(
|
||||||
[
|
[
|
||||||
force_text(self.fields['dynamic_label'].help_text),
|
force_text(self.fields['dynamic_label'].help_text),
|
||||||
ModelAttribute.help_text_for(
|
ModelProperty.get_help_text_for(
|
||||||
Document, type_names=['field', 'related', 'property']
|
model=Document, show_name=True
|
||||||
).replace('\n', '<br>')
|
).replace('\n', '<br>')
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -31,15 +31,15 @@ class SmartLinkConditionForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SmartLinkConditionForm, self).__init__(*args, **kwargs)
|
super(SmartLinkConditionForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['foreign_document_data'] = forms.ChoiceField(
|
self.fields['foreign_document_data'] = forms.ChoiceField(
|
||||||
choices=ModelAttribute.get_choices_for(
|
choices=ModelField.get_choices_for(
|
||||||
Document, type_names=['field', 'query']
|
model=Document,
|
||||||
), label=_('Foreign document attribute')
|
), label=_('Foreign document field')
|
||||||
)
|
)
|
||||||
self.fields['expression'].help_text = ' '.join(
|
self.fields['expression'].help_text = ' '.join(
|
||||||
[
|
[
|
||||||
force_text(self.fields['expression'].help_text),
|
force_text(self.fields['expression'].help_text),
|
||||||
ModelAttribute.help_text_for(
|
ModelProperty.get_help_text_for(
|
||||||
Document, type_names=['field', 'related', 'property']
|
model=Document, show_name=True
|
||||||
).replace('\n', '<br>')
|
).replace('\n', '<br>')
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from common import (
|
|||||||
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
||||||
menu_setup, menu_sidebar
|
menu_setup, menu_sidebar
|
||||||
)
|
)
|
||||||
from common.classes import ModelAttribute
|
from common.classes import ModelAttribute, ModelField
|
||||||
from common.widgets import TwoStateWidget
|
from common.widgets import TwoStateWidget
|
||||||
from documents.search import document_page_search, document_search
|
from documents.search import document_page_search, document_search
|
||||||
from documents.signals import post_document_type_change
|
from documents.signals import post_document_type_change
|
||||||
@@ -92,26 +92,18 @@ class MetadataApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
ModelAttribute(
|
ModelAttribute(
|
||||||
Document, 'metadata', type_name='related',
|
Document, 'metadata_value_of',
|
||||||
description=_(
|
|
||||||
'Queryset containing a MetadataType instance reference and a '
|
|
||||||
'value for that metadata type'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ModelAttribute(
|
|
||||||
Document, 'metadata__metadata_type__name',
|
|
||||||
label=_('Metadata type name'), type_name='query'
|
|
||||||
)
|
|
||||||
ModelAttribute(
|
|
||||||
Document, 'metadata__value', label=_('Metadata type value'),
|
|
||||||
type_name='query'
|
|
||||||
)
|
|
||||||
ModelAttribute(
|
|
||||||
Document, 'metadata_value_of', label=_('Value of a metadata'),
|
|
||||||
description=_(
|
description=_(
|
||||||
'Return the value of a specific document metadata'
|
'Return the value of a specific document metadata'
|
||||||
),
|
),
|
||||||
type_name=['property', 'indexing']
|
)
|
||||||
|
|
||||||
|
ModelField(
|
||||||
|
Document, 'metadata__metadata_type__name',
|
||||||
|
label=_('Metadata type name')
|
||||||
|
)
|
||||||
|
ModelField(
|
||||||
|
Document, 'metadata__value', label=_('Metadata type value'),
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelEventType.register(
|
ModelEventType.register(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from common import (
|
|||||||
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
||||||
menu_tools
|
menu_tools
|
||||||
)
|
)
|
||||||
|
from common.classes import ModelField
|
||||||
from common.settings import settings_db_sync_task_delay
|
from common.settings import settings_db_sync_task_delay
|
||||||
from documents.search import document_search, document_page_search
|
from documents.search import document_search, document_page_search
|
||||||
from documents.signals import post_version_upload
|
from documents.signals import post_version_upload
|
||||||
@@ -98,6 +99,10 @@ class OCRApp(MayanAppConfig):
|
|||||||
'submit_for_ocr', document_version_ocr_submit
|
'submit_for_ocr', document_version_ocr_submit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ModelField(
|
||||||
|
Document, name='versions__pages__ocr_content__content'
|
||||||
|
)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
permission_ocr_document, permission_ocr_content_view
|
permission_ocr_document, permission_ocr_content_view
|
||||||
@@ -111,6 +116,7 @@ class OCRApp(MayanAppConfig):
|
|||||||
ModelPermission.register_inheritance(
|
ModelPermission.register_inheritance(
|
||||||
model=DocumentTypeSettings, related='document_type',
|
model=DocumentTypeSettings, related='document_type',
|
||||||
)
|
)
|
||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=DocumentVersionOCRError, label=_('Document'),
|
source=DocumentVersionOCRError, label=_('Document'),
|
||||||
func=lambda context: document_link(context['object'].document_version.document)
|
func=lambda context: document_link(context['object'].document_version.document)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from common import (
|
|||||||
MayanAppConfig, menu_facet, menu_object, menu_main, menu_multi_item,
|
MayanAppConfig, menu_facet, menu_object, menu_main, menu_multi_item,
|
||||||
menu_sidebar
|
menu_sidebar
|
||||||
)
|
)
|
||||||
|
from common.classes import ModelField
|
||||||
from documents.search import document_page_search, document_search
|
from documents.search import document_page_search, document_search
|
||||||
from events import ModelEventType
|
from events import ModelEventType
|
||||||
from events.links import (
|
from events.links import (
|
||||||
@@ -73,6 +74,13 @@ class TagsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ModelField(
|
||||||
|
Document, name='tags__label'
|
||||||
|
)
|
||||||
|
ModelField(
|
||||||
|
Document, name='tags__color'
|
||||||
|
)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
permission_tag_attach, permission_tag_remove,
|
permission_tag_attach, permission_tag_remove,
|
||||||
|
|||||||
Reference in New Issue
Block a user