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:
Roberto Rosario
2018-09-17 02:43:04 -04:00
parent fafdb538b3
commit 03c54395cc
11 changed files with 196 additions and 77 deletions

View File

@@ -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)
==================

View File

@@ -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
--------

View File

@@ -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)

View File

@@ -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>')
]
)

View File

@@ -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)

View File

@@ -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(

View File

@@ -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(

View File

@@ -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>')
]
)

View File

@@ -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(

View File

@@ -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)

View File

@@ -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,