summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ishtar_common/forms_common.py56
-rw-r--r--ishtar_common/migrations/0062_searchquery.py33
-rw-r--r--ishtar_common/models.py19
-rw-r--r--ishtar_common/static/js/ishtar.js14
-rw-r--r--ishtar_common/templates/base.html3
-rw-r--r--ishtar_common/templates/blocks/DataTables.html3
-rw-r--r--ishtar_common/templates/blocks/bs_form_snippet.html5
-rw-r--r--ishtar_common/templates/ishtar/forms/search_query.html122
-rw-r--r--ishtar_common/templates/ishtar/forms/success.html20
-rw-r--r--ishtar_common/templates/ishtar/wizard/search.html2
-rw-r--r--ishtar_common/templates/widgets/search_input.html32
-rw-r--r--ishtar_common/urls.py6
-rw-r--r--ishtar_common/views.py45
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">&times;</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 >
+ &nbsp;<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">
+ &nbsp;<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">&times;</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')