Add year search on timeline
authorveltr
Thu, 19 Sep 2013 17:57:10 +0200
changeset 111 439485fd5961
parent 110 597fa9d09973
child 112 ea73469b44c3
Add year search on timeline
src/jocondelab/management/commands/import_extra_years.py
src/jocondelab/management/commands/import_term_list.py
src/jocondelab/management/commands/populate_notice_years.py
src/jocondelab/migrations/0012_auto__add_notice_years.py
src/jocondelab/models/__init__.py
src/jocondelab/models/data.py
src/jocondelab/static/jocondelab/css/front-common.css
src/jocondelab/static/jocondelab/css/front-timeline.css
src/jocondelab/static/jocondelab/js/front-common.js
src/jocondelab/static/jocondelab/js/front-geo.js
src/jocondelab/static/jocondelab/js/front-timeline.js
src/jocondelab/templates/jocondelab/front_base.html
src/jocondelab/templates/jocondelab/front_home.html
src/jocondelab/templates/jocondelab/front_search.html
src/jocondelab/templates/jocondelab/front_timeline.html
src/jocondelab/templates/jocondelab/partial/notice_list.html
src/jocondelab/views/front_office.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jocondelab/management/commands/import_extra_years.py	Thu Sep 19 17:57:10 2013 +0200
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+'''
+Created on Sep 19, 2013
+
+@author: rvelt
+'''
+
+from core.models import Term
+from jocondelab.models import DbpediaYears
+from django.core.management import BaseCommand
+import csv
+import os
+
+class Command(BaseCommand):
+
+    args = "csv_file"
+    
+    help = "Import csv file containing dbpedia uris and years"
+    
+    def handle(self, *args, **options):
+        
+        filepath = os.path.abspath(args[0])
+        self.stdout.write("Importing %s" % filepath)
+        
+        with open(filepath,'rb') as csv_file:
+            dialect = csv.Sniffer().sniff(csv_file.read(1024))
+            csv_file.seek(0)
+            reader = csv.DictReader(csv_file, dialect=dialect)
+            for i,row in enumerate(reader):
+                print row
+                dbpedia_uri = row['dbpedia_uri']
+                start_year = int(row['start_year'])
+                end_year = int(row['end_year'])
+                ts = Term.objects.filter(dbpedia_uri = dbpedia_uri)
+                for t in ts:
+                    dyobj, created = DbpediaYears.objects.get_or_create(term=t, defaults={'start_year': start_year, 'end_year': end_year})
+                    if not created:
+                        dyobj.start_year = start_year
+                        dyobj.end_year = end_year
+                        dyobj.save()
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jocondelab/management/commands/import_term_list.py	Thu Sep 19 17:57:10 2013 +0200
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+'''
+Created on Jul 31, 2013
+
+@author: ymh
+'''
+
+from core.models import Term
+from jocondelab.models import ContributableTerm, TagcloudTerm
+from django.core.management.base import BaseCommand
+from optparse import make_option
+
+class Command(BaseCommand):
+    
+    help = "Import a list of term for inclusion in the tagcloud or available contributable terms"
+    
+    option_list = BaseCommand.option_list + (
+        make_option('-c', '--clear',
+            dest= 'clear',
+            action= 'store_true',
+            default= False,
+            help= 'Clear table' 
+        ),
+        make_option('-t', '--tagcloud',
+            dest= 'tagcloud',
+            action= 'store_true',
+            default= False,
+            help= 'Store in TagCloud table instead of ' 
+        ),
+        make_option('-l','--lang',
+            dest= 'language_code',
+            default= None,
+            help= 'Language Code. If not provided, the labels in the Term table will be used' 
+        ),
+        make_option('-f','--file',
+            dest= 'file_name',
+            default= None,
+            help= 'Extract keywords from a text file instead of from command line args'
+        ),
+    )
+    
+    def handle(self, *args, **options):
+        
+        terms_to_add = None
+        
+        if args:
+            terms_to_add = args
+        
+        file_name = options.get('file_name', None)
+        if file_name:
+            try:
+                f = open(file_name, 'r')
+                terms_to_add = [l.strip() for l in f.readlines()]
+                f.close()
+            except:
+                print "Can't open file %s"%file_name
+                
+        if not terms_to_add:
+            print "No terms to add"
+            return
+        
+        is_tagcloud = options.get('tagcloud', False)
+        DestinationModel = TagcloudTerm if is_tagcloud else ContributableTerm
+        
+        print "Target model is %s"%DestinationModel.__name__
+                
+        if options.get('clear', False):
+            print "Clearing table"
+            DestinationModel.objects.all().delete()
+        
+        language_code = options.get('language_code', None)
+        
+        for t in terms_to_add:
+            if t:
+                tt = t.split("|")
+                label = tt[0]
+                ts = Term.objects
+                if language_code:
+                    ts = ts.filter(dbpedia_fields__label=label,dbpedia_fields__language_code=language_code)
+                else:
+                    ts = ts.filter(label=label)
+                if len(tt) > 1:
+                    thesaurus = tt[1]
+                    ts = ts.filter(thesaurus__label=thesaurus)
+                print "%d term(s) found for label '%s'"%(ts.count(),t)
+                for to in ts:
+                    DestinationModel.objects.get_or_create(term=to)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jocondelab/management/commands/populate_notice_years.py	Thu Sep 19 17:57:10 2013 +0200
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+from django.core.management.base import BaseCommand
+from core.models import (Notice)
+from core.utils import show_progress
+from jocondelab.models import NoticeYears
+from django.db import reset_queries, transaction
+import re
+
+class Command(BaseCommand):
+    
+    help = "Extract Years from MILL field"
+    
+    option_list = NoArgsCommand.option_list + (
+        make_option('-b', '--batch-size',
+            dest= 'batch_size',
+            type='int',
+            default= 50,
+            help= 'number of object to import in bulk operations' 
+        ),                                               
+    )
+    
+    def handle(self, *args, **options):
+        
+        millcache = {}
+        yearre = re.compile("\d+")
+        rejectre = re.compile("\d-\d")
+        beforre = re.compile("av(\.|ant)? JC|- ", re.I)
+        splitre = re.compile("\s*[,;]\s*")
+        
+        writer = None
+        
+        def getyear(millesime):
+            year = None
+            if not rejectre.search(millesime):
+                yearmatch = yearre.search(millesime)
+                if yearmatch:
+                    year = int(millesime[yearmatch.start():yearmatch.end()])
+                    if beforre.search(millesime):
+                        year = - year
+                    if year > 2012:
+                        year = None
+            if year is None:
+                millcache[millesime] = year
+            return year
+        
+        transaction.enter_transaction_management()
+        transaction.managed()
+        
+        qs = Notice.objects.exclude(mill=None).exclude(mill='')
+        count = qs.count()
+        for i, notice in enumerate(qs):
+            millfield = notice.mill
+            writer = show_progress(i+1, count, millfield, 50, writer)
+            years = []
+            if millfield:
+                millesimes = splitre.split(millfield)
+                years = [millcache[m] if m in millcache else getyear(m) for m in millesimes]
+                years = [y for y in years if y is not None]
+            if len(years):
+                sy = min(years)
+                ey = max(years)
+                nyobj, created = NoticeYears.objects.get_or_create(notice=notice, defaults={'start_year':sy,'end_year':ey})
+                if not created:
+                    nyobj.start_year = sy
+                    nyobj.end_year = ey
+                    nyobj.save()
+            
+            if not ((i+1) % 50):
+                transaction.commit()
+                reset_queries()
+                
+        transaction.commit()
+        reset_queries()
+        transaction.leave_transaction_management()
+        
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jocondelab/migrations/0012_auto__add_notice_years.py	Thu Sep 19 17:57:10 2013 +0200
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'NoticeYears'
+        db.create_table(u'jocondelab_noticeyears', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('notice', self.gf('django.db.models.fields.related.ForeignKey')(related_name='years', unique=True, to=orm['core.Notice'])),
+            ('start_year', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+            ('end_year', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+        ))
+        db.send_create_signal('jocondelab', ['NoticeYears'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'NoticeYears'
+        db.delete_table(u'jocondelab_noticeyears')
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'core.autrnoticeterm': {
+            'Meta': {'object_name': 'AutrNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.domnnoticeterm': {
+            'Meta': {'object_name': 'DomnNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.ecolnoticeterm': {
+            'Meta': {'object_name': 'EcolNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.epoqnoticeterm': {
+            'Meta': {'object_name': 'EpoqNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.lieuxnoticeterm': {
+            'Meta': {'object_name': 'LieuxNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.notice': {
+            'Meta': {'object_name': 'Notice'},
+            'adpt': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'appl': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'aptn': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'attr': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'autr': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'autr_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'autr+'", 'symmetrical': 'False', 'through': "orm['core.AutrNoticeTerm']", 'to': "orm['core.Term']"}),
+            'bibl': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'comm': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'contact': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'coor': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'copy': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'dacq': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'data': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'dation': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'ddpt': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'decv': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'deno': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'depo': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'desy': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'dims': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'dmaj': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'dmis': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'domn': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'domn_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'domn+'", 'symmetrical': 'False', 'through': "orm['core.DomnNoticeTerm']", 'to': "orm['core.Term']"}),
+            'drep': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'ecol': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'ecol_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ecol+'", 'symmetrical': 'False', 'through': "orm['core.EcolNoticeTerm']", 'to': "orm['core.Term']"}),
+            'epoq': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'epoq_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'epoq+'", 'symmetrical': 'False', 'through': "orm['core.EpoqNoticeTerm']", 'to': "orm['core.Term']"}),
+            'etat': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'expo': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'gene': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'geohi': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'hist': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'insc': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'inv': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'labo': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'lieux': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'lieux_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lieux+'", 'symmetrical': 'False', 'through': "orm['core.LieuxNoticeTerm']", 'to': "orm['core.Term']"}),
+            'loca': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'loca2': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'mill': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'milu': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'mosa': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'msgcom': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'museo': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'nsda': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'onom': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'paut': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'pdat': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'pdec': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'peoc': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'peri': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+            'peri_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'peri+'", 'symmetrical': 'False', 'through': "orm['core.PeriNoticeTerm']", 'to': "orm['core.Term']"}),
+            'peru': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'phot': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'pins': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'plieux': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'prep': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'puti': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'reda': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'ref': ('django.db.models.fields.CharField', [], {'max_length': '20', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'refim': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'repr': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'repr_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'repr+'", 'symmetrical': 'False', 'through': "orm['core.ReprNoticeTerm']", 'to': "orm['core.Term']"}),
+            'srep': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'srep_terms': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'srep+'", 'symmetrical': 'False', 'through': "orm['core.SrepNoticeTerm']", 'to': "orm['core.Term']"}),
+            'stat': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'tech': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'tico': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'titr': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'util': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'video': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'www': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'})
+        },
+        'core.noticeterm': {
+            'Meta': {'object_name': 'NoticeTerm'},
+            'graph': ('django.db.models.fields.URLField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'notice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Notice']"}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Term']"})
+        },
+        'core.perinoticeterm': {
+            'Meta': {'object_name': 'PeriNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.reprnoticeterm': {
+            'Meta': {'object_name': 'ReprNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.srepnoticeterm': {
+            'Meta': {'object_name': 'SrepNoticeTerm', '_ormbases': ['core.NoticeTerm']},
+            u'noticeterm_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.NoticeTerm']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'core.term': {
+            'Meta': {'object_name': 'Term'},
+            'alternative_wikipedia_pageid': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'alternative_wikipedia_url': ('django.db.models.fields.URLField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'dbpedia_uri': ('django.db.models.fields.URLField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
+            'lang': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'link_semantic_level': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'nb_notice': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'normalized_label': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
+            'notices': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'terms+'", 'symmetrical': 'False', 'through': "orm['core.NoticeTerm']", 'to': "orm['core.Notice']"}),
+            'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['core.Term']"}),
+            u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'thesaurus': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Thesaurus']"}),
+            u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'uri': ('django.db.models.fields.URLField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'url_status': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'validated': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'validation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'validator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}),
+            'wikipedia_edition': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'wikipedia_pageid': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'wikipedia_revision_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'wikipedia_url': ('django.db.models.fields.URLField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'wp_alternative_label': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'wp_label': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'})
+        },
+        'core.thesaurus': {
+            'Meta': {'ordering': "['label']", 'object_name': 'Thesaurus'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'uri': ('django.db.models.fields.URLField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'})
+        },
+        'core.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'jocondelab.contributableterm': {
+            'Meta': {'object_name': 'ContributableTerm'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Term']", 'unique': 'True'})
+        },
+        'jocondelab.contributedfields': {
+            'Meta': {'object_name': 'ContributedFields'},
+            'abstract': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dbpedia_uri': ('django.db.models.fields.URLField', [], {'max_length': '2048', 'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dbpedia_fields'", 'to': "orm['jocondelab.ContributedTerm']"}),
+            'thumbnail': ('django.db.models.fields.URLField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'})
+        },
+        'jocondelab.contributedterm': {
+            'Meta': {'object_name': 'ContributedTerm'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'dbpedia_language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
+            'dbpedia_uri': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'jocondelab.contribution': {
+            'Meta': {'object_name': 'Contribution'},
+            'contribution_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'notice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Notice']"}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contributions'", 'to': "orm['jocondelab.ContributedTerm']"}),
+            'thesaurus': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Thesaurus']", 'null': 'True', 'blank': 'True'})
+        },
+        'jocondelab.country': {
+            'Meta': {'object_name': 'Country'},
+            'dbpedia_uri': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'iso_code_2': ('django.db.models.fields.CharField', [], {'max_length': '2', 'db_index': 'True'}),
+            'iso_code_3': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
+            'nb_notices': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'jocondelab.dbpediafields': {
+            'Meta': {'object_name': 'DbpediaFields'},
+            'abstract': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dbpedia_uri': ('django.db.models.fields.URLField', [], {'max_length': '2048', 'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dbpedia_fields'", 'to': "orm['core.Term']"}),
+            'thumbnail': ('django.db.models.fields.URLField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'})
+        },
+        'jocondelab.dbpediageo': {
+            'Meta': {'object_name': 'DbpediaGeo'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'latitude': ('django.db.models.fields.FloatField', [], {'db_index': 'True'}),
+            'longitude': ('django.db.models.fields.FloatField', [], {'db_index': 'True'}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'geo'", 'unique': 'True', 'to': "orm['core.Term']"})
+        },
+        'jocondelab.dbpediayears': {
+            'Meta': {'object_name': 'DbpediaYears'},
+            'end_year': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'start_year': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'years'", 'unique': 'True', 'to': "orm['core.Term']"})
+        },
+        'jocondelab.noticeyears': {
+            'Meta': {'object_name': 'NoticeYears'},
+            'end_year': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'notice': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'years'", 'unique': 'True', 'to': "orm['core.Notice']"}),
+            'start_year': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'})
+        },
+        'jocondelab.tagcloudterm': {
+            'Meta': {'object_name': 'TagcloudTerm'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Term']", 'unique': 'True'})
+        },
+        'jocondelab.termlinks': {
+            'Meta': {'object_name': 'TermLinks'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'termlinks_objects'", 'to': "orm['core.Term']"}),
+            'subject': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'termlinks_subjects'", 'to': "orm['core.Term']"})
+        }
+    }
+
+    complete_apps = ['jocondelab']
\ No newline at end of file
--- a/src/jocondelab/models/__init__.py	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/models/__init__.py	Thu Sep 19 17:57:10 2013 +0200
@@ -1,5 +1,5 @@
-__all__ = ['Country', 'DbpediaFields', 'TermLinks', 'DbpediaYears', 'DbpediaGeo', 'ContributedTerm', 'ContributedFields', 'Contribution', 'ContributableTerm', 'TagcloudTerm' ]
+__all__ = ['Country', 'DbpediaFields', 'TermLinks', 'DbpediaYears', 'DbpediaGeo', 'ContributedTerm', 'ContributedFields', 'Contribution', 'ContributableTerm', 'TagcloudTerm', 'NoticeYears' ]
 
-from jocondelab.models.data import Country, DbpediaFields, TermLinks, DbpediaYears, DbpediaGeo
+from jocondelab.models.data import Country, DbpediaFields, TermLinks, DbpediaYears, DbpediaGeo, NoticeYears
 from jocondelab.models.contribution import ContributedTerm, ContributedFields, Contribution, ContributableTerm
 from jocondelab.models.displaysettings import TagcloudTerm
--- a/src/jocondelab/models/data.py	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/models/data.py	Thu Sep 19 17:57:10 2013 +0200
@@ -5,7 +5,7 @@
 @author: ymh
 '''
 from django.db import models
-from core.models import Term
+from core.models import (Term, Notice)
 
 class Country(models.Model):
     dbpedia_uri = models.URLField(max_length=2048, unique=True, blank=False, null=False, db_index=True)
@@ -63,4 +63,15 @@
         app_label = 'jocondelab'
         
     def __unicode__(self):
-        return u'%s: %.4f%s, %.4f%s'%(self.term.dbpedia_uri, abs(self.latitude), 'N' if self.latitude > 0 else 'S', abs(self.longitude), 'E' if self.longitude > 0 else 'W')
\ No newline at end of file
+        return u'%s: %.4f%s, %.4f%s'%(self.term.dbpedia_uri, abs(self.latitude), 'N' if self.latitude > 0 else 'S', abs(self.longitude), 'E' if self.longitude > 0 else 'W')
+
+class NoticeYears(models.Model):
+    notice = models.ForeignKey(Notice, unique=True, blank=False, null=False, db_index=True, related_name="years")
+    start_year = models.IntegerField(null=False, blank=False, db_index=True)
+    end_year = models.IntegerField(null=False, blank=False, db_index=True)
+    
+    class Meta:
+        app_label = 'jocondelab'
+    
+    def __unicode__(self):
+        return u'%s: %d - %d'%(self.notice.titr or self.notice.deno, self.start_year, self.end_year)
\ No newline at end of file
--- a/src/jocondelab/static/jocondelab/css/front-common.css	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/static/jocondelab/css/front-common.css	Thu Sep 19 17:57:10 2013 +0200
@@ -376,3 +376,7 @@
 .load-more {
     display: none; margin: 20px 0 10px; text-align: center; font-size: 14px; font-weight: 700;
 }
+
+.notice-year {
+    font-weight: bold;
+}
--- a/src/jocondelab/static/jocondelab/css/front-timeline.css	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/static/jocondelab/css/front-timeline.css	Thu Sep 19 17:57:10 2013 +0200
@@ -1,5 +1,9 @@
+.timeline-section-title {
+    font-size: 16px; font-weight: bold; margin: 5px 0 10px;
+}
+
 .timeline-container {
-    position: relative; width: 100%; height: 376px; margin-top: 10px;
+    position: relative; width: 100%; height: 390px; margin-top: 10px;
     background: #ffffff; overflow: hidden;
 }
 
@@ -8,23 +12,39 @@
 }
 
 .timeline-list {
-    position: absolute; left: 0; top: 20px; width: 100%;
+    position: absolute; left: 0; top: 60px; width: 100%;
 }
 
 .timeline-item {
-    position: relative; height: 28px; width: 100%;
+    position: relative; height: 26px; width: 100%;
 }
 
 .timeline-item-box {
-    position: absolute; height: 16px; top: 0; margin-bottom: 4px;
+    position: absolute; height: 15px; top: 0; margin-bottom: 4px;
     border-bottom: 8px solid #0000c0; cursor: pointer;
 }
 
 .timeline-item-label {
     position: absolute; top: 0; text-align: center; width: 200px; margin-left: -100px;
-    font-size: 13px; left: 50%; line-height: 16px;
+    font-size: 13px; left: 50%; line-height: 15px;
 }
 
 .timeline-current {
     border-color: #e06000;
 }
+
+.timeline-mill-slider {
+    font-size: 16px; margin-bottom: 10px;
+}
+
+.timeline-mill-slider .ui-slider-range {
+    background: #ffcc99;
+}
+
+.timeline-mill-submit {
+    float: right; clear: both; padding: 5px 10px; line-height: 20px; font-size: 14px;
+}
+
+.timeline-mill-submit span {
+    font-weight: bold;
+}
--- a/src/jocondelab/static/jocondelab/js/front-common.js	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-common.js	Thu Sep 19 17:57:10 2013 +0200
@@ -3,15 +3,28 @@
     /* SEARCH TAG-IT */
    
     var $searchInput = $(".search-input"),
-        originalValue = $searchInput.val();
+        originalValue = $searchInput.val(),
+        allowSubmit = false,
+        ajaxsearch = window.ajaxsearch || false;
     
     function submitIfChanged(e, ui) {
         var val = $searchInput.val();
-        if (!ui.duringInitialization && val && val !== originalValue) {
+        if (allowSubmit && val && val !== originalValue) {
             $(".search-form").submit();
         }
     }
     
+    if (ajaxsearch) {
+        $(".search-form").submit(function() {
+            var val = $searchInput.val();
+            if (val && val !== originalValue) {
+                originalValue = val;
+                loadSearchResults({ q: val });
+            }
+            return false;
+        });
+    }
+    
     $searchInput.tagit({
         autocomplete: {
             source: urls.ajax_terms,
@@ -25,6 +38,9 @@
         afterTagRemoved: submitIfChanged,
         singleFieldDelimiter: ";"
     });
+    allowSubmit = true;
+    
+    /* END SEARCH TAG-IT */
     
     /* DBPEDIA OVERLAY */
     
@@ -179,6 +195,18 @@
             }
             showDbpediaBox(dbpediaUri);
         });
+        $sel.click(function(e) {
+            if (ajaxsearch) {
+                var $this = $(this);
+                allowSubmit = false;
+                $searchInput.tagit("removeAll");
+                $searchInput.tagit("createTag",$this.text());
+                allowSubmit = true;
+                hideDbpediaBox();
+                loadSearchResults({ dbpedia_uri: $this.attr("data-dbpedia-uri") });
+                return false;
+            }
+        });
         $sel.mouseleave(hideDbpediaBox);
     }
         
@@ -307,16 +335,17 @@
         adaptGrid();
     }
     
+    /* END NOTICE LIST MANAGEMENT */
+    
     /* AJAX SCROLL LOAD */
     
-    var max_scroll_pages = 3;
+    var max_scroll_pages = 4;
     
-    window.scrollLoad = function(data) {
+    window.scrollLoad = function(query) {
         var loadingnext = false,
             currentpage = parseInt($(".notice-list").attr("data-current-page")),
             page_count = parseInt($(".notice-list").attr("data-page-count")),
-            max_page = Math.min(currentpage + max_scroll_pages, page_count),
-            margin = 20;
+            max_page = Math.min(currentpage + max_scroll_pages, page_count);
         $win.on("scroll.ajaxload", function() {
             if (loadingnext || currentpage >= max_page) {
                 return;
@@ -324,12 +353,12 @@
             var $datablock = $(".notice-list"),
                 winbottom = $win.scrollTop() + $win.height(),
                 databottom = $datablock.offset().top + $datablock.height();
-            if (winbottom > databottom - margin) {
+            if (winbottom >= databottom) {
                 loadingnext = true;
                 $(".loading-please-wait").show();
                 $.ajax({
                     url: urls.ajax_search,
-                    data: _({ page: ++currentpage }).extend(data),
+                    data: _({ page: ++currentpage }).extend(query),
                     dataType: "html",
                     success: function(html) {
                         $datablock.append(html);
@@ -347,25 +376,26 @@
         });
     }
     
-    window.loadContentsByUri = function(dbpedia_uri) {
+    window.loadSearchResults = function(query) {
+        $(".hide-on-search").hide();
         $win.off("scroll.ajaxload");
         $(".results").empty();
+        $(".loading-please-wait").show();
         $.ajax({
             url: urls.ajax_search,
-            data: {
-                dbpedia_uri: dbpedia_uri
-            },
+            data: query,
             dataType: "html",
             success: function(html) {
                 $(".results").html(html);
                 bindResultsMouseover();
-                scrollLoad({ dbpedia_uri: dbpedia_uri });
+                $(".loading-please-wait").hide();
+                scrollLoad(query);
             }
         });
     }
+    
     /* */
     
-    
     $win.resize(function() {
         adaptGrid();
         recentreDbpediaBox();
@@ -377,18 +407,15 @@
         var $menu = $($(this).attr("href"));
         $menu.stop(true).slideDown(function(){ $menu.css("height",""); });
     }).mouseout(function() {
-        var $menu = $($(this).attr("href"));
-        $menu.stop(true).delay(3000).slideUp(function(){ $menu.css("height",""); });
+        $(".menu-list").stop(true).delay(3000).slideUp(function(){ $(".menu-list").css("height",""); });
     }).click(function() {
         return false;
     });
     
     $(".menu-list").mouseover(function() {
-        $menu = $(this);
-        $menu.stop(true).show().css("height","");
+        $(this).stop(true).show().css("height","");
     }).mouseout(function() {
-        $menu = $(this);
-        $menu.stop(true).delay(3000).slideUp(function(){ $menu.css("height",""); });
+        $(".menu-list").stop(true).delay(3000).slideUp(function(){ $(".menu-list").css("height",""); });
     });
     
     $("#language-menu a").click(function() {
--- a/src/jocondelab/static/jocondelab/js/front-geo.js	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-geo.js	Thu Sep 19 17:57:10 2013 +0200
@@ -64,7 +64,7 @@
         })
         stickyFeature = feature;
         showDbpedia(feature);
-        loadContentsByUri(feature.dbpedia_uri);
+        loadSearchResults({ dbpedia_uri: feature.dbpedia_uri });
     }
     
     function selectMarker(coord) {
--- a/src/jocondelab/static/jocondelab/js/front-timeline.js	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-timeline.js	Thu Sep 19 17:57:10 2013 +0200
@@ -1,19 +1,28 @@
 $(function() {
-    var startYear = -500,
+    
+    var startYear = -10000,
         endYear = 2012,
-        zoomLevel = 1,
+        zoomLevel = 8,
         zoomStep = Math.SQRT2,
-        minZoomLevel = .5,
-        maxZoomLevel = 128,
+        minZoomLevel = 1,
+        maxZoomLevel = 64,
+        sliderWidth = 3000,
         baseSpan = (endYear - startYear),
+        $slider = $(".timeline-mill-slider"),
         $tlcontainer = $(".timeline-container"),
         canvas = $tlcontainer.find('canvas')[0],
-        lineDistances = [ 1000, 500, 200, 100, 50, 25, 10, 5, 2, 1 ],
+        lineDistances = [ 1000, 500, 250, 100, 50, 25, 10, 5, 2 ],
+        miniLineDist = 1000,
         cWidth, cHeight,
-        wSpan, wStart, wEnd, wScale, wLineDist,
-        wCenter = baseSpan / 2 + startYear,
+        wSpan, wStart, wEnd, cScale, wLineDist, sScale, mScale,
+        wCenter = endYear - baseSpan / 16,
         itemCount = 12,
-        tlCache = [], tlIdCache = [], currentTerm = null;
+        tlCache = [], tlIdCache = [], currentTerm = null,
+        startSlide = startYear, endSlide = endYear, userStartSlide = startSlide, userEndSlide = endSlide;
+    
+    function boundValue(val) {
+        return Math.max(Math.floor(wStart), Math.min(Math.floor(wEnd), val));
+    }
     
     function updateCoords() {
         cWidth = $tlcontainer.width();
@@ -21,22 +30,36 @@
         wSpan = baseSpan / zoomLevel;
         wStart = wCenter - wSpan / 2;
         wEnd = wCenter + wSpan / 2;
-        wScale = cWidth / wSpan;
+        cScale = cWidth / wSpan;
+        sScale = sliderWidth / wSpan;
+        mScale = cWidth / baseSpan;
         wLineDist = null;
         for (var i = 0; i < lineDistances.length; i++) {
-            if (lineDistances[i] * wScale < 50) {
+            if (lineDistances[i] * cScale < 50) {
                 break;
             }
             wLineDist = lineDistances[i];
         }
+        startSlide = boundValue(userStartSlide);
+        endSlide = boundValue(userEndSlide);
+        $slider.slider("values",[yrToSliderPos(startSlide),yrToSliderPos(endSlide)]);
+        updateSpans();
     }
     
-    function yrToX(yr) {
-        return wScale * (yr - wStart);
+    function yrToSliderPos(yr) {
+        return sScale * (yr - wStart);
+    }
+    function sliderPosToYr(x) {
+        return wStart + x / sScale;
     }
-    
+    function yrToX(yr) {
+        return cScale * (yr - wStart);
+    }
     function xToYr(x) {
-        return wStart + x / wScale;
+        return wStart + x / cScale;
+    }
+    function yrToMiniX(yr) {
+        return (yr - startYear)*mScale;
     }
     
     var itemTpl = _.template(
@@ -51,15 +74,53 @@
         
         var ctx = canvas.getContext("2d");
         
+        var xstart = yrToMiniX(wStart),
+            xend = yrToMiniX(wEnd);
+        ctx.fillStyle = "#f0f2ff";
+        ctx.strokeStyle = "#3090ff";
+        ctx.beginPath();
+        ctx.moveTo(xstart, 0);
+        ctx.lineTo(xend, 0);
+        ctx.lineTo(xend, 35);
+        ctx.lineTo(cWidth, 70);
+        ctx.lineTo(cWidth, cHeight);
+        ctx.lineTo(0, cHeight);
+        ctx.lineTo(0, 70);
+        ctx.lineTo(xstart, 35);
+        ctx.closePath();
+        ctx.stroke();
+        ctx.fill();
+        ctx.fillStyle = "#666666";
+        ctx.strokeStyle = "#666666";
+        ctx.fillStyle = "rgba(220, 220, 220, .5)";
+        ctx.fillRect( 0, 0, cWidth, 30 );
+        ctx.font = 'bold 12px Arial,Helvetica';
+        ctx.fillStyle = "#333333";
+        for (var y = miniLineDist * Math.ceil(startYear/miniLineDist); y <= endYear; y += miniLineDist ) {
+            var x = yrToMiniX(y);
+            ctx.beginPath();
+            ctx.moveTo(x,0);
+            ctx.lineTo(x,6);
+            ctx.moveTo(x,24);
+            ctx.lineTo(x,30);
+            ctx.stroke();
+            ctx.textAlign = ((y - startYear) < miniLineDist ? "left" : ((endYear - y) < miniLineDist ? "right" : "center") );
+            ctx.fillText(y || 1, x,20);
+        }
+        ctx.textAlign = 'center';
+        ctx.font = 'bold 14px Arial,Helvetica';
+        ctx.fillStyle = "#000000";
         for (var y = wLineDist * Math.ceil(wStart/wLineDist); y <= wEnd; y += wLineDist ) {
-            var x = yrToX(y+1/2);
+            var x = yrToX(y+1/2),
+                isPrimary = !((y/wLineDist) % 2);
+            ctx.strokeStyle = (isPrimary ? "#000000": "#999999");
             ctx.beginPath();
-            ctx.moveTo(x, 20);
+            ctx.moveTo(x, 60);
             ctx.lineTo(x, cHeight - 20);
             ctx.stroke();
-            ctx.textAlign = 'center';
-            ctx.fillText(y || 1, x, 12);
-            ctx.fillText(y || 1, x, cHeight - 8);
+            if (isPrimary) {
+                ctx.fillText(y || 1, x, cHeight - 4);
+            }
         }
         var html = _(tlCache).chain()
             .filter(function(item) {
@@ -84,8 +145,8 @@
             currentTerm = $this.attr("data-dbpedia-uri")
             $(".timeline-item-box").removeClass("timeline-current");
             $this.addClass("timeline-current");
-            loadContentsByUri(currentTerm);
-        })
+            loadSearchResults({ dbpedia_uri: currentTerm });
+        });
     }
     
     function getData() {
@@ -117,20 +178,27 @@
     
     var debouncedGetData = _.debounce(getData, 1000);
         
-    var mousedown = false, dragging = false, xstart = null, wcStart;
-    
-    throttledRedraw();
+    var mousedown = false, dragging = false, xstart = null, wcStart, dragmini;
     
     $tlcontainer.mousedown(function(e) {
         dragging = false;
         mousedown = true;
+        dragmini = (e.clientY - $tlcontainer.offset().top < 40);
         xstart = e.clientX;
         wcStart = wCenter;
         return false;
     }).mousemove(function(e) {
         if (mousedown) {
             dragging = true;
-            wCenter = Math.max(startYear, Math.min(endYear, wcStart + (xstart - e.clientX) / wScale ));
+            wCenter = Math.max(
+                startYear + wSpan / 2,
+                Math.min(
+                    endYear - wSpan / 2,
+                    dragmini ?
+                        (wcStart + (e.clientX - xstart) / mScale) :
+                        (wcStart + (xstart - e.clientX) / cScale)
+                    )
+                );
             throttledRedraw();
             debouncedGetData();
             return false;
@@ -145,8 +213,8 @@
             return;
         }
         zoomLevel *= scaleRatio;
-        console.log(zoomLevel);
-        wCenter = Math.max(startYear, Math.min(endYear, yrMouse * (1 - 1 / scaleRatio) + wCenter / scaleRatio ));
+        var newSpan = baseSpan / ( 2 * zoomLevel );
+        wCenter = Math.max(startYear + newSpan, Math.min(endYear - newSpan, yrMouse * (1 - 1 / scaleRatio) + wCenter / scaleRatio ));
         throttledRedraw();
         debouncedGetData();
     });
@@ -156,6 +224,34 @@
         dragging = false;
     });
     
+    var $startSpan = $(".timeline-mill-from-year"),
+        $endSpan = $(".timeline-mill-to-year");
+    
+    function updateSpans() {
+        $startSpan.text(startSlide);
+        $endSpan.text(endSlide);
+    }
+    
+    //updateSpans();
+    
+    $slider.slider({
+        range: true,
+        min: 0,
+        max: sliderWidth,
+        values: [0, sliderWidth],
+        slide: function(e, ui) {
+            userStartSlide = startSlide = Math.floor(sliderPosToYr(ui.values[0]));
+            userEndSlide = endSlide = Math.floor(sliderPosToYr(ui.values[1]));
+            updateSpans();
+        }
+    });
+    
+    $(".timeline-mill-submit").click(function() {
+        loadSearchResults({ from_year: startSlide, to_year: endSlide, show_years: 1 });
+    });
+    
+    throttledRedraw();
+    
     getData();
     
     $(window).resize(throttledRedraw);
--- a/src/jocondelab/templates/jocondelab/front_base.html	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_base.html	Thu Sep 19 17:57:10 2013 +0200
@@ -42,7 +42,7 @@
             <header>
                 {% block header %}
                     <div class="main-title">
-                        <h1><a href="{% url 'front_search' %}">Joconde<span class="title-lab">Lab</span></a></h1>
+                        <h1><a href="{% url 'front_home' %}">Joconde<span class="title-lab">Lab</span></a></h1>
                         <h2 class="breadcrumbs">{% block breadcrumbs %}{% endblock %}</h2>
                     </div>
                     {% block header_search %}
--- a/src/jocondelab/templates/jocondelab/front_home.html	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_home.html	Thu Sep 19 17:57:10 2013 +0200
@@ -11,6 +11,7 @@
 {% block breadcrumbs %}<a href="{% url 'front_home' %}">{% trans 'Accueil' %}</a>{% endblock %}
 
 {% block main %}
+        <div class="hide-on-search">
             <h2 class="about-jl">{% trans 'Bienvenue sur JocondeLab' %}</h2>
             
             <ul class="module-list">
@@ -27,7 +28,6 @@
                     <h3><a href="{% url 'front_search' %}">{% trans 'À propos' %}</a></h3>
                 </li>
             </ul>
+        </div>
 {{block.super}}
 {% endblock %}
-
-{% block term_cloud %}{% endblock %}
--- a/src/jocondelab/templates/jocondelab/front_search.html	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_search.html	Thu Sep 19 17:57:10 2013 +0200
@@ -5,7 +5,8 @@
     {{block.super}}
     <script type="text/javascript" src="{{STATIC_URL}}jocondelab/js/front-search.js"></script>
     <script type="text/javascript">
-        var searchterm = "{{searchterm|safe}}";
+        var searchterm = "{{searchterm|safe}}",
+            ajaxsearch = true;
     </script>
 {% endblock %}
 
@@ -21,7 +22,7 @@
                 <input class="big-search-input search-input" type="search" name="q" value="{{searchterm}}" />
             </form>
             
-            {% include 'jocondelab/partial/wrapped_notice_list.html' %}
+            <div class="results">{% include 'jocondelab/partial/wrapped_notice_list.html' %}</div>
             
             <div class="loading-please-wait"><img src="{{STATIC_URL}}jocondelab/img/loader.gif" /></div>
             <p class="load-more"><a href="#">{% trans 'Afficher plus de résultats' %}</a></p>
--- a/src/jocondelab/templates/jocondelab/front_timeline.html	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_timeline.html	Thu Sep 19 17:57:10 2013 +0200
@@ -25,10 +25,19 @@
 
 {% block main %}
             
+            <h2 class="timeline-section-title">{% trans 'Rechercher par période nommée' %}</h2>
             <div class="timeline-container">
                 <canvas class="timeline-canvas"></canvas>
                 <ul class="timeline-list"></ul>
             </div>
+            <h2 class="timeline-section-title">{% trans 'Rechercher par millésime' %}</h2>
+            <div class="timeline-mill-slider"></div>
+            <button class="timeline-mill-submit">
+                {% trans 'Afficher les contenus de ' %}
+                <span class="timeline-mill-from-year">0</span>
+                {% trans ' à ' %}
+                <span class="timeline-mill-to-year">0</span>
+            </button>
             
             <div class="results"></div>
             
--- a/src/jocondelab/templates/jocondelab/partial/notice_list.html	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/partial/notice_list.html	Thu Sep 19 17:57:10 2013 +0200
@@ -26,6 +26,12 @@
                          </ul>
                      </li>
                 {% endfor %}
+                {% if notice.years %}
+                    <li class="notice-thesaurus">
+                        <h3 class="notice-thesaurus-title">{% trans 'MILL' context 'Thesaurus label' %}{% trans ':' %}</h3>
+                        <p class="notice-terms-list"><span class="notice-year">{{notice.years.0}}</span>{% if notice.years.0 != notice.years.1 %} – <span class="notice-year">{{notice.years.1}}</span>{% endif %}</p>
+                    </li>
+                {% endif %}
                 </ul>
             </div>
         </div>
--- a/src/jocondelab/views/front_office.py	Wed Sep 18 18:31:29 2013 +0200
+++ b/src/jocondelab/views/front_office.py	Thu Sep 19 17:57:10 2013 +0200
@@ -25,9 +25,11 @@
         is_ajax = request.is_ajax()
         queryterms = [s.strip(" ") for s in querystr.split(";") if s.strip(" ")]
         dbpedia_uri = request.GET.get('dbpedia_uri', None)
-        emptysearch = (not queryterms and not dbpedia_uri)
-        npp = request.GET.get('count', 12 if emptysearch else 36)
-        wpp = 50
+        from_year = request.GET.get('from_year', None)
+        to_year = request.GET.get('to_year', from_year)
+        show_years = request.GET.get('show_years',False)
+        emptysearch = ((not queryterms) and (not dbpedia_uri) and (not from_year))
+        npp = request.GET.get('count', 12 if emptysearch else 30)
         context["current_page"] = page
         
         if self.template_name is None:
@@ -56,6 +58,9 @@
                 for term in queryterms:
                     fs = DbpediaFields.objects.filter(label=term, language_code=lang).values('term_id').distinct()
                     qs = qs.filter(noticeterm__term__in=fs)
+            if from_year:
+                context["searchterm"] = u"%s – %s"%(from_year, to_year)
+                qs = qs.filter(years__start_year__lte=to_year, years__end_year__gte=from_year)
             qs = qs.distinct()
             paginator = Paginator(qs, npp)
             context["count"] = paginator.count
@@ -80,10 +85,12 @@
                 "imagetitle": n.titr if n.titr else n.deno,
                 "title": n.titr,
                 "denomination": n.deno,
-                "image": (settings.JOCONDE_IMG_BASE_URL + n.images.all()[0].url) if n.images.count() else "",
+                "image": (settings.JOCONDE_IMG_BASE_URL + n.images.all()[0].url) if n.images.exists() else "",
                 "author": n.autr,
-                "terms_by_thesaurus": termsbythesaurus
+                "terms_by_thesaurus": termsbythesaurus,
             }
+            if show_years and n.years.exists():
+                noticedict["years"] = [n.years.all()[0].start_year, n.years.all()[0].end_year]
             notices.append(noticedict)
         context["notices"] = notices