forked from wiktor/spejstore-new
xDDDDD
This commit is contained in:
parent
10330601b8
commit
fe073182f1
|
@ -9,6 +9,7 @@ set -e
|
|||
psql --dbname template1 -U postgres <<EOSQL
|
||||
CREATE EXTENSION hstore;
|
||||
CREATE EXTENSION ltree;
|
||||
CREATE EXTENSION pg_trgm;
|
||||
DROP DATABASE $POSTGRES_USER;
|
||||
CREATE DATABASE $POSTGRES_USER TEMPLATE template1;
|
||||
EOSQL
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
var xD = $;
|
||||
|
||||
var initDjangoHStoreWidget = function(hstore_field_name, inline_prefix) {
|
||||
// ignore inline templates
|
||||
// if hstore_field_name contains "__prefix__"
|
||||
if(hstore_field_name.indexOf('__prefix__') > -1){
|
||||
return;
|
||||
}
|
||||
|
||||
var $ = django.jQuery;
|
||||
|
||||
// processing inlines
|
||||
if(hstore_field_name.indexOf('inline') > -1){
|
||||
var inlineClass = $('#id_'+hstore_field_name).parents('.inline-related, .grp-group').attr('class');
|
||||
// if using TabularInlines stop here
|
||||
// TabularInlines not supported
|
||||
if (inlineClass.indexOf('tabular') > -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// reusable function that retrieves a template even if ID is not correct
|
||||
// (written to support inlines)
|
||||
var retrieveTemplate = function(template_name, field_name){
|
||||
var specific_template = $('#'+template_name+'-'+field_name);
|
||||
// if found specific template return that
|
||||
if(specific_template.length){
|
||||
return specific_template.html();
|
||||
}
|
||||
else{
|
||||
// get fallback template
|
||||
var html = $('.'+template_name+'-inline').html();
|
||||
// replace all occurrences of __prefix__ with field_name
|
||||
// and return
|
||||
html = html.replace(/__prefix__/g, inline_prefix);
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
// reusable function that compiles the UI
|
||||
var compileUI = function(params){
|
||||
var hstore_field_id = 'id_'+hstore_field_name,
|
||||
original_textarea = $('#'+hstore_field_id),
|
||||
original_value = original_textarea.val(),
|
||||
original_container = original_textarea.parents('.form-row, .grp-row').eq(0),
|
||||
errorHtml = original_container.find('.errorlist').html(),
|
||||
json_data = {};
|
||||
|
||||
if(original_value !== ''){
|
||||
// manage case in which textarea is blank
|
||||
try{
|
||||
json_data = JSON.parse(original_value);
|
||||
}
|
||||
catch(e){
|
||||
alert('invalid JSON:\n'+e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var hstore_field_data = {
|
||||
"id": hstore_field_id,
|
||||
"label": original_container.find('label').text(),
|
||||
"name": hstore_field_name,
|
||||
"value": original_textarea.val(),
|
||||
"help": original_container.find('.grp-help, .help').text(),
|
||||
"errors": errorHtml,
|
||||
"data": json_data
|
||||
},
|
||||
// compile template
|
||||
ui_html = retrieveTemplate('hstore-ui-template', hstore_field_name),
|
||||
compiled_ui_html = _.template(ui_html, hstore_field_data);
|
||||
|
||||
// this is just to DRY up a bit
|
||||
if(params && params.replace_original === true){
|
||||
// remove original textarea to avoid having two textareas with same ID
|
||||
original_textarea.remove();
|
||||
// inject compiled template and hide original
|
||||
original_container.after(compiled_ui_html).hide();
|
||||
}
|
||||
|
||||
return compiled_ui_html;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// generate UI
|
||||
compileUI({ replace_original: true });
|
||||
|
||||
// cache other objects that we'll reuse
|
||||
var row_html = retrieveTemplate('hstore-row-template', hstore_field_name),
|
||||
empty_row = _.template(row_html, { 'key': '', 'value': '' }),
|
||||
$hstore = $('#id_'+hstore_field_name).parents('.hstore');
|
||||
|
||||
// reusable function that updates the textarea value
|
||||
var updateTextarea = function(container) {
|
||||
// init empty json object
|
||||
var new_value = {},
|
||||
raw_textarea = container.find('textarea'),
|
||||
rows = container.find('.form-row, .grp-row');
|
||||
|
||||
// loop over each object and populate json
|
||||
rows.each(function() {
|
||||
var inputs = $(this).find('input'),
|
||||
key = $(this).find('.hs-key').val(),
|
||||
value = $(this).find('.hs-val').val();
|
||||
new_value[key] = value;
|
||||
});
|
||||
|
||||
// update textarea value
|
||||
$(raw_textarea).val(JSON.stringify(new_value, null, 4));
|
||||
};
|
||||
|
||||
// remove row link
|
||||
$hstore.delegate('a.remove-row', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
// cache container jquery object before $(this) gets removed
|
||||
$(this).parents('.form-row, .grp-row').eq(0).remove();
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
|
||||
// add row link
|
||||
$hstore.delegate('a.hs-add-row, .hs-add-row a', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
$hstore.find('.hstore-rows').append(empty_row);
|
||||
xD('.django-select2').djangoSelect2()
|
||||
xD('select').on( 'select2:close', function () {
|
||||
$(this).focus();
|
||||
});
|
||||
});
|
||||
|
||||
// toggle textarea link
|
||||
$hstore.delegate('.hstore-toggle-txtarea', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var raw_textarea = $hstore.find('.hstore-textarea'),
|
||||
hstore_rows = $hstore.find('.hstore-rows'),
|
||||
add_row = $hstore.find('.hs-add-row');
|
||||
|
||||
if(raw_textarea.is(':visible')) {
|
||||
|
||||
var compiled_ui = compileUI();
|
||||
|
||||
// in case of JSON error
|
||||
if(compiled_ui === false){
|
||||
return;
|
||||
}
|
||||
|
||||
// jquery < 1.8
|
||||
try{
|
||||
var $ui = $(compiled_ui);
|
||||
}
|
||||
// jquery >= 1.8
|
||||
catch(e){
|
||||
var $ui = $($.parseHTML(compiled_ui));
|
||||
}
|
||||
|
||||
// update rows with only relevant content
|
||||
hstore_rows.html($ui.find('.hstore-rows').html());
|
||||
|
||||
raw_textarea.hide();
|
||||
hstore_rows.show();
|
||||
add_row.show();
|
||||
|
||||
xD('.django-select2').djangoSelect2()
|
||||
}
|
||||
else{
|
||||
raw_textarea.show();
|
||||
hstore_rows.hide();
|
||||
add_row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// update textarea whenever a field changes
|
||||
$hstore.delegate('.hs-val', 'keyup', function() {
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
|
||||
$hstore.delegate('.hs-key', 'change', function() {
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
};
|
||||
|
||||
django.jQuery(window).load(function() {
|
||||
// support inlines
|
||||
// bind only once
|
||||
if(django.hstoreWidgetBoundInlines === undefined){
|
||||
var $ = django.jQuery;
|
||||
$('.grp-group .grp-add-handler, .inline-group .hs-add-row a, .inline-group .add-row').click(function(e){
|
||||
var hstore_original_textareas = $(this).parents('.grp-group, .inline-group').eq(0).find('.hstore-original-textarea');
|
||||
// if module contains .hstore-original-textarea
|
||||
if(hstore_original_textareas.length > 0){
|
||||
// loop over each inline
|
||||
$(this).parents('.grp-group, .inline-group').find('.grp-items div.grp-dynamic-form, .inline-related').each(function(e, i){
|
||||
var prefix = i;
|
||||
// loop each textarea
|
||||
$(this).find('.hstore-original-textarea').each(function(e, i){
|
||||
// cache field name
|
||||
var field_name = $(this).attr('name');
|
||||
// ignore templates
|
||||
// if name attribute contains __prefix__
|
||||
if(field_name.indexOf('prefix') > -1){
|
||||
// skip to next
|
||||
return;
|
||||
}
|
||||
initDjangoHStoreWidget(field_name, prefix);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
django.hstoreWidgetBoundInlines = true;
|
||||
}
|
||||
});
|
|
@ -1,21 +1,11 @@
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
from .models import Item, ItemImage, Category, Label
|
||||
|
||||
from django_select2.forms import ModelSelect2Widget, Select2MultipleWidget
|
||||
|
||||
from .models import Item, ItemImage, Category, Label
|
||||
from .widgets import ItemSelectWidget, PropsSelectWidget
|
||||
|
||||
class ItemSelectWidget(ModelSelect2Widget):
|
||||
search_fields = [
|
||||
'name__icontains',
|
||||
'description__icontains'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['data_view'] = 'item-complete'
|
||||
super(ItemSelectWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
|
||||
class ItemForm(forms.ModelForm):
|
||||
name = forms.CharField(widget=forms.TextInput())
|
||||
|
@ -25,16 +15,20 @@ class ItemForm(forms.ModelForm):
|
|||
exclude = []
|
||||
widgets = {
|
||||
'parent': ItemSelectWidget,
|
||||
'categories': Select2MultipleWidget
|
||||
'categories': Select2MultipleWidget,
|
||||
'props': PropsSelectWidget
|
||||
}
|
||||
|
||||
|
||||
class ItemImageInline(admin.TabularInline):
|
||||
model = ItemImage
|
||||
extra = 1
|
||||
|
||||
|
||||
class LabelInline(admin.TabularInline):
|
||||
model = Label
|
||||
|
||||
|
||||
class ItemAdmin(admin.ModelAdmin):
|
||||
list_display = ('_name',)
|
||||
list_filter = ('categories',)
|
||||
|
@ -75,5 +69,6 @@ class ItemAdmin(admin.ModelAdmin):
|
|||
with Item.disabled_tree_trigger():
|
||||
return super(ItemAdmin, self).response_action(request, queryset)
|
||||
|
||||
|
||||
admin.site.register(Item, ItemAdmin)
|
||||
admin.site.register(Category)
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
from django.conf.urls import include, url
|
||||
from storage.views import index, search, item_display, label_lookup, ItemSelectView
|
||||
from storage.views import (
|
||||
index, search, item_display, label_lookup, ItemSelectView, PropSelectView
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', index),
|
||||
url(r'^search$', search),
|
||||
url(r'^item/(?P<pk>.*)$', item_display, name='item-display'),
|
||||
url(r'^autocomplete.json$', ItemSelectView.as_view(), name='item-complete'),
|
||||
url(r'^autocomplete_prop.json$', PropSelectView.as_view(), name='prop-complete'),
|
||||
url(r'^(?P<pk>[^/]*)$', label_lookup, name='label-lookup'),
|
||||
]
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import shlex
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.postgres.search import SearchVector
|
||||
from django.contrib.postgres.search import SearchVector, TrigramSimilarity
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.contrib.admin.models import LogEntry
|
||||
from django_select2.views import AutoResponseView
|
||||
from django.db.models import Q
|
||||
from django.db import connection
|
||||
|
||||
from storage.models import Item, Label
|
||||
|
||||
|
@ -19,30 +21,37 @@ def apply_smart_search(query, objects):
|
|||
general_term.append(prop)
|
||||
else:
|
||||
key, value = prop.split(':', 1)
|
||||
if hasattr(Item, key):
|
||||
if key in ['owner', 'taken_by']:
|
||||
filters[key + '__username'] = value
|
||||
elif hasattr(Item, key):
|
||||
filters[key + '__search'] = value
|
||||
elif key == 'ancestor':
|
||||
objects = Item.objects.get(pk=value).get_children()
|
||||
elif key == 'prop' or value:
|
||||
if key == 'prop':
|
||||
key, value = value.split(':', 1)
|
||||
|
||||
if 'props__contains' not in filters:
|
||||
filters['props__contains'] = {}
|
||||
filters['props__contains'] = {key: value}
|
||||
|
||||
key, _, value = value.partition(':')
|
||||
if not value:
|
||||
filters['props__isnull'] = {key: False}
|
||||
else:
|
||||
filters['props__contains'] = {key: value}
|
||||
else:
|
||||
# "Whatever:"
|
||||
general_term.append(prop)
|
||||
|
||||
if general_term:
|
||||
objects = objects.annotate(
|
||||
search=SearchVector('name', 'description', 'props', config='simple'),
|
||||
)
|
||||
filters['search'] = ' '.join(general_term)
|
||||
|
||||
objects = objects.filter(**filters)
|
||||
|
||||
if not general_term:
|
||||
return objects
|
||||
objects = objects.annotate(
|
||||
search=SearchVector('name', 'description', 'props', config='simple'),
|
||||
)
|
||||
general_term = ' '.join(general_term)
|
||||
|
||||
objects = objects.annotate(
|
||||
similarity=TrigramSimilarity('name', general_term)
|
||||
).filter(
|
||||
similarity__gte=0.15
|
||||
).order_by('-similarity')
|
||||
return objects
|
||||
|
||||
|
||||
|
@ -53,14 +62,14 @@ def index(request):
|
|||
def search(request):
|
||||
query = request.GET.get('q', '')
|
||||
|
||||
results = apply_smart_search(query, Item.objects)
|
||||
results = apply_smart_search(query, Item.objects).all()
|
||||
|
||||
if results.count() == 1:
|
||||
return redirect(results.all()[0])
|
||||
if results and len(results) == 1 or getattr(results[0], 'similarity', 0) == 1:
|
||||
return redirect(results[0])
|
||||
|
||||
return render(request, 'results.html', {
|
||||
'query': query,
|
||||
'results': results.all(),
|
||||
'results': results,
|
||||
})
|
||||
|
||||
|
||||
|
@ -105,3 +114,23 @@ class ItemSelectView(AutoResponseView):
|
|||
],
|
||||
'more': context['page_obj'].has_next()
|
||||
})
|
||||
|
||||
|
||||
class PropSelectView(AutoResponseView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
# self.widget = self.get_widget_or_404()
|
||||
self.term = kwargs.get('term', request.GET.get('term', ''))
|
||||
# context = self.get_context_data()
|
||||
with connection.cursor() as c:
|
||||
c.execute("select e from (select skeys(props) as e, count(skeys(props)) as e_count from storage_item group by e order by e_count desc) as xD where e like %s limit 10;", ['%' + self.term + '%'])
|
||||
props = c.fetchall()
|
||||
|
||||
return JsonResponse({
|
||||
'results': [
|
||||
{
|
||||
'text': p,
|
||||
'id': p,
|
||||
}
|
||||
for p in props
|
||||
],
|
||||
})
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
from pkg_resources import parse_version
|
||||
|
||||
from django_select2.forms import ModelSelect2Widget, HeavySelect2Widget
|
||||
from django_hstore.forms import DictionaryFieldWidget
|
||||
|
||||
from django import get_version
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.template import Context
|
||||
from django.template.loader import get_template
|
||||
from django.contrib.admin.widgets import AdminTextareaWidget
|
||||
|
||||
|
||||
class ItemSelectWidget(ModelSelect2Widget):
|
||||
search_fields = [
|
||||
'name__icontains',
|
||||
'description__icontains'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['data_view'] = 'item-complete'
|
||||
super(ItemSelectWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
||||
class PropsSelectWidget(DictionaryFieldWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
# it's called "original" because it will be replaced by a copy
|
||||
attrs['class'] = 'hstore-original-textarea'
|
||||
w = HeavySelect2Widget(data_view='prop-complete', attrs={'data-tags': 'true', 'class': 'hs-key'})
|
||||
|
||||
# get default HTML from AdminTextareaWidget
|
||||
html = AdminTextareaWidget.render(self, name, value, attrs)
|
||||
# prepare template context
|
||||
template_context = Context({
|
||||
'field_name': name,
|
||||
'STATIC_URL': settings.STATIC_URL,
|
||||
'use_svg': parse_version(get_version()) >= parse_version('1.9'), # use svg icons if django >= 1.9
|
||||
'ajax_url': reverse('prop-complete'),
|
||||
'w': w.build_attrs()
|
||||
})
|
||||
# get template object
|
||||
template = get_template('hstore_%s_widget.html' % self.admin_style)
|
||||
# render additional html
|
||||
additional_html = template.render(template_context)
|
||||
|
||||
# append additional HTML and mark as safe
|
||||
html = html + additional_html
|
||||
html = mark_safe(html)
|
||||
|
||||
return html
|
|
@ -0,0 +1,75 @@
|
|||
{% load i18n %}
|
||||
<script type="text/html" id="hstore-row-template-{{ field_name }}" class="hstore-row-template-inline">
|
||||
<div class="form-row field-data">
|
||||
<div>
|
||||
<select {% for k, v in w.items %} {{k}}="{{v}}" {% endfor %}>
|
||||
<option value="<%= _.escape(key) %>"><%= _.escape(key) %></option>
|
||||
</select>
|
||||
<strong>:</strong>
|
||||
<input class='hs-val'
|
||||
style="min-width:300px;"
|
||||
value="<%= _.escape(value) %>"
|
||||
type="text"
|
||||
placeholder="{% trans 'value' %}">
|
||||
|
||||
<a href="#" class="remove-row" title="{% trans 'remove row' %}">
|
||||
<img src="{{ STATIC_URL }}admin/img/{% if use_svg %}icon-deletelink.svg{% else %}icon_deletelink.gif{% endif %}">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="hstore-ui-template-{{ field_name }}"{% if '__prefix__' in field_name %} class="hstore-ui-template-inline"{% endif %}>
|
||||
<div class="hstore" id="hstore-{{ field_name }}">
|
||||
<h2><%= label %></h2>
|
||||
|
||||
<% if(help && help != '') { %>
|
||||
<div class="form-row">
|
||||
<p class="help" style="margin:0;padding:0"><%= help %></p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="hstore-rows">
|
||||
<% if(errors){ %>
|
||||
<div class="form-row field-data">
|
||||
<div>
|
||||
<ul class="errorlist">
|
||||
<%= errors %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% for(key in data){ %>
|
||||
<%= _.template(django.jQuery('.hstore-row-template-inline').eq(0).html(), { 'key': key, 'value': data[key] }) %>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="form-row field-data hstore-textarea" style="display:none">
|
||||
<div>
|
||||
<label for="<%= id %>" class="required">{% trans 'Raw textarea' %}:</label>
|
||||
<textarea class="vLargeTextField" cols="40" id="<%= id %>" name="<%= name %>" rows="10"><%= value %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<a href="#" class="hs-add-row" title="{% trans 'Add another row' %}">
|
||||
<img src="{{ STATIC_URL }}admin/img/{% if use_svg %}icon-addlink.svg{% else %}icon_addlink.gif{% endif %}" alt="{% trans 'Add Another' %}">
|
||||
{% trans "Add row" %}
|
||||
</a>
|
||||
|
||||
<a href="#" class="hstore-toggle-txtarea" title="{% trans 'toggle textarea' %}" style="float:right">
|
||||
<img src="{{ STATIC_URL }}admin/img/{% if use_svg %}icon-changelink.svg{% else %}icon_changelink.gif{% endif %}" alt="{% trans 'toggle textarea' %}">
|
||||
{% trans 'toggle textarea' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
django.jQuery('a.hs-add-row').on('click', console.log);
|
||||
django.jQuery(function() { initDjangoHStoreWidget('{{ field_name }}') });
|
||||
|
||||
// function(e) {
|
||||
// $('.django-select2').select2();
|
||||
// });
|
||||
</script>
|
Loading…
Reference in New Issue