From 29eaa661d5bc825eb3e16677998e127a6ca761e5 Mon Sep 17 00:00:00 2001 From: Kevin Diale Date: Tue, 30 Jul 2013 16:28:24 -0400 Subject: [PATCH 1/6] Drilling down works on a functional level. Gotta set up interface and tests. On a related note, TAKE THAT DJANGO FILTERS. --- djadmin2/filters.py | 30 ++++++++++++++++++++++++++++++ djadmin2/settings.py | 13 ++++++------- djadmin2/types.py | 1 + djadmin2/views.py | 26 +++++++++++++++++++++++++- example/blog/admin.py | 3 ++- example/blog/admin2.py | 3 ++- example/blog/models.py | 1 + 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/djadmin2/filters.py b/djadmin2/filters.py index 763a85b..d488bc8 100644 --- a/djadmin2/filters.py +++ b/djadmin2/filters.py @@ -4,6 +4,7 @@ from __future__ import division, absolute_import, unicode_literals import collections from itertools import chain +from django import forms from django.forms.util import flatatt from django.utils.html import format_html from django.utils.encoding import force_text @@ -16,6 +17,10 @@ import django_filters LINK_TEMPLATE = '{3}' +class NumericDateFilter(django_filters.DateFilter): + field_class = forms.IntegerField + + class ChoicesAsLinksWidget(django_widgets.Select): """Select form widget taht renders links for choices instead of select element with options. @@ -95,6 +100,31 @@ def build_list_filter(request, model_admin, queryset): )(request.GET, queryset=queryset) +def build_date_filter(request, model_admin, queryset): + filterset_dict = { + "year": NumericDateFilter( + name="published_date", + lookup_type="year", + ), + "month": NumericDateFilter( + name="published_date", + lookup_type="month", + ), + "day": NumericDateFilter( + name="published_date", + lookup_type="day", + ) + } + + newset = type( + b'%sDateFilterSet' % queryset.model.__name__, + (django_filters.FilterSet,), + filterset_dict, + )(request.GET, queryset=queryset) + + return newset + + def get_filter_for_field_name(model, field_name): """Returns filter for model field by field name. """ diff --git a/djadmin2/settings.py b/djadmin2/settings.py index 5153979..1203d0e 100644 --- a/djadmin2/settings.py +++ b/djadmin2/settings.py @@ -8,12 +8,11 @@ from django.conf import settings # views. This is a security feature. # See the docstring on djadmin2.types.ModelAdmin2 for more detail. MODEL_ADMIN_ATTRS = ( - 'actions_selection_counter', 'list_display', 'list_display_links', - 'list_filter', 'admin', 'search_fields', 'field_renderers', - 'index_view', 'detail_view', 'create_view', 'update_view', 'delete_view', - 'get_default_view_kwargs', 'get_list_actions', - 'actions_on_bottom', 'actions_on_top', - 'save_on_top', 'save_on_bottom', - 'readonly_fields', ) + 'actions_selection_counter', "date_hierarchy", 'list_display', + 'list_display_links', 'list_filter', 'admin', 'search_fields', + 'field_renderers', 'index_view', 'detail_view', 'create_view', + 'update_view', 'delete_view', 'get_default_view_kwargs', + 'get_list_actions', 'actions_on_bottom', 'actions_on_top', + 'save_on_top', 'save_on_bottom', 'readonly_fields', ) ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "djadmin2theme_default") diff --git a/djadmin2/types.py b/djadmin2/types.py index 8be8dd2..3c2a795 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -59,6 +59,7 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): bypass the blocking features of the ImmutableAdmin. """ actions_selection_counter = True + date_hierarchy = False list_display = ('__str__',) list_display_links = () list_filter = () diff --git a/djadmin2/views.py b/djadmin2/views.py index 2883f22..fe273cb 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -26,7 +26,7 @@ from . import permissions, utils from .forms import AdminAuthenticationForm from .models import LogEntry from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin -from .filters import build_list_filter +from .filters import build_list_filter, build_date_filter class AdminView(object): @@ -155,6 +155,9 @@ class ModelListView(AdminModel2Mixin, generic.ListView): if self.model_admin.list_filter: queryset = self.build_list_filter(queryset).qs + if self.model_admin.date_hierarchy: + queryset = self.build_date_filter(queryset).qs + queryset = self._modify_queryset_for_sort(queryset) if search_use_distinct: @@ -194,6 +197,18 @@ class ModelListView(AdminModel2Mixin, generic.ListView): ) return self._list_filter + def build_date_filter(self, queryset=None): + if not hasattr(self, "_date_filter"): + if queryset is None: + queryset = self.get_queryset() + self._date_filter = build_date_filter( + self.request, + self.model_admin, + queryset, + ) + + return self._date_filter + def get_context_data(self, **kwargs): context = super(ModelListView, self).get_context_data(**kwargs) context['model'] = self.get_model() @@ -202,6 +217,15 @@ class ModelListView(AdminModel2Mixin, generic.ListView): context['search_term'] = self.request.GET.get('q', '') context['list_filter'] = self.build_list_filter() context['sort_term'] = self.request.GET.get('sort', '') + + if self.model_admin.date_hierarchy: + date_field = self.model_admin.date_hierarchy + context['years'] = ( + context['object_list'].dates(date_field, "year") + ) + context["days"] = ( + context['object_list'].dates(date_field, "day") + ) return context def get_success_url(self): diff --git a/example/blog/admin.py b/example/blog/admin.py index d5e95bd..545806c 100644 --- a/example/blog/admin.py +++ b/example/blog/admin.py @@ -12,8 +12,9 @@ class CommentInline(admin.TabularInline): class PostAdmin(admin.ModelAdmin): inlines = [CommentInline, ] - search_fields = ('title', 'body') + search_fields = ('title', 'body', "published_date") list_filter = ['published', 'title'] + date_hierarchy = "published_date" admin.site.register(Post, PostAdmin) admin.site.register(Comment) diff --git a/example/blog/admin2.py b/example/blog/admin2.py index 5b9739d..fb0d222 100644 --- a/example/blog/admin2.py +++ b/example/blog/admin2.py @@ -29,11 +29,12 @@ class PostAdmin(djadmin2.ModelAdmin2): list_actions = [DeleteSelectedAction, CustomPublishAction, unpublish_items] inlines = [CommentInline] search_fields = ('title', '^body') - list_display = ('title', 'body', 'published') + list_display = ('title', 'body', 'published', "published_date",) field_renderers = { 'title': renderers.title_renderer, } save_on_top = True + date_hierarchy = "published_date" class CommentAdmin(djadmin2.ModelAdmin2): diff --git a/example/blog/models.py b/example/blog/models.py index 7222041..348af5b 100644 --- a/example/blog/models.py +++ b/example/blog/models.py @@ -11,6 +11,7 @@ class Post(models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) body = models.TextField(verbose_name=_('body')) published = models.BooleanField(default=False, verbose_name=_('published')) + published_date = models.DateField(auto_now=True) def __unicode__(self): return self.title From 8dc082153956e6877f58bd7455ad1ab7471c9288 Mon Sep 17 00:00:00 2001 From: Kevin Diale Date: Tue, 30 Jul 2013 17:20:27 -0400 Subject: [PATCH 2/6] Left in a wee bit of debugging code. --- djadmin2/filters.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/djadmin2/filters.py b/djadmin2/filters.py index d488bc8..4151b94 100644 --- a/djadmin2/filters.py +++ b/djadmin2/filters.py @@ -116,14 +116,12 @@ def build_date_filter(request, model_admin, queryset): ) } - newset = type( + return type( b'%sDateFilterSet' % queryset.model.__name__, (django_filters.FilterSet,), filterset_dict, )(request.GET, queryset=queryset) - return newset - def get_filter_for_field_name(model, field_name): """Returns filter for model field by field name. From 966e549aceb944b8bf13709db4d7182e3045df95 Mon Sep 17 00:00:00 2001 From: Kevin Diale Date: Wed, 31 Jul 2013 12:57:48 -0400 Subject: [PATCH 3/6] Basic interface done. --- .../djadmin2theme_default/model_list.html | 9 +++- djadmin2/views.py | 52 ++++++++++++++++--- example/blog/models.py | 2 +- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html b/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html index b84a3ba..865f88a 100644 --- a/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html +++ b/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html @@ -36,13 +36,20 @@ {% endif %} -
{% csrf_token %}
+ {% if dates %} + + {% endif %} + {% if view.model_admin.actions_on_top %} {% include 'djadmin2theme_default/includes/list_actions.html' with position='top' %} {% endif %} diff --git a/djadmin2/views.py b/djadmin2/views.py index fe273cb..278c39f 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -2,6 +2,7 @@ from __future__ import division, absolute_import, unicode_literals import operator +from collections import OrderedDict from django.contrib.auth import get_user_model from django.contrib.auth.forms import (PasswordChangeForm, @@ -219,15 +220,52 @@ class ModelListView(AdminModel2Mixin, generic.ListView): context['sort_term'] = self.request.GET.get('sort', '') if self.model_admin.date_hierarchy: - date_field = self.model_admin.date_hierarchy - context['years'] = ( - context['object_list'].dates(date_field, "year") - ) - context["days"] = ( - context['object_list'].dates(date_field, "day") - ) + year = self.request.GET.get("year", False) + month = self.request.GET.get("month", False) + day = self.request.GET.get("day", False) + + if year and month and day: + context["dates"] = self._format_days(context) + elif year and month: + context["dates"] = self._format_days(context) + elif year: + context["dates"] = self._format_months(context) + else: + context["dates"] = self._format_years(context) + return context + def _format_years(self, context): + return [ + (("?year=%s" % year.strftime("%Y")), year.strftime("%Y")) + for year in + context['object_list'].dates('published_date', 'year') + ] + + def _format_months(self, context): + return [ + ( + "?year=%s&month=%s" % ( + date.strftime("%Y"), date.strftime("%m") + ), + date.strftime("%B %Y") + ) for date in + context["object_list"].dates('published_date', 'day') + ] + + def _format_days(self, context): + return [ + ( + "?year=%s&month=%s&day=%s" % ( + date.strftime("%Y"), + date.strftime("%m"), + date.strftime("%d"), + ), + date.strftime("%B %d") + ) for date in + context["object_list"].dates('published_date', 'day') + ] + def get_success_url(self): view_name = 'admin2:{}_{}_index'.format( self.app_label, self.model_name) diff --git a/example/blog/models.py b/example/blog/models.py index 348af5b..01a0582 100644 --- a/example/blog/models.py +++ b/example/blog/models.py @@ -11,7 +11,7 @@ class Post(models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) body = models.TextField(verbose_name=_('body')) published = models.BooleanField(default=False, verbose_name=_('published')) - published_date = models.DateField(auto_now=True) + published_date = models.DateField() def __unicode__(self): return self.title From bab18c97c118a02286e300fd117292e9b4d38913 Mon Sep 17 00:00:00 2001 From: Kevin Diale Date: Wed, 31 Jul 2013 14:58:08 -0400 Subject: [PATCH 4/6] Interface is most of the way there. --- djadmin2/static/themes/bootstrap/css/base.css | 9 +++++++ .../djadmin2theme_default/model_list.html | 13 ++++++++-- djadmin2/views.py | 25 +++++++++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/djadmin2/static/themes/bootstrap/css/base.css b/djadmin2/static/themes/bootstrap/css/base.css index 773ef1b..62307e2 100644 --- a/djadmin2/static/themes/bootstrap/css/base.css +++ b/djadmin2/static/themes/bootstrap/css/base.css @@ -7,3 +7,12 @@ .sort_link:hover { color: black; } + +.previous-link a { + color: gray; +} + +.date-drilldown { + padding-bottom: 0; + padding-top: 0; +} diff --git a/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html b/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html index 865f88a..5b5755f 100644 --- a/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html +++ b/djadmin2/themes/djadmin2theme_default/templates/djadmin2theme_default/model_list.html @@ -43,9 +43,18 @@
{% if dates %} -