Changeset f9de3a071de31c2330c09829345ebe75ff4453e1
- Timestamp:
- 01/18/12 19:39:25 (4 months ago)
- Children:
- 2251676f7059cf73b5b11b110f24cb2d6e734901
- Parents:
- 8e5456649376cd5502e368c808d7e01aed7163b0
- git-committer:
- Paul Winkler <slinkp@…> (01/18/12 19:39:25)
- Location:
- ebpub/ebpub/db
- Files:
-
- 6 edited
-
schemafilters.py (modified) (29 diffs)
-
tests/test_models.py (modified) (3 diffs)
-
tests/test_schemafilters.py (modified) (15 diffs)
-
tests/test_views.py (modified) (10 diffs)
-
urlresolvers.py (modified) (2 diffs)
-
views.py (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ebpub/ebpub/db/schemafilters.py
r732439 rf9de3a 55 55 import ebpub.streets.models 56 56 import logging 57 import posixpath 57 58 import re 58 59 import urllib … … 73 74 74 75 slug = None # ID for this type of filter. Used with FilterChain.add() / .remove() 75 url = None # Actually a URL fragment.76 76 value = None # Value fed to the filter, for display. 77 77 label = None # Human-readable name of the filter, for display. 78 78 short_value = None # Shorter version of value fed to the filter, for display. 79 argname = None # For generating query parameters for URLs. 80 query_param_value = None # For generating query parameters for URLs. 79 81 80 82 def __init__(self, request, context, queryset=None, *args, **kw): … … 111 113 except AttributeError: 112 114 raise KeyError(key) 115 116 def get_query_params(self): 117 """ 118 Suitable for composing query strings out of dictionaries. 119 """ 120 return {self.argname: self.query_param_value or ''} 113 121 114 122 … … 172 180 self.slug = self.schemafield.name 173 181 self.argname = 'by-%s' % self.slug 174 self.url = 'by-%s=' % self.slug175 182 self.value = self.short_value = '' # Descriptions of this filter, for display. 176 183 self.label = self.schemafield.pretty_name … … 188 195 else: 189 196 str_att_value = str(self.att_value) 190 self. url += str_att_value197 self.query_param_value = str_att_value 191 198 192 199 def apply(self): … … 209 216 self.short_value = self.query 210 217 self.value = self.query 211 self.url = 'by-%s=%s' % (self.schemafield.name, self.query) 218 self.argname = 'by-' + self.schemafield.name 219 self.query_param_value = ','.join(args) 212 220 213 221 def apply(self): … … 234 242 if self.real_val not in (True, False, None): 235 243 raise FilterError('Invalid boolean value %r' % self.boolslug) 236 self.url = 'by-%s=%s' % (self.schemafield.name, self.boolslug) 244 self.argname = 'by-%s' % self.schemafield.name 245 self.query_param_value = self.boolslug 237 246 self._got_args = True 238 247 else: … … 290 299 self.value = ', '.join([lk.name for lk in self.lookups]) 291 300 self.short_value = self.value 292 self. url = 'by-%s=%s' % (self.schemafield.name, ','.join(slugs))301 self.query_param_value = ','.join(slugs) 293 302 294 303 def validate(self): … … 315 324 316 325 slug = 'location' 326 argname = 'locations' 317 327 318 328 def __init__(self, request, context, queryset, *args, **kwargs): … … 331 341 raise FilterError("not enough args") 332 342 self.location_type_slug = args[0] 333 self.url = 'locations=%s' % self.location_type_slug334 343 self.value = 'Choose %s' % self.location_type_slug.title() 335 344 try: … … 350 359 self.short_value = loc.name 351 360 self.value = loc.name 352 self.url = 'locations=%s,%s' % (self.location_type_slug, self.location_slug)353 361 self.location_name = loc.name 354 362 self.location_object = loc 363 self.query_param_value = '%s,%s' % (self.location_type_slug, 364 self.location_slug) 365 355 366 356 367 def validate(self): … … 397 408 self.short_value = value 398 409 self.value = value 399 self.url = 'streets=' 410 self.argname = 'streets' 411 self.query_param_value = [] 400 412 if get_metro()['multiple_cities']: 401 self.url += self.city_slug + ',' 402 self.url += '%s,%s%s,%s' % (block.street_slug, 403 block.number(), block.dir_url_bit(), 404 radius_slug(self.block_radius)) 413 self.query_param_value.append(self.city_slug) 414 self.query_param_value.extend([block.street_slug, 415 block.number() + block.dir_url_bit(), 416 radius_slug(self.block_radius)]) 417 self.query_param_value = ','.join(self.query_param_value) 405 418 self.location_name = block.pretty_name 406 419 … … 438 451 # view. 439 452 xy_radius, block_radius, cookies_to_set = block_radius_value(request) 440 radius_ url = u'%s,%s/' % (request.path.rstrip('/'),441 radius_slug(block_radius))453 radius_param = urllib.quote(',' + radius_slug(block_radius)) 454 radius_url = request.get_full_path() + radius_param 442 455 raise FilterError('missing radius', url=radius_url) 443 456 … … 477 490 """Filters on NewsItem.item_date. 478 491 The start_date and end_date args are *inclusive*. 492 They can be the same; missing end_date implies start == end. 479 493 """ 480 494 481 495 slug = 'date' 482 496 date_field_name = 'item_date' 483 _argname = 'by-date'497 argname = 'by-date' 484 498 485 499 _sort_value = 1.0 … … 498 512 gte_kwarg = '%s__gte' % self.date_field_name 499 513 lt_kwarg = '%s__lt' % self.date_field_name 500 try: 501 start_date, end_date = args 502 if isinstance(start_date, basestring): 503 start_date = datetime.date(*map(int, start_date.split('-'))) 504 self.start_date = start_date 505 if isinstance(end_date, basestring): 506 end_date = datetime.date(*map(int, end_date.split('-'))) 507 self.end_date = end_date 508 except (IndexError, ValueError, TypeError): 509 raise FilterError("Missing or invalid date range") 514 if not args: 515 raise FilterError("Missing date range") 516 517 start_date = args[0] 518 end_date = args[-1] 519 520 def _parse(date): 521 # Ugh, papering over wild proliferation of date formats. 522 try: 523 date = parse_date(date, '%m/%d/%Y') 524 except ValueError: 525 try: 526 date = parse_date(date, '%Y/%m/%d') 527 except ValueError: 528 try: 529 date = datetime.date(*map(int, date.split('-'))) 530 except ValueError: 531 raise BadDateException("Unknown date format on %r" % date) 532 return date 533 534 assert end_date is not None 535 536 if isinstance(start_date, basestring): 537 start_date = _parse(start_date) 538 if isinstance(end_date, basestring): 539 end_date = _parse(end_date) 540 elif end_date is None: 541 end_date = start_date 542 543 for d in (start_date, end_date): 544 if d and d.year < 1900: 545 # This prevents strftime from throwing a ValueError. 546 raise BadDateException('Dates before 1900 are not supported.') 547 548 assert end_date is not None 549 self.start_date = start_date 550 self.end_date = end_date 510 551 511 552 self.kwargs = { … … 519 560 self.value = u'%s - %s' % (dateformat.format(self.start_date, 'N j, Y'), dateformat.format(self.end_date, 'N j, Y')) 520 561 521 self.short_value = self.value 522 self.url = '%s=%s,%s' % (self._argname, 523 self.start_date.strftime('%Y-%m-%d'), 524 self.end_date.strftime('%Y-%m-%d')) 525 526 562 def get_query_params(self): 563 return {'start_date': self.start_date.strftime('%Y-%m-%d'), 564 'end_date': self.end_date.strftime('%Y-%m-%d')} 527 565 528 566 def validate(self): … … 541 579 """ 542 580 543 _argname = 'by-pub-date'581 argname = 'by-pubdate' 544 582 date_field_name = 'pub_date' 545 583 … … 549 587 DateFilter.__init__(self, request, context, queryset, *args, **kwargs) 550 588 self.label = 'date published' 589 590 def get_query_params(self): 591 return {'start_pubdate': self.start_date.strftime('%Y-%m-%d'), 592 'end_pubdate': self.end_date.strftime('%Y-%m-%d')} 551 593 552 594 … … 607 649 self[k] = v 608 650 609 def update_from_request(self, argstring, filter_sf_dict): 610 """Update the list of filters based on the path and/or query string. 611 612 ``argstring`` is the portion of the path that describes the 613 filters (or None, in the case of "/filter/"). 651 def update_from_request(self, filter_sf_dict): 652 """Update the list of filters based on the request params. 653 654 After this is called, it's recommended to redirect to a 655 normalized form of the URL, which you can get via self.sort(); 656 self.make_url() 614 657 615 658 ``filter_sf_dict`` is a mapping of name -> SchemaField which have … … 620 663 """ 621 664 request, context = self.request, self.context 622 # TODO: can we remove some args now that we're not using623 # get_place_info_for_request?624 argstring = urllib.unquote((argstring or '').rstrip('/'))625 argstring = argstring.replace('+', ' ')626 args = []627 628 if argstring and argstring != 'filter':629 for arg in argstring.split(';'):630 try:631 argname, argvalues = arg.split('=', 1)632 except ValueError:633 raise FilterError('Invalid filter parameter %r, no equals sign' % arg)634 argname = argname.strip()635 argvalues = [v.strip() for v in argvalues.split(',')]636 if argname:637 args.append((argname, argvalues))638 else:639 # No filters specified. Do nothing?640 pass641 642 665 qs = self.qs 643 while args:644 argname, argvalues = args.pop(0)645 argvalues = [v for v in argvalues if v]646 # Date range647 if argname == 'by-date':648 self['date'] = DateFilter(request, context, qs, *argvalues, schema=self.schema)649 elif argname == 'by-pub-date':650 self['date'] = PubDateFilter(request, context, qs, *argvalues, schema=self.schema)651 652 # Street/address653 elif argname.startswith('streets'):654 self['location'] = BlockFilter(request, context, qs, *argvalues)655 # Location filtering656 elif argname.startswith('locations'):657 self['location'] = LocationFilter(request, context, qs, *argvalues)658 659 # Attribute filtering660 elif argname.startswith('by-'):661 sf_name = argname[3:]662 try:663 sf = filter_sf_dict.pop(sf_name)664 except KeyError:665 raise FilterError('Invalid or duplicate SchemaField name %r' % sf_name)666 self.add_by_schemafield(sf, *argvalues, _replace=True)667 668 else:669 raise FilterError('Invalid filter type')670 671 self.update_from_query_params(request)672 self.sort()673 return self674 675 def update_from_query_params(self, request):676 """677 Update the filters based on query parameters.678 679 After this is called, it's recommended to redirect680 to a normalized form of the URL, eg. self.sort(); self.make_url()681 682 This takes care to preserve query parameters that aren't used683 by any of the NewsitemFilters.684 """685 # Make a mutable copy so we can leave only the params that FilterChain686 # doesn't know about.687 666 params = request.GET.copy() 688 def pop_key(key): 689 # request.GET.pop() returns a sequence. 690 # We only want a single value, stripped. 691 val = params.pop(key, [''])[0] 692 return val.strip() 693 694 address = pop_key('address') 667 668 def pop_key(key, single=False): 669 """ 670 Pop the value(s) from params, treat it as a 671 comma-separated list of values, and split that into a 672 list. So ?foo=bar,baz is equivalent to ?foo=bar&foo=baz. 673 674 If single==True, return only the first one; in the example 675 we'd return 'bar'. Otherwise, by default, return the 676 list; in the example we'd return ['bar', 'baz'] 677 """ 678 result = [] 679 for value in params.pop(key, [u'']): 680 value = value.replace(u'+', u' ') # XXX does django do this already? 681 values = [s.strip() for s in value.split(u',')] 682 result.extend(values) 683 result = [r for r in result if r] 684 if single: 685 return result[0] if result else u'' 686 return result 687 688 # Address. 689 address = pop_key('address', single=True) 695 690 if address: 696 691 xy_radius, block_radius, cookies_to_set = block_radius_value(request) 697 p arams.pop('radius', None)692 pop_key('radius') # Just to remove it, block_radius_value() used it. 698 693 result = None 699 694 try: … … 703 698 except (GeocodingException, ParsingError): 704 699 raise BadAddressException(address, block_radius, address_choices=()) 705 706 700 assert result 707 701 if result['block']: … … 722 716 self.replace('location', block, block_radius) 723 717 724 725 start_date = pop_key('start_date') 726 end_date = pop_key('end_date') 727 if start_date and end_date: 728 try: 729 start_date = parse_date(start_date, '%m/%d/%Y') 730 end_date = parse_date(end_date, '%m/%d/%Y') 731 except ValueError, e: 732 old_e = str(e) 733 del(e) 734 try: 735 # Ugh, papering over wild proliferation of date formats. 736 start_date = parse_date(start_date, '%Y/%m/%d') 737 end_date = parse_date(end_date, '%Y/%m/%d') 738 except ValueError, e: 739 raise BadDateException("%s; %s" % (str(e), old_e)) 740 if start_date.year < 1900 or end_date.year < 1900: 741 # This prevents strftime from throwing a ValueError. 742 raise BadDateException('Dates before 1900 are not supported.') 743 744 self.replace('date', start_date, end_date) 745 746 lookup_name = pop_key('textsearch') 747 search_string = pop_key('q') 718 # Dates. 719 # For hysterical reasons we support several ways of passing 720 # these in. TODO: consolidate these into ONLY the start_ and 721 # end_ variants, no more of the comma-separated by-date stuff. 722 # The latter are more compact, but a) more work on the client 723 # side, and b) look uglier in URLs due to the url-quoted 724 # comma. 725 pub_start_and_end = [pop_key('start_pubdate', single=True), 726 pop_key('end_pubdate', single=True)] 727 pub_start_and_end = [s for s in pub_start_and_end if s] 728 pub_dates = pop_key('by-pubdate') or pub_start_and_end 729 730 start_and_end = [pop_key('start_date', single=True), 731 pop_key('end_date', single=True)] 732 start_and_end = [s for s in start_and_end if s] 733 dates = pop_key('by-date') or start_and_end 734 735 if dates and pub_dates: 736 raise DuplicateFilterError("You can only filter by one set of dates.") 737 elif dates: 738 self['date'] = DateFilter(request, context, qs, *dates, 739 schema=self.schema) 740 elif pub_dates: 741 self['date'] = PubDateFilter(request, context, qs, *pub_dates, 742 schema=self.schema) 743 744 # Text searches. Apparently we only support one at a time. 745 lookup_name = pop_key('textsearch', single=True) 746 search_string = pop_key('q', single=True) 748 747 if lookup_name and search_string: 749 748 # Can raise DoesNotExist. Should that be FilterError? … … 752 751 self.replace(schemafield, search_string) 753 752 754 # Stash away all query params we didn't consume. 753 # All remaining args. 754 for argname in params.keys(): 755 756 # Street/address 757 if argname.startswith('streets'): 758 argvalues = pop_key(argname) 759 self['location'] = BlockFilter(request, context, qs, *argvalues) 760 761 # Location filtering 762 elif argname.startswith('locations'): 763 argvalues = pop_key(argname) 764 self['location'] = LocationFilter(request, context, qs, *argvalues) 765 766 # Attribute filtering 767 elif argname.startswith('by-'): 768 argvalues = pop_key(argname) 769 sf_name = argname[3:] 770 try: 771 sf = filter_sf_dict.pop(sf_name) 772 except KeyError: 773 raise FilterError('Invalid or duplicate SchemaField name %r' % sf_name) 774 self.add_by_schemafield(sf, *argvalues, _replace=True) 775 else: 776 # Unknown param, ignore it. 777 pass 778 779 780 self.sort() 781 # Stash any un-consumed query params for URL construction. 755 782 self.other_query_params = params 783 return self 784 756 785 757 786 def validate(self): … … 935 964 return self 936 965 937 938 966 def make_breadcrumbs(self, additions=(), removals=(), stop_at=None, 939 967 base_url=None): … … 964 992 relevant to the FilterChain. 965 993 994 In all URLs, query parameters will be sorted alphabetically by 995 name. 966 996 """ 967 997 # TODO: Can filter_reverse leverage this? Or vice-versa? … … 977 1007 978 1008 base_url = base_url or clone.base_url 1009 base_url = posixpath.normpath(base_url) + '/' 979 1010 980 1011 crumbs = [] 981 filter_params = [] 982 other_query_params = urllib.urlencode(sorted(self.other_query_params.items()), 983 doseq=True) 1012 params_so_far = self.other_query_params.copy() 984 1013 for key, filt in clone._items_with_labels(): 985 1014 # I'm not sure why we prefer short_value to label, but that's what … … 988 1017 assert label is not None 989 1018 label = label.title() 990 if label and getattr(filt, 'url', None) is not None: 991 filter_params.append(filt.url) 992 url = '%s%s/' % (base_url, ';'.join(filter_params)) 993 if other_query_params: 994 url = '%s?%s' % (url, other_query_params) 1019 if label: 1020 params_so_far.update(filt.get_query_params()) 1021 # We need doseq=True in case any of other_query_params have multiple values. 1022 query_string = urllib.urlencode(sorted(params_so_far.items()), 1023 doseq=True) 1024 url = '%s?%s' % (base_url, query_string) 995 1025 crumbs.append((label, url)) 996 1026 if key == stop_at: … … 1007 1037 return crumbs[-1][1] 1008 1038 else: 1009 return base_url or self.base_url 1039 if self.other_query_params: 1040 return '%s?%s' % (base_url or self.base_url, 1041 urllib.urlencode(self.other_query_params)) 1042 else: 1043 return base_url or self.base_url 1010 1044 1011 1045 def add_by_place_id(self, pid): … … 1049 1083 1050 1084 1051 class BadDateException( Exception):1085 class BadDateException(FilterError): 1052 1086 pass 1053 1087 -
ebpub/ebpub/db/tests/test_models.py
r03d55a rf9de3a 206 206 def test_top_lookups__m2m(self): 207 207 from ebpub.db.models import SchemaField 208 sf = SchemaField.objects.get(name='tag s many-to-many')208 sf = SchemaField.objects.get(name='tag') 209 209 # from ebpub.db.bin.update_aggregates import update_aggregates 210 210 # update_aggregates(sf.schema.id) … … 254 254 from ebpub.db.models import NewsItem, SchemaField, Lookup 255 255 by_attribute = NewsItem.objects.by_attribute 256 sf = SchemaField.objects.get(name='tag s many-to-many')256 sf = SchemaField.objects.get(name='tag') 257 257 lookups = Lookup.objects.filter(schema_field=sf, code__in=['1', '2']) 258 258 qs = by_attribute(sf, lookups, is_lookup=True) … … 272 272 from ebpub.db.models import NewsItem, SchemaField 273 273 by_attribute = NewsItem.objects.by_attribute 274 sf = SchemaField.objects.get(name='tag s many-to-many')274 sf = SchemaField.objects.get(name='tag') 275 275 qs = by_attribute(sf, ['1', '2'], is_lookup=True) 276 276 self.assertEqual(qs.count(), 3) -
ebpub/ebpub/db/tests/test_schemafilters.py
re0f4b3 rf9de3a 33 33 import random 34 34 35 36 35 class TestNewsitemFilter(TestCase): 37 36 … … 104 103 return filt 105 104 106 def test_filter__e rrors(self):105 def test_filter__empty(self): 107 106 self.assertRaises(FilterError, self._make_filter) 108 107 … … 135 134 # TODO: have some NewsItems overlapping this location? 136 135 136 self.assertEqual(filt.get_query_params(), 137 {'locations': 'neighborhoods,hood-1'}) 137 138 138 139 class TestBlockFilter(TestCase): … … 202 203 self.assertRaises(FilterError, self._make_filter, 'by-date', 'bogus') 203 204 self.assertRaises(FilterError, self._make_filter, 'by-date', 'bogus', 'bogus') 204 self.assertRaises(FilterError, self._make_filter, 'by-date', '2011-04-07')205 205 206 206 def test_filter__ok(self): … … 211 211 self.assertEqual(self.mock_qs.filter.call_args, ((), filt.kwargs)) 212 212 213 def test__single_arg_date(self): 214 filt = self._make_filter('by-date', '2011-04-07') 215 filt.apply() 216 self.assertEqual(filt.value, u'April 7, 2011') 217 213 218 214 219 def test_filter__ok__one_day(self): … … 220 225 221 226 def test_pub_date_filter(self): 222 filt = self._make_filter('by-pub -date', '2006-11-08', '2006-11-09')227 filt = self._make_filter('by-pubdate', '2006-11-08', '2006-11-09') 223 228 from ebpub.db.schemafilters import PubDateFilter 224 229 filt2 = PubDateFilter(filt.request, filt.context, filt.qs, … … 247 252 request = context = None 248 253 filter = AttributeFilter(request, context, qs, schemafield=schemafield) 249 self.assertEqual(filter.url, 'by-mock-sf=') 254 self.assertEqual(filter.get_query_params(), 255 {'by-mock-sf': ''}) 250 256 251 257 … … 257 263 request = context = None 258 264 filter = AttributeFilter(request, context, qs, when, schemafield=schemafield) 259 self.assertEqual(filter.url, 'by-mock-sf=2009-01-23') 265 self.assertEqual(filter.get_query_params(), 266 {'by-mock-sf': '2009-01-23'}) 260 267 261 268 def test_init__with_datetime(self): … … 266 273 request = context = None 267 274 filter = AttributeFilter(request, context, qs, when, schemafield=schemafield) 268 self.assertEqual(filter.url, 'by-mock-sf=2009-01-23T22:40:00') 275 self.assertEqual(filter.get_query_params(), 276 {'by-mock-sf': '2009-01-23T22:40:00'}) 269 277 270 278 def test_init__with_time(self): … … 275 283 request = context = None 276 284 filter = AttributeFilter(request, context, qs, when, schemafield=schemafield) 277 self.assertEqual(filter.url, 'by-mock-sf=22:40:00') 285 self.assertEqual(filter.get_query_params(), 286 {'by-mock-sf': '22:40:00'}) 278 287 279 288 … … 370 379 self.assertEqual(filt.label, 'Beat') 371 380 self.assertEqual(filt.argname, 'by-beat') 381 self.assertEqual(filt.get_query_params(), 382 {'by-beat': 'beat-214,beat-64'}) 372 383 373 384 … … 538 549 self.assertRaises(FilterError, chain.add, 'date') 539 550 540 def test_update_from_request__bad(self): 541 chain = FilterChain() 542 argstring = 'foobar' 543 self.assertRaises(FilterError, chain.update_from_request, argstring, {}) 551 def test_update_from_request__empty(self): 552 request = mock.Mock() 553 request.GET = {} 554 chain = FilterChain(request=request) 555 chain.update_from_request({}) 556 self.assertEqual(len(chain), 0) 544 557 545 558 def test_add_by_place_id__bad(self): … … 588 601 def _make_chain(self, url): 589 602 request = RequestFactory().get(url) 590 argstring = request.path.split('filter/', 1)[-1]591 603 crime = models.Schema.objects.get(slug='crime') 592 604 context = {'schema': crime} 593 605 chain = FilterChain(request=request, context=context, schema=crime) 594 chain.update_from_request( argstring=argstring,filter_sf_dict={})606 chain.update_from_request(filter_sf_dict={}) 595 607 return chain 596 608 … … 692 704 url += '?start_date=2010/12/01&end_date=2011/01/01' 693 705 chain = self._make_chain(url) 694 expected = filter_reverse('crime', [('by-date', '2010-12-01', '2011-01-01')]) 706 expected = filter_reverse('crime', [('start_date', '2010-12-01'), 707 ('end_date', '2011-01-01')]) 695 708 self.assertEqual(chain.make_url(), expected) 696 709 … … 708 721 self.assertEqual(chain.make_url(), expected) 709 722 710 def test_make_url__both_args_and_query(self): 711 url = filter_reverse('crime', [('by-date', '2011-04-05', '2011-04-06')]) 712 url += '?textsearch=status&q=bar' 723 def test_make_url__preserves_other_query_params_sorted(self): 724 url = filter_reverse('crime', [('start_date', '2011-04-05'), 725 ('end_date', '2011-04-06')]) 726 url += '&textsearch=status&q=bar' 727 # Add the extra params. 728 url += '&zzz=yes&A=no' 713 729 chain = self._make_chain(url) 714 expected = filter_reverse('crime', [('by-date', '2011-04-05', '2011-04-06'), 730 expected = filter_reverse('crime', [('start_date', '2011-04-05'), 731 ('end_date', '2011-04-06'), 715 732 ('by-status', 'bar')]) 733 # The extra params should end up sorted alphanumerically. 734 expected = expected.replace('?', '?A=no&') + '&zzz=yes' 716 735 self.assertEqual(chain.make_url(), expected) 717 736 718 719 def test_make_url__preserves_other_query_params_sorted(self):720 url = filter_reverse('crime', [('by-date', '2011-04-05', '2011-04-06')])721 url += '?textsearch=status&q=bar'722 # Add some params that we don't know about.723 url += '&B=no&A=yes'724 chain = self._make_chain(url)725 expected = filter_reverse('crime', [('by-date', '2011-04-05', '2011-04-06'),726 ('by-status', 'bar')])727 expected += '?A=yes&B=no'728 self.assertEqual(chain.make_url(), expected)729 -
ebpub/ebpub/db/tests/test_views.py
ree6774 rf9de3a 273 273 self.assertEqual(response['location'], 'http://testserver/crime/') 274 274 275 @mock.patch('ebpub.db.schemafilters.FilterChain.update_from_query_params') 275 def test_filter__bad_params(self): 276 url = filter_reverse('crime', [('by-foo', 'bar')]) 277 url = url.replace(urllib.quote('='), 'X') 278 response = self.client.get(url) 279 self.assertEqual(response.status_code, 404) 280 281 @mock.patch('ebpub.db.schemafilters.FilterChain.update_from_request') 276 282 def test_filter__bad_date(self, mock_update): 277 283 from ebpub.db.views import BadDateException … … 281 287 self.assertEqual(response.status_code, 404) 282 288 283 def test_filter__bad_params(self):284 url = filter_reverse('crime', [('by-foo', 'bar')])285 url = url.replace(urllib.quote('='), 'X')286 response = self.client.get(url)287 self.assertEqual(response.status_code, 404)288 289 289 def test_filter_by_daterange(self): 290 url = filter_reverse('crime', [('by-date', '2006-11-01', '2006-11-30')]) 290 url = filter_reverse('crime', [('start_date', '2006-11-01'), 291 ('end_date', '2006-11-30')]) 291 292 response = self.client.get(url) 292 293 self.assertContains(response, 'Clear') … … 296 297 297 298 def test_filter_by_pubdate_daterange(self): 298 url = filter_reverse('crime', [('by-pub-date', '2006-11-01', '2006-11-30')]) 299 url = filter_reverse('crime', [('start_pubdate', '2006-11-01'), 300 ('end_pubdate', '2006-11-30')]) 299 301 response = self.client.get(url) 300 302 self.assertContains(response, 'Clear') … … 306 308 url = filter_reverse('crime', 307 309 [('by-date', '2006-11-01', '2006-11-30'), 308 ('by-pub-date', '2006-11-01', '2006-11-30')]) 309 response = self.client.get(url) 310 self.assertEqual(response.status_code, 404) 311 310 ('by-pubdate', '2006-11-01', '2006-11-30')]) 311 response = self.client.get(url) 312 self.assertEqual(response.status_code, 404) 313 314 315 def test_filter__date_missing(self): 316 url = filter_reverse('crime', [('start_date', '')]) 317 response = self.client.get(url) 318 self.assertEqual(response.status_code, 302) 319 self.assert_(response['location'].endswith('/crime/filter/')) 312 320 313 321 def test_filter__invalid_daterange(self): 314 url = filter_reverse('crime', [('by-date', '')]) 315 response = self.client.get(url) 316 self.assertEqual(response.status_code, 404) 317 url = filter_reverse('crime', [('by-date', 'whoops', 'ouchie')]) 318 response = self.client.get(url) 319 self.assertEqual(response.status_code, 404) 320 url = filter_reverse('crime', [('by-date', '2006-11-30', 'ouchie')]) 322 url = filter_reverse('crime', [('start_date', 'whoops'), 323 ('end_date', 'ouchie')]) 324 response = self.client.get(url) 325 self.assertEqual(response.status_code, 404) 326 url = filter_reverse('crime', [('start_date', '2006-11-30'), 327 ('end_date', 'ouchie')]) 321 328 response = self.client.get(url) 322 329 self.assertEqual(response.status_code, 404) … … 324 331 325 332 def test_filter_by_day(self): 326 url = filter_reverse('crime', [('by-date', '2006-09-26', '2006-09-26')]) 333 url = filter_reverse('crime', [('start_date', '2006-09-26'), 334 ('end_date', '2006-09-26')]) 327 335 response = self.client.get(url) 328 336 self.assertContains(response, "crime title 1") 329 337 self.assertNotContains(response, "crime title 2") 330 338 self.assertNotContains(response, "crime title 3") 339 340 341 def test_filter__by_date__legacy_redirects(self): 342 base = 'http://testserver' + filter_reverse('crime', []) 343 expected_to_actual = ( 344 (filter_reverse('crime', [('by-date', '2011-09-25', '2012-10-31')]), 345 base + '?end_date=2012-10-31&start_date=2011-09-25' 346 ), 347 (filter_reverse('crime', [('by-pubdate', '2011-09-25', '2012-10-31')]), 348 base + '?end_pubdate=2012-10-31&start_pubdate=2011-09-25' 349 ), 350 351 ) 352 for expected, actual in expected_to_actual: 353 response = self.client.get(expected) 354 self.assertEqual(response.status_code, 302) 355 self.assertEqual(response['location'], actual) 356 response2 = self.client.get(actual) 357 self.assertEqual(response2.status_code, 200) 358 331 359 332 360 … … 384 412 fixed_url = filter_reverse('crime', [ 385 413 ('streets', 'wabash-ave', '216-299n-s', '8-blocks')]) 386 self.assert _(response['location'].endswith(urllib.unquote(fixed_url)))414 self.assertEqual(response['location'], 'http://testserver' + fixed_url) 387 415 388 416 @mock.patch('ebpub.streets.models.proper_city') … … 475 503 476 504 def test_filter__invalid_argname(self): 505 # These are ignored. 477 506 url = filter_reverse('crime', [('bogus-key', 'bogus-value')]) 478 507 response = self.client.get(url) 479 self.assertEqual(response.status_code, 404) 480 508 self.assertEqual(response.status_code, 200) 481 509 482 510 @mock.patch('ebpub.db.schemafilters.logger') … … 500 528 def test_filter__pagination__invalid_page(self): 501 529 url = filter_reverse('crime', [('by-status', 'status 9-19')]) 502 url += ' ?page=oops'530 url += '&page=oops' 503 531 response = self.client.get(url) 504 532 self.assertEqual(response.status_code, 404) … … 506 534 def test_filter__pagination__empty_page(self): 507 535 url = filter_reverse('crime', [('by-status', 'status 9-19')]) 508 url += ' ?page=99'536 url += '&page=99' 509 537 response = self.client.get(url) 510 538 self.assertEqual(response.status_code, 404) … … 513 541 def test_filter__pagination__has_more(self, mock_chain): 514 542 url = filter_reverse('crime', [('by-status', 'status 9-19')]) 515 url += ' ?page=2'543 url += '&page=2' 516 544 # We can mock the FilterChain to get a very long list of NewsItems 517 545 # without actually creating them in the db, but it means -
ebpub/ebpub/db/urlresolvers.py
r44188d rf9de3a 19 19 from django.core import urlresolvers 20 20 import posixpath 21 import urllib 21 22 22 23 def filter_reverse(slug, args): … … 29 30 name = a[0] 30 31 values = ','.join(a[1:]) 31 args[i] = '%s=%s' %(name, values)32 args[i] = (name, values) 32 33 else: 34 # No values. 33 35 # This is allowed eg. for showing a list of available 34 36 # Blocks, or Lookup values, etc. 35 args[i] = a[0]37 args[i] = (a[0], '') 36 38 else: 37 assert isinstance(a, basestring) 38 #argstring = urllib.quote(';'.join(args)) #['%s=%s' % (k, v) for (k, v) in args]) 39 argstring = ';'.join(args) #['%s=%s' % (k, v) for (k, v) in args]) 39 raise TypeError("Need a list or tuple, got: %s" % a) 40 40 41 if not argstring.lstrip('/').startswith('filter'):42 argstring = 'filter/%s' % argstring43 41 url = urlresolvers.reverse('ebpub-schema-filter', args=[slug]) 44 url = '%s/%s/' % (url, argstring) 42 # TODO: does this really need hardcodign here? 43 if not url.rstrip('/').endswith('filter'): 44 url = '%s/filter/' % url 45 45 # Normalize duplicate slashes, dots, and the like. 46 46 url = posixpath.normpath(url) + '/' 47 if args: 48 # Normalize a bit. 49 args = sorted(args) 50 querystring = urllib.urlencode(args) 51 url = '%s?%s' % (url, querystring) 47 52 return url 48 53 -
ebpub/ebpub/db/views.py
r704a4d rf9de3a 241 241 # As an optimization, limit the NewsItems to those published in the 242 242 # last few days. 243 filters.update_from_query_params(request) 243 filter_sf_dict = _get_filter_schemafields(schema) 244 filters.update_from_request(filter_sf_dict) 244 245 if not filters.has_key('date'): 245 246 end_date = today() … … 630 631 631 632 633 def _get_filter_schemafields(schema): 634 """Given a Schema, get a sorted mapping of schemafield names to 635 SchemaField instances. 636 637 Only SchemaFields that have is_searchable or is_filter enabled 638 will be returned. 639 """ 640 filter_sf_list = list(SchemaField.objects.filter(schema=schema, is_filter=True).order_by('display_order')) 641 textsearch_sf_list = list(SchemaField.objects.filter(schema=schema, is_searchable=True).order_by('display_order')) 642 # Use SortedDict to preserve the display_order. 643 filter_sf_dict = SortedDict([(sf.name, sf) for sf in filter_sf_list] + [(sf.name, sf) for sf in textsearch_sf_list]) 644 return filter_sf_dict 645 646 632 647 def schema_filter_geojson(request, slug, args_from_url): 633 648 s = get_object_or_404(get_schema_manager(request), slug=slug, is_special_report=False) … … 635 650 return HttpResponse(status=404) 636 651 637 filter_sf_list = list(SchemaField.objects.filter(schema__id=s.id, is_filter=True).order_by('display_order'))638 textsearch_sf_list = list(SchemaField.objects.filter(schema__id=s.id, is_searchable=True).order_by('display_order'))639 640 # Use SortedDict to preserve the display_order.641 filter_sf_dict = SortedDict([(sf.name, sf) for sf in filter_sf_list] + [(sf.name, sf) for sf in textsearch_sf_list])642 643 652 # Determine what filters to apply, based on path and/or query string. 644 653 filterchain = FilterChain(request=request, schema=s) 654 filter_sf_dict = _get_filter_schemafields(s) 645 655 try: 646 filterchain.update_from_request( args_from_url,filter_sf_dict)656 filterchain.update_from_request(filter_sf_dict) 647 657 filters_need_more = filterchain.validate() 648 658 except FilterError: … … 710 720 context['breadcrumbs'] = breadcrumbs.schema_filter(context) 711 721 712 filter_sf_list = list(SchemaField.objects.filter(schema__id=s.id, is_filter=True).order_by('display_order')) 713 textsearch_sf_list = list(SchemaField.objects.filter(schema__id=s.id, is_searchable=True).order_by('display_order')) 714 715 # Use SortedDict to preserve the display_order. 716 filter_sf_dict = SortedDict([(sf.name, sf) for sf in filter_sf_list] + [(sf.name, sf) for sf in textsearch_sf_list]) 722 filter_sf_dict = _get_filter_schemafields(s) 717 723 718 724 # Determine what filters to apply, based on path and/or query string. … … 720 726 context['filters'] = filterchain 721 727 try: 722 filterchain.update_from_request( args_from_url,filter_sf_dict)728 filterchain.update_from_request(filter_sf_dict) 723 729 filters_need_more = filterchain.validate() 724 730 except FilterError, e:
Note: See TracChangeset
for help on using the changeset viewer.
