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.
|
||||
Closes GitLab issue #511. Thanks to Daniel Carrico
|
||||
@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)
|
||||
==================
|
||||
|
||||
@@ -367,6 +367,11 @@ classes beyond the provide line chart.
|
||||
templates that only refresh the menu when there are changes.
|
||||
Closes GitLab issue #511. Thanks to Daniel Carrico
|
||||
@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
|
||||
--------
|
||||
|
||||
@@ -178,19 +178,15 @@ class MissingItem(object):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ModelAttribute(object):
|
||||
__registry = {}
|
||||
_registry = {}
|
||||
|
||||
@classmethod
|
||||
def get_for(cls, model, type_names=None):
|
||||
def get_for(cls, model):
|
||||
result = []
|
||||
|
||||
try:
|
||||
for type_name, attributes in cls.__registry[model].items():
|
||||
if not type_names or type_name in type_names:
|
||||
result.extend(attributes)
|
||||
|
||||
return result
|
||||
except IndexError:
|
||||
return cls._registry[model]
|
||||
except KeyError:
|
||||
# We were passed a model instance, try again using the model of
|
||||
# the instance
|
||||
|
||||
@@ -198,30 +194,39 @@ class ModelAttribute(object):
|
||||
if model.__class__ == models.base.ModelBase:
|
||||
raise
|
||||
|
||||
return cls.get_for[type(model)]
|
||||
return cls.get_for(model=type(model))
|
||||
|
||||
@classmethod
|
||||
def get_choices_for(cls, model, type_names=None):
|
||||
def get_choices_for(cls, model):
|
||||
return [
|
||||
(
|
||||
attribute.name, attribute
|
||||
) for attribute in cls.get_for(model, type_names)
|
||||
(attribute.name, attribute) for attribute in cls.get_for(model)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def help_text_for(cls, model, type_names=None):
|
||||
def get_help_text_for(cls, model, show_name=False):
|
||||
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(
|
||||
'{}) {}'.format(
|
||||
count, force_text(attribute.get_display(show_name=True))
|
||||
count, force_text(attribute.get_display(show_name=show_name))
|
||||
)
|
||||
)
|
||||
|
||||
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):
|
||||
if self.description:
|
||||
return '{} - {}'.format(
|
||||
@@ -230,29 +235,101 @@ class ModelAttribute(object):
|
||||
else:
|
||||
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):
|
||||
self.model = model
|
||||
self.label = label
|
||||
self.name = name
|
||||
self.description = description
|
||||
class ModelField(ModelAttribute):
|
||||
"""Subclass to handle model database fields"""
|
||||
_registry = {}
|
||||
|
||||
for field in model._meta.fields:
|
||||
if field.name == name:
|
||||
self.label = field.verbose_name
|
||||
self.description = field.help_text
|
||||
@classmethod
|
||||
def get_help_text_for(cls, model, show_name=False):
|
||||
result = []
|
||||
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):
|
||||
for single_type in type_name:
|
||||
self.__registry[model].setdefault(single_type, [])
|
||||
self.__registry[model][single_type].append(self)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ModelField, self).__init__(*args, **kwargs)
|
||||
self._final_model_verbose_name = None
|
||||
|
||||
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:
|
||||
self.__registry[model].setdefault(type_name, [])
|
||||
self.__registry[model][type_name].append(self)
|
||||
self._final_model_verbose_name = model._meta.verbose_name
|
||||
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):
|
||||
@@ -321,3 +398,7 @@ class Template(object):
|
||||
self.html = result.content
|
||||
self.hex_hash = hashlib.sha256(result.content).hexdigest()
|
||||
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 acls.models import AccessControlList
|
||||
from common.classes import ModelAttribute
|
||||
from common.classes import ModelProperty
|
||||
from documents.models import Document
|
||||
|
||||
from .models import Index, IndexTemplateNode
|
||||
@@ -40,8 +40,9 @@ class IndexTemplateNodeForm(forms.ModelForm):
|
||||
self.fields['expression'].help_text = ' '.join(
|
||||
[
|
||||
force_text(self.fields['expression'].help_text),
|
||||
ModelAttribute.help_text_for(
|
||||
Document, type_names=['indexing']
|
||||
'<br>',
|
||||
ModelProperty.get_help_text_for(
|
||||
model=Document, show_name=True
|
||||
).replace('\n', '<br>')
|
||||
]
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ from common import (
|
||||
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
||||
menu_tools
|
||||
)
|
||||
from common.classes import ModelField
|
||||
from common.settings import settings_db_sync_task_delay
|
||||
from documents.search import document_search, document_page_search
|
||||
from documents.signals import post_version_upload
|
||||
@@ -97,6 +98,10 @@ class DocumentParsingApp(MayanAppConfig):
|
||||
'submit_for_parsing', document_version_parsing_submit
|
||||
)
|
||||
|
||||
ModelField(
|
||||
Document, name='versions__pages__content__content'
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Document, permissions=(
|
||||
permission_content_view, permission_parse_document
|
||||
@@ -110,6 +115,7 @@ class DocumentParsingApp(MayanAppConfig):
|
||||
ModelPermission.register_inheritance(
|
||||
model=DocumentTypeSettings, related='document_type',
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
source=DocumentVersionParseError, label=_('Document'),
|
||||
func=lambda context: document_link(context['object'].document_version.document)
|
||||
|
||||
@@ -90,18 +90,19 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
WorkflowAction.initialize()
|
||||
|
||||
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=_(
|
||||
'Return the current state of the selected workflow'
|
||||
), type_name=['property', 'indexing']
|
||||
)
|
||||
)
|
||||
ModelAttribute(
|
||||
Document,
|
||||
'workflow.< workflow internal name >.get_current_state.completion',
|
||||
model=Document,
|
||||
name='workflow.< workflow internal name >.get_current_state.completion',
|
||||
label=_('Current state of a workflow'), description=_(
|
||||
'Return the completion value of the current state of the '
|
||||
'selected workflow'
|
||||
), type_name=['property', 'indexing']
|
||||
)
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
|
||||
@@ -14,7 +14,7 @@ from common import (
|
||||
MayanAppConfig, MissingItem, menu_facet, menu_main, menu_object,
|
||||
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.signals import post_initial_setup
|
||||
from common.widgets import TwoStateWidget
|
||||
@@ -142,14 +142,29 @@ class DocumentsApp(MayanAppConfig):
|
||||
view='documents:document_type_list'
|
||||
)
|
||||
|
||||
ModelAttribute(
|
||||
Document, label=_('Label'), name='label', type_name='field'
|
||||
ModelField(Document, name='description')
|
||||
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'
|
||||
)
|
||||
|
||||
ModelAttribute(
|
||||
Document,
|
||||
description=_('The MIME type of any of the versions of a document'),
|
||||
label=_('MIME type'), name='versions__mimetype', type_name='field'
|
||||
ModelField(
|
||||
Document, label=_('Versions comment'), name='versions__comment'
|
||||
)
|
||||
ModelField(
|
||||
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(
|
||||
|
||||
@@ -4,7 +4,7 @@ from django import forms
|
||||
from django.utils.encoding import force_text
|
||||
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 .models import SmartLink, SmartLinkCondition
|
||||
@@ -16,8 +16,8 @@ class SmartLinkForm(forms.ModelForm):
|
||||
self.fields['dynamic_label'].help_text = ' '.join(
|
||||
[
|
||||
force_text(self.fields['dynamic_label'].help_text),
|
||||
ModelAttribute.help_text_for(
|
||||
Document, type_names=['field', 'related', 'property']
|
||||
ModelProperty.get_help_text_for(
|
||||
model=Document, show_name=True
|
||||
).replace('\n', '<br>')
|
||||
]
|
||||
)
|
||||
@@ -31,15 +31,15 @@ class SmartLinkConditionForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SmartLinkConditionForm, self).__init__(*args, **kwargs)
|
||||
self.fields['foreign_document_data'] = forms.ChoiceField(
|
||||
choices=ModelAttribute.get_choices_for(
|
||||
Document, type_names=['field', 'query']
|
||||
), label=_('Foreign document attribute')
|
||||
choices=ModelField.get_choices_for(
|
||||
model=Document,
|
||||
), label=_('Foreign document field')
|
||||
)
|
||||
self.fields['expression'].help_text = ' '.join(
|
||||
[
|
||||
force_text(self.fields['expression'].help_text),
|
||||
ModelAttribute.help_text_for(
|
||||
Document, type_names=['field', 'related', 'property']
|
||||
ModelProperty.get_help_text_for(
|
||||
model=Document, show_name=True
|
||||
).replace('\n', '<br>')
|
||||
]
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ from common import (
|
||||
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
||||
menu_setup, menu_sidebar
|
||||
)
|
||||
from common.classes import ModelAttribute
|
||||
from common.classes import ModelAttribute, ModelField
|
||||
from common.widgets import TwoStateWidget
|
||||
from documents.search import document_page_search, document_search
|
||||
from documents.signals import post_document_type_change
|
||||
@@ -92,26 +92,18 @@ class MetadataApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
ModelAttribute(
|
||||
Document, 'metadata', type_name='related',
|
||||
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'),
|
||||
Document, 'metadata_value_of',
|
||||
description=_(
|
||||
'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(
|
||||
|
||||
@@ -15,6 +15,7 @@ from common import (
|
||||
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
|
||||
menu_tools
|
||||
)
|
||||
from common.classes import ModelField
|
||||
from common.settings import settings_db_sync_task_delay
|
||||
from documents.search import document_search, document_page_search
|
||||
from documents.signals import post_version_upload
|
||||
@@ -98,6 +99,10 @@ class OCRApp(MayanAppConfig):
|
||||
'submit_for_ocr', document_version_ocr_submit
|
||||
)
|
||||
|
||||
ModelField(
|
||||
Document, name='versions__pages__ocr_content__content'
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Document, permissions=(
|
||||
permission_ocr_document, permission_ocr_content_view
|
||||
@@ -111,6 +116,7 @@ class OCRApp(MayanAppConfig):
|
||||
ModelPermission.register_inheritance(
|
||||
model=DocumentTypeSettings, related='document_type',
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
source=DocumentVersionOCRError, label=_('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,
|
||||
menu_sidebar
|
||||
)
|
||||
from common.classes import ModelField
|
||||
from documents.search import document_page_search, document_search
|
||||
from events import ModelEventType
|
||||
from events.links import (
|
||||
@@ -73,6 +74,13 @@ class TagsApp(MayanAppConfig):
|
||||
)
|
||||
)
|
||||
|
||||
ModelField(
|
||||
Document, name='tags__label'
|
||||
)
|
||||
ModelField(
|
||||
Document, name='tags__color'
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Document, permissions=(
|
||||
permission_tag_attach, permission_tag_remove,
|
||||
|
||||
Reference in New Issue
Block a user