diff --git a/advanced_filters/admin.py b/advanced_filters/admin.py index 043d042..8c59bdb 100644 --- a/advanced_filters/admin.py +++ b/advanced_filters/admin.py @@ -1,5 +1,6 @@ import logging +from django.conf import settings from django.contrib import admin, messages from django.http import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ @@ -138,5 +139,26 @@ def change_view(self, request, object_id, form_url='', extra_context=None): return HttpResponseRedirect(url) return orig_response + @staticmethod + def user_has_permission(user): + """Filters by user if not superuser or explicitly allowed in settings""" + return user.is_superuser or not getattr(settings, "ADVANCED_FILTER_EDIT_BY_USER", True) + + def get_queryset(self, request): + if self.user_has_permission(request.user): + return super(AdvancedFilterAdmin, self).get_queryset(request) + else: + return self.model.objects.filter_by_user(request.user) + + def has_change_permission(self, request, obj=None): + if obj is None: + return super(AdvancedFilterAdmin, self).has_change_permission(request) + return self.user_has_permission(request.user) or obj in self.model.objects.filter_by_user(request.user) + + def has_delete_permission(self, request, obj=None): + if obj is None: + return super(AdvancedFilterAdmin, self).has_delete_permission(request) + return self.user_has_permission(request.user) or obj in self.model.objects.filter_by_user(request.user) + admin.site.register(AdvancedFilter, AdvancedFilterAdmin) diff --git a/advanced_filters/forms.py b/advanced_filters/forms.py index 06e2699..a821448 100644 --- a/advanced_filters/forms.py +++ b/advanced_filters/forms.py @@ -53,6 +53,10 @@ class AdvancedFilterQueryForm(CleanWhiteSpacesMixin, forms.Form): ("isnull", _("Is NULL")), ("istrue", _("Is TRUE")), ("isfalse", _("Is FALSE")), + ("lt", _("Less Than")), + ("gt", _("Greater Than")), + ("lte", _("Less Than or Equal To")), + ("gte", _("Greater Than or Equal To")), ) FIELD_CHOICES = ( @@ -102,8 +106,9 @@ def _parse_query_dict(query_data, model): """ Take a list of query field dict and return data for form initialization """ + operator = 'iexact' if query_data['field'] == '_OR': - query_data['operator'] = 'iexact' + query_data['operator'] = operator return query_data parts = query_data['field'].split('__') @@ -112,6 +117,7 @@ def _parse_query_dict(query_data, model): else: if parts[-1] in dict(AdvancedFilterQueryForm.OPERATORS).keys(): field = '__'.join(parts[:-1]) + operator = parts[-1] else: field = query_data['field'] @@ -134,7 +140,7 @@ def _parse_query_dict(query_data, model): # this is a date/datetime field query_data['operator'] = "range" # default else: - query_data['operator'] = "iexact" # default + query_data['operator'] = operator # default if isinstance(query_data.get('value'), list) and query_data['operator'] == 'range': @@ -355,7 +361,7 @@ def initialize_form(self, instance, model, data=None, extra=None): AdvancedFilterQueryForm._parse_query_dict( field_data, model)) - formset = AFQFormSetNoExtra if extra is None else AFQFormSet + formset = AFQFormSetNoExtra if not extra else AFQFormSet self.fields_formset = formset( data=data, initial=forms or None, diff --git a/advanced_filters/tests/test_admin.py b/advanced_filters/tests/test_admin.py index 6da2ced..d915875 100644 --- a/advanced_filters/tests/test_admin.py +++ b/advanced_filters/tests/test_admin.py @@ -28,7 +28,9 @@ def test_change_page_renders(self): codename='change_advancedfilter')) url = reverse('admin:advanced_filters_advancedfilter_change', args=(self.a.pk,)) - res = self.client.get(url) + + with self.settings(ADVANCED_FILTER_EDIT_BY_USER=False): + res = self.client.get(url) assert res.status_code == 200 def test_change_and_goto(self): @@ -38,7 +40,8 @@ def test_change_and_goto(self): args=(self.a.pk,)) form_data = {'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 0, '_save_goto': 1} - res = self.client.post(url, data=form_data) + with self.settings(ADVANCED_FILTER_EDIT_BY_USER=False): + res = self.client.post(url, data=form_data) assert res.status_code == 302 # django == 1.5 support if hasattr(res, 'url'): @@ -71,7 +74,8 @@ def test_changelist_includes_form(self): self.user.user_permissions.add(Permission.objects.get( codename='change_client')) url = reverse('admin:customers_client_changelist') - res = self.client.get(url) + with self.settings(ADVANCED_FILTER_EDIT_BY_USER=False): + res = self.client.get(url) assert res.status_code == 200 title = ['Create advanced filter'] fields = ['First name', 'Language', 'Sales Rep'] diff --git a/advanced_filters/tests/test_forms.py b/advanced_filters/tests/test_forms.py index eab6b21..d92f069 100644 --- a/advanced_filters/tests/test_forms.py +++ b/advanced_filters/tests/test_forms.py @@ -142,6 +142,51 @@ def test_restore_stored_complex_query(self): assert isinstance(res, dict) assert res == expected[i] + def test_all_operators_are_restored(self): + Rep = get_user_model() + date_range = (datetime(1980, 1, 1), datetime(1986, 1, 1)) + complex_query = (Q(first_name='bar') | + (Q(first_name__iexact='fez') & + Q(is_staff=False) & + Q(date_joined__range=date_range) & + Q(is_superuser=True) & + Q(is_active=None) & + Q(is_staff=False) & + Q(last_name__icontains='foo') & + Q(last_name__lt='q') & + Q(last_name__lte='r') & + Q(last_name__gt='b') & + Q(last_name__gte='c') & + Q(email__iregex=r'^(foo|bar)$') + )) + af = AdvancedFilter(query=complex_query) + + expected = [ + {'field': 'first_name', 'negate': False, 'operator': 'iexact', 'value': 'bar'}, + {'field': '_OR', 'operator': 'iexact', 'value': 'null'}, + {'field': 'first_name', 'negate': False, 'operator': 'iexact', 'value': 'fez'}, + {'field': 'is_staff', 'negate': False, 'operator': 'isfalse', 'value': False}, + {'field': 'date_joined', + 'negate': False, + 'operator': 'range', + 'value': '1980-01-01,1986-01-01', + 'value_from': time.mktime(date_range[0].timetuple()), + 'value_to': time.mktime(date_range[1].timetuple())}, + {'field': 'is_superuser', 'negate': False, 'operator': 'istrue', 'value': True}, + {'field': 'is_active', 'negate': False, 'operator': 'isnull', 'value': None}, + {'field': 'is_staff', 'negate': False, 'operator': 'isfalse', 'value': False}, + {'field': 'last_name', 'negate': False, 'operator': 'icontains', 'value': 'foo'}, + {'field': 'last_name', 'negate': False, 'operator': 'lt', 'value': 'q'}, + {'field': 'last_name', 'negate': False, 'operator': 'lte', 'value': 'r'}, + {'field': 'last_name', 'negate': False, 'operator': 'gt', 'value': 'b'}, + {'field': 'last_name', 'negate': False, 'operator': 'gte', 'value': 'c'}, + {'field': 'email', 'negate': False, 'operator': 'iregex', 'value': r'^(foo|bar)$'} + ] + for i, field in enumerate(af.list_fields()): + res = AdvancedFilterQueryForm._parse_query_dict(field, Rep) + assert isinstance(res, dict) + assert res == expected[i] + class CommonFormTest(TestCase): mgmg_form_data = {