Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django 1.4 Support #6

Closed
frnhr opened this issue Mar 6, 2012 · 10 comments
Closed

Django 1.4 Support #6

frnhr opened this issue Mar 6, 2012 · 10 comments

Comments

@frnhr
Copy link

frnhr commented Mar 6, 2012

Django 1.4 brings some changes to admin filters, so Filtrate filter will brake. Loudly.

http://yuji.wordpress.com/2011/06/15/django-1-4-alpha-custom-list-filter-rip-filterspec/

@frnhr
Copy link
Author

frnhr commented Mar 6, 2012

I changed Date filter so that it works with Django 1.4 rc1. Maybe it can be of help in rewriting Filtrate for new version.

__init__.py is now empty

filters.py:

from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.forms.widgets import Media, MEDIA_TYPES, Input, HiddenInput
from django import forms as f
from django.contrib.admin.filters import SimpleListFilter

from filtrate import settings



class FiltrateFilter(SimpleListFilter):

    def __init__(self, request, params, model, model_admin, *args, **kwargs):
        self.request = request
        super(FiltrateFilter, self).__init__(request, params, model, model_admin, *args, **kwargs)
        self._add_media(model_admin)

    def title(self):
        """Triggers the alternate rendering in "filter.html"."""
        return '__filtrate__'

    def has_output(self):
        return True

    def lookups(self, request, model_admin):
        return None

    def queryset(self, request, queryset):
        """
        Returns the filtered queryset.
        """
        return None

    def choices(self, cl):
        """As only title and choices is passed to "filter.html" template, we
        sets title to "__filtrate__" and passes real title and content from
        here.
        """
        return [{
            'title': self.get_title(),
            'content': self.get_content(),
        }]


    # Must be overridden.

    def get_title(self):
        """The title of the filter. Must include "After" in the beginning."""
        raise NotImplementedError()

    def get_content(self):
        """The content part of the filter in html."""
        raise NotImplementedError()

    class Media():
        js = ( 'filtrate/js/filtrate.js',)
        css = { 'all': ('filtrate/css/filtrate.css',) }

    def _add_media(self, model_admin):
        def _get_media(obj):
            return Media(media=getattr(obj, 'Media', None))

        media = _get_media(model_admin) + _get_media(FiltrateFilter)\
        + _get_media(self)

        for name in MEDIA_TYPES:
            setattr(model_admin.Media, name, getattr(media, "_" + name))

    def _form_duplicate_getparams(self, omitted_fields):
        """Replicates the get parameters as hidden form fields."""
        s = '<input type="hidden" name="%s" value="%s"/>'
        _omitted_fields = tuple(omitted_fields) + ('e',)
        return "".join([s % (k,v) for k,v in self.request.GET.iteritems()
                        if k not in _omitted_fields])



class DateRangeFilter(FiltrateFilter):

    class Media():
        js = (
            'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js',
            'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/i18n/jquery-ui-i18n.min.js',
            'filtrate/js/daterangefilter.js',
            )
        css = { 'all': ('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/flick/jquery-ui.css',) }

    def _get_form(self, parameter_name):
        """
        Returns form with from and to fields. The '__alt' fields are alternative
        fields with the correct non localized dateform needed for Django,
        handled by jsTree.
        """
        from_name = self.parameter_name + '__gte'
        to_name = self.parameter_name + '__lte'

        display_widget = Input(attrs={'class': 'filtrate_date'})
        hidden_widget = HiddenInput(attrs={'class': 'filtrate_date_hidden'})
        def add_fields(fields, name, label):
            fields[name + '__alt'] = f.CharField(label=label,
                widget=display_widget, required=False)
            fields[name] = f.CharField(widget=hidden_widget, required=False)

        def add_data(data, name, request):
            date = request.GET.get(name)

            if date:
                data[name + '__alt'] = date

        class DateRangeForm(f.Form):
            def __init__(self, *args, **kwargs):
                super(DateRangeForm, self).__init__(*args, **kwargs)
                add_fields(self.fields, from_name, _('From'))
                add_fields(self.fields, to_name, _('To'))

        data = {}
        add_data(data, from_name, self.request)
        add_data(data, to_name, self.request)
        return DateRangeForm(data=data)

    def get_title(self):
        """The title of the filter. Must include "After" in the beginning."""
        # "After"... say what?
        return u'After title'

    def get_content(self):
        form = self._get_form(self.parameter_name)
        return mark_safe(u"""
            <script>
                var filtrate = filtrate || {};
                filtrate.datepicker_region = '%(datepicker_region)s';
                filtrate.datepicker_date_format = '%(datepicker_date_format)s';
            </script>
            <form class="filtrate_daterange_form" method="get">
                %(form)s
            <input type="submit" value="%(submit)s" />
            <input type="submit" value="%(clear)s" class="cancel" />
            %(get_params)s
            </form>
            """ % ({
                    'form': form.as_p(),
                    'submit': _('Apply filter'),
                    'clear': _('Clear'),
                    'datepicker_region': settings.FILTRATE['datepicker_region'],
                    'datepicker_date_format': settings.FILTRATE['datepicker_date_format'],
                    'get_params': self._form_duplicate_getparams(form.fields.keys()),
                }))

It's definitely not a pretty solution (note the queryset method!), but surprisingly it works.
Anyhow, hope it helps.

There is no TreeFilter, I don't use it so I didn't get around to tackle it...

@runekaagaard
Copy link
Owner

Thanks for that, hopefully I/we can get django-admin-filtrate 1.4 compatible before it hits stable. Cheers!

@brunosmartin
Copy link

I This worked in django 1.4:

class DateRangeFilter2(DateFieldListFilter):
template = 'filter.html'

def __init__(self, field, request, params, model, model_admin, field_path): 
    super(DateRangeFilter2, self).__init__( 
        field, request, params, model, model_admin, field_path)
    self._add_media(model_admin)
    self.request = request

def choices(self, cl): 
    for title, param_dict in self.links: 
        yield { 
              'selected': self.date_params == param_dict, 
              'query_string': cl.get_query_string( 
                  param_dict, [self.field_generic]), 
              'display': title, 
          }
    yield { 
          'selected': self.date_params == param_dict, 
          'content': self.get_content(), 
          'display': u'Data variavel', 
          }


def _form_duplicate_getparams(self, omitted_fields):
    """Replicates the get parameters as hidden form fields."""
    s = '<input type="hidden" name="%s" value="%s"/>'
    _omitted_fields = tuple(omitted_fields) + ('e',)
    return "".join([s % (k,v) for k,v in self.request.GET.iteritems()
                    if k not in _omitted_fields])

def _get_form(self):
    """
    Returns form with from and to fields. The '__alt' fields are alternative
    fields with the correct non localized dateform needed for Django,
    handled by jsTree.
    """
    from_name = self.field_path + '__gte'
    to_name = self.field_path + '__lte'

    display_widget = Input(attrs={'class': 'filtrate_date'})
    hidden_widget = HiddenInput(attrs={'class': 'filtrate_date_hidden'})
    def add_fields(fields, name, label):
        fields[name + '__alt'] = f.CharField(label=label,
            widget=display_widget, required=False)
        fields[name] = f.CharField(widget=hidden_widget, required=False)

    def add_data(data, name, request):
        date = request.GET.get(name)

        if date:
            data[name + '__alt'] = date

    class DateRangeForm(f.Form):
        def __init__(self, *args, **kwargs):
            super(DateRangeForm, self).__init__(*args, **kwargs)
            add_fields(self.fields, from_name, _('From'))
            add_fields(self.fields, to_name, _('To'))

    data = {}
    add_data(data, from_name, self.request)
    add_data(data, to_name, self.request)
    return DateRangeForm(data=data)

def get_content(self):
    form = self._get_form()
    return mark_safe(u"""
        <script>
            var filtrate = filtrate || {};
            filtrate.datepicker_region = '%(datepicker_region)s';
            filtrate.datepicker_date_format = '%(datepicker_date_format)s';
        </script>
        <form class="filtrate_daterange_form" method="get">
            %(form)s
        <input type="submit" value="%(submit)s" />
        <input type="submit" value="%(clear)s" class="cancel" />
        %(get_params)s
        </form>
        """ % ({
                'form': form.as_p(),
                'submit': _('Apply filter'),
                'clear': _('Clear'),
                'datepicker_region': settings.FILTRATE['datepicker_region'],
                'datepicker_date_format': settings.FILTRATE['datepicker_date_format'],
                'get_params': self._form_duplicate_getparams(form.fields.keys()),
            }))

class Media():
    js = (
        'filtrate/js/filtrate.js',
        'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js',
        'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/i18n/jquery-ui-i18n.min.js',
        'filtrate/js/daterangefilter.js',
        )
    css = { 'all': ('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/flick/jquery-ui.css', 'filtrate/css/filtrate.css') }

def _add_media(self, model_admin):
    def _get_media(obj):
        return Media(media=getattr(obj, 'Media', None))

    media = _get_media(model_admin) + _get_media(DateRangeFilter2)\
    + _get_media(self)

    for name in MEDIA_TYPES:
        setattr(model_admin.Media, name, getattr(media, "_" + name))

@brunosmartin
Copy link

I changed the tempalte too...

{% comment %}
This is a modification of the standard filter.html template that allows for
more flexible filters.

If a filter returns its title as "__filtrate__" the more flexible rendering
is activated and the `choices()` method of the filter must return a dict
wrapped in a list with the keys `title` and `content` instead.

{% endcomment %}

{% load i18n %}

{% blocktrans with title as filter_title %} By {{ filter_title }} {% endblocktrans %}

    {% for choice in choices %}
    {% if not choice.content %}
    <li{% if choice.selected %} class="selected"{% endif %}>
        <a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
    {% else %}
        <li{% if choice.selected %} class="selected"{% endif %}>
            <div class="filtrate">
                <div class="content">
                    {{choice.content}}
                </div>
            </div>
        </li>
    {% endif %}
    

    {% endfor %}

@runekaagaard
Copy link
Owner

Just writing here to say that I've started working on bringing the entire app up to Django 1.4. Expect more news soon :)

@andybak
Copy link

andybak commented Sep 8, 2014

I'd recommend jumping straight to 1.6 (if not 1.7) if you're going to the trouble. 1.7 is a bit more fiddly but targeting 1.6 instead of 1.4 shouldn't be a significant increase in complexity.

@runekaagaard
Copy link
Owner

I got 1.4 (and probably later versions too) working in the v1.4 branch. Both TreeFilter and DateRangeFilter works:

class CompanyDepartmentFilter(TreeFilter):
    parameter_name = 'client__department__id__in'
    title = 'By department'

    def get_tree(self):
        from myapp.models import Department
        qs = Department.objects.all()
        return groupby(qs, lambda obj: getattr(obj, 'customer'))

class CaseAddedDateRangeFilter(DateRangeFilter):
    parameter_name = 'start_date'
    title = 'By start date'

So if anyone feels adventurous, then go play with it :)

@runekaagaard
Copy link
Owner

The v1.4 branch now works (for me) in both Django 1.4 and 1.7. https://github.com/runekaagaard/django-admin-filtrate/tree/v1.4

@mvattuone
Copy link

This sort of works, but I had to do a few things to get it to work using 1.7.1, rather than having immediate out of the box support. It still saved me a ton of time though, so thank you! I just wanted to pass off some notes for you, hopefully useful...

  • I had to override the is_active function such that it was only looking for the filtrate class. I think maybe it is because I'm using a bootstrap admin package, but I did not have #changelist-filter in my DOM.
  • In your test for the 1.4 version, you have a list filter that contains a tuple of the parameter name and the class name. It should just be the class name.
  • If I click on the datepicker, it closes the dropdown behind it, which is sort of poor UX. Not sure if that's something at the datepicker level, but I may look into it some more if I can find some time.

@runekaagaard
Copy link
Owner

This is fixed think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants