Skip to content

Commit

Permalink
Allow charting of extra() and annotate() fields. Fixes #8. Fixes #12
Browse files Browse the repository at this point in the history
this is a slight refactoring of PR#15 to allow extra and annotated
fields be used not only in categories but other places as well.
  • Loading branch information
atodorov committed Aug 9, 2016
1 parent 5e96304 commit a086261
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 10 deletions.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Changelog
`#5 <https://github.com/chartit/django-chartit/issues/5>`_
* Demo project updated with Chart and PivotChart examples of
rendering DateField values on the X axis
* Allow charting of extra() or annotate() fields. Fixes
`#8 <https://github.com/chartit/django-chartit/issues/8>`_ and
`#12 <https://github.com/chartit/django-chartit/issues/12>`_

* 0.2.5 (August 3, 2016)
* Workaround Python 3 vs. Python 2 list sort issue which breaks
Expand Down
23 changes: 14 additions & 9 deletions chartit/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def get_all_field_names(meta):
return list(names)


def _validate_field_lookup_term(model, term):
def _validate_field_lookup_term(model, term, query):
"""Checks whether the term is a valid field_lookup for the model.
**Args**:
Expand All @@ -42,6 +42,8 @@ def _validate_field_lookup_term(model, term):
the term is a valid field_lookup.
- **term** (**required**) - the term to check whether it is a valid
field lookup for the model supplied.
- **query** - the source query so we can check for aggregate or extra
fields.
**Returns**:
Expand All @@ -52,6 +54,10 @@ def _validate_field_lookup_term(model, term):
- APIInputError: If the term supplied is not a valid field lookup
parameter for the model.
"""
# if this is an extra or annotated field then return
if term in query.annotations.keys() or term in query.extra.keys():
return term

# TODO: Memoization for speed enchancements?
terms = term.split('__')
model_fields = get_all_field_names(model._meta)
Expand All @@ -68,7 +74,7 @@ def _validate_field_lookup_term(model, term):
else:
m = model

return _validate_field_lookup_term(m, '__'.join(terms[1:]))
return _validate_field_lookup_term(m, '__'.join(terms[1:]), query)


def _clean_source(source):
Expand Down Expand Up @@ -104,11 +110,8 @@ def _clean_categories(categories, source):
% (categories, type(categories)))
field_aliases = {}
for c in categories:
if c in source.query.annotations.keys() or \
c in source.query.extra.keys():
field_aliases[c] = c
else:
field_aliases[c] = _validate_field_lookup_term(source.model, c)
field_aliases[c] = _validate_field_lookup_term(source.model, c,
source.query)
return categories, field_aliases


Expand All @@ -126,7 +129,8 @@ def _clean_legend_by(legend_by, source):
% (legend_by, type(legend_by)))
field_aliases = {}
for lg in legend_by:
field_aliases[lg] = _validate_field_lookup_term(source.model, lg)
field_aliases[lg] = _validate_field_lookup_term(source.model, lg,
source.query)
return legend_by, field_aliases


Expand Down Expand Up @@ -310,7 +314,8 @@ def clean_dps(series):
except KeyError:
raise APIInputError("%s is missing the 'source' key." % td)
td.setdefault('field', tk)
fa = _validate_field_lookup_term(td['source'].model, td['field'])
fa = _validate_field_lookup_term(td['source'].model, td['field'],
td['source'].query)
# If the user supplied term is not a field name, use it as an alias
if tk != td['field']:
fa = tk
Expand Down
129 changes: 128 additions & 1 deletion demoproject/demoproject/chartdemo.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from chartit import DataPool, Chart
from django.db.models import Avg, Count
from django.shortcuts import render_to_response
from .decorators import add_source_code_and_doc
from .models import MonthlyWeatherByCity, MonthlyWeatherSeattle, DailyWeather
from .models import SalesHistory, BookStore
from .models import SalesHistory, BookStore, Book


@add_source_code_and_doc
Expand Down Expand Up @@ -928,3 +929,129 @@ def datetimefield_from_related_model(request, title, code, doc, sidebar_items):
'title': title,
'doc': doc,
'sidebar_items': sidebar_items})


@add_source_code_and_doc
def extra_datefield(request, title, code, doc, sidebar_items):
"""
A Basic Line Chart using QuerySet.extra() with DateField
--------------------------------------------------------
This chart plots sales quantities per day from the first book store.
We're using QuerySet.extra() to format the date value directly at
the DB level.
"""

# start_code
ds = DataPool(
series=[{
'options': {
# NOTE: strftime is SQLite function.
# For MySQL use DATE_FORMAT
'source': SalesHistory.objects.extra(
select={
'sold_at': \
"strftime('%%Y/%%m/%%d', sale_date)"
}
).filter(
bookstore=BookStore.objects.first()
)[:10]
},
'terms': [
'sold_at',
'sale_qty',
]
}]
)

cht = Chart(
datasource=ds,
series_options=[{
'options': {
'type': 'line',
'stacking': False
},
'terms': {
'sold_at': [
'sale_qty',
]
}
}],
chart_options={
'title': {
'text': 'Sales QTY per day'
},
'xAxis': {
'title': {
'text': 'Sale date'
}
}
}
)
# end_code
return render_to_response('chart_code.html',
{
'chart_list': cht,
'code': code,
'title': title,
'doc': doc,
'sidebar_items': sidebar_items})


@add_source_code_and_doc
def avg_count(request, title, code, doc, sidebar_items):
"""
A Basic Line Chart using Avg() and Count()
------------------------------------------
This chart plots the average book rating in each genre
together with the number of books in each genre.
"""

# start_code
ds = DataPool(
series=[{
'options': {
'source': Book.objects.values('genre').annotate(
Avg('rating'),
Count('genre')
)
},
'terms': [
'genre__name',
'rating__avg',
'genre__count'
]
}]
)

cht = Chart(
datasource=ds,
series_options=[{
'options': {
'type': 'line',
'stacking': False
},
'terms': {
'genre__name': [
'rating__avg', 'genre__count'
]
}
}],
chart_options={
'title': {
'text': 'Book rating and count per Genre'
},
'xAxis': {
'title': {
'text': 'Genre'
}
}
}
)
# end_code
return render_to_response('chart_code.html',
{
'chart_list': cht,
'code': code,
'title': title,
'doc': doc,
'sidebar_items': sidebar_items})
16 changes: 16 additions & 0 deletions demoproject/demoproject/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@
name='line_datetime_related',
),

url(r'^demo/chart/extra-datefield/$', chartdemo.extra_datefield,
{
'title': 'Line chart with extra() DateField',
'sidebar_section': 'Charts',
},
name='line_extra_datefield',
),

url(r'^demo/chart/avg-count/$', chartdemo.avg_count,
{
'title': 'Line chart with Avg() and Count()',
'sidebar_section': 'Charts',
},
name='line_avg_count',
),

# pivot chart examples
url(r'^demo/pivot/simple/$', pivotdemo.simplepivot,
{
Expand Down

0 comments on commit a086261

Please sign in to comment.