Add new UI method to switch between required metadata and optional
metadata without incurring in metadata value loss. Add modifying relationship of document type and metadata type from the document type and from the metadata type views. Closes GitLab issues #337 #373. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -44,7 +44,14 @@ Other Changes
|
||||
its own separate $HOME directory. Additionally give every libreoffice
|
||||
its own UserInstallation file in the $HOME directory. Works around
|
||||
Libre Office issue: https://bugs.documentfoundation.org/show_bug.cgi?id=37531
|
||||
Solves or affects GitLab issues #393 #258 #198 #175
|
||||
Solves or affects GitLab issues #393 #258 #198 #175
|
||||
- Document type-metadata type relationship. Add new UI method to switch between
|
||||
required metadata and optional metadata for a document type. This new
|
||||
method does not forces users to remove a metadata type before making the
|
||||
switch and thus avoid deletion of existing metadata entries. GitLab issue #337.
|
||||
- It is now possible to change the relationship between metadata types and
|
||||
document types from either the metadata type list or document type list.
|
||||
GitLab issue #373.
|
||||
|
||||
|
||||
Removals
|
||||
@@ -103,5 +110,7 @@ Bugs fixed or issues closed
|
||||
* `GitHub issue #255 <https://github.com/mayan-edms/mayan-edms/issues/255>`_ Uploading a local file via api
|
||||
* `GitLab issue #215 <https://gitlab.com/mayan-edms/mayan-edms/issues/215>`_ Download text contents
|
||||
* `GitLab issue #286 <https://gitlab.com/mayan-edms/mayan-edms/issues/286>`_ User configurable mailer
|
||||
* `GitLab issue #337 <https://gitlab.com/mayan-edms/mayan-edms/issues/337>`_ Better way to switch Optional to Required Metadata
|
||||
* `GitLab issue #373 <https://gitlab.com/mayan-edms/mayan-edms/issues/373>`_ (feature request) Allow selecting document types for metadata
|
||||
|
||||
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||
|
||||
@@ -22,7 +22,17 @@
|
||||
{% for field in form.visible_fields %}
|
||||
<td title="{% if field.errors %}{% for error in field.errors %}{{ error }}{% if not forloop.last %} | {% endif %}{% endfor %}{% else %}{{ field.help_text }}{% endif %}">
|
||||
{% if field.errors %}<div class="has-error">{% endif %}
|
||||
{% render_field field class+="form-control" %}
|
||||
{% if field|widget_type == 'radioselect' %}
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
{% for option in field %}
|
||||
<label class="btn btn-default {% if field.value == option.choice_value %}active{% endif %}" for="{{ option.attrs.id }}">
|
||||
<input type="radio" name="{{ option.name }}" id="{{ option.attrs.id }}" value="{{ option.choice_value }}" {% if field.value == option.choice_value %}checked{% endif %}> {{ option.choice_label }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% render_field field class+="form-control" %}
|
||||
{% endif %}
|
||||
{% if field.errors %}</div>{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
@@ -38,7 +48,6 @@
|
||||
{% if field|widget_type != 'checkboxinput' and not field.field.widget.attrs.hidden %}
|
||||
{% if not hide_labels %}{{ field.label_tag }}{% if field.field.required and not read_only %} ({% trans 'required' %}){% endif %}{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if field|widget_type == 'checkboxinput' %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
|
||||
@@ -32,9 +32,8 @@ from .links import (
|
||||
link_metadata_add, link_metadata_edit, link_metadata_multiple_add,
|
||||
link_metadata_multiple_edit, link_metadata_multiple_remove,
|
||||
link_metadata_remove, link_metadata_view,
|
||||
link_setup_document_type_metadata,
|
||||
link_setup_document_type_metadata_required,
|
||||
link_setup_metadata_type_create, link_setup_metadata_type_delete,
|
||||
link_setup_document_type_metadata_types, link_setup_metadata_type_create,
|
||||
link_setup_metadata_type_delete, link_setup_metadata_type_document_types,
|
||||
link_setup_metadata_type_edit, link_setup_metadata_type_list,
|
||||
)
|
||||
from .permissions import (
|
||||
@@ -206,13 +205,13 @@ class MetadataApp(MayanAppConfig):
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_setup_document_type_metadata,
|
||||
link_setup_document_type_metadata_required
|
||||
link_setup_document_type_metadata_types,
|
||||
), sources=(DocumentType,)
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_setup_metadata_type_edit,
|
||||
link_setup_metadata_type_document_types,
|
||||
link_setup_metadata_type_delete
|
||||
), sources=(MetadataType,)
|
||||
)
|
||||
|
||||
@@ -170,3 +170,70 @@ class DocumentMetadataRemoveForm(DocumentMetadataForm):
|
||||
DocumentMetadataRemoveFormSet = formset_factory(
|
||||
DocumentMetadataRemoveForm, extra=0
|
||||
)
|
||||
|
||||
|
||||
class DocumentTypeMetadataTypeRelationshipForm(forms.Form):
|
||||
label = forms.CharField(
|
||||
label=_('Label'), required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'})
|
||||
)
|
||||
relationship = forms.ChoiceField(
|
||||
label=_('Relationship'),
|
||||
widget=forms.RadioSelect(), choices=(
|
||||
('none', _('None')),
|
||||
('optional', _('Optional')),
|
||||
('required', _('Required')),
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DocumentTypeMetadataTypeRelationshipForm, self).__init__(
|
||||
*args, **kwargs
|
||||
)
|
||||
if self.initial['main_model'] == 'metadata_type':
|
||||
self.fields['label'].initial = self.initial['document_type'].label
|
||||
else:
|
||||
self.fields['label'].initial = self.initial['metadata_type'].label
|
||||
|
||||
relationship = self.initial['document_type'].metadata.filter(
|
||||
metadata_type=self.initial['metadata_type']
|
||||
)
|
||||
if relationship.exists():
|
||||
if relationship.get().required:
|
||||
self.fields['relationship'].initial = 'required'
|
||||
else:
|
||||
self.fields['relationship'].initial = 'optional'
|
||||
else:
|
||||
self.fields['relationship'].initial = 'none'
|
||||
|
||||
def save(self):
|
||||
relationship = self.initial['document_type'].metadata.filter(
|
||||
metadata_type=self.initial['metadata_type']
|
||||
)
|
||||
if self.cleaned_data['relationship'] == 'none':
|
||||
relationship.delete()
|
||||
elif self.cleaned_data['relationship'] == 'optional':
|
||||
if relationship.exists():
|
||||
instance = relationship.get()
|
||||
instance.required = False
|
||||
instance.save()
|
||||
else:
|
||||
relationship.create(
|
||||
document_type=self.initial['document_type'],
|
||||
metadata_type=self.initial['metadata_type']
|
||||
)
|
||||
elif self.cleaned_data['relationship'] == 'required':
|
||||
if relationship.exists():
|
||||
instance = relationship.get()
|
||||
instance.required = True
|
||||
instance.save()
|
||||
else:
|
||||
relationship.create(
|
||||
document_type=self.initial['document_type'],
|
||||
metadata_type=self.initial['metadata_type'], required=True
|
||||
)
|
||||
|
||||
|
||||
DocumentTypeMetadataTypeRelationshipFormSet = formset_factory(
|
||||
DocumentTypeMetadataTypeRelationshipForm, extra=0
|
||||
)
|
||||
|
||||
@@ -36,15 +36,17 @@ link_metadata_remove = Link(
|
||||
)
|
||||
link_metadata_view = Link(
|
||||
icon='fa fa-pencil', permissions=(permission_metadata_document_view,),
|
||||
text=_('Metadata'), view='metadata:metadata_view', args='resolved_object.pk'
|
||||
text=_('Metadata'), view='metadata:metadata_view',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_setup_document_type_metadata = Link(
|
||||
permissions=(permission_document_type_edit,), text=_('Optional metadata'),
|
||||
view='metadata:setup_document_type_metadata', args='resolved_object.pk'
|
||||
link_setup_document_type_metadata_types = Link(
|
||||
permissions=(permission_document_type_edit,), text=_('Metadata types'),
|
||||
view='metadata:setup_document_type_metadata_types',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_setup_document_type_metadata_required = Link(
|
||||
permissions=(permission_document_type_edit,), text=_('Required metadata'),
|
||||
view='metadata:setup_document_type_metadata_required',
|
||||
link_setup_metadata_type_document_types = Link(
|
||||
permissions=(permission_document_type_edit,), text=_('Document types'),
|
||||
view='metadata:setup_metadata_type_document_types',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_setup_metadata_type_create = Link(
|
||||
|
||||
@@ -14,7 +14,22 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='metadatatype',
|
||||
name='parser',
|
||||
field=models.CharField(blank=True, help_text='The parser will reformat the value entered to conform to the expected format.', max_length=64, verbose_name='Parser', choices=[(b'metadata.parsers.DateAndTimeParser', b'metadata.parsers.DateAndTimeParser'), (b'metadata.parsers.DateParser', b'metadata.parsers.DateParser'), (b'metadata.parsers.TimeParser', b'metadata.parsers.TimeParser')]),
|
||||
preserve_default=True,
|
||||
field=models.CharField(
|
||||
blank=True, help_text='The parser will reformat the value '
|
||||
'entered to conform to the expected format.', max_length=64,
|
||||
verbose_name='Parser',
|
||||
choices=[
|
||||
(
|
||||
b'metadata.parsers.DateAndTimeParser',
|
||||
b'metadata.parsers.DateAndTimeParser'
|
||||
), (
|
||||
b'metadata.parsers.DateParser',
|
||||
b'metadata.parsers.DateParser'
|
||||
), (
|
||||
b'metadata.parsers.TimeParser',
|
||||
b'metadata.parsers.TimeParser'
|
||||
)
|
||||
]
|
||||
), preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -11,8 +11,8 @@ from .views import (
|
||||
DocumentMetadataAddView, DocumentMetadataEditView,
|
||||
DocumentMetadataListView, DocumentMetadataRemoveView,
|
||||
MetadataTypeCreateView, MetadataTypeDeleteView, MetadataTypeEditView,
|
||||
MetadataTypeListView, SetupDocumentTypeMetadataOptionalView,
|
||||
SetupDocumentTypeMetadataRequiredView
|
||||
MetadataTypeListView, SetupDocumentTypeMetadataTypes,
|
||||
SetupMetadataTypesDocumentTypes
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -61,16 +61,15 @@ urlpatterns = [
|
||||
r'^setup/type/(?P<pk>\d+)/delete/$',
|
||||
MetadataTypeDeleteView.as_view(), name='setup_metadata_type_delete'
|
||||
),
|
||||
|
||||
url(
|
||||
r'^setup/document/type/(?P<pk>\d+)/metadata/edit/$',
|
||||
SetupDocumentTypeMetadataOptionalView.as_view(),
|
||||
name='setup_document_type_metadata'
|
||||
r'^setup/document_types/(?P<pk>\d+)/metadata_types/$',
|
||||
SetupDocumentTypeMetadataTypes.as_view(),
|
||||
name='setup_document_type_metadata_types'
|
||||
),
|
||||
url(
|
||||
r'^setup/document/type/(?P<pk>\d+)/metadata/edit/required/$',
|
||||
SetupDocumentTypeMetadataRequiredView.as_view(),
|
||||
name='setup_document_type_metadata_required'
|
||||
r'^setup/metadata_types/(?P<pk>\d+)/document_types/$',
|
||||
SetupMetadataTypesDocumentTypes.as_view(),
|
||||
name='setup_metadata_type_document_types'
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _, ungettext
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from common.generics import (
|
||||
AssignRemoveView, MultipleObjectFormActionView, SingleObjectCreateView,
|
||||
FormView, MultipleObjectFormActionView, SingleObjectCreateView,
|
||||
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
|
||||
)
|
||||
from documents.models import Document, DocumentType
|
||||
@@ -23,7 +23,8 @@ from documents.permissions import (
|
||||
from .api import save_metadata_list
|
||||
from .forms import (
|
||||
DocumentAddMetadataForm, DocumentMetadataFormSet,
|
||||
DocumentMetadataRemoveFormSet, MetadataTypeForm
|
||||
DocumentMetadataRemoveFormSet,
|
||||
DocumentTypeMetadataTypeRelationshipFormSet, MetadataTypeForm
|
||||
)
|
||||
from .models import DocumentMetadata, MetadataType
|
||||
from .permissions import (
|
||||
@@ -44,7 +45,9 @@ class DocumentMetadataAddView(MultipleObjectFormActionView):
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
result = super(DocumentMetadataAddView, self).dispatch(request, *args, **kwargs)
|
||||
result = super(
|
||||
DocumentMetadataAddView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
@@ -198,7 +201,9 @@ class DocumentMetadataEditView(MultipleObjectFormActionView):
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
result = super(DocumentMetadataEditView, self).dispatch(request, *args, **kwargs)
|
||||
result = super(
|
||||
DocumentMetadataEditView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
@@ -274,7 +279,9 @@ class DocumentMetadataEditView(MultipleObjectFormActionView):
|
||||
document.add_as_recent_document_for_user(self.request.user)
|
||||
|
||||
for document_metadata in document.metadata.all():
|
||||
metadata_dict.setdefault(document_metadata.metadata_type, set())
|
||||
metadata_dict.setdefault(
|
||||
document_metadata.metadata_type, set()
|
||||
)
|
||||
|
||||
if document_metadata.value:
|
||||
metadata_dict[
|
||||
@@ -364,7 +371,9 @@ class DocumentMetadataRemoveView(MultipleObjectFormActionView):
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
result = super(DocumentMetadataRemoveView, self).dispatch(request, *args, **kwargs)
|
||||
result = super(
|
||||
DocumentMetadataRemoveView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
@@ -543,59 +552,87 @@ class MetadataTypeListView(SingleObjectListView):
|
||||
}
|
||||
|
||||
|
||||
class SetupDocumentTypeMetadataOptionalView(AssignRemoveView):
|
||||
decode_content_type = True
|
||||
class SetupDocumentTypeMetadataTypes(FormView):
|
||||
form_class = DocumentTypeMetadataTypeRelationshipFormSet
|
||||
main_model = 'document_type'
|
||||
model = DocumentType
|
||||
submodel = MetadataType
|
||||
view_permission = permission_document_type_edit
|
||||
left_list_title = _('Available metadata types')
|
||||
right_list_title = _('Metadata types assigned')
|
||||
|
||||
def add(self, item):
|
||||
self.get_object().metadata.create(metadata_type=item, required=False)
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
for instance in form:
|
||||
instance.save()
|
||||
except Exception as exception:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('Error updating relationship; %s') % exception
|
||||
)
|
||||
else:
|
||||
messages.success(
|
||||
self.request, _('Relationships updated successfully')
|
||||
)
|
||||
|
||||
return super(
|
||||
SetupDocumentTypeMetadataTypes, self
|
||||
).form_valid(form=form)
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(DocumentType, pk=self.kwargs['pk'])
|
||||
|
||||
def left_list(self):
|
||||
return AssignRemoveView.generate_choices(
|
||||
set(MetadataType.objects.all()) - set(
|
||||
MetadataType.objects.filter(
|
||||
id__in=self.get_object().metadata.values_list(
|
||||
'metadata_type', flat=True
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def right_list(self):
|
||||
return AssignRemoveView.generate_choices(
|
||||
self.get_object().metadata.filter(required=False)
|
||||
)
|
||||
|
||||
def remove(self, item):
|
||||
item.delete()
|
||||
return get_object_or_404(self.model, pk=self.kwargs['pk'])
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'form_display_mode_table': True,
|
||||
'object': self.get_object(),
|
||||
'title': _(
|
||||
'Optional metadata types for document type: %s'
|
||||
) % self.get_object(),
|
||||
'Metadata types for document type: %s'
|
||||
) % self.get_object()
|
||||
}
|
||||
|
||||
def get_initial(self):
|
||||
obj = self.get_object()
|
||||
initial = []
|
||||
|
||||
class SetupDocumentTypeMetadataRequiredView(SetupDocumentTypeMetadataOptionalView):
|
||||
def add(self, item):
|
||||
self.get_object().metadata.create(metadata_type=item, required=True)
|
||||
for element in self.get_queryset():
|
||||
initial.append({
|
||||
'document_type': obj,
|
||||
'main_model': self.main_model,
|
||||
'metadata_type': element,
|
||||
})
|
||||
return initial
|
||||
|
||||
def right_list(self):
|
||||
return AssignRemoveView.generate_choices(
|
||||
self.get_object().metadata.filter(required=True)
|
||||
)
|
||||
def get_post_action_redirect(self):
|
||||
return reverse('documents:document_type_list')
|
||||
|
||||
def get_queryset(self):
|
||||
return self.submodel.objects.all()
|
||||
|
||||
|
||||
class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes):
|
||||
main_model = 'metadata_type'
|
||||
model = MetadataType
|
||||
submodel = DocumentType
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'form_display_mode_table': True,
|
||||
'object': self.get_object(),
|
||||
'title': _(
|
||||
'Required metadata types for document type: %s'
|
||||
) % self.get_object(),
|
||||
'Document types for metadata type: %s'
|
||||
) % self.get_object()
|
||||
}
|
||||
|
||||
def get_initial(self):
|
||||
obj = self.get_object()
|
||||
initial = []
|
||||
|
||||
for element in self.get_queryset():
|
||||
initial.append({
|
||||
'document_type': element,
|
||||
'main_model': self.main_model,
|
||||
'metadata_type': obj,
|
||||
})
|
||||
return initial
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
return reverse('metadata:setup_metadata_type_list')
|
||||
|
||||
Reference in New Issue
Block a user