Commits

magicrebirth committed beacceb Draft

added support for number-based facets

  • Participants
  • Parent commits acd12d9

Comments (0)

Files changed (12)

File demoproject/apps/djfacet/docs/_build/html/index.html

 <p>Django Faceted Browser is a <a class="reference external" href="https://www.djangoproject.com/">Django</a> application that allows you to navigate your data using an approach based on &#8216;dynamic taxonomies&#8217;, also known as &#8216;facets&#8217;.</p>
 <p>The application relies heavily on <a class="reference external" href="https://docs.djangoproject.com/en/1.3/topics/db/queries/">Django&#8217;s database API</a>, so it requires some working knowledge of Django and especially of the <a class="reference external" href="https://docs.djangoproject.com/en/1.3/topics/db/models/">model layer</a>. In short, if you have completed the online Django <a class="reference external" href="https://docs.djangoproject.com/en/1.3/intro/tutorial01/">tutorial</a> or have already developed web apps using Django, you&#8217;re likely to get the most out of DJFacet too.</p>
 <div class="section" id="at-a-glance">
+	<div class="admonition warning">
+	<p class="first admonition-title">Warning</p>
+	<p class="last">This documentation is now partially outdated! Check out the new <a href="https://bitbucket.org/magicrebirth/djfacet">repository on BitBucket</a> for more info. The new version is going to be released soon...</p>
+	</div>
 <h2>At a glance<a class="headerlink" href="#at-a-glance" title="Permalink to this headline">¶</a></h2>
 <p>DJFacet is an easy-to-use faceted search system built on top of the Django python framework. Its main features are:</p>
 <ul class="simple">

File demoproject/apps/djfacet/docs/configuration.rst

 Configuration
 ************************
 
-Once you've installed DJFacet, the final step before having a fully-working faceted browser is (obviously) the definition of the facets that can be used for a search. 
+Once you've installed DJFacet, the final step before having a fully-working faceted browser is  the definition of the facets that can be used for searching an information space. 
 
 This involves three steps: 
 
-1. Defining an *information space*
-2. Defining the *facet-groups*
+1. Defining an *information space*.
+2. Defining the *facet-groups*.
 3. Defining the *facets* appearance and behaviour, with respect to the information space.
 
 The configuration file is a standard python module, whose location is defined by the :ref:`DJF_SPECS_MODULE` setting (by default, DJFacet will look for a file called ``facetspecs.py`` at the root of your django project).  

File demoproject/apps/djfacet/docs/installation.rst

 Dependencies
 =============
 
-- Django 1.1 or above (`download <https://www.djangoproject.com/download/>`_). DJFacet 0.9 is compatible with Django 1.1+; future versions will be tested only on Django 1.3 only.
+- Django 1.3 or above (`download <https://www.djangoproject.com/download/>`_). Older versions of Django haven't been tested, although they may work.
 - Django picklefield (`download <http://pypi.python.org/pypi/django-picklefield>`_).
 - **Optional**: Django MPTT (`download <http://code.google.com/p/django-mptt/>`_).
 
 	$ cp -r djfacet /path/to/my/project/
 
 
-Step 2: Set up the templates directory
-++++++++++++++++++++++++++++++++++++++++
 
-DJFacets includes some default templates, which need to be made accessible by putting them where Django can find them. To this purpose, you must copy the folder named ``djfacet`` inside your project's ``templates`` directory  (usually defined via Django's ``TEMPLATE_DIRS`` setting):: 
-
-	$ cd /path/to/my/project/djfacet
-	$ cp -r templates/djfacet /path/to/my/project-templates/
-
-Alternatively, you can just create a symlink::
-
-	$ ln -s templates/djfacet /path/to/my/project-templates/
-
-
-Step 3: Set up the media directory
-++++++++++++++++++++++++++++++++++++
-
-Do the same operation as above with DJFacet's built-in static files (css, js). Copy the folder named ``djfacet``, located inside the ``static`` of DJFacet distribution, to your project's media folder (usually defined via Django's ``MEDIA_ROOT`` setting), or create a symlink::
-
-	$ cd /path/to/my/project/djfacet
-	$ cp -r static/djfacet /path/to/my/project-media-files/
-
-Alternatively, create a symlink::
-
-	$ ln -s static/djfacet /path/to/my/project-media-files/
-	
-
-
-Step 4: Add the application to your project
+Step 2: Add the application to your project
 ++++++++++++++++++++++++++++++++++++++++++++
 
 Add the DJFacet app to your project's ``settings.py``::
 
 
 	INSTALLED_APPS += (	
-		'mptt',             # optional, if installed
+		'mptt',             # optional, for hierarchical facets
 		'picklefield',      # REQUIRED
 		'djfacet',          # REQUIRED
 	)
 	Creating table djfacet_cachedfacetvalue
 	Creating table djfacet_cachedfacetedmanager
 
- 
-Step 5: Wire up the application urls
-++++++++++++++++++++++++++++++++++++++++++++
 
-Add the DJFacet app to your urls definitions in ``urls.py``::
+
+Fianlly, add the DJFacet app to your urls definitions in ``urls.py``::
 
 	urlpatterns += patterns('',
-		(r'^browser/', 'djfacet.urls'), # change "browser" to whatever you like!
+		(r'^browser/', 'djfacet.urls'), # change "browser" to whatever suits you
 	)
 
 
 
-Once that works, congratulations! You’ve successfully installed DJFacet. Now it's time to proceed to its :ref:`configuration`.
+(Optional) Step 3: Override the templates directory
+++++++++++++++++++++++++++++++++++++++++
 
+DJFacets includes several templates, which are located in the /templates folder of the djfacet app. It is likely that you will want to override some (or all) of them. This is easily achieved by copying them into a folder named ``djfacet`` inside your project's ``templates`` directory  (usually defined via Django's ``TEMPLATE_DIRS`` setting).:: 
+
+	$ cd /path/to/my/project/djfacet
+	$ cp -r templates/djfacet /path/to/my/project-templates/
+
+Valid templates files found in this location will always take precedence over the ones that come with the DJFacet app. If you copy only some of the templates, make sure you preserve the folder structure.
+
+
+
+
+(Optional) Step 4: Override the media directory
+++++++++++++++++++++++++++++++++++++
+
+Do the same operation as above with DJFacet's built-in static files (css, js). Copy the folder named ``djfacet``, located inside the ``static`` of DJFacet distribution, to your project's media folder (usually defined via Django's ``MEDIA_ROOT`` setting)::
+
+	$ cd /path/to/my/project/djfacet
+	$ cp -r static/djfacet /path/to/my/project-media-files/
+
+Valid static files files found in this location will always take precedence over the ones that come with the DJFacet app.
+
+
+
+
+Once that works, congratulations! You’ve successfully installed DJFacet. If you run the django server and try to go to 127.0.0.1:8000/browse, you'll see an error in the console. This is ok, because a key component is still missing: the :ref:`configuration` file for the search facets. 
+

File demoproject/apps/djfacet/docs/settings.rst

 
 It determines what file name the configuration file has, and where it is located. 
 
-By default, DJFacet expects to find the configuration file (``djfacetspecs.py``) at the root of your django project, that is, where your ``setting.py`` file is. In general, you shouldn't need to change this setting.	
+By default, DJFacet expects to find the configuration file (``facetspecs.py``) at the root of your django project, that is, where your ``setting.py`` file is. In general, you shouldn't need to change this setting.	
 
 
 .. _DJF_SHOWLOGS:
 
 Defaults to ``False``. 
 
-It determines whether the logs DJFacet produces should be printed out in the shell. To be used only in development mode.
+It determines whether the logs DJFacet produces should be printed out in the shell. This is useful in development mode, to see what's going on when facets are created and populated.
 
 
 .. _DJFACET_COUNT:

File demoproject/apps/djfacet/facet.py

 						
 		hierarchy = True of False, refers to whether we're using an mptt model 
 		TODO FIX THE NAMING!!!
+		
+		2012-08-08:
+		added rangeinterval=self.rangeinterval
+		
 		=+=
 	"""
-	def __init__(self, uniquename, name, originalModel, originalAttribute, displayAttribute = None, ordering = None, behaviour = None, hierarchyoptions= None, 
-						mask=None, customvalues = None, mptt = False, explanation = "", exclude=False, group=[]):
+	def __init__(self, uniquename, name, originalModel, originalAttribute, displayAttribute = None, ordering = None, behaviour = None, hierarchyoptions= None, rangeinterval = None, mask=None, customvalues = None, mptt = False, explanation = "", exclude=False, group=[]):
 		self.uniquename = uniquename
 		self.name = name
 		self.originalModel = originalModel
 		self.ordering = ordering
 		self.behaviour = behaviour	
 		self.hierarchyoptions = hierarchyoptions		
+		self.rangeinterval = rangeinterval		
 		self.mask = mask	
 		self.customvalues = customvalues	
 		self.facetvalues = []
 		if self.customvalues: 
 			for x in self.customvalues:
 				# rem that in this case the name doesn't match the DB! it's really a label..
-				v =	 FacetValue(name=x, facet=self, customvalues=self.customvalues[x])
+				v =	 FacetValue(name=x, facet=self, customvalues=self.customvalues[x], rangeinterval=self.rangeinterval)
 				self.add_facetvalue(v)
 		
 		# **case2:		
 				# remove duplicates, and make everything uppercase
 				lookup_choices = list(set(val[0].upper() for val in values_list if val))
 				for x in lookup_choices:
-					v =	 FacetValue(name=x, facet=self, hierarchytype='alpha', mask=self.mask)	# father=0 implied
+					v =	 FacetValue(name=x, facet=self, hierarchytype='alpha', rangeinterval=self.rangeinterval, mask=self.mask)	# father=0 implied
 					self.add_facetvalue(v)
 					# TODO: instantiates also the subfacets of 'A', 'B', etc...
 					#  for the mom we can do without this
 				rangelist = buildranges(themin, themax, therange) #[('990-1020', (990, 1020))]
 				for x in rangelist:
 					v =	 FacetValue(name=x[0], facet=self, hierarchytype='range', hierarchyextra=x[1], 
-										mask=self.mask)
+										rangeinterval=self.rangeinterval, mask=self.mask)
 					self.add_facetvalue(v)
 		
 		# **case3:
 			values_list = sorted(list(set(values_list)))
 			for x in values_list:
 				if not self.get_facetvalue_from_displayname(x[1]):	 #extra check for non-deterministic displayAttribute
-					v =	 FacetValue(name=x[0], displayname=x[1], facet=self, mask=self.mask) # father=0 implied
+					v =	 FacetValue(name=x[0], displayname=x[1], facet=self, rangeinterval=self.rangeinterval, mask=self.mask) # father=0 implied
 					self.add_facetvalue(v)
 		
 		# **case5:
 			values_list = self.originalModel.objects.exclude(id__in=[x.id for x in self.exclude]).values(self.originalAttribute).annotate(howmany=Count(self.originalAttribute))
 			# values_list = sorted(list(set(values_list)))
 			for x in values_list:
-				v =	 FacetValue(name=x[self.originalAttribute], facet=self, mask=self.mask, howmany=x['howmany']) # father=0 implied
+				v =	 FacetValue(name=x[self.originalAttribute], facet=self, rangeinterval=self.rangeinterval,  mask=self.mask, howmany=x['howmany']) # father=0 implied
 				self.add_facetvalue(v)
 
 	

File demoproject/apps/djfacet/facetsgroup.py

 				dbfield = appearance_specs.get('dbfield', None)
 				displayfield = appearance_specs.get('displayfield', None)
 				hierarchyoptions = appearance_specs.get('hierarchy', None) # ... or the hierarchy opts
+				rangeinterval = appearance_specs.get('range_interval', None) 				
 				ordering = appearance_specs.get('ordering', None) 				
 				exclude = appearance_specs.get('exclude', False)
 				
 				# Facet(name, originalModel, originalAttribute, displayAttribute = None, behaviour = None, hierarchyoptions= None,  mask=None, customvalues = None, mptt = False, exclude=False, group=[])
 				djfacetlog("..adding facet: %s" % uniquename, True)
 				x = Facet(uniquename, appearance_specs['label'], model, 
-								dbfield, displayfield, ordering, behaviour, hierarchyoptions, mask, customvalues, mptt, explanation, group=group)
+								dbfield, displayfield, ordering, behaviour, hierarchyoptions, rangeinterval, mask, customvalues, mptt, explanation, group=group)
 							
 				self.facets.append(x)
 				# also instantiates all the values!

File demoproject/apps/djfacet/facetvalue.py

 		father = contains the ID of the father (0 represents the top level).
 		hierarchytype = a string, 'alpha' or 'range' for now
 		hierarchyextra = other info required for the hierarchy (not standardized at the moment)
+		rangeinterval = if it's a numeric value, it's the range used for display purposes in single_facet view		
 		customvalues = a dict used to compose the behaviour-query for a facetvalue 
 						eg: {'hammondnumber' : 1, 'hammondnumb2__gt' : 0, 'hammondnumb2__lt' :10}
 						
 		=+=
 	
 	"""
-	def __init__(self, name, facet, father=0, hierarchytype="", hierarchyextra=None, displayname="", 
+	def __init__(self, name, facet, father=0, hierarchytype="", hierarchyextra=None, rangeinterval=None, displayname="", 
 					mask=None, customvalues=None, howmany=0, rootfather=None, mpttoriginalID = None, mptt_treeID = None ):
 		self.name = name
 		self.facet = facet
 		self.hierarchytype = hierarchytype	# a string
 		self.hierarchyextra = hierarchyextra  
+		self.rangeinterval = rangeinterval  
 		self.displayname = displayname or name
 		self.mask = mask
 		self.customvalues = customvalues

File demoproject/apps/djfacet/fb_utils/utils.py

 from time import strftime
 from djfacet.constants import *
 from django.http import QueryDict
+import locale
+locale.setlocale(locale.LC_ALL, '')	 # UK
+
 try:
 	from settings import DEBUG
 except:
 		djfacetlog("\n DONE VISUALIZING THE TREE \n")			
 	return stringa 
 	
-	
+
+
+def num(s):
+	"""
+	Returns a number from a string
+	"""
+	try:
+		return int(s)
+	except:
+		return float(s) 
 	
 	
 
 
 def findmin(rangen, n):
+	""" Given an interval-range-value (eg 30) and a number (eg 80) return the minimum range value
+		available for that number (=60). Used to decide where to START a range sequence
+	 """
 	e = 0
 	while e < n:
 		e += rangen
 	return e - rangen
 
 def findmax(rangen, n):
+	""" Given an interval-range-value (eg 30) and a number (eg 80) return the maximum range value
+		available for that number (=60). Used to decide where to END a range sequence
+	 """
 	e = 0
 	while e < n:
 		e += rangen
 		# returns the one after
 	return e
 
+
+def format_float_nice(f):
+	"""
+	abstraction for nice formatting of floats:
+	http://stackoverflow.com/questions/2440692/formatting-floats-in-python-without-superfluous-zeros
+	"""
+	return (locale.format("%f", f, grouping=True)).rstrip('0').rstrip('.')
+
+
+def buildranges_withFormat(amin, amax, rangeval):
+	"""Retunrs a list of tuples, with the string-range first and then
+	 the numbers in a tuple: 
+	[('990-1020', (990, 1020)), ('1020-1050', (1020, 1050))]  
+	
+	This is an enhanced version of the code below. 
+	It includes numbers formatting (good for big numbers) and support for int/float too
+	"""
+	r = []
+	allvalues = range(amin, amax, rangeval)
+	for index, item in enumerate(allvalues):
+		if (index + 1) < len(allvalues):
+			if type(rangeval) == type(1):  # int: will cut decimal values
+				a = "%s-%s" % (locale.format("%d", item, grouping=True),  locale.format("%d", allvalues[index + 1], grouping=True))
+			if type(rangeval) == type(1.5):	 #float: will allow 5 decimal digit (todo: less?)
+				a = "%s-%s" % (format_float_nice(item), format_float_nice(allvalues[index + 1]))				
+			
+			r.append((a, (item, allvalues[index + 1])))
+	return r
+
+
+
+
 def buildranges(amin, amax, rangeval):
 	"""Retunrs a list of tuples, with the string-range first and then
-	 the numbers in a tuple: [('990-1020', (990, 1020)), ('1020-1050', (1020, 1050))]  """
+	 the numbers in a tuple: 
+	[('990-1020', (990, 1020)), ('1020-1050', (1020, 1050))]  
+	"""
 	r = []
 	allvalues = range(amin, amax, rangeval)
 	for index, item in enumerate(allvalues):
 	return [x for x in list1+list2 if x not in list1 or x not in list2]
 
 
+def is_number(s):
+	try:
+		float(s) # for int, long and float
+		return True
+	except ValueError:
+		return False
 
 
 
 
-

File demoproject/apps/djfacet/queryhelper.py

 		
 		"""
 		resulttypeName, facet, activeIDs, queryargs, LIMIT = self.resulttypeName, self.facet, self.activeIDs, self.queryargs, self.limit
+		if not queryargs:  #prevent error when caching from console
+			queryargs = []
 		resulttypeModel = self.resulttypeModel
 		dbfield = facet.originalAttribute
 		tot_allvalues = False # used to store info on the tot number of fvalues, irrespectively of the limit
 			# IF normal value - or MPTT but NOT DJF_MPTT_INHERITANCE
 			
 			else:
-				
 				fv = facet.get_facetvalue_from_name(value[dbfield])
 				if fv and value['count'] and (fv.id not in [x.id for x in queryargs]):
 					counter += 1

File demoproject/apps/djfacet/templatetags/djf_tags.py

-	#	2010-11-17:	  NOT USED ANYMORE !!!!!!!	  
-	# 
-	# to delete in the future.....
-	
-	# OR USED??? 2012-01-10
-	 
-
+# templatetags used in djfacet 
 
 from django import template
-from djfacet.fb_utils.utils import djfacetlog, split_list_into_two
+from djfacet.fb_utils.utils import *
 
 register = template.Library()
 
 
 
-
-
-
 @register.filter
 def extract_father(fvalue):
 	""" splits a list into two parts"""
 
 
 
+
 @register.filter
 def fvlist_formatter2(facetvalues, case_sensitive=False):
 	""" 
 	2012-05-15
+
+	Takes a list of facetvalues and returns a nested list usable in singlefacet.html. 
+		{{ with somelist|fvlist_formatter1 as xx }}, or {{ with somelist|fvlist_formatter1:"True" as xx }}
+
+	The nested list is organized by initials and it also splits the facetvalues list into two groups, as required by the template. Eg.
+
+	First we construct a dict
+		{ "A" : [(fv1, fv2, fv3 etc), (fv4, fv5, fv6 etc)], "B" : [(fv1, fv2, fv3 etc), (fv4, fv5, fv6 etc)], etc.. }
+	We transform it into an ordered tuple containing nested lists:
+		[ ("A" : [(fv1, fv2, fv3 etc), (fv4, fv5, fv6 etc)]), ("B" : [(fv1, fv2, fv3 etc), (fv4, fv5, fv6 etc)]), etc.. }
+
+	This is a better version than the one below, as it maintains ordering.. 
+	(it may take longer though!!! TODO:check)
+	
+	2012-08-08:
+	added support for numbers
+	
+	If the interval is not provided, and interval is created by taking the min and max values and 
+	dividing that space in 20 parts. The interval is the 1/20th of that space.
+	
+	"""
+	mydict = {}
+	NUMBERS_FLAG = False
+	
+	def match_range(ranges, num):
+		"""
+		Matches a num against a range-list as outputted by  buildranges() eg
+		[('990-1020', (990, 1020)), ('1020-1050', (1020, 1050))] 		
+		"""
+		for rang in ranges:
+			nums_tuple = rang[1]
+			if num >= nums_tuple[0] and num < nums_tuple[1]:
+				return rang[0]
+		return None
+	
+	
+	if facetvalues[0] and is_number(facetvalues[0].displayname):
+		NUMBERS_FLAG = True
+		
+		all_numbers = [x.displayname for x in facetvalues]
+		# get the interval from the specs, or create a default one based on current data-range
+		interval = facetvalues[0].rangeinterval
+		if not interval:
+			interval = (max(all_numbers) - min(all_numbers)) / 20
+		maxN = findmax(interval, max(all_numbers))	
+		minN = findmin(interval, min(all_numbers))	
+
+		# create ranges set
+		ranges_opts = buildranges_withFormat(minN, maxN, interval)
+		for val in facetvalues:
+			range_key = match_range(ranges_opts, val.displayname)
+			if range_key:
+				if range_key not in mydict.keys():
+					mydict[range_key] = [val]
+				else:
+					mydict[range_key] += [val]					
+
+	else:		
+		for val in facetvalues:
+			if case_sensitive:
+				if val.displayname[0] not in mydict.keys():
+					mydict[val.displayname[0]] = [val]
+				else:
+					mydict[val.displayname[0]] += [val]
+			else:
+				if val.displayname[0].upper() not in mydict.keys():
+					mydict[val.displayname[0].upper()]= [val]
+				else:
+					mydict[val.displayname[0].upper()] += [val]
+	
+					
+	# now split the lists, to accommodate the 2-columns display
+	for el in mydict.keys():
+		mydict[el] = split_list_into_two(mydict[el])
+	# sort
+	if NUMBERS_FLAG:
+		keys = mydict.keys()
+		keys.sort(key=lambda x: num(x.split("-")[0].replace(",", "")))
+	else:
+		keys = mydict.keys()
+		keys.sort()
+	return [(key, mydict[key]) for key in keys]
+	# return mydict
+	
+	
+	
+	
+	
+
+@register.filter
+def __fvlist_formatter2(facetvalues, case_sensitive=False):
+	""" 
+	2012-05-15
 	
 	Takes a list of facetvalues and returns a nested list usable in singlefacet.html. 
 		{{ with somelist|fvlist_formatter1 as xx }}, or {{ with somelist|fvlist_formatter1:"True" as xx }}
 					mydict[val.displayname[0].upper()]= [val]
 				else:
 					mydict[val.displayname[0].upper()] += [val]
-	# now split the lists
+	# now split the lists, to accommodate the 2-columns display
 	for el in mydict.keys():
 		mydict[el] = split_list_into_two(mydict[el])
 	# sort
 	# return mydict
 	
 	
+	
+	
+
 
 
 # 2012-05-16: not used anymore cause it messes up the ordering

File demoproject/facetspecs.py

 
 facetslist +=	[ 
 
+
+# @@@	
+# 	PLACE FACETS
+# @@@
+
+
 			{	'active' : True, 
-				'explanation': "no explanation yet", 
+				'explanation': "Standard name of Geographical Regions", 
 				'uniquename' : 'regionname',
 				'model' : Region ,
 				'appearance' : {'label' : 'Region name' , 
 
 
 			{	'active' : True, 
-				'explanation': "no explanation yet",
+				'explanation': "Alternative (IDB) Name for Geographical Regions",
 				'uniquename' : 'regionidbname',
 				'model' : Region ,
 				'appearance' : {'label' : 'Region idbname' , 
 						 },
 
 			{	'active' : True, 
-				'explanation': "no explanation yet",
+				'explanation': "Standard Country Names",
 				'uniquename' : 'countryname',
 				'model' : Country ,
 				'appearance' : {'label' : 'Country name' , 
 								#  'explanation' : "showing all...." },
 								]
 						 },
-						
+	
+	
+			{	'active' : True, 
+				'explanation': "Population in 2008",
+				'uniquename' : 'pop2008',
+				'model' : Country ,
+				'appearance' : {'label' : 'Population (2008)' , 
+								'dbfield' : "pop2008", 
+								'displayfield' : "pop2008", 
+								'grouping'	: ['countrygroup'],
+								'ordering' : 'pop2008',
+								'range_interval' : 10000000,
+								} ,
+				'behaviour' :  [{'resulttype' : 'religions',
+								 'querypath' : 'country__pop2008', 
+								 'inversepath' : None,
+								 'explanation' : "showing all...." },
+								{'resulttype' : 'country',
+								 'querypath' : 'pop2008', 
+								 'inversepath' : None,
+								 'explanation' : "showing all...." },
+								]
+						 },
+	
+	
+	
+# @@@	
+# 	RELIGION FACETS
+# @@@
 						
 				# THIS IS AN MPTT/HIERARCHICAL FACET		
 
 				{	'mptt' : True,
-					'explanation': "no explanation yet", 
+					'explanation': "Hierarchical classification of religions", 
 					'uniquename' : 'religionname',
 					'model' : Religion ,
 					'appearance' : {'label' : 'Religion name' , 
 
 
 
-							# NOW LET'S DEFINE A CUSTOM FACET (NOT WORKING YET SO I DISABLED IT) :
+				# NOW LET'S DEFINE A CUSTOM FACET (NOT WORKING YET SO It's DISABLED) :
 
 				{	'active' : False,
 					'explanation': "no explanation yet", 
 =============
 ===============
 
+
+2012-08-08
+-----------------------------------------
+- added support for number-based facets
+	- in specs: 'range_interval' : 10000000,
+- fixed small bug in queryhelper, when caching from console
+- modified the docs for intro a little...
+
+
+
 2012-06-15
 -----------------------------------------
 - fixed bug with non unicode values in cache_manager.py