xDDDDD
This commit is contained in:
parent
10330601b8
commit
fe073182f1
|
@ -9,6 +9,7 @@ set -e
|
||||||
psql --dbname template1 -U postgres <<EOSQL
|
psql --dbname template1 -U postgres <<EOSQL
|
||||||
CREATE EXTENSION hstore;
|
CREATE EXTENSION hstore;
|
||||||
CREATE EXTENSION ltree;
|
CREATE EXTENSION ltree;
|
||||||
|
CREATE EXTENSION pg_trgm;
|
||||||
DROP DATABASE $POSTGRES_USER;
|
DROP DATABASE $POSTGRES_USER;
|
||||||
CREATE DATABASE $POSTGRES_USER TEMPLATE template1;
|
CREATE DATABASE $POSTGRES_USER TEMPLATE template1;
|
||||||
EOSQL
|
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 import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Item, ItemImage, Category, Label
|
|
||||||
from django_select2.forms import ModelSelect2Widget, Select2MultipleWidget
|
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):
|
class ItemForm(forms.ModelForm):
|
||||||
name = forms.CharField(widget=forms.TextInput())
|
name = forms.CharField(widget=forms.TextInput())
|
||||||
|
@ -25,16 +15,20 @@ class ItemForm(forms.ModelForm):
|
||||||
exclude = []
|
exclude = []
|
||||||
widgets = {
|
widgets = {
|
||||||
'parent': ItemSelectWidget,
|
'parent': ItemSelectWidget,
|
||||||
'categories': Select2MultipleWidget
|
'categories': Select2MultipleWidget,
|
||||||
|
'props': PropsSelectWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ItemImageInline(admin.TabularInline):
|
class ItemImageInline(admin.TabularInline):
|
||||||
model = ItemImage
|
model = ItemImage
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
class LabelInline(admin.TabularInline):
|
class LabelInline(admin.TabularInline):
|
||||||
model = Label
|
model = Label
|
||||||
|
|
||||||
|
|
||||||
class ItemAdmin(admin.ModelAdmin):
|
class ItemAdmin(admin.ModelAdmin):
|
||||||
list_display = ('_name',)
|
list_display = ('_name',)
|
||||||
list_filter = ('categories',)
|
list_filter = ('categories',)
|
||||||
|
@ -75,5 +69,6 @@ class ItemAdmin(admin.ModelAdmin):
|
||||||
with Item.disabled_tree_trigger():
|
with Item.disabled_tree_trigger():
|
||||||
return super(ItemAdmin, self).response_action(request, queryset)
|
return super(ItemAdmin, self).response_action(request, queryset)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Item, ItemAdmin)
|
admin.site.register(Item, ItemAdmin)
|
||||||
admin.site.register(Category)
|
admin.site.register(Category)
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
from django.conf.urls import include, url
|
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 = [
|
urlpatterns = [
|
||||||
url(r'^$', index),
|
url(r'^$', index),
|
||||||
url(r'^search$', search),
|
url(r'^search$', search),
|
||||||
url(r'^item/(?P<pk>.*)$', item_display, name='item-display'),
|
url(r'^item/(?P<pk>.*)$', item_display, name='item-display'),
|
||||||
url(r'^autocomplete.json$', ItemSelectView.as_view(), name='item-complete'),
|
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'),
|
url(r'^(?P<pk>[^/]*)$', label_lookup, name='label-lookup'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
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.http import Http404, JsonResponse
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
from django_select2.views import AutoResponseView
|
from django_select2.views import AutoResponseView
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
from storage.models import Item, Label
|
from storage.models import Item, Label
|
||||||
|
|
||||||
|
@ -19,30 +21,37 @@ def apply_smart_search(query, objects):
|
||||||
general_term.append(prop)
|
general_term.append(prop)
|
||||||
else:
|
else:
|
||||||
key, value = prop.split(':', 1)
|
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
|
filters[key + '__search'] = value
|
||||||
elif key == 'ancestor':
|
elif key == 'ancestor':
|
||||||
objects = Item.objects.get(pk=value).get_children()
|
objects = Item.objects.get(pk=value).get_children()
|
||||||
elif key == 'prop' or value:
|
elif key == 'prop' or value:
|
||||||
if key == 'prop':
|
if key == 'prop':
|
||||||
key, value = value.split(':', 1)
|
key, _, value = value.partition(':')
|
||||||
|
if not value:
|
||||||
if 'props__contains' not in filters:
|
filters['props__isnull'] = {key: False}
|
||||||
filters['props__contains'] = {}
|
else:
|
||||||
filters['props__contains'] = {key: value}
|
filters['props__contains'] = {key: value}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# "Whatever:"
|
# "Whatever:"
|
||||||
general_term.append(prop)
|
general_term.append(prop)
|
||||||
|
|
||||||
if general_term:
|
objects = objects.filter(**filters)
|
||||||
|
|
||||||
|
if not general_term:
|
||||||
|
return objects
|
||||||
objects = objects.annotate(
|
objects = objects.annotate(
|
||||||
search=SearchVector('name', 'description', 'props', config='simple'),
|
search=SearchVector('name', 'description', 'props', config='simple'),
|
||||||
)
|
)
|
||||||
filters['search'] = ' '.join(general_term)
|
general_term = ' '.join(general_term)
|
||||||
|
|
||||||
objects = objects.filter(**filters)
|
|
||||||
|
|
||||||
|
objects = objects.annotate(
|
||||||
|
similarity=TrigramSimilarity('name', general_term)
|
||||||
|
).filter(
|
||||||
|
similarity__gte=0.15
|
||||||
|
).order_by('-similarity')
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,14 +62,14 @@ def index(request):
|
||||||
def search(request):
|
def search(request):
|
||||||
query = request.GET.get('q', '')
|
query = request.GET.get('q', '')
|
||||||
|
|
||||||
results = apply_smart_search(query, Item.objects)
|
results = apply_smart_search(query, Item.objects).all()
|
||||||
|
|
||||||
if results.count() == 1:
|
if results and len(results) == 1 or getattr(results[0], 'similarity', 0) == 1:
|
||||||
return redirect(results.all()[0])
|
return redirect(results[0])
|
||||||
|
|
||||||
return render(request, 'results.html', {
|
return render(request, 'results.html', {
|
||||||
'query': query,
|
'query': query,
|
||||||
'results': results.all(),
|
'results': results,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,3 +114,23 @@ class ItemSelectView(AutoResponseView):
|
||||||
],
|
],
|
||||||
'more': context['page_obj'].has_next()
|
'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