summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2020-11-27 11:26:55 +0100
committerÉtienne Loks <etienne.loks@iggdrasil.net>2021-02-28 12:15:21 +0100
commit975b971ae6efacbd5614be7f057f32d7c302966b (patch)
tree300713d38252ed0fb9d25e48e8499b29523b7985 /ishtar_common
parent30bf32d6768f3c013b44e1ed76d94f9954d6e565 (diff)
downloadIshtar-975b971ae6efacbd5614be7f057f32d7c302966b.tar.bz2
Ishtar-975b971ae6efacbd5614be7f057f32d7c302966b.zip
Documents: dynamic filter of support and medium by document type - collapse related fields on edition
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/admin.py20
-rw-r--r--ishtar_common/forms.py116
-rw-r--r--ishtar_common/forms_common.py70
-rw-r--r--ishtar_common/lookups.py9
-rw-r--r--ishtar_common/migrations/0208_auto_20201126_1516.py (renamed from ishtar_common/migrations/0208_auto_20201126_1217.py)12
-rw-r--r--ishtar_common/models.py9
-rw-r--r--ishtar_common/static/js/ishtar.js19
-rw-r--r--ishtar_common/templates/blocks/bs_form_snippet.html2
-rw-r--r--ishtar_common/templates/ishtar/forms/document.html20
-rw-r--r--ishtar_common/templatetags/table_form.py5
-rw-r--r--ishtar_common/views.py15
11 files changed, 271 insertions, 26 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py
index b5ff67567..26ceb01cd 100644
--- a/ishtar_common/admin.py
+++ b/ishtar_common/admin.py
@@ -982,8 +982,8 @@ class GeneralTypeAdmin(ImportActionAdmin, ImportJSONActionAdmin):
general_models = [models.OrganizationType, models.SourceType,
- models.AuthorType, models.TitleType, models.Format,
- models.SupportType, models.PersonType, models.LicenseType,
+ models.AuthorType, models.TitleType,
+ models.PersonType, models.LicenseType,
models.Language]
for model in general_models:
admin_site.register(model, GeneralTypeAdmin)
@@ -1081,6 +1081,22 @@ class CreateDepartmentActionAdmin(GeneralTypeAdmin):
{'form': form, 'current_action': 'create_area'}
)
+@admin.register(models.SupportType, site=admin_site)
+class SupportType(GeneralTypeAdmin):
+ model = models.SupportType
+ form = make_ajax_form(model, {'document_types': 'source_type'})
+
+
+@admin.register(models.Format, site=admin_site)
+class Format(GeneralTypeAdmin):
+ model = models.Format
+ form = make_ajax_form(model, {'document_types': 'source_type'})
+
+
+@admin.register(models.DocumentTag, site=admin_site)
+class DocumentTag(MergeActionAdmin, GeneralTypeAdmin):
+ pass
+
class AreaAdmin(CreateDepartmentActionAdmin):
list_display = ('label', 'reference', 'parent', 'available')
diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py
index 6193c72c4..f1e5b34ca 100644
--- a/ishtar_common/forms.py
+++ b/ishtar_common/forms.py
@@ -622,6 +622,7 @@ class FormHeader(object):
class IshtarForm(forms.Form, BSForm):
TYPES = [] # FieldType list
CONDITIONAL_FIELDS = [] # dynamic conditions on field display
+ # can be dynamic with "get_conditional_fields"
PROFILE_FILTER = {} # profile key associated to field list
HEADERS = {} # field key associated to FormHeader instance
# permission check for widget options, ex: forms_common.DocumentForm
@@ -668,14 +669,19 @@ class IshtarForm(forms.Form, BSForm):
self.fields[field.key].choices = field.get_choices()
self.fields[field.key].help_text = field.get_help()
+ def get_headers(self):
+ return self.HEADERS
+
def headers(self, key):
- if key not in self.HEADERS:
+ headers = self.get_headers()
+ if key not in headers:
return
- self.current_header = self.HEADERS[key]
+ self.current_header = headers[key]
return self.current_header
def extra_render(self):
- return self.get_conditional()
+ return (self.get_conditional() or "") + (
+ self.get_conditional_filters() or "")
HIDE_JS_TEMPLATE = """
var %(id)s_item_show_list = ['%(item_list)s'];
@@ -688,7 +694,6 @@ class IshtarForm(forms.Form, BSForm):
var %(id)s_item_show_list = ['%(item_list)s'];
var %(id)s_hide_display = function(){
var current_val = $("#id_%(name)s").val();
- console.log("#id_%(name)s");
if (%(id)s_check_list.indexOf(current_val) != -1){
for (idx in %(id)s_item_show_list){
$("#main_div-id_" + %(id)s_item_show_list[idx]).removeClass("d-none");
@@ -707,12 +712,15 @@ class IshtarForm(forms.Form, BSForm):
"""
def get_conditional(self):
- if not self.CONDITIONAL_FIELDS or not self.TYPES:
+ conditional_fields = self.CONDITIONAL_FIELDS
+ if hasattr(self, 'get_conditional_fields'):
+ conditional_fields = self.get_conditional_fields()
+ if not conditional_fields or not self.TYPES:
return
type_dict = dict([(typ.key, typ.model) for typ in self.TYPES])
html = ""
- for condition, target_names in self.CONDITIONAL_FIELDS:
+ for condition, target_names in conditional_fields:
condition_field, condition_attr, condition_val = condition
if condition_field not in type_dict:
continue
@@ -740,6 +748,102 @@ class IshtarForm(forms.Form, BSForm):
html = "<script type='text/javascript'>" + html + "</script>"
return html
+ CONDITIONAL_FILTER_JS_TEMPLATE = """
+ %(filter_list)s;
+ var %(id)s_prefix = "%(prefix)s";
+ var %(id)s_filter_display = function(){
+ var current_val = $("#id_%(name)s").val();
+ if (current_val in %(id)s_filter_list){
+ for (var k in %(id)s_filter_list[current_val]){
+ var cname = k;
+ if (%(id)s_prefix) cname = %(id)s_prefix + cname;
+ update_select_widget(
+ cname,
+ %(id)s_all_value_list[k],
+ %(id)s_filter_list[current_val][k]);
+ }
+ } else {
+ for (var k in %(id)s_exclude_list){
+ var cname = k;
+ if (%(id)s_prefix) cname = %(id)s_prefix + cname;
+ update_select_widget(
+ cname,
+ %(id)s_all_value_list[k],
+ null,
+ %(id)s_exclude_list[k]);
+ }
+ }
+ };
+
+ $("#id_%(name)s").change(%(id)s_filter_display);
+ setTimeout(function(){
+ %(id)s_filter_display();
+ }, 500);
+ """
+
+ def get_conditional_filters(self):
+ if not hasattr(self, 'get_conditional_filter_fields'):
+ return
+ conditional_fields, excluded_fields, all_values = \
+ self.get_conditional_filter_fields()
+
+ types = [typ.key for typ in self.TYPES]
+ html = ""
+
+ outputs = set()
+ for input_key in conditional_fields:
+ if input_key not in types:
+ continue
+ name = input_key
+ if self.prefix:
+ name = self.prefix + "-" + input_key
+ cidx = name.replace("-", "_")
+ filter_list = "var %s_filter_list = {\n" % cidx
+ for idx, input_pk in enumerate(conditional_fields[input_key]):
+ if idx:
+ filter_list += ",\n"
+ filter_list += ' "%s": {\n' % input_pk
+ for idx2, output in enumerate(
+ conditional_fields[input_key][input_pk]):
+ if idx2:
+ filter_list += ",\n"
+ if output[0] in excluded_fields:
+ outputs.add(output[0])
+ filter_list += ' "{}": [{}]'.format(*output)
+ filter_list += " }"
+ filter_list += "};\n"
+
+ html += self.CONDITIONAL_FILTER_JS_TEMPLATE % {
+ "id": cidx,
+ "name": name,
+ "filter_list": filter_list,
+ "prefix": self.prefix or ""
+ }
+ html += "var %s_other_widget_list = [" % cidx
+ for idx, k in enumerate(all_values):
+ if idx:
+ html += ", "
+ html += '"' + k + '"'
+ html += "];\n"
+
+ html += "var %s_exclude_list = {\n" % cidx
+ for idx, output in enumerate(outputs):
+ if idx:
+ html += ",\n"
+ html += ' "%s": [%s]' % (output, excluded_fields[output])
+ html += "\n};\n"
+
+ html += "var %s_all_value_list = {\n" % cidx
+ for idx, k in enumerate(all_values):
+ if idx:
+ html += ",\n"
+ html += ' "%s": %s' % (k, all_values[k])
+ html += "\n};\n"
+
+ if html:
+ html = "<script type='text/javascript'>" + html + "</script>"
+ return html
+
class TableSelect(IshtarForm):
def __init__(self, *args, **kwargs):
diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py
index c76577e65..c191a2e6e 100644
--- a/ishtar_common/forms_common.py
+++ b/ishtar_common/forms_common.py
@@ -1398,6 +1398,7 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType):
]
HEADERS = {
+ 'finds': FormHeader(_("Related items")),
'title': FormHeader(_("Identification")),
'format_type': FormHeader(_("Format")),
'image': FormHeader(_("Content")),
@@ -1407,7 +1408,6 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType):
'source': FormHeader(_("Source"), collapse=True),
'container_id': FormHeader(_("Warehouse"), collapse=True),
'comment': FormHeader(_("Advanced"), collapse=True),
- 'finds': FormHeader(_("Related items")),
}
OPTIONS_PERMISSIONS = [
# field name, permission, options
@@ -1427,6 +1427,7 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType):
self.user = None
if kwargs.get("user", None):
self.user = kwargs.pop("user")
+ self.is_instancied = bool(kwargs.get('instance', False))
super(DocumentForm, self).__init__(*args, **kwargs)
fields = OrderedDict()
for related_key in models.Document.RELATED_MODELS_ALT:
@@ -1447,6 +1448,73 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType):
fields[k] = self.fields[k]
self.fields = fields
+ def get_headers(self):
+ headers = self.HEADERS.copy()
+ if self.is_instancied:
+ headers["finds"] = FormHeader(headers["finds"].label, collapse=True)
+ return headers
+
+ def get_conditional_filter_fields(self):
+ """
+ Helper to get values for filtering select widget on select by another
+ widget
+ :return: conditional_fields, excluded_fields, all_values
+ * conditional_fields:
+ {input_key: {
+ input_pk: [(output_key1, "pk1,pk2,..."),
+ (output_key2, "pk1,pk2,...")],
+ input_pk2: ...
+ },
+ input_key2: {...}
+ }
+ * excluded_fields: {output_key: "pk1,pk2,...", ...} # list pk for all
+ output_key in condition_fields
+ * all_values: {output_key: [(pk1, label1), (pk2, label2), ...]}
+ """
+ conditional_fields = {}
+ excluded_fields = {}
+ key = 'source_type'
+ for doc_type in models.SourceType.objects.filter(
+ available=True, formats__pk__isnull=False).all():
+ if key not in conditional_fields:
+ conditional_fields[key] = {}
+ sub_key = doc_type.pk
+ if sub_key in conditional_fields[key]:
+ continue
+ lst = [str(f.pk) for f in models.Format.objects.filter(
+ available=True, document_types__pk=doc_type.pk)]
+ if 'format_type' not in excluded_fields:
+ excluded_fields['format_type'] = []
+ for k in lst:
+ if k not in excluded_fields['format_type']:
+ excluded_fields['format_type'].append(k)
+ conditional_fields[key][sub_key] = []
+ conditional_fields[key][sub_key].append(
+ ('format_type', ",".join(lst)))
+ for doc_type in models.SourceType.objects.filter(
+ available=True, supports__pk__isnull=False).all():
+ if key not in conditional_fields:
+ conditional_fields[key] = {}
+ lst = [str(f.pk) for f in models.SupportType.objects.filter(
+ available=True, document_types__pk=doc_type.pk)]
+ if 'support_type' not in excluded_fields:
+ excluded_fields['support_type'] = []
+ for k in lst:
+ if k not in excluded_fields['support_type']:
+ excluded_fields['support_type'].append(k)
+ sub_key = doc_type.pk
+ if sub_key not in conditional_fields[key]:
+ conditional_fields[key][sub_key] = []
+ conditional_fields[key][sub_key].append(
+ ('support_type', ",".join(lst)))
+ for k in excluded_fields:
+ excluded_fields[k] = ",".join(excluded_fields[k])
+ all_values = {
+ "format_type": [list(tp) for tp in models.Format.get_types()],
+ "support_type": [list(tp) for tp in models.SupportType.get_types()]
+ }
+ return conditional_fields, excluded_fields, all_values
+
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get('title', None) and \
diff --git a/ishtar_common/lookups.py b/ishtar_common/lookups.py
index 3481ba1a9..66b43f3e4 100644
--- a/ishtar_common/lookups.py
+++ b/ishtar_common/lookups.py
@@ -156,3 +156,12 @@ class DocumentLookup(LookupChannel):
internal_reference__icontains=q
)
return self.model.objects.filter(query).order_by('title')[:20]
+
+
+@register('source_type')
+class SourceTypeLookup(LookupChannel):
+ model = models.SourceType
+
+ def get_query(self, q, request):
+ query = Q(label__icontains=q)
+ return self.model.objects.filter(query).order_by('label')[:20]
diff --git a/ishtar_common/migrations/0208_auto_20201126_1217.py b/ishtar_common/migrations/0208_auto_20201126_1516.py
index 53b883075..0bd51e4c0 100644
--- a/ishtar_common/migrations/0208_auto_20201126_1217.py
+++ b/ishtar_common/migrations/0208_auto_20201126_1516.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Generated by Django 1.11.27 on 2020-11-26 12:17
+# Generated by Django 1.11.27 on 2020-11-26 15:16
from __future__ import unicode_literals
from django.db import migrations, models
@@ -12,6 +12,16 @@ class Migration(migrations.Migration):
]
operations = [
+ migrations.AddField(
+ model_name='format',
+ name='document_types',
+ field=models.ManyToManyField(blank=True, help_text='Only available for theses document types', related_name='formats', to='ishtar_common.SourceType'),
+ ),
+ migrations.AddField(
+ model_name='supporttype',
+ name='document_types',
+ field=models.ManyToManyField(blank=True, help_text='Only available for theses document types', related_name='supports', to='ishtar_common.SourceType'),
+ ),
migrations.AlterField(
model_name='document',
name='isbn',
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index e251fee89..ffe302454 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -2636,6 +2636,11 @@ post_delete.connect(post_save_cache, sender=SourceType)
class SupportType(GeneralType):
+ document_types = models.ManyToManyField(
+ "SourceType", blank=True, related_name='supports',
+ help_text=_("Only available for theses document types")
+ )
+
class Meta:
verbose_name = _("Support type")
verbose_name_plural = _("Support types")
@@ -2651,6 +2656,10 @@ class Format(GeneralType):
help_text=_("Template to insert an iframe for this format. Use django "
"template with a {{document}} variable matching the "
"current document."))
+ document_types = models.ManyToManyField(
+ "SourceType", blank=True, related_name='formats',
+ help_text=_("Only available for theses document types")
+ )
class Meta:
verbose_name = _("Format type")
diff --git a/ishtar_common/static/js/ishtar.js b/ishtar_common/static/js/ishtar.js
index fcaa73053..6528d3797 100644
--- a/ishtar_common/static/js/ishtar.js
+++ b/ishtar_common/static/js/ishtar.js
@@ -1856,3 +1856,22 @@ var is_valid_issn = function(str) {
}
return (check == str[str.length-1].toUpperCase());
}
+
+var update_select_widget = function(input_name, values, only_values, excluded_values){
+ cvalue = $("#id_" + input_name).val();
+ var options = "";
+ for (var idx in values){
+ var option = values[idx];
+ if (!(option[0] && option[0] != cvalue
+ && ((excluded_values &&
+ excluded_values.indexOf(option[0]) != -1) ||
+ (only_values && only_values.indexOf(option[0]) == -1)
+ ))){
+ var selected = "";
+ if (option[0] == cvalue) selected = " selected";
+ options += "<option value='" + option[0] + "'" + selected + ">";
+ options += option[1] + "</option>";
+ }
+ }
+ $("#id_" + input_name).html(options);
+}; \ No newline at end of file
diff --git a/ishtar_common/templates/blocks/bs_form_snippet.html b/ishtar_common/templates/blocks/bs_form_snippet.html
index e24220005..3d84ce3dc 100644
--- a/ishtar_common/templates/blocks/bs_form_snippet.html
+++ b/ishtar_common/templates/blocks/bs_form_snippet.html
@@ -1,5 +1,5 @@
{% load i18n from_dict %}
-{% if form.non_field_errors %}
+{% if form.non_field_errors and not no_error %}
<div class="alert alert-danger" role="alert">
{{form.non_field_errors}}
</div>
diff --git a/ishtar_common/templates/ishtar/forms/document.html b/ishtar_common/templates/ishtar/forms/document.html
index ed5a39daf..8e2683a03 100644
--- a/ishtar_common/templates/ishtar/forms/document.html
+++ b/ishtar_common/templates/ishtar/forms/document.html
@@ -13,18 +13,18 @@
<div class='form{% if not form.SEARCH_AND_SELECT %} container{% endif %}'>
{% if form.non_field_errors or form.errors %}
<div class="alert alert-danger">
- <div><i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
- {% trans "Error on validation. Check all your fields. Modification not saved." %}
+ <div><i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
+ {% trans "Error on validation. Check all your fields. Modification not saved." %}
+ </div>
+ {% for key, error_details in form.errors.items %}
+ {% with field=key|from_dict:form.fields %}<strong>{{ field.label }}</strong>{{error_details}}{% endwith %}
+ {% endfor %}
</div>
- {% for error in form.non_field_errors %}
- <p>{{ error }}</p>
- {% endfor %}
- {% for key, error_details in form.errors.items %}
- {% with field=key|from_dict:form.fields %}<strong>{{ field.label }}</strong>{{error_details}}{% endwith %}
- {% endfor %}
-</div>
{% endif %}
- {% bs_form form %}
+ {% if item_related_label %}<div class="alert alert-info">
+ <strong>{% trans "Related items" %}{% trans ":" %}</strong> {{ item_related_label }}
+ </div>{% endif %}
+ {% bs_form form 0 True %}
</div>
{% endblock %}
diff --git a/ishtar_common/templatetags/table_form.py b/ishtar_common/templatetags/table_form.py
index 5da2171d0..d5b20da26 100644
--- a/ishtar_common/templatetags/table_form.py
+++ b/ishtar_common/templatetags/table_form.py
@@ -8,11 +8,12 @@ register = Library()
@register.inclusion_tag('blocks/bs_form_snippet.html', takes_context=True)
-def bs_form(context, form, position=0):
+def bs_form(context, form, position=0, no_error=False):
user = context['user']
show_field_number = user.ishtaruser and user.ishtaruser.show_field_number()
return {'form': form, 'odd': position % 2,
- 'show_field_number': show_field_number}
+ 'show_field_number': show_field_number,
+ 'no_error': no_error}
@register.inclusion_tag('blocks/bs_compact_form_snippet.html',
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index 704894d52..ceb8db200 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -1923,7 +1923,7 @@ display_document = display_item(models.Document)
document_search_wizard = wizards.DocumentSearch.as_view(
[('selec-document_search', forms.DocumentFormSelection)],
- label=_(u"Document: search"),
+ label=_("Document: search"),
url_name='search-document',
)
@@ -2016,12 +2016,21 @@ class DocumentEditView(DocumentFormMixin, UpdateView):
for related_item in getattr(document, k).all():
key = "{}_{}_main_image".format(k, related_item.pk)
kwargs["main_items_fields"][k].append(
- (key, u"{} - {}".format(
- _(u"Main image for"), related_item)))
+ (key, "{} - {}".format(
+ _("Main image for"), related_item)))
if related_item.main_image == document:
initial[key] = True
kwargs['initial'] = initial
kwargs["user"] = self.request.user
+ self.document = document
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ kwargs = super(DocumentEditView, self).get_context_data(**kwargs)
+ rel = self.document.cache_related_label
+ if len(rel) == 1000: # truncated
+ rel += " (...)"
+ kwargs["item_related_label"] = rel
return kwargs