neithere / django-view-shortcuts
A set of shortcuts for Django views
Clone this repository (size: 74.4 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/neithere/django-view-shortcuts/
| commit 47: | c8efddc05532 |
| parent 46: | 72b89c4d33d1 |
| branch: | default |
Deprecated tuple notation, added facet() instead.
- View neithere's profile
-
neithere's public repos »
- plasma-test
- glasnaegel
- django-autoslug
- pyrant
- datashaping
- libpluck
- scripts
- raindrop2
- pytyrant-rearranged
- nuvola
- django-ljsync
- django-view-shortcuts
- django-timeinput
- textutils
- glashammer-patches
- feedzilla
- django-common
- eav-django
- pytyrant
- django-todoist
- svarga-schemaless
- django-organizer-iad
- django-navigation
- django-organizer-gtd
- django-harness
- pymodels
- Send message
7 months ago
Changed (Δ2.1 KB):
raw changeset »
view_shortcuts/__init__.py (1 lines added, 1 lines removed)
view_shortcuts/filters.py (136 lines added, 56 lines removed)
view_shortcuts/tests.py (19 lines added, 14 lines removed)
Up to file-list view_shortcuts/__init__.py:
12 |
12 |
__author__ = 'Andy Mikhailenko' |
13 |
13 |
__license__ = 'GNU Lesser General Public License (GPL), Version 3' |
14 |
14 |
__url__ = 'http://bitbucket.org/neithere/django-view-shortcuts/' |
15 |
__version__ = '1. |
|
15 |
__version__ = '1.2' |
Up to file-list view_shortcuts/filters.py:
| … | … | @@ -17,13 +17,14 @@ from django.db import models |
17 |
17 |
from django.utils.translation import ugettext_lazy as _ |
18 |
18 |
from decorators import cached_property |
19 |
19 |
|
20 |
||
20 |
21 |
def filter_date(items, field_name, year, month=None, day=None): |
21 |
22 |
""" |
22 |
23 |
Filters given queryset by date if any provided. Accepts three scopes: year, month and day. |
23 |
24 |
Similar to date_based generic view but for ordinary views. |
24 |
||
25 |
||
25 |
26 |
Instead of: |
26 |
||
27 |
||
27 |
28 |
def my_entry_list(request, year=None, month=None, day=None): |
28 |
29 |
entries = Entry.objects.all() |
29 |
30 |
if year and month and day: |
| … | … | @@ -32,9 +33,9 @@ def filter_date(items, field_name, year, |
32 |
33 |
entries = entries.filter(pub_date__month=month) |
33 |
34 |
if year: |
34 |
35 |
entries = entries.filter(pub_date__year=year) |
35 |
||
36 |
||
36 |
37 |
...You can just write: |
37 |
||
38 |
||
38 |
39 |
def my_entry_list(request, year=None, month=None, day=None): |
39 |
40 |
entries = Entry.objects.all() |
40 |
41 |
entries = filter_date(entries, 'pub_date', year, month, day) |
| … | … | @@ -50,33 +51,33 @@ def filter_date(items, field_name, year, |
50 |
51 |
def filter_date_range(items, start, end): |
51 |
52 |
""" |
52 |
53 |
Filters given queryset by date range. |
53 |
||
54 |
||
54 |
55 |
Useful for queries with lookups (i.e. 'foo__bar') where nested date lookups |
55 |
56 |
are not possible (e.g. things like 'foo__somedate__year__gte' are not allowed). |
56 |
||
57 |
||
57 |
58 |
Usage: |
58 |
59 |
filter_date_range(queryset, start, end) |
59 |
60 |
where "start" and "end" are tuples of this form: (field_name, year, [month, [day]) |
60 |
||
61 |
||
61 |
62 |
Example: |
62 |
||
63 |
||
63 |
64 |
def my_entry_list(request, from_year=None, to_year=None): |
64 |
65 |
people = Entry.objects.all() |
65 |
66 |
# show all entries of this year |
66 |
67 |
people = filter_date_range(people, ('joined', from_year), ('left', to_year)) |
67 |
68 |
""" |
68 |
||
69 |
||
69 |
70 |
def __make_qargs(mode, field_name, year, month, day): |
70 |
71 |
q_field = '%s__%s' % (field_name, mode) |
71 |
72 |
q_date = '%s-%s-%s' % (year, month, day) |
72 |
73 |
return {q_field:q_date} |
73 |
||
74 |
||
74 |
75 |
def __make_qargs_till(field_name, year, month='12', day='31'): |
75 |
76 |
return __make_qargs('lte', field_name, year, month, day) |
76 |
||
77 |
||
77 |
78 |
def __make_qargs_from(field_name, year, month='01', day='01'): |
78 |
79 |
return __make_qargs('gte', field_name, year, month, day) |
79 |
||
80 |
||
80 |
81 |
qargs = dict(__make_qargs_from(*start), **__make_qargs_till(*end)) |
81 |
82 |
items = items.filter(**qargs) |
82 |
83 |
return items |
| … | … | @@ -85,18 +86,18 @@ def filter_field(items, field_name, valu |
85 |
86 |
""" |
86 |
87 |
Filters given queryset by given field. |
87 |
88 |
This filter does not significantly shorten your code but does make it a bit more readable. |
88 |
||
89 |
||
89 |
90 |
Instead of: |
90 |
||
91 |
||
91 |
92 |
def my_entry_list(request, foo=None, bar=None): |
92 |
93 |
entries = Entry.objects.all() |
93 |
94 |
if foo: |
94 |
95 |
entries = entries.filter(foo=foo) |
95 |
96 |
if bar: |
96 |
97 |
entries = entries.filter(bar=bar) |
97 |
||
98 |
||
98 |
99 |
...You can write: |
99 |
||
100 |
||
100 |
101 |
def my_entry_list(request, foo=None, bar=None): |
101 |
102 |
entries = Entry.objects.all() |
102 |
103 |
entries = filter_field(entries, 'foo', foo) |
| … | … | @@ -114,18 +115,18 @@ def filter_param(items, request, field_n |
114 |
115 |
Filters given queryset by given field with its value automatically taken from |
115 |
116 |
given Request parameter. If the param is not specified, it's assumed to be |
116 |
117 |
of the same name with the field. |
117 |
||
118 |
||
118 |
119 |
Instead of: |
119 |
||
120 |
||
120 |
121 |
def my_entry_list(request): |
121 |
122 |
entries = Entry.objects.all() |
122 |
123 |
if request.GET.get('foo'): |
123 |
124 |
entries = entries.filter(foo=request.GET.get('foo')) |
124 |
125 |
if request.GET.get('barbar'): |
125 |
126 |
entries = entries.filter(bar=request.GET.get('barbar')) |
126 |
||
127 |
||
127 |
128 |
You can write: |
128 |
||
129 |
||
129 |
130 |
def my_entry_list(request): |
130 |
131 |
entries = Entry.objects.all() |
131 |
132 |
entries = filter_param(entries, request, 'foo') |
| … | … | @@ -139,6 +140,26 @@ def filter_param(items, request, field_n |
139 |
140 |
items = items.filter(**{field_name:value}) |
140 |
141 |
return items |
141 |
142 |
|
143 |
||
144 |
class facet(dict): |
|
145 |
""""A dictionary representing a facet filter settings. |
|
146 |
A Filter instance will be built using them. |
|
147 |
||
148 |
Example: |
|
149 |
||
150 |
>>> facets = ( |
|
151 |
... facet('category'), |
|
152 |
... facet('author__pk', 'author', AlphabetRelationFilter), |
|
153 |
... ) |
|
154 |
>>> FilterList(qs, request, facets) |
|
155 |
""" |
|
156 |
def __init__(self, lookup, param=None, kind=None): |
|
157 |
super(dict, self).__init__() |
|
158 |
if kind: assert issubclass(kind, Filter) |
|
159 |
self['lookup'] = lookup |
|
160 |
self['param'] = param or lookup |
|
161 |
self['kind'] = kind or Filter |
|
162 |
||
142 |
163 |
class FilterList(list): |
143 |
164 |
"""Filters given queryset by multiple fields with their values automatically |
144 |
165 |
taken from given HttpRequest parameters. If a parameter is not specified, |
| … | … | @@ -244,17 +265,23 @@ class FilterList(list): |
244 |
265 |
def _generate_filters(request, qs, params, single, sort_by_usage): |
245 |
266 |
single_triggered = False |
246 |
267 |
for p in params: |
247 |
|
|
268 |
klass = Filter |
|
269 |
if isinstance(p, facet): |
|
270 |
lookup, param, klass = p['lookup'], p['param'], p['kind'] |
|
271 |
elif isinstance(p, (tuple,list)): |
|
272 |
warnings.warn("using tuple for lookup/param coupling is"\ |
|
273 |
"deprecated, use filters.facet() instead.", |
|
274 |
DeprecationWarning, 2) |
|
248 |
275 |
lookup, param = p |
249 |
276 |
else: |
250 |
277 |
lookup = param = p |
278 |
active = False |
|
251 |
279 |
value = request.GET.get(param) |
252 |
f = Filter.create(param, qs=qs, lookup=lookup, active=False, sort_by_usage=sort_by_usage) |
|
253 |
280 |
if value and not single_triggered: |
254 |
281 |
if single: |
255 |
282 |
single_triggered = True |
256 |
f.active = True |
|
257 |
f.value = value |
|
283 |
active = True |
|
284 |
f = klass.create(param, qs, lookup, value, active, sort_by_usage) |
|
258 |
285 |
yield f |
259 |
286 |
super(FilterList, self).__init__( |
260 |
287 |
_generate_filters(request, qs, params, single, sort_by_usage) |
| … | … | @@ -308,44 +335,70 @@ class FilterList(list): |
308 |
335 |
) |
309 |
336 |
return self._qs.model.objects.filter(**lookup_params) |
310 |
337 |
|
338 |
||
311 |
339 |
class Filter(object): |
312 |
340 |
""" A facet filter. Objects of this class are instantiated by filter_params() |
313 |
341 |
and returned along with the queryset. |
314 |
342 |
Filter objects can then be passed to template and used to generate UI |
315 |
343 |
for tuning the queryset. |
316 |
||
344 |
||
317 |
345 |
By default the choices are sorted by usage (most referenced on top). To reset |
318 |
this behaviour, set sort_by_usage=False, |
|
319 |
||
346 |
this behaviour, set sort_by_usage=False, |
|
347 |
||
320 |
348 |
Usage: see FilterList. |
321 |
349 |
|
322 |
350 |
Each filter subclass knows how to display a filter for a field that passes a |
323 |
351 |
certain test -- e.g. being a DateField or ForeignKey. |
324 |
352 |
""" |
325 |
filter_specs = [] |
|
326 |
def __init__(self, param, qs, lookup, field, active, value, sort_by_usage): |
|
353 |
_cached_fields = {} |
|
354 |
_filter_specs = [] |
|
355 |
def __init__(self, param, qs, lookup, value, active=False, sort_by_usage=False): |
|
327 |
356 |
self.param = param |
328 |
357 |
self.qs = qs |
329 |
358 |
self.lookup = lookup |
330 |
self. |
|
359 |
self.value = value |
|
331 |
360 |
self.active = active |
332 |
self.value = value |
|
333 |
361 |
self.sort_by_usage = sort_by_usage |
362 |
self.field = self.resolve_field(qs, lookup) |
|
334 |
363 |
|
335 |
364 |
def __repr__(self): |
336 |
365 |
return u'<%s "%s": %s>' % (self.__class__.__name__, self.param, self.active) |
337 |
366 |
|
338 |
367 |
@classmethod |
339 |
def register(cls, test, factory): |
|
340 |
cls.filter_specs.append((test, factory)) |
|
368 |
def register(cls, factory): |
|
369 |
"""Registers Filter subclass so that it can be automatically chosen if |
|
370 |
no concrete class is explicitly specified. Note that classes are checked |
|
371 |
one by one in the order they are registered, and the first one which passes |
|
372 |
the test (i.e. which ``suitable_for()`` method returns True for given |
|
373 |
field) is chosen. The last one should be the universal AllValuesFilter. |
|
374 |
If your class is registered after it, it will *not* be used. If your |
|
375 |
class appears to be "all values" too, do not register it -- just specify |
|
376 |
it in your views in a facet. |
|
377 |
""" |
|
378 |
cls._filter_specs.append(factory) |
|
379 |
||
380 |
@staticmethod |
|
381 |
def resolve_field(qs, lookup): |
|
382 |
f = None |
|
383 |
if lookup not in Filter._cached_fields: |
|
384 |
# we need the "author" part of "author__pk" lookup |
|
385 |
f = qs.model._meta.get_field(lookup.split('__')[0]) |
|
386 |
return Filter._cached_fields.setdefault(lookup, f) |
|
341 |
387 |
|
342 |
388 |
@classmethod |
343 |
def create(cls, param, qs=None, lookup=None, active=False, value=None, sort_by_usage=True): |
|
344 |
fn = lookup.split('__')[0] # we need the "author" part of "author__pk" |
|
345 |
field = qs.model._meta.get_field(fn) |
|
346 |
for test, factory in cls.filter_specs: |
|
347 |
if test(field): |
|
348 |
return factory(param, qs, lookup, field, active, value, sort_by_usage) |
|
389 |
def create(cls, param, qs, lookup, value, active=False, sort_by_usage=True): |
|
390 |
# chosen by user |
|
391 |
if not cls == Filter: |
|
392 |
return cls(param, qs, lookup, value, active, sort_by_usage) |
|
393 |
# autoselect |
|
394 |
field = cls.resolve_field(qs,lookup) |
|
395 |
for factory in cls._filter_specs: |
|
396 |
if factory.suitable_for(field): |
|
397 |
return factory(param, qs, lookup, value, active, sort_by_usage) |
|
398 |
||
399 |
@classmethod |
|
400 |
def suitable_for(cls, field): |
|
401 |
return True |
|
349 |
402 |
|
350 |
403 |
@cached_property |
351 |
404 |
def urlencode(self): |
| … | … | @@ -371,24 +424,24 @@ class Filter(object): |
371 |
424 |
"""Returns possible choices, each annotated with the number of linked |
372 |
425 |
objects. Multiple FKs from one model to another are supported as well as |
373 |
426 |
explicit choice lists and implicit ones (i.e. any possible values). |
374 |
||
427 |
||
375 |
428 |
Resulting list contains FilterChoice objects. |
376 |
||
429 |
||
377 |
430 |
An existing choice will be excluded from results if: |
378 |
||
431 |
||
379 |
432 |
a) it is not used by any object in current queryset; |
380 |
||
433 |
||
381 |
434 |
b) it is used but is not in the field's explicit list of choices |
382 |
435 |
(i.e. the "choices" keyword). |
383 |
436 |
""" |
384 |
437 |
return list(self.generate_choices()) |
385 |
||
438 |
||
386 |
439 |
def get_active_choices(self): |
387 |
440 |
"Returns list of currently selected options for this filter." |
388 |
441 |
for c in self.choices: |
389 |
442 |
if c.active: |
390 |
443 |
yield c |
391 |
||
444 |
||
392 |
445 |
def get_first_active_choice(self): |
393 |
446 |
"Returns one of currently selected options for this filter (usually enough)." |
394 |
447 |
for c in self.get_active_choices(): |
| … | … | @@ -398,20 +451,27 @@ class Filter(object): |
398 |
451 |
"""Counts related objects for each choice in given queryset (i.e. discover |
399 |
452 |
how popular is each option). Returns annotated queryset. |
400 |
453 |
""" |
401 |
# |
|
454 |
# |
|
402 |
455 |
choices = choices.annotate(items_count=models.Count(by)) |
403 |
456 |
if self.sort_by_usage: |
404 |
457 |
choices = choices.order_by('-items_count') |
405 |
458 |
return choices |
406 |
459 |
|
460 |
||
461 |
@Filter.register |
|
407 |
462 |
class RelationFilter(Filter): |
463 |
@classmethod |
|
464 |
def suitable_for(cls, field): |
|
465 |
if field.rel: |
|
466 |
return True |
|
467 |
||
408 |
468 |
def extra_title(self): |
409 |
469 |
if isinstance(self.field, models.ManyToManyField): |
410 |
470 |
return self.field.rel.to._meta.verbose_name |
411 |
471 |
|
412 |
472 |
def generate_choices(self): |
413 |
473 |
# TODO: when multiple filters are active, count only intersections (? - can be heavy) |
414 |
||
474 |
||
415 |
475 |
related_name = getattr(self.field.rel, 'related_name', None) or \ |
416 |
476 |
self.qs.model._meta.module_name |
417 |
477 |
# get all possible choices |
| … | … | @@ -425,22 +485,39 @@ class RelationFilter(Filter): |
425 |
485 |
|
426 |
486 |
for c in choices: |
427 |
487 |
yield FilterChoice(self, unicode(c), c.pk, c.items_count) |
428 |
Filter.register(lambda f: f.rel, RelationFilter) |
|
488 |
||
429 |
489 |
|
430 |
490 |
''' |
491 |
@Filter.register |
|
431 |
492 |
class DateFadeoutFilter(Filter): |
432 |
493 |
"Represents dates as single-level categories by remoteness from now." |
494 |
||
495 |
@staticmethod |
|
496 |
def suitable_for(field): |
|
497 |
return isinstance(f, models.DateField) |
|
498 |
||
433 |
499 |
# see django.contrib.admin.filterspecs.DateFieldFilterSpec |
434 |
500 |
pass |
435 |
Filter.register(lambda f: isinstance(f, models.DateField), DateDrilldownFilter) |
|
436 |
501 |
|
502 |
||
503 |
@Filter.register |
|
437 |
504 |
class DateDrilldownFilter(Filter): |
438 |
505 |
"Represents dates as nested levels for year, month and day." |
506 |
||
507 |
@staticmethod |
|
508 |
def suitable_for(field): |
|
509 |
return isinstance(f, models.DateField) |
|
510 |
||
439 |
511 |
pass |
440 |
Filter.register(lambda f: isinstance(f, models.DateField), DateDrilldownFilter) |
|
441 |
512 |
''' |
442 |
513 |
|
514 |
||
515 |
@Filter.register |
|
443 |
516 |
class BooleanFilter(Filter): |
517 |
@staticmethod |
|
518 |
def suitable_for(field): |
|
519 |
return isinstance(field, models.BooleanField) |
|
520 |
||
444 |
521 |
def generate_choices(self): |
445 |
522 |
# retrieve unique values and count how many times each is used |
446 |
523 |
choices = self.qs.values(self.lookup).distinct() |
| … | … | @@ -455,8 +532,9 @@ class BooleanFilter(Filter): |
455 |
532 |
v = unicode(c.get(self.lookup)) |
456 |
533 |
if v == val: |
457 |
534 |
yield FilterChoice(self, name, val, c['items_count']) |
458 |
Filter.register(lambda f: isinstance(f, models.BooleanField), BooleanFilter) |
|
459 |
535 |
|
536 |
||
537 |
@Filter.register |
|
460 |
538 |
class AllValuesFilter(Filter): |
461 |
539 |
def generate_choices(self): |
462 |
540 |
# retrieve unique values and count how many times each is used |
| … | … | @@ -476,7 +554,7 @@ class AllValuesFilter(Filter): |
476 |
554 |
|
477 |
555 |
for c in choices: |
478 |
556 |
yield FilterChoice(self, _title(c), _value(c), c['items_count']) |
479 |
Filter.register(lambda f: True, AllValuesFilter) |
|
557 |
||
480 |
558 |
|
481 |
559 |
class FilterChoice(object): |
482 |
560 |
def __init__(self, filter, title, value, items_count): |
| … | … | @@ -485,13 +563,14 @@ class FilterChoice(object): |
485 |
563 |
self.title = title |
486 |
564 |
self.value = value |
487 |
565 |
self.items_count = items_count |
488 |
||
489 |
__repr__ = lambda self: u'<Choice %s="%s">' % (self.filter.param, self.value) |
|
490 |
||
566 |
||
567 |
def __repr__(self): |
|
568 |
return u'<Choice %s="%s">' % (self.filter.param, self.value) |
|
569 |
||
491 |
570 |
@cached_property |
492 |
571 |
def urlencode(self): |
493 |
572 |
return urlencode({self.filter.param: self.value}) |
494 |
||
573 |
||
495 |
574 |
@cached_property |
496 |
575 |
def active(self): |
497 |
576 |
"Returns True if the choice value equals to the filter's current value." |
| … | … | @@ -500,6 +579,7 @@ class FilterChoice(object): |
500 |
579 |
except TypeError, ValueError: |
501 |
580 |
return False |
502 |
581 |
|
582 |
||
503 |
583 |
def filter_params(qs, request, params, single=False): |
504 |
584 |
warnings.warn("filter_params() is deprecated, use FilterList() instead.", |
505 |
585 |
DeprecationWarning, 2) |
Up to file-list view_shortcuts/tests.py:
| … | … | @@ -21,10 +21,15 @@ __doc__=""" |
21 |
21 |
>>> s3.category=[c2] |
22 |
22 |
>>> s3.save() |
23 |
23 |
>>> qs = Story.objects.all() |
24 |
>>> params = ('category', 'author', 'status', 'paid') |
|
25 |
>>> from view_shortcuts.filters import FilterList |
|
24 |
>>> from view_shortcuts.filters import FilterList, facet, RelationFilter |
|
25 |
>>> filter_settings = ( |
|
26 |
... facet('category'), |
|
27 |
... facet('author__id', 'author', RelationFilter), # redundant manual setting |
|
28 |
... facet('status'), |
|
29 |
... facet('paid') |
|
30 |
... ) |
|
26 |
31 |
>>> request = mock_request() |
27 |
>>> filters = FilterList(request, qs, |
|
32 |
>>> filters = FilterList(request, qs, filter_settings) |
|
28 |
33 |
>>> isinstance(filters, FilterList) |
29 |
34 |
True |
30 |
35 |
>>> len(filters) |
| … | … | @@ -36,7 +41,7 @@ 4 |
36 |
41 |
>>> filters.object_list |
37 |
42 |
[<Story: s1>, <Story: s2>, <Story: s3>] |
38 |
43 |
>>> request = mock_request(author=a1.pk) |
39 |
>>> filters = FilterList(request, qs, |
|
44 |
>>> filters = FilterList(request, qs, filter_settings) |
|
40 |
45 |
>>> filters |
41 |
46 |
[<RelationFilter "category": False>, <RelationFilter "author": True>, <AllValuesFilter "status": False>, <BooleanFilter "paid": False>] |
42 |
47 |
>>> filters.active |
| … | … | @@ -46,12 +51,12 @@ 4 |
46 |
51 |
>>> filters.object_list |
47 |
52 |
[<Story: s1>, <Story: s3>] |
48 |
53 |
>>> request = mock_request(author=a1.pk, status=Story.PUBLISHED) |
49 |
>>> filters = FilterList(request, qs, |
|
54 |
>>> filters = FilterList(request, qs, filter_settings) |
|
50 |
55 |
>>> filters.active |
51 |
56 |
[<RelationFilter "author": True>, <AllValuesFilter "status": True>] |
52 |
57 |
>>> filters.urlencode |
53 |
58 |
'author=1&status=pub' |
54 |
>>> filters = FilterList(request, qs, |
|
59 |
>>> filters = FilterList(request, qs, filter_settings) |
|
55 |
60 |
>>> filters |
56 |
61 |
[<RelationFilter "category": False>, <RelationFilter "author": True>, <AllValuesFilter "status": True>, <BooleanFilter "paid": False>] |
57 |
62 |
>>> filters.object_list |
| … | … | @@ -97,7 +102,7 @@ Paid: [paid=None] |
97 |
102 |
- yes (2) --> [paid=True] |
98 |
103 |
- no (1) --> [paid=False] |
99 |
104 |
>>> qs_predefined = Story.objects.filter(status=Story.PUBLISHED) |
100 |
>>> filters = FilterList(request, qs_predefined, |
|
105 |
>>> filters = FilterList(request, qs_predefined, filter_settings) |
|
101 |
106 |
>>> for f in filters: |
102 |
107 |
... print u'%s: [%s]' % (f.title, f.urlencode) |
103 |
108 |
... for c in f.choices: |
| … | … | @@ -114,7 +119,7 @@ Paid: [paid=None] |
114 |
119 |
- yes (1) --> [paid=True] |
115 |
120 |
- no (1) --> [paid=False] |
116 |
121 |
>>> qs_predefined = Story.objects.filter(author__name__contains='J') |
117 |
>>> filters = FilterList(mock_request(status=Story.PUBLISHED), qs_predefined, |
|
122 |
>>> filters = FilterList(mock_request(status=Story.PUBLISHED), qs_predefined, filter_settings) |
|
118 |
123 |
>>> filters._qs # predefined query |
119 |
124 |
[<Story: s1>, <Story: s3>] |
120 |
125 |
>>> filters.clean_query # query made from scratch, no traces of predefined stuff |
| … | … | @@ -167,19 +172,19 @@ class Story(Model): |
167 |
172 |
class RequestFactory(Client): |
168 |
173 |
""" |
169 |
174 |
Class that lets you create mock Request objects for use in testing. |
170 |
||
175 |
||
171 |
176 |
Usage: |
172 |
||
177 |
||
173 |
178 |
rf = RequestFactory() |
174 |
179 |
get_request = rf.get('/hello/') |
175 |
180 |
post_request = rf.post('/submit/', {'foo': 'bar'}) |
176 |
||
181 |
||
177 |
182 |
This class re-uses the django.test.client.Client interface, docs here: |
178 |
183 |
http://www.djangoproject.com/documentation/testing/#the-test-client |
179 |
||
180 |
Once you have a request object you can pass it to any view function, |
|
184 |
||
185 |
Once you have a request object you can pass it to any view function, |
|
181 |
186 |
just as if that view had been hooked up using a URLconf. |
182 |
||
187 |
||
183 |
188 |
Source: http://www.djangosnippets.org/snippets/963/ |
184 |
189 |
""" |
185 |
190 |
def request(self, **request): |
