handle state in js, incorporate history.js, permanent urls work (almost).

This commit is contained in:
Sanj 2011-10-03 08:13:28 +05:30
parent 9ac2e8dab4
commit 26440df33d
13 changed files with 139 additions and 28 deletions

View File

@ -56,7 +56,7 @@ class ItfModel(models.Model):
return tab
def get_absolute_url(self):
return "%s?tab_id=%d&object_id=%d" % (self.get_module().get_absolute_url(), self.get_tab().id, self.id)
return "%s/?tab=%s&object_id=%d" % (self.get_module().get_absolute_url(), self.get_tab().slug, self.id)
def get_module(self):
tab = self.get_tab()

View File

@ -34,7 +34,7 @@ class ModuleTab(models.Model):
return self.model.model.model_class()
def get_absolute_url(self): #FIXME!!
return self.slug
return "/m/%s/?tab=%s" % (self.module.slug, self.slug,)
def get_list(self, options):
return self.model_class().get_list(options)

View File

@ -5,5 +5,5 @@ urlpatterns = patterns('',
(r'^get_list$', views.get_list),
(r'^get_details$', views.get_details),
(r'^get_tab$', views.get_tab),
(r'^(?P<module_slug>.*)$', views.main),
(r'^(?P<module_slug>.*)/$', views.main),
)

View File

@ -19,15 +19,16 @@ def main(request, module_slug):
})
return render_to_response("noel/insidepage.html", context)
def get_tab(request):
tab_id = request.GET.get("tab_id", 0)
tab = get_object_or_404(ModuleTab, pk=tab_id)
tab_slug = request.GET.get("tab", "")
tab = get_object_or_404(ModuleTab, slug=tab_slug)
return render_to_json_response(tab.get_dict())
def get_list(request):
tab_id = request.GET.get("tab_id", 0)
tab = get_object_or_404(ModuleTab, pk=tab_id)
tab_slug = request.GET.get("tab", "")
tab = get_object_or_404(ModuleTab, slug=tab_slug)
sortString = request.GET.get("sort", "")
if sortString == "" or sortString == 'null':
@ -51,8 +52,8 @@ def get_list(request):
def get_details(request):
tab_id = request.GET.get("tab_id", 0)
tab = get_object_or_404(ModuleTab, pk=tab_id)
tab_slug = request.GET.get("tab", '')
tab = get_object_or_404(ModuleTab, slug=tab_slug)
model_class = tab.model_class()
object_id = request.GET.get("object_id", 0)
obj = get_object_or_404(model_class, pk=object_id)

View File

@ -8,6 +8,8 @@ DEBUG = True
TEMPLATE_DEBUG = DEBUG
JSON_DEBUG = DEBUG
LOCAL_DEVELOPMENT = True
LOGGING_INTERCEPT_REDIRECTS = True
LOGGING_LOG_SQL = True
@ -18,6 +20,11 @@ PROJECT_PATH = os.path.dirname(__file__)
PROJECT_ROOT = PROJECT_PATH
SITE_CONFIG = join(PROJECT_ROOT, 'itf.json')
HAYSTACK_SITECONF = 'itf.search_sites'
HAYSTACK_SEARCH_ENGINE = 'whoosh'
HAYSTACK_WHOOSH_PATH = join(PROJECT_PATH, "../whoosh/itf_index")
CKEDITOR_MEDIA_PREFIX = "/static/ckeditor/"
CKEDITOR_UPLOAD_PATH = join(PROJECT_PATH, "static/upload/images/")
@ -161,6 +168,7 @@ INSTALLED_APPS = (
'user',
'ckeditor',
'fccv',
'haystack',
)
ACCOUNT_ACTIVATION_DAYS = 30

View File

@ -0,0 +1 @@
(function(a,b){function d(a,d){var e=d.__amplify__?JSON.parse(d.__amplify__):{};c.addType(a,function(f,g,h){var i=g,j=(new Date).getTime(),k,l;if(!f){i={};for(f in e)k=d[f],l=k?JSON.parse(k):{expires:-1},l.expires&&l.expires<=j?(delete d[f],delete e[f]):i[f.replace(/^__amplify__/,"")]=l.data;d.__amplify__=JSON.stringify(e);return i}f="__amplify__"+f;if(g===b){if(e[f]){k=d[f],l=k?JSON.parse(k):{expires:-1};if(l.expires&&l.expires<=j)delete d[f],delete e[f];else return l.data}}else if(g===null)delete d[f],delete e[f];else{l=JSON.stringify({data:g,expires:h.expires?j+h.expires:null});try{d[f]=l,e[f]=!0}catch(m){c[a]();try{d[f]=l,e[f]=!0}catch(m){throw c.error()}}}d.__amplify__=JSON.stringify(e);return i})}JSON.stringify=JSON.stringify||JSON.encode,JSON.parse=JSON.parse||JSON.decode;var c=a.store=function(a,b,d,e){var e=c.type;d&&d.type&&d.type in c.types&&(e=d.type);return c.types[e](a,b,d||{})};c.types={},c.type=null,c.addType=function(a,b){c.type||(c.type=a),c.types[a]=b,c[a]=function(b,d,e){e=e||{},e.type=a;return c(b,d,e)}},c.error=function(){return"amplify.store quota exceeded"};for(var e in{localStorage:1,sessionStorage:1})try{window[e].getItem&&d(e,window[e])}catch(f){}window.globalStorage&&(d("globalStorage",window.globalStorage[window.location.hostname]),c.type==="sessionStorage"&&(c.type="globalStorage")),function(){var a=document.createElement("div"),d="amplify",e;a.style.display="none",document.getElementsByTagName("head")[0].appendChild(a),a.addBehavior&&(a.addBehavior("#default#userdata"),a.load(d),e=a.getAttribute(d)?JSON.parse(a.getAttribute(d)):{},c.addType("userData",function(f,g,h){var i=g,j=(new Date).getTime(),k,l,m;if(!f){i={};for(f in e)k=a.getAttribute(f),l=k?JSON.parse(k):{expires:-1},l.expires&&l.expires<=j?(a.removeAttribute(f),delete e[f]):i[f]=l.data;a.setAttribute(d,JSON.stringify(e)),a.save(d);return i}f=f.replace(/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g,"-");if(g===b){if(f in e){k=a.getAttribute(f),l=k?JSON.parse(k):{expires:-1};if(l.expires&&l.expires<=j)a.removeAttribute(f),delete e[f];else return l.data}}else g===null?(a.removeAttribute(f),delete e[f]):(m=a.getAttribute(f),l=JSON.stringify({data:g,expires:h.expires?j+h.expires:null}),a.setAttribute(f,l),e[f]=!0);a.setAttribute(d,JSON.stringify(e));try{a.save(d)}catch(n){m===null?(a.removeAttribute(f),delete e[f]):a.setAttribute(f,m),c.userData();try{a.setAttribute(f,l),e[f]=!0,a.save(d)}catch(n){m===null?(a.removeAttribute(f),delete e[f]):a.setAttribute(f,m);throw c.error()}}return i}))}(),d("memory",{})})(this.amplify=this.amplify||{})

View File

@ -0,0 +1 @@
(function(a,b){var c=a.History=a.History||{},d=a.jQuery;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d(a).bind(b,c)},trigger:function(a,b){d(a).trigger(b)},onDomLoad:function(a){d(a)}},typeof c.init!="undefined"&&c.init()})(window)

View File

@ -0,0 +1 @@
(function(a,b){"use strict";var c=a.document,d=a.setTimeout||d,e=a.clearTimeout||e,f=a.setInterval||f,g=a.History=a.History||{};if(typeof g.initHtml4!="undefined")throw new Error("History.js HTML4 Support has already been loaded...");g.initHtml4=function(){if(typeof g.initHtml4.initialized!="undefined")return!1;g.initHtml4.initialized=!0,g.enabled=!0,g.savedHashes=[],g.isLastHash=function(a){var b=g.getHashByIndex(),c=a===b;return c},g.saveHash=function(a){if(g.isLastHash(a))return!1;g.savedHashes.push(a);return!0},g.getHashByIndex=function(a){var b=null;typeof a=="undefined"?b=g.savedHashes[g.savedHashes.length-1]:a<0?b=g.savedHashes[g.savedHashes.length+a]:b=g.savedHashes[a];return b},g.discardedHashes={},g.discardedStates={},g.discardState=function(a,b,c){var d=g.getHashByState(a),e={discardedState:a,backState:c,forwardState:b};g.discardedStates[d]=e;return!0},g.discardHash=function(a,b,c){var d={discardedHash:a,backState:c,forwardState:b};g.discardedHashes[a]=d;return!0},g.discardedState=function(a){var b=g.getHashByState(a),c=g.discardedStates[b]||!1;return c},g.discardedHash=function(a){var b=g.discardedHashes[a]||!1;return b},g.recycleState=function(a){var b=g.getHashByState(a);g.discardedState(a)&&delete g.discardedStates[b];return!0},g.emulated.hashChange&&(g.hashChangeInit=function(){g.checkerFunction=null;var b="";if(g.isInternetExplorer()){var d="historyjs-iframe",e=c.createElement("iframe");e.setAttribute("id",d),e.style.display="none",c.body.appendChild(e),e.contentWindow.document.open(),e.contentWindow.document.close();var h="",i=!1;g.checkerFunction=function(){if(i)return!1;i=!0;var c=g.getHash()||"",d=g.unescapeHash(e.contentWindow.document.location.hash)||"";c!==b?(b=c,d!==c&&(h=d=c,e.contentWindow.document.open(),e.contentWindow.document.close(),e.contentWindow.document.location.hash=g.escapeHash(c)),g.Adapter.trigger(a,"hashchange")):d!==h&&(h=d,g.setHash(d,!1)),i=!1;return!0}}else g.checkerFunction=function(){var c=g.getHash();c!==b&&(b=c,g.Adapter.trigger(a,"hashchange"));return!0};g.intervalList.push(f(g.checkerFunction,g.options.hashChangeInterval));return!0},g.Adapter.onDomLoad(g.hashChangeInit)),g.emulated.pushState&&(g.onHashChange=function(b){var d=b&&b.newURL||c.location.href,e=g.getHashByUrl(d),f=null,h=null,i=null;if(g.isLastHash(e)){g.busy(!1);return!1}g.doubleCheckComplete(),g.saveHash(e);if(e&&g.isTraditionalAnchor(e)){g.Adapter.trigger(a,"anchorchange"),g.busy(!1);return!1}f=g.extractState(g.getFullUrl(e||c.location.href,!1),!0);if(g.isLastSavedState(f)){g.busy(!1);return!1}h=g.getHashByState(f);var j=g.discardedState(f);if(j){g.getHashByIndex(-2)===g.getHashByState(j.forwardState)?g.back(!1):g.forward(!1);return!1}g.pushState(f.data,f.title,f.url,!1);return!0},g.Adapter.bind(a,"hashchange",g.onHashChange),g.pushState=function(b,d,e,f){if(g.getHashByUrl(e))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(f!==!1&&g.busy()){g.pushQueue({scope:g,callback:g.pushState,args:arguments,queue:f});return!1}g.busy(!0);var h=g.createStateObject(b,d,e),i=g.getHashByState(h),j=g.getState(!1),k=g.getHashByState(j),l=g.getHash();g.storeState(h),g.expectedStateId=h.id,g.recycleState(h),g.setTitle(h);if(i===k){g.busy(!1);return!1}if(i!==l&&i!==g.getShortUrl(c.location.href)){g.setHash(i,!1);return!1}g.saveState(h),g.Adapter.trigger(a,"statechange"),g.busy(!1);return!0},g.replaceState=function(a,b,c,d){if(g.getHashByUrl(c))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(d!==!1&&g.busy()){g.pushQueue({scope:g,callback:g.replaceState,args:arguments,queue:d});return!1}g.busy(!0);var e=g.createStateObject(a,b,c),f=g.getState(!1),h=g.getStateByIndex(-2);g.discardState(f,e,h),g.pushState(e.data,e.title,e.url,!1);return!0},g.getHash()&&!g.emulated.hashChange&&g.Adapter.onDomLoad(function(){g.Adapter.trigger(a,"hashchange")}))},g.init()})(window)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)d=rep[c],typeof d=="string"&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(b&&typeof b!="function"&&(typeof b!="object"||typeof b.length!="number"))throw new Error("JSON.stringify");return str("",{"":a})}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()

View File

@ -1,15 +1,17 @@
$('#listLeft ul li a').live("click", function() {
// alert("foo");
// e.preventDefault();
if ($(this).hasClass("listLeftSelected")) {
return false;
}
$('.listLeftSelected').removeClass("listLeftSelected");
$(this).addClass('listLeftSelected');
var objId = $(this).attr("data-id");
var tabId = $('.innerSelected').attr("data-id");
var tab = $('.innerSelected').attr("data-slug");
var formData = getSearchFormJSON();
formData.object_id = objId;
var urlString = JSONtoQueryString(formData);
History.pushState(formData, "", urlString);
$.getJSON("/m/get_details", {
'tab_id': tabId,
'tab': tab,
'object_id': objId
}, function(data) {
$('#textRight').text(data.title).formatTitle();
@ -25,6 +27,7 @@ $('#listLeft ul li a').live("click", function() {
return false;
});
$('.toggleNext').live("click", function(e) {
e.preventDefault();
var $next = $(this).next();
@ -57,6 +60,7 @@ function getLi(item) {
}
$(function() {
/* search button toggle */
$('.searchInnerField').hide();
$('#searchInner').toggle(function() {
@ -67,7 +71,7 @@ $(function() {
/* search button end */
$('#listLeft ul li a').eq(0).click();
// $('#listLeft ul li a').eq(0).click();
// alert("hi");
@ -80,11 +84,12 @@ $(function() {
$('.innerSelected').removeClass("innerSelected");
$this.addClass("innerSelected");
$('.displayedTab').removeClass("displayedTab");
var tab = $this.attr("data-slug");
var tabId = $this.attr("data-id");
$('#tabText_' + tabId).addClass("displayedTab");
doListLoading();
$.getJSON("/m/get_tab", {
'tab_id': tabId,
'tab': tab,
// 'page_no': 1
}, function(data) {
// $('#loadingList').hide();
@ -111,7 +116,8 @@ $(function() {
/* end if has_list */
$('#textRight').text(data.title).formatTitle();
var $html = getNoListHtml(data.page.items);
$('#bottomRight').empty().append($html);
$('#bottomRight').empty().append($html);
History.pushState({'tab': tab}, "", "?tab=" + tab);
}
});
});
@ -136,14 +142,13 @@ $(function() {
$('#listForm').submit(function(e) {
e.preventDefault();
var sortString = $('#orderBySelect').val();
var searchTerm = $('.searchListField').val();
doListLoading();
$.getJSON("/m/get_list", {
'tab_id': $('.innerSelected').attr("data-id"),
'sort': sortString,
'search': searchTerm
}, function(data) {
// var urlString = "?tab_id=" + $('.innerSelected').attr("data-id") + "&sort=" + sortString + "&search=" + searchTerm;
var formData = getSearchFormJSON();
var urlString = JSONtoQueryString(formData);
// console.log(urlString);
History.pushState(formData, "", urlString);
$.getJSON("/m/get_list", formData, function(data) {
stopListLoading(data);
displayList(data.items);
});
@ -158,14 +163,84 @@ $(function() {
goNextPage();
});
var State = History.getState();
// console.log(State);
var queryData = QueryStringToJSON(State.hash);
doState(queryData);
});
/*
this function will grow to be a monster. be gentle with it, ideally move state functions to a state namespace or so.
*/
function doState(queryData) {
// console.log(queryData);
// var tab_slug = queryData.tab;
// alert(tab_slug);
if (queryData.tab == undefined || queryData.tab == '') {
// alert("foo");
var $tab = $('.defaultTab');
} else {
var $tab = $('.tabCategory[data-slug=' + queryData.tab + ']');
if ($tab.length == 0) {
$tab = $('.defaultTab');
}
}
$tab.click();
}
/*
>>>var foo = {'var1': 'bar', 'var2': 'baz'}
>>> JSONtoQueryString(foo);
'?var1=bar&var2=baz'
*/
function JSONtoQueryString(obj) {
var s = "?";
for (var o in obj) {
if (obj.hasOwnProperty(o)) {
s += o + "=" + obj[o] + "&";
}
}
return s.substring(0, s.length - 1);
}
/*
>>>var foo = "/something/bla/?var1=bar&var2=baz";
>>>QueryStringToJSON(foo);
{'var1': 'bar', 'var2': 'baz'}
*/
function QueryStringToJSON(qstring) {
var qstring = qstring.split("?")[1];
var args = {};
var vars = qstring.split('&');
// console.log(vars);
for (var i=0; i<vars.length; i++) {
var kv = vars[i].split('=');
var key = kv[0];
var value = kv[1];
args[key] = value;
}
return args;
}
function displayList(items) {
var state = QueryStringToJSON(History.getState().hash);
console.log(state);
for (var i=0; i<items.length; i++) {
var $li = getLi(items[i]);
$('#listLeft ul').append($li);
}
$('#listLeft ul li a').eq(0).click();
if (state.object_id == undefined || state.object_id == '') {
$('#listLeft ul li a').eq(0).click();
} else {
$('#listLeft ul li a[data-id=' + state.object_id + ']').click(); //FIXME: FIXME!!!!!!!!!!
}
}
function doListLoading() {
@ -205,6 +280,9 @@ function goPreviousPage() {
}
function getPage(pageNo) {
$('#page_no').val(pageNo);
$('#listForm').submit();
/*
doListLoading();
$.getJSON("/m/get_list", {
'tab_id': $('.innerSelected').attr("data-id"),
@ -214,6 +292,7 @@ function getPage(pageNo) {
stopListLoading(page);
displayList(page.items);
});
*/
}
function getNoListHtml(items) {
@ -240,6 +319,14 @@ function getNoListItemHtml(item) {
return $ret;
}
function getSearchFormJSON() {
return {
'tab': $('.innerSelected').attr("data-slug"),
'sort': $('#orderBySelect').val(),
'search': $('#searchList').val(),
'page': $('#page_no').val()
}
}
jQuery.fn.formatTitle = function() {
var txt = $(this).text();

View File

@ -5,6 +5,11 @@
{% block extra_head %}
<link rel="stylesheet" href="/static/css/noel/inner.css" type="text/css" />
<link rel="stylesheet" href="/static/css/noel/inner-details.css" type="text/css" />
<script type="text/javascript" src="/static/js/history/history.js"></script>
<script type="text/javascript" src="/static/js/history/amplify.store.js"></script>
<script type="text/javascript" src="/static/js/history/history.html4.js"></script>
<script type="text/javascript" src="/static/js/history/history.adapter.jquery.js"></script>
<script type="text/javascript" src="/static/js/query_parser.js"></script>
<script type="text/javascript" src="/static/js/insidepage.js"></script>
{% endblock %}
@ -43,9 +48,9 @@
<li class="tabCategory innerSelected">FAQs</li>
<li class="tabCategory innerSelected">Guidelines</li>
-->
<li class="tabCategory innerSelected" data-id="{{ default_tab.id }}">{{ default_tab.title }}</li>
<li class="tabCategory defaultTab" data-id="{{ default_tab.id }}" id="tab_{{default_tab.slug}}" data-slug="{{ default_tab.slug }}">{{ default_tab.title }}</li>
{% for t in tabs %}
<li class="tabCategory" data-id="{{ t.id }}">{{ t.title }}</li>
<li class="tabCategory" data-id="{{ t.id }}" id="tab_{{ t.slug }}" data-slug="{{ t.slug }}">{{ t.title }}</li>
{% endfor %}
</ul>
@ -71,8 +76,9 @@
<option value="mostpopular">Most popular</option>
-->
</select>
<input id="page_no" name="page_no" type="hidden" value="1" />
<input type="text" placeholder="search list" name="field" class="searchListField"/>
<input type="text" placeholder="search list" id="searchList" name="field" class="searchListField"/>
<img src="/static/images/noel/search-icon.png" width="18" height="14" alt="search-icon" id="searchListIcon">
<span id="pageDisplay" style="display:none;">Page <span id="currPageNo">1</span> of <span id="totalPages">10</span></span>
</form>
@ -84,9 +90,11 @@
<div id="listLeft">
<ul>
<li id="loadingList">Loading...</li>
<!--
{% for l in default_list.items %}
<li class="tabListItem"><a href="{{ l.url }}" data-id="{{ l.id }}"><span>{{ l.title }}</span></a></li>
<li class="tabListItem"><a href="{{ l.url }}" data-id="{{ l.id }}""><span>{{ l.title }}</span></a></li>
{% endfor %}
-->
<!-- <li><a href=""><span>The Mystery of vanishing rights<span></a></li> -->
</ul>

View File

@ -11,3 +11,5 @@ django-debug-toolbar
South
django-ckeditor
fccv #for comment-spam protection
Whoosh
django-haystack