Commit ef33f656 authored by Alan Trick's avatar Alan Trick

Improve support for ListFitler other than FieldListFilter

parent 04da3164
Pipeline #57312328 passed with stage
in 1 minute and 59 seconds
......@@ -8,9 +8,10 @@ import django.http
import django.urls
from django import forms
from django.apps import apps
from django.contrib.admin import FieldListFilter
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib.auth.views import redirect_to_login
from django.db.models import Count
from django.db.models import Count, When, Value, Case, CharField
from django.http import Http404
from django.urls import reverse
from django.views.generic.list import BaseListView
......@@ -18,7 +19,7 @@ from django.conf.urls import url
from django.contrib import admin
from django.template.response import TemplateResponse
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from . import models, registry, explore
......@@ -244,36 +245,62 @@ class ChartAdmin(admin.ModelAdmin):
mock_request = explore.mock_request(request, '_group')
changelist = model_admin.get_changelist_instance(mock_request)
# set up groupings & used filters
used_filters = collections.OrderedDict()
groupings = collections.OrderedDict()
# set up filters
filters = collections.OrderedDict()
for spec in changelist.filter_specs:
if spec.field_path in un_grouped_query_dict:
used_filters[spec.field_path] = explore.Filter(
spec, changelist, un_grouped_query_dict, group_filters)
if (spec.field_path not in un_grouped_query_dict or
spec.field_path in group_filters):
groupings[spec.field_path] = explore.Grouping(
if spec.has_output():
filters[spec.field_path] = explore.Filter(
spec, changelist, un_grouped_query_dict, group_filters)
# generate data
qs = explore.get_queryset(changelist, mock_request)
chart_rows = collections.OrderedDict()
if group_filters:
qs = qs.values(*group_filters)
qs = qs.annotate(axis0=Count('id')).distinct()
paths = {}
for idx, path in enumerate(group_filters):
if path in filters:
list_filter = filters[path].list_filter
default_label = filters[path].default_label
case_args = []
row_params = {}
for choice in filters[path].choices:
qd = choice.query_dict
if choice.query_dict:
cls = list_filter.__class__
args = (mock_request, qd, model_cls, model_admin)
if issubclass(cls, FieldListFilter):
args = (list_filter.field, mock_request,
qd.dict(), model_cls, model_admin,
list_filter.field_path)
new_list_filter = cls(*args)
new_qs = new_list_filter.queryset(mock_request, qs)
case_args.append(
When(id__in=new_qs.values('id'),
then=Value(str(choice.label))))
row_params[str(choice.label)] = qd
case = Case(*case_args, default=Value(str(default_label)),
output_field=CharField(blank=True))
arg = 'adminstats_col{}'.format(idx)
qs = qs.annotate(**{arg: case})
paths[arg] = row_params
qs = qs.values(*paths.keys())
qs = qs.annotate(axis0=Count('id'))
for result in qs:
label = '/'.join((groupings[gf].get_label(result[gf])
for gf in group_filters))
label = '/'.join(result[p] for p in paths.keys())
query_dict = copy.copy(un_grouped_query_dict)
for gf in group_filters:
query_dict[gf] = result[gf]
for key, value in paths.items():
result_value = result[key]
if result_value in value:
query_dict.update(value[result_value])
chart_rows[label] = explore.ChartRow(
[result['axis0']], '?' + query_dict.urlencode())
else:
for value in qs.aggregate(Count('id')).values():
# there should only be one, of course
label = _('Count')
label = _('All')
chart_rows[label] = explore.ChartRow([value])
entry_url = reverse('admin:{}_{}_changelist'.format(app, model))
entry_url = '{}?{}'.format(
......@@ -288,13 +315,9 @@ class ChartAdmin(admin.ModelAdmin):
'has_change_permission': True,
'has_absolute_url': False,
'opts': getattr(model_cls, '_meta'),
'groupings': groupings,
'used_filters': used_filters,
# 'chart': chart,
# 'header': chart_header,
'filters': filters,
'rows': chart_rows,
'entry_url': entry_url,
# 'exceptions': exceptions,
'save_as': False,
'show_save': True,
}
......
......@@ -2,8 +2,9 @@ import copy
import typing
import django
from django.contrib.admin import FieldListFilter, SimpleListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.http import HttpRequest
from django.http import HttpRequest, QueryDict
class ChartRow:
......@@ -12,59 +13,71 @@ class ChartRow:
self.path = path
class Spec:
def __init__(self, spec, changelist, un_grouped_query_dict, group_filters):
self.spec = spec
self.changelist = changelist
self.query = un_grouped_query_dict
class Choice:
def __init__(self, selected, query_dict, label):
self.selected = selected
self.query_dict = query_dict
self.label = label
class Filter:
def __init__(self, list_filter,
changelist, un_grouped_query_dict, group_filters):
self.list_filter = list_filter
self.choices = []
self.query_dict = None
self.is_filtered = False
self.default_label = ''
choices = self.list_filter.choices(changelist)
try:
first_choice = next(choices)
self.default_label = first_choice['display']
if not first_choice['selected']:
params = QueryDict(first_choice['query_string'].lstrip('?'))
self.query_dict = params
self.is_filtered = True
except StopIteration:
pass
for choice in choices:
params = QueryDict(choice['query_string'].lstrip('?'))
self.choices.append(Choice(
choice['selected'], params, choice['display']))
if isinstance(list_filter, FieldListFilter):
self.id = list_filter.field_path
else:
assert isinstance(list_filter, SimpleListFilter)
self.id = list_filter.parameter_name
if self.query_dict is None:
self.query_dict = un_grouped_query_dict
self.group = group_filters
self.in_group = self.spec.field_path in self.group
self.in_query = self.spec.field_path in self.query
self.in_group = self.id in self.group
@property
def url(self):
return '?' + self.get_query().urlencode()
return '?' + self.get_querystring().urlencode()
@property
def title(self):
return self.spec.title
def get_label(self, value):
qs = self.changelist.get_query_string({self.spec.lookup_kwarg: value})
for choice in self.spec.choices(self.changelist):
if choice['query_string'] == qs:
return str(choice['display'])
return str(value)
return self.list_filter.title
class Grouping(Spec):
def get_query(self):
query = copy.copy(self.query)
def get_querystring(self):
query = copy.copy(self.query_dict)
group = copy.copy(self.group)
if self.in_group:
group.remove(self.spec.field_path)
if self.id in group:
group.remove(self.id)
else:
group.append(self.spec.field_path)
query.setlist('_group', group)
return query
class Filter(Spec):
def get_query(self):
query = copy.copy(self.query)
group = copy.copy(self.group)
if self.in_query:
del query[self.spec.field_path]
if not self.in_group:
group.append(self.spec.field_path)
if self.in_group:
del group[self.spec.field_path]
group.append(self.id)
query.setlist('_group', group)
return query
def value(self):
values = self.query.getlist(self.spec.field_path)
return ', '.join(self.get_label(v) for v in values)
for choice in self.choices:
if choice.selected:
return str(choice.label)
return '??'
def mock_request(request, ignored_keys):
......
......@@ -26,27 +26,27 @@
<li><a href="{{ entry_url }}">{% trans 'View Entries' %}</a></li>
</ul>
{% if groupings %}
<h3>{% trans 'Groupings' %}</h3>
<ul class="adminstats-groupings">
{% for grouping in groupings.values %}
<li><a href="{{ grouping.url }}" class="{% if grouping.in_group %}deletelink{% else %}addlink{% endif %}">
{% blocktrans with filter_title=grouping.title %}By {{ filter_title }}{% endblocktrans %}
{% for f in filters.values %}
{% if not f.is_filtered %}
<li><a href="{{ f.url }}" class="{% if f.in_group %}deletelink{% else %}addlink{% endif %}">
{% blocktrans with filter_title=f.title %}By {{ filter_title }}{% endblocktrans %}
</a></li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if used_filters %}
<h3>{% trans 'Filters' %}</h3>
<ul class="adminstats-filters">
{% for filter in used_filters.values %}
<li><a href="{{ filter.url }}" class="deletelink">
{% blocktrans with filter_title=filter.title filter_value=filter.value %}{{ filter_title }}: {{ filter_value }}{% endblocktrans %}
{% for f in filters.values %}
{% if f.is_filtered %}
<li><a href="{{ f.url }}" class="deletelink">
{% blocktrans with filter_title=f.title filter_value=f.value %}{{ filter_title }}: {{ filter_value }}{% endblocktrans %}
</a></li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
<table id="chart-data" data-chart-type="pie">
<tr id="chart-axis">
......
......@@ -6,7 +6,7 @@ from . import models
class PageViewAdmin(admin.ModelAdmin):
list_display = ['ip_address', 'date']
list_filter = ['ip_address', 'page', 'page__category']
list_filter = ['ip_address', 'page', 'page__category', 'date']
class CustomerAdmin(admin.ModelAdmin):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment