diff options
| -rw-r--r-- | ishtar_common/forms_common.py | 56 | ||||
| -rw-r--r-- | ishtar_common/migrations/0062_searchquery.py | 33 | ||||
| -rw-r--r-- | ishtar_common/models.py | 19 | ||||
| -rw-r--r-- | ishtar_common/static/js/ishtar.js | 14 | ||||
| -rw-r--r-- | ishtar_common/templates/base.html | 3 | ||||
| -rw-r--r-- | ishtar_common/templates/blocks/DataTables.html | 3 | ||||
| -rw-r--r-- | ishtar_common/templates/blocks/bs_form_snippet.html | 5 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/forms/search_query.html | 122 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/forms/success.html | 20 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/wizard/search.html | 2 | ||||
| -rw-r--r-- | ishtar_common/templates/widgets/search_input.html | 32 | ||||
| -rw-r--r-- | ishtar_common/urls.py | 6 | ||||
| -rw-r--r-- | ishtar_common/views.py | 45 | 
13 files changed, 346 insertions, 14 deletions
| diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 47e976452..43652ba07 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -27,6 +27,7 @@ import tempfile  from django import forms  from django.conf import settings  from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType  from django.core import validators  from django.core.cache import cache  from django.core.exceptions import ObjectDoesNotExist @@ -1276,3 +1277,58 @@ AuthorFormset = formset_factory(AuthorFormSelection, can_delete=True,  AuthorFormset.form_label = _("Authors")  AuthorFormset.form_admin_name = _(u"Authors")  AuthorFormset.form_slug = "authors" + + +class SearchQueryForm(forms.Form): +    query = forms.CharField(max_length=None, label=_(u"Query"), disabled=True, +                            initial='*') +    search_query = forms.ChoiceField(label=_(u"Search query"), required=False, +                                     choices=[]) +    label = forms.CharField(max_length=None, label=_(u"Name"), required=False) +    is_alert = forms.BooleanField(label=_(u"Is an alert"), required=False) +    create_or_update = forms.ChoiceField( +        choices=(('create', _(u"Create")), +                 ('update', _(u"Update"))), initial='create') + +    def __init__(self, profile, content_type, *args, **kwargs): +        self.profile = profile +        self.content_type = content_type +        super(SearchQueryForm, self).__init__(*args, **kwargs) +        self.fields['search_query'].choices = [ +            (c.pk, c.label) for c in models.SearchQuery.objects.filter( +                content_type=content_type, profile=profile).all()] +        if not self.fields['search_query'].choices: +            self.fields.pop('search_query') + +    def clean(self): +        data = self.cleaned_data +        if data['create_or_update'] == 'create' and not data['label']: +            raise forms.ValidationError(_(u"A label is required for a new " +                                          u"search query.")) +        elif data['create_or_update'] == 'update': +            if not data['search_query']: +                raise forms.ValidationError(_(u"Select the search query to " +                                              u"update")) +            q = models.SearchQuery.objects.filter( +                profile=self.profile, content_type=self.content_type, +                pk=data['search_query']) +            if not q.count(): +                raise forms.ValidationError(_(u"Query does not exist.")) +        return data + +    def save(self): +        data = self.cleaned_data +        if data['create_or_update'] == 'create': +            sq = models.SearchQuery.objects.create( +                label=data['label'], query=data['query'], profile=self.profile, +                content_type=self.content_type, is_alert=data['is_alert']) +        else: +            try: +                sq = models.SearchQuery.objects.get( +                    profile=self.profile, content_type=self.content_type, +                    pk=data['search_query']) +            except models.SearchQuery.DoesNotExist: +                raise forms.ValidationError(_(u"Query does not exist.")) +            sq.query = data['query'] +            sq.save() +        return sq diff --git a/ishtar_common/migrations/0062_searchquery.py b/ishtar_common/migrations/0062_searchquery.py new file mode 100644 index 000000000..e886fe45a --- /dev/null +++ b/ishtar_common/migrations/0062_searchquery.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.10 on 2018-07-09 13:12 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + +    dependencies = [ +        ('contenttypes', '0002_remove_content_type_name'), +        ('ishtar_common', '0061_auto_20180704_1225'), +    ] + +    operations = [ +        migrations.CreateModel( +            name='SearchQuery', +            fields=[ +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), +                ('label', models.TextField(blank=True, verbose_name='Label')), +                ('query', models.TextField(blank=True, verbose_name='Query')), +                ('is_alert', models.BooleanField(default=False, verbose_name='Is an alert')), +                ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Content type')), +                ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.UserProfile', verbose_name='Profile')), +            ], +            options={ +                'ordering': ['label'], +                'verbose_name': 'Search query', +                'verbose_name_plural': 'Search queries', +            }, +        ), +    ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index d65c08f93..016883abe 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1110,6 +1110,8 @@ class FullSearch(models.Model):          :param save: True if you want to save the object immediately          :return: True if modified          """ +        if not hasattr(self, 'search_vector'): +            return          if not self.pk:              logger.warning("Cannot update search vector before save or "                             "after deletion.") @@ -1555,6 +1557,23 @@ def post_delete_record_relation(sender, instance, **kwargs):          q.delete() +class SearchQuery(models.Model): +    label = models.TextField(_(u"Label"), blank=True) +    query = models.TextField(_(u"Query"), blank=True) +    content_type = models.ForeignKey(ContentType, +                                     verbose_name=_(u"Content type")) +    profile = models.ForeignKey("UserProfile", verbose_name=_(u"Profile")) +    is_alert = models.BooleanField(_(u"Is an alert"), default=False) + +    class Meta: +        verbose_name = _(u"Search query") +        verbose_name_plural = _(u"Search queries") +        ordering = ['label'] + +    def __unicode__(self): +        return unicode(self.label) + +  class ShortMenuItem(object):      @classmethod      def get_short_menu_class(cls, pk): diff --git a/ishtar_common/static/js/ishtar.js b/ishtar_common/static/js/ishtar.js index 279d43b0e..0df286c26 100644 --- a/ishtar_common/static/js/ishtar.js +++ b/ishtar_common/static/js/ishtar.js @@ -30,6 +30,8 @@ function manage_async_link(event){      $.get(url, function(data) {          $(target).html(data);      }); +    var modal_open = $(this).attr('data-modal-open'); +    if (modal_open) $(modal_open).modal('show');  }  /* default function to prevent undefined */ @@ -266,6 +268,7 @@ $(document).ready(function(){      $(".chosen-select").chosen();      $(".clear-search").click(function(){          $(this).parent().parent().children('input').prop("value", ""); +        enable_save();      });  }); @@ -534,9 +537,18 @@ function get_label_from_input(input){      return input.parent().attr('data-alt-name');  } +var enable_save = function(){ +    if ($(".search-widget input").val()){ +        $("#save-search-button").removeClass('disabled'); +    } else { +        $("#save-search-button").addClass('disabled'); +    } +} +  function clear_search_field(){      $("#id_search_vector").val("");      add_message("-", 'info', "#advanced-search-info", true); +    enable_save();  }  function update_search_field(){ @@ -595,6 +607,7 @@ function update_search_field(){      } else {          add_message("-", 'info', "#advanced-search-info", true);      } +    enable_save();  }  var sheet_list = new Array(); @@ -646,3 +659,4 @@ function manage_pinned_search(name, data){          $('#pinned_search_' + name).hide();      }  } + diff --git a/ishtar_common/templates/base.html b/ishtar_common/templates/base.html index bbf030409..40e30706c 100644 --- a/ishtar_common/templates/base.html +++ b/ishtar_common/templates/base.html @@ -148,6 +148,9 @@          </div>        </div>      </div> +    <div class="modal" id="modal-dynamic-form" tabindex="-1" role="dialog" +         data-backdrop="static" data-keyboard="true" aria-hidden="true"> +    </div>      <div id='message'>          <div class='information'><i class="fa fa-info-circle" aria-hidden="true"></i> <span class='content'></span></div>      </div> diff --git a/ishtar_common/templates/blocks/DataTables.html b/ishtar_common/templates/blocks/DataTables.html index fdd358363..4beabe82a 100644 --- a/ishtar_common/templates/blocks/DataTables.html +++ b/ishtar_common/templates/blocks/DataTables.html @@ -2,7 +2,8 @@  <p class="text-center"> -<button id='search_{{name}}' class='btn btn-primary' type="button"> +<button id='search_{{name}}' class='btn btn-primary search_button' +        type="button">      {% trans "Search" %}</button>  {% if url_new %} diff --git a/ishtar_common/templates/blocks/bs_form_snippet.html b/ishtar_common/templates/blocks/bs_form_snippet.html index 7bdac3bf3..95b8ac086 100644 --- a/ishtar_common/templates/blocks/bs_form_snippet.html +++ b/ishtar_common/templates/blocks/bs_form_snippet.html @@ -40,7 +40,7 @@  {% else %}  {% if search and forloop.counter0 == 1 %}  <div class="modal" id="modal-advanced-search" tabindex="-1" role="dialog" -     data-backdrop="static" data-keyboard="false" aria-hidden="true"> +     data-backdrop="static" data-keyboard="true" aria-hidden="true">      <div class="modal-dialog modal-lg">          <div class="modal-content">              <div class="modal-header"> @@ -114,5 +114,6 @@  {% if search %}  <script type="text/javascript"> -$(register_advanced_search);</script> +$(register_advanced_search); +</script>  {% endif %} diff --git a/ishtar_common/templates/ishtar/forms/search_query.html b/ishtar_common/templates/ishtar/forms/search_query.html new file mode 100644 index 000000000..e842fd591 --- /dev/null +++ b/ishtar_common/templates/ishtar/forms/search_query.html @@ -0,0 +1,122 @@ +{% load i18n inline_formset table_form %} +<div class="modal-dialog modal-sm" id="save-search-div"> +    <div class="modal-content"> +        <div class="modal-header"> +            <h5>{% trans "Save search" %}</h5> +            <button type="button" class="close" data-dismiss="modal" aria-label="Close"> +                <span aria-hidden="true">×</span> +            </button> +        </div> +        <form enctype="multipart/form-data" +              action="{% url 'save-search-query' app_label model %}" +              method="post" +              id="save-search-form"> +            <div class="modal-body form-row"> +              {% csrf_token %} + +              <div class='form'> + +                {% if form.non_field_errors %} +                <div class="alert alert-danger" role="alert"> +                    {{form.non_field_errors}} +                </div> +                {% endif %} + +                {% for hidden in form.hidden_fields %} +                {{hidden}} +                {% if hidden.errors %}<div class="invalid-feedback"> +                    {{ hidden.errors }} +                </div>{% endif %} +                {% endfor %} + +                <div class="form-row"> +                    <div class="form-group col-lg-12"> +                        {{form.query}} +                    </div> +                </div> +                {% if form.search_query %} +                <div class="form-row"> +                    <input type="radio" name="create_or_update" value="create" +                           id="create-choice" checked > +                     <label for="create-choice">{% trans "New" %}</label> +                </div> +                {% else %} +                <input type="hidden" name="create_or_update" value="create"> +                {% endif %} +                <div id="new-search-query"> +                    <div class="form-row"> +                        {% with form.label as field %} +                        {% include "blocks/bs_field_snippet.html" %} +                        {% endwith %} +                    </div> +                    <div class="form-row"> +                        {% with form.is_alert as field %} +                        {% include "blocks/bs_field_snippet.html" %} +                        {% endwith %} +                    </div> +                </div> +                {% if form.search_query %} +                <div class="form-row"> +                    <input type="radio" name="create_or_update" value="update" +                           id="update-choice"> +                     <label for="update-choice">{% trans "Update" %}</label> +                </div> +                <div id="update-search-query"> +                    <div class="form-row"> +                        {% with form.search_query as field %} +                        {% include "blocks/bs_field_snippet.html" %} +                        {% endwith %} +                    </div> +                </div> +                {% endif %} +              </div> +            </div> +            <div class="modal-footer"> +                <div class="col-sm"> +                    <button type="button" id="search-save-submit" +                            name='validate' +                            value="validate" class="btn btn-success"> +                        {% trans "Validate" %} +                    </button> +                </div> +            </div> +        </form> +    </div> +</div> + +<script type='text/javascript'> + +var update_form_display = function(){ +    if ($("#create-choice:checked").length){ +        $("#update-search-query").hide(); +        $("#new-search-query").show(); +    } else { +        $("#new-search-query").hide(); +        $("#update-search-query").show(); +    } +} + +$(document).ready(function(){ +    $("#id_query").val($(".search-vector").val()); +    $("#search-save-submit").click( +        function(){ +            $.ajax({ +                type: "POST", +                url: "{% url 'save-search-query' app_label model %}", +                data: $("#save-search-form").serialize(), +                success: function(data){ +                    $("#save-search-div").parent().html(data); +                }, +                dataType: 'html' +            }); +            return false; +        } +    ); +    $("#create-choice").click(update_form_display); +    $("#update-choice").click(update_form_display); +    update_form_display(); +}); + +</script> + + diff --git a/ishtar_common/templates/ishtar/forms/success.html b/ishtar_common/templates/ishtar/forms/success.html new file mode 100644 index 000000000..125ba62e7 --- /dev/null +++ b/ishtar_common/templates/ishtar/forms/success.html @@ -0,0 +1,20 @@ +{% load i18n inline_formset table_form %} +<script type='text/javascript'> + +$(document).ready(function(){ +    $("#form-result-div").parent().modal("hide"); +}); + +</script> +<div class="modal-dialog modal-sm" id="form-result-div"> +    <div class="modal-content"> +        <div class="modal-header"> +            <button type="button" class="close" data-dismiss="modal" aria-label="Close"> +                <span aria-hidden="true">×</span> +            </button> +        </div> +        <div class="modal-body form-row"> +            {% trans "Form successfully submited" %} +        </div> +    </div> +</div> diff --git a/ishtar_common/templates/ishtar/wizard/search.html b/ishtar_common/templates/ishtar/wizard/search.html index e1f2433ea..eebd58d89 100644 --- a/ishtar_common/templates/ishtar/wizard/search.html +++ b/ishtar_common/templates/ishtar/wizard/search.html @@ -18,7 +18,7 @@  <form action="." class='wizard-form' id="wizard-form" method="post" name='wizard'>{% csrf_token %}  {% if wizard.form.forms %}  <div class='form'> -<div class='top_button'><input type="submit" id="submit_form" value="{% trans "Validate" %}"/></div> +<div class='top_button'><input type="submit" id="submit_form" value="{% trans 'Validate' %}"/></div>  <table class='formset'>    {%if wizard.form.non_form_errors%}<tr class='error'><th colspan='2'>{{wizard.form.non_form_errors}}</th></tr>{%endif%}    {{ wizard.form.management_form }} diff --git a/ishtar_common/templates/widgets/search_input.html b/ishtar_common/templates/widgets/search_input.html index a1e5aa2e4..ddb62969f 100644 --- a/ishtar_common/templates/widgets/search_input.html +++ b/ishtar_common/templates/widgets/search_input.html @@ -4,28 +4,40 @@           <i class="fa fa-search" aria-hidden="true"></i>        </span>      </span> -    <input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} /> +    <input type="{{ widget.type }}" name="{{ widget.name }}" +           {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />      <span class="input-group-append">          <button type="button" class="btn btn-secondary" data-toggle="modal" -                data-target="#modal-advanced-search"> +                data-target="#modal-advanced-search" +                title="{% trans 'Criteria' %}">              <i class="fa fa-cogs" aria-hidden="true"></i>          </button> -    </span> -    <span class="input-group-append"> -      <span class="input-group-text clear-search"> -        <i class="fa fa-times" aria-hidden="true"></i> -      </span> +        <a type="button" class="async-link btn btn-secondary disabled" +           id="save-search-button" +           data-target="#modal-dynamic-form" +           data-modal-open="#modal-dynamic-form" +           title="{% trans 'Save search' %}" +           href="{% url 'save-search-query' 'archaeological-operations' 'operation'%}"> +            <i class="fa fa-floppy-o" aria-hidden="true"></i> +        </a> +        <span class="input-group-text clear-search"> +            <i class="fa fa-times" aria-hidden="true"></i> +        </span>      </span>  </div>  <script type="text/javascript"> +  $(document).ready(function(){      $(".search-widget input").keypress(function(e) { -        if(e.which == 13) { -            $(".search-widget").parents( -                '.search').find("p > .btn-primary").click(); +        if (e.which == 13) { +            $(".search_button").click();              $(this).focus();          }      }); +    $(".search-widget input").keyup(function(e) { +        enable_save(); +    }); +    enable_save();  });  </script> diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index 4ba551857..6b778ae45 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -124,6 +124,12 @@ urlpatterns = [          views.ImportStepByStepView.as_view(), name='import_step_by_step'),      url(r'^profile(?:/(?P<pk>[0-9]+))?/$', views.ProfileEdit.as_view(),          name='profile'), +    url(r'^save-search/(?P<app_label>[a-z-]+)/(?P<model>[a-z-]+)/$', +        views.SearchQueryEdit.as_view(), +        name='save-search-query'), +    url(r'^success/$', +        TemplateView.as_view(template_name="ishtar/forms/success.html"), +        name='success'),  ]  menu = Menu(None) diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 5a35c2509..586e1f0a1 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -27,6 +27,7 @@ import unicodecsv  from django.conf import settings  from django.contrib.auth import logout  from django.contrib.auth.decorators import login_required +from django.contrib.contenttypes.models import ContentType  from django.core.exceptions import ObjectDoesNotExist  from django.core.urlresolvers import reverse, NoReverseMatch  from django.db.models import Q @@ -1659,3 +1660,47 @@ document_deletion_wizard = wizards.DocumentDeletionWizard.as_view(      document_deletion_steps,      label=_(u"Document deletion"),      url_name='document_deletion',) + + +class SearchQueryEdit(LoginRequiredMixin, FormView): +    template_name = 'ishtar/forms/search_query.html' +    form_class = forms.SearchQueryForm + +    def dispatch(self, request, *args, **kwargs): +        if not request.user.pk: +            raise Http404() +        try: +            self.profile = models.UserProfile.objects.get( +                current=True, person__ishtaruser__user_ptr=request.user) +        except models.UserProfile.DoesNotExist: +            # no current profile +            raise Http404() +        self.app_label = kwargs.get('app_label') +        self.model = kwargs.get('model') +        try: +            self.content_type = ContentType.objects.get( +                app_label=self.app_label.replace('-', '_'), +                model=self.model.replace('-', '_') +            ) +        except ContentType.DoesNotExist: +            raise Http404() +        return super(SearchQueryEdit, self).dispatch(request, *args, **kwargs) + +    def get_form_kwargs(self): +        kwargs = super(SearchQueryEdit, self).get_form_kwargs() +        kwargs['profile'] = self.profile +        kwargs['content_type'] = self.content_type +        return kwargs + +    def get_context_data(self, **kwargs): +        data = super(SearchQueryEdit, self).get_context_data(**kwargs) +        data['app_label'] = self.app_label +        data['model'] = self.model +        return data + +    def form_valid(self, form): +        form.save() +        return HttpResponseRedirect(self.get_success_url()) + +    def get_success_url(self): +        return reverse('success') | 
