diff --git a/autocomplete/widgets.py b/autocomplete/widgets.py
index 9f861d6..f224b01 100644
--- a/autocomplete/widgets.py
+++ b/autocomplete/widgets.py
@@ -34,8 +34,8 @@ from django.utils.functional import update_wrapper
#from django.db import models, transaction, router
#csrf_protect_m = method_decorator( csrf_protect )
-media_css={ 'all': ( '%s/jquery.autocomplete.css'%settings.STATIC_URL, ) }
-media_js=( '%sadmin/js/jquery.autocomplete.js'%settings.STATIC_URL, )
+media_css={ 'all': ( '%s/jquery.autocomplete.css' % settings.MEDIA_URL, ) }
+media_js=( '%sadmin/js/jquery.autocomplete.js' % settings.MEDIA_URL, )
class ForeignKeySearchInput( forms.HiddenInput ):
@@ -95,8 +95,8 @@ class ManyToManySearchInput( forms.MultipleHiddenInput ):
css=media_css
js=media_js
- input_type='hidden' # чтобы не отрисовывать текстовое поле
- is_hidden=False # Чтобы отрисовывать заголовок в Inline
+ input_type='hidden'
+ is_hidden=False
def __init__( self, rel, search_fields, attrs = None ):
self.rel=rel
@@ -292,7 +292,7 @@ class AutocompleteWidgetWrapper( RelatedFieldWidgetWrapper ):
# API to determine the ID dynamically.
output.append( u' '%\
( related_url, name ) )
- output.append( u''%( settings.ADMIN_MEDIA_PREFIX, _( 'Add Another' ) ) )
+ output.append( u''%( settings.ADMIN_MEDIA_PREFIX, _( 'Add Another' ) ) )
return mark_safe( u''.join( output ) )
diff --git a/cards/admin.py b/cards/admin.py
index f7b85c2..2b2f18b 100644
--- a/cards/admin.py
+++ b/cards/admin.py
@@ -1,3 +1,5 @@
+#coding=utf-8
+
from cards.models import *
from django.contrib import admin
from xcards.autocomplete.widgets import *
@@ -6,8 +8,9 @@ admin.site.register(Country)
admin.site.register(Tag)
class CardAdmin(AutocompleteModelAdmin):
+ ### à checker par la suite, gros gros bug prévu, tout ça...
related_search_fields = {
'tags' : ('label',),
}
-admin.site.register(Card)
+admin.site.register(Card, CardAdmin)
diff --git a/cards/models.py b/cards/models.py
index 36597d7..a56d523 100644
--- a/cards/models.py
+++ b/cards/models.py
@@ -68,7 +68,7 @@ class Card(models.Model):
emissionDate = models.CharField(verbose_name="Année ou date d'émission", max_length=10, blank=True)
expirationDate = models.CharField(verbose_name="Année ou date d'expiration", max_length=10, blank=True)
numberOfCopies = models.CharField(verbose_name='Tirage', max_length=50, blank=True)
- tags = models.ManyToManyField(Tag, null=True, blank=True, verbose_name='Liste des tags', related_name='cards')
+ tags = models.ManyToManyField(Tag, null=True, blank=True, verbose_name='Liste des tags', related_name='tags')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
diff --git a/cards/views.py b/cards/views.py
index 46f1bfa..dc15aec 100644
--- a/cards/views.py
+++ b/cards/views.py
@@ -122,7 +122,7 @@ def search_by_country(request, country_id):
def search_by_tag(request, tag_id):
tag = get_object_or_404(Tag, pk=tag_id)
- cards_list = tag.cards.all()
+ cards_list = tag.tags.all()
cards = pagination(cards_list, request)
diff --git a/media/admin/js/jquery.autocomplete.js b/media/admin/js/jquery.autocomplete.js
new file mode 100644
index 0000000..f9a8378
--- /dev/null
+++ b/media/admin/js/jquery.autocomplete.js
@@ -0,0 +1,653 @@
+django.jQuery(document).ready(function(){
+ // --- удаление изначально выбраных элементов ---
+ django.jQuery(".to_delete").click(function () {django.jQuery(this).remove();});
+
+ // --- добавляю автозаполнение для уже показываемых элементов ---
+ initAutocomplete();
+
+ setTimeout(function() {
+ // Ждем пока всё загрузится
+ console.log(django.jQuery(".add-row td a"));
+
+ // --- добавляю автозаполнение для новых элементов ---
+ django.jQuery(".add-row td a").click( initAutocomplete );
+
+ }, 500);
+
+});
+
+
+
+
+function initAutocomplete() {
+ django.jQuery.each( django.jQuery(".to_autocomplete") , function(key, value) {
+ var obj = django.jQuery(value);
+ var obj_name = obj.attr('id').replace("lookup_",""); ;
+ if (obj_name.search("__prefix__")==-1){
+ console.log("to_autocomplete",obj_name,value);
+ obj.autocomplete("../search/").removeClass("to_autocomplete");
+ }
+ });
+}
+
+
+
+function showAutocompletePopup(triggeringLink) {
+ var name = triggeringLink.id.replace(/^add_/, '');
+ name = id_to_windowname(name);
+ href = triggeringLink.href
+ if (href.indexOf('?') == -1) {
+ href += '?_popup=2';
+ } else {
+ href += '&_popup=2';
+ }
+ var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+ win.focus();
+ return false;
+}
+
+function dismissAddAnotherPopup(win, newId, newRepr) {
+ // newId and newRepr are expected to have previously been escaped by
+ // django.utils.html.escape.
+ newId = html_unescape(newId);
+ newRepr = html_unescape(newRepr);
+ var name = windowname_to_id(win.name);
+
+ odj_name = name.replace("id_","");
+
+ //console.log('Autocomplete: dismissAddAnotherPopup', name,newId, newRepr);
+ //console.log(odj_name, django.jQuery('#lookup_'+odj_name));
+
+ autocomplete_mode = django.jQuery('#lookup_'+odj_name).attr( 'autocomplete_mode' );
+
+ //console.log(autocomplete_mode);
+
+ if (autocomplete_mode=="ForeignKey"){
+ // --- подставляем значение из попапа ---
+ django.jQuery("#id_"+odj_name).val( newId );
+ django.jQuery("#lookup_"+odj_name).val( newRepr );
+ //console.log('added');
+ win.close();
+ }else if(autocomplete_mode=="ManyToMany"){
+ // --- добавляю новый элемент и значение из попапа ---
+ django.jQuery('
'+newRepr+'
')
+ .click(function () {django.jQuery(this).remove();})
+ .appendTo('#box_'+odj_name);
+ // очищаем текстовое поле
+ django.jQuery('#lookup_'+odj_name).val( '' );
+ win.close();
+ }else{
+ // --- повторяем стандартное поведение, мало ли что :) ---
+ newId = html_unescape(newId);
+ newRepr = html_unescape(newRepr);
+ var name = windowname_to_id(win.name);
+ var elem = document.getElementById(name);
+ if (elem) {
+ if (elem.nodeName == 'SELECT') {
+ var o = new Option(newRepr, newId);
+ elem.options[elem.options.length] = o;
+ o.selected = true;
+ } else if (elem.nodeName == 'INPUT') {
+ if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
+ elem.value += ',' + newId;
+ } else {
+ elem.value = newId;
+ }
+ }
+ } else {
+ var toId = name + "_to";
+ elem = document.getElementById(toId);
+ var o = new Option(newRepr, newId);
+ SelectBox.add_to_cache(toId, o);
+ SelectBox.redisplay(toId);
+ }
+ win.close();
+
+ }
+}
+
+
+function autocomplete_liFormat (row, i, num) {
+ var result = row[0] ;
+ return result;
+}
+
+function autocomplete_delete(obj) {
+ parent = django.jQuery(obj).parent();
+ console.log('autocomplete_to_delete',parent);
+ django.jQuery("input",parent).val( '' );
+}
+
+django.jQuery.autocomplete = function(input, options) {
+ // Create a link to self
+ var me = this;
+
+ // Create jQuery object for input element
+ var $input = django.jQuery(input).attr("autocomplete", "off");
+
+ // Apply inputClass if necessary
+ if (options.inputClass) $input.addClass(options.inputClass);
+
+ // Create results
+ var results = document.createElement("div");
+ // Create jQuery object for results
+ var $results = django.jQuery(results);
+ $results.hide().addClass(options.resultsClass).css("position", "absolute");
+ if( options.width > 0 ) $results.css("width", options.width);
+
+ // Add to body element
+ django.jQuery("body").append(results);
+
+ input.autocompleter = me;
+
+ var timeout = null;
+ var prev = "";
+ var active = -1;
+ var cache = {};
+ var keyb = false;
+ var hasFocus = false;
+ var lastKeyPressCode = null;
+
+ // flush cache
+ function flushCache(){
+ cache = {};
+ cache.data = {};
+ cache.length = 0;
+ };
+
+ // flush cache
+ flushCache();
+
+ // if there is a data array supplied
+ if( options.data != null ){
+ var sFirstChar = "", stMatchSets = {}, row = [];
+
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
+ if( typeof options.url != "string" ) options.cacheLength = 1;
+
+ // loop through the array and create a lookup structure
+ for( var i=0; i < options.data.length; i++ ){
+ // if row is a string, make an array otherwise just reference the array
+ row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]);
+
+ // if the length is zero, don't add to list
+ if( row[0].length > 0 ){
+ // get the first character
+ sFirstChar = row[0].substring(0, 1).toLowerCase();
+ // if no lookup array for this character exists, look it up now
+ if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = [];
+ // if the match is a string
+ stMatchSets[sFirstChar].push(row);
+ }
+ }
+
+ // add the data items to the cache
+ for( var k in stMatchSets ){
+ // increase the cache size
+ options.cacheLength++;
+ // add to the cache
+ addToCache(k, stMatchSets[k]);
+ }
+ }
+
+ $input
+ .keydown(function(e) {
+ // track last key pressed
+ lastKeyPressCode = e.keyCode;
+ switch(e.keyCode) {
+ case 38: // up
+ e.preventDefault();
+ moveSelect(-1);
+ break;
+ case 40: // down
+ e.preventDefault();
+ moveSelect(1);
+ break;
+ case 9: // tab
+ case 13: // return
+ if( selectCurrent() ){
+ // make sure to blur off the current field
+ $input.get(0).blur();
+ e.preventDefault();
+ }
+ break;
+ default:
+ active = -1;
+ if (timeout) clearTimeout(timeout);
+ timeout = setTimeout(function(){onChange();}, options.delay);
+ break;
+ }
+ })
+ .focus(function(){
+ // track whether the field has focus, we shouldn't process any results if the field no longer has focus
+ hasFocus = true;
+ })
+ .blur(function() {
+ // track whether the field has focus
+ hasFocus = false;
+ hideResults();
+ });
+
+ hideResultsNow();
+
+ function onChange() {
+ // ignore if the following keys are pressed: [del] [shift] [capslock]
+ if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide();
+ var v = $input.val();
+ if (v == prev) return;
+ prev = v;
+ if (v.length >= options.minChars) {
+ $input.addClass(options.loadingClass);
+ requestData(v);
+ } else {
+ $input.removeClass(options.loadingClass);
+ $results.hide();
+ }
+ };
+
+ function moveSelect(step) {
+
+ var lis = django.jQuery("li", results);
+ if (!lis) return;
+
+ active += step;
+
+ if (active < 0) {
+ active = 0;
+ } else if (active >= lis.size()) {
+ active = lis.size() - 1;
+ }
+
+ lis.removeClass("ac_over");
+
+ django.jQuery(lis[active]).addClass("ac_over");
+
+ // Weird behaviour in IE
+ // if (lis[active] && lis[active].scrollIntoView) {
+ // lis[active].scrollIntoView(false);
+ // }
+
+ };
+
+ function selectCurrent() {
+ var li = django.jQuery("li.ac_over", results)[0];
+ if (!li) {
+ var $li = django.jQuery("li", results);
+ if (options.selectOnly) {
+ if ($li.length == 1) li = $li[0];
+ } else if (options.selectFirst) {
+ li = $li[0];
+ }
+ }
+ if (li) {
+ selectItem(li);
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ function selectItem(li) {
+ if (!li) {
+ li = document.createElement("li");
+ li.extra = [];
+ li.selectValue = "";
+ }
+ var v = django.jQuery.trim(li.selectValue ? li.selectValue : li.innerHTML);
+ input.lastSelected = v;
+ prev = v;
+ $results.html("");
+ $input.val(v);
+ hideResultsNow();
+ if (options.onItemSelect){
+ setTimeout(function() { options.onItemSelect(li) }, 1);
+ }else{
+ autocomplete_mode = options.lookup_obj.attr('autocomplete_mode');
+ if (autocomplete_mode == "ForeignKey"){
+ if( li == null ) var sValue = '';
+ if( !!li.extra ) var sValue = li.extra[0];
+ else var sValue = li.selectValue;
+ django.jQuery("input:hidden",options.lookup_obj.parent()).val( sValue );
+ }else if(autocomplete_mode == "ManyToMany"){
+ //if( li == null ) return
+ obj_name = options.lookup_obj.attr('id').replace("lookup_",""); ;
+ //console.log('obj_name',obj_name);
+ // --- Создаю новый элемент ---
+ django.jQuery(''+li.selectValue+'
')
+ .click(function () {$(this).remove();})
+ .appendTo("#box_"+obj_name);
+ django.jQuery("#lookup_"+obj_name).val( '' );
+ }
+ }
+ };
+
+ // selects a portion of the input string
+ function createSelection(start, end){
+ // get a reference to the input element
+ var field = $input.get(0);
+ if( field.createTextRange ){
+ var selRange = field.createTextRange();
+ selRange.collapse(true);
+ selRange.moveStart("character", start);
+ selRange.moveEnd("character", end);
+ selRange.select();
+ } else if( field.setSelectionRange ){
+ field.setSelectionRange(start, end);
+ } else {
+ if( field.selectionStart ){
+ field.selectionStart = start;
+ field.selectionEnd = end;
+ }
+ }
+ field.focus();
+ };
+
+ // fills in the input box w/the first match (assumed to be the best match)
+ function autoFill(sValue){
+ // if the last user key pressed was backspace, don't autofill
+ if( lastKeyPressCode != 8 ){
+ // fill in the value (keep the case the user has typed)
+ $input.val($input.val() + sValue.substring(prev.length));
+ // select the portion of the value not typed by the user (so the next character will erase)
+ createSelection(prev.length, sValue.length);
+ }
+ };
+
+ function showResults() {
+ // get the position of the input field right now (in case the DOM is shifted)
+ var pos = findPos(input);
+ // either use the specified width, or autocalculate based on form element
+ var iWidth = (options.width > 0) ? options.width : $input.width();
+ // reposition
+ $results.css({
+ width: parseInt(iWidth) + "px",
+ top: (pos.y + input.offsetHeight) + "px",
+ left: pos.x + "px"
+ }).show();
+ };
+
+ function hideResults() {
+ if (timeout) clearTimeout(timeout);
+ timeout = setTimeout(hideResultsNow, 200);
+ };
+
+ function hideResultsNow() {
+ if (timeout) clearTimeout(timeout);
+ $input.removeClass(options.loadingClass);
+ if ($results.is(":visible")) {
+ $results.hide();
+ }
+ if (options.mustMatch) {
+ var v = $input.val();
+ if (v != input.lastSelected) {
+ selectItem(null);
+ }
+ }
+ };
+
+ function receiveData(q, data) {
+ if (data) {
+ $input.removeClass(options.loadingClass);
+ results.innerHTML = "";
+
+ // if the field no longer has focus or if there are no matches, do not display the drop down
+ if( !hasFocus || data.length == 0 ) return hideResultsNow();
+
+ if (django.jQuery.browser.msie) {
+ // we put a styled iframe behind the calendar so HTML SELECT elements don't show through
+ $results.append(document.createElement('iframe'));
+ }
+ results.appendChild(dataToDom(data));
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
+ if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]);
+ showResults();
+ } else {
+ hideResultsNow();
+ }
+ };
+
+ function parseData(data) {
+ if (!data) return null;
+ var parsed = [];
+ var rows = data.split(options.lineSeparator);
+ for (var i=0; i < rows.length; i++) {
+ var row = django.jQuery.trim(rows[i]);
+ if (row) {
+ parsed[parsed.length] = row.split(options.cellSeparator);
+ }
+ }
+ return parsed;
+ };
+
+ function dataToDom(data) {
+ var ul = document.createElement("ul");
+ var num = data.length;
+
+ // limited results to a max number
+ if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow;
+
+ for (var i=0; i < num; i++) {
+ var row = data[i];
+ if (!row) continue;
+ var li = document.createElement("li");
+ if (options.formatItem) {
+ li.innerHTML = options.formatItem(row, i, num);
+ li.selectValue = row[0];
+ } else {
+ li.innerHTML = row[0];
+ li.selectValue = row[0];
+ }
+ var extra = null;
+ if (row.length > 1) {
+ extra = [];
+ for (var j=1; j < row.length; j++) {
+ extra[extra.length] = row[j];
+ }
+ }
+ li.extra = extra;
+ ul.appendChild(li);
+ django.jQuery(li).hover(
+ function() { django.jQuery("li", ul).removeClass("ac_over"); django.jQuery(this).addClass("ac_over"); active = django.jQuery("li", ul).indexOf(django.jQuery(this).get(0)); },
+ function() { django.jQuery(this).removeClass("ac_over"); }
+ ).click(function(e) { e.preventDefault(); e.stopPropagation(); selectItem(this) });
+ }
+ return ul;
+ };
+
+ function requestData(q) {
+ if (!options.matchCase) q = q.toLowerCase();
+ var data = options.cacheLength ? loadFromCache(q) : null;
+ // recieve the cached data
+ if (data) {
+ receiveData(q, data);
+ // if an AJAX url has been supplied, try loading the data now
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+ django.jQuery.get(makeUrl(q), function(data) {
+ data = parseData(data);
+ addToCache(q, data);
+ receiveData(q, data);
+ });
+ // if there's been no data found, remove the loading class
+ } else {
+ $input.removeClass(options.loadingClass);
+ }
+ };
+
+ function makeUrl(q) {
+ var url = options.url + "?q=" + encodeURI(q);
+ for (var i in options.extraParams) {
+ url += "&" + i + "=" + encodeURI(options.extraParams[i]);
+ }
+ return url;
+ };
+
+ function loadFromCache(q) {
+ if (!q) return null;
+ if (cache.data[q]) return cache.data[q];
+ if (options.matchSubset) {
+ for (var i = q.length - 1; i >= options.minChars; i--) {
+ var qs = q.substr(0, i);
+ var c = cache.data[qs];
+ if (c) {
+ var csub = [];
+ for (var j = 0; j < c.length; j++) {
+ var x = c[j];
+ var x0 = x[0];
+ if (matchSubset(x0, q)) {
+ csub[csub.length] = x;
+ }
+ }
+ return csub;
+ }
+ }
+ }
+ return null;
+ };
+
+ function matchSubset(s, sub) {
+ if (!options.matchCase) s = s.toLowerCase();
+ var i = s.indexOf(sub);
+ if (i == -1) return false;
+ return i == 0 || options.matchContains;
+ };
+
+ this.flushCache = function() {
+ flushCache();
+ };
+
+ this.setExtraParams = function(p) {
+ options.extraParams = p;
+ };
+
+ this.findValue = function(){
+ var q = $input.val();
+
+ if (!options.matchCase) q = q.toLowerCase();
+ var data = options.cacheLength ? loadFromCache(q) : null;
+ if (data) {
+ findValueCallback(q, data);
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+ django.jQuery.get(makeUrl(q), function(data) {
+ data = parseData(data)
+ addToCache(q, data);
+ findValueCallback(q, data);
+ });
+ } else {
+ // no matches
+ findValueCallback(q, null);
+ }
+ }
+
+ function findValueCallback(q, data){
+ if (data) $input.removeClass(options.loadingClass);
+
+ var num = (data) ? data.length : 0;
+ var li = null;
+
+ for (var i=0; i < num; i++) {
+ var row = data[i];
+
+ if( row[0].toLowerCase() == q.toLowerCase() ){
+ li = document.createElement("li");
+ if (options.formatItem) {
+ li.innerHTML = options.formatItem(row, i, num);
+ li.selectValue = row[0];
+ } else {
+ li.innerHTML = row[0];
+ li.selectValue = row[0];
+ }
+ var extra = null;
+ if( row.length > 1 ){
+ extra = [];
+ for (var j=1; j < row.length; j++) {
+ extra[extra.length] = row[j];
+ }
+ }
+ li.extra = extra;
+ }
+ }
+
+ if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1);
+ }
+
+ function addToCache(q, data) {
+ if (!data || !q || !options.cacheLength) return;
+ if (!cache.length || cache.length > options.cacheLength) {
+ flushCache();
+ cache.length++;
+ } else if (!cache[q]) {
+ cache.length++;
+ }
+ cache.data[q] = data;
+ };
+
+ function findPos(obj) {
+ var curleft = obj.offsetLeft || 0;
+ var curtop = obj.offsetTop || 0;
+ while (obj = obj.offsetParent) {
+ curleft += obj.offsetLeft
+ curtop += obj.offsetTop
+ }
+ return {x:curleft,y:curtop};
+ }
+}
+
+django.jQuery.fn.autocomplete = function(url, options, data) {
+ // Make sure options exists
+ options = options || {};
+ // Set url as option
+ options.url = url;
+ // set some bulk local data
+ options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null;
+
+ lookup_obj = django.jQuery(this);
+ options.lookup_obj = lookup_obj;
+
+ options.extraParams = options.extraParams || {
+ search_fields: lookup_obj.attr('search_fields'),
+ app_label: lookup_obj.attr('app_label'),
+ model_name: lookup_obj.attr('model_name')
+ }
+
+ //console.log(options.extraParams);
+
+ // Set default values for required options
+ options.inputClass = options.inputClass || "ac_input";
+ options.resultsClass = options.resultsClass || "ac_results";
+ options.lineSeparator = options.lineSeparator || "\n";
+ options.cellSeparator = options.cellSeparator || "|";
+ options.minChars = options.minChars || 2;
+ options.delay = options.delay || 10;
+ options.matchCase = options.matchCase || 0;
+ options.matchSubset = options.matchSubset || 1;
+ options.matchContains = options.matchContains || 1;
+ options.cacheLength = options.cacheLength || 0;
+ options.mustMatch = options.mustMatch || 0;
+ options.extraParams = options.extraParams || {};
+ options.loadingClass = options.loadingClass || "ac_loading";
+ options.selectFirst = options.selectFirst || true;
+ options.selectOnly = options.selectOnly || false;
+ options.maxItemsToShow = options.maxItemsToShow || 10;
+ options.autoFill = options.autoFill || false;
+ options.width = parseInt(options.width, 10) || 0;
+ options.formatItem = options.formatItem || autocomplete_liFormat
+
+ this.each(function() {
+ var input = this;
+ new django.jQuery.autocomplete(input, options);
+ });
+
+ // Don't break the chain
+ return this;
+}
+
+django.jQuery.fn.autocompleteArray = function(data, options) {
+ return this.autocomplete(null, options, data);
+}
+
+django.jQuery.fn.indexOf = function(e){
+ for( var i=0; i