Skip to content

Commit 0d28dfc

Browse files
committed
Merge branch 'feature/refactor_view' into develop
2 parents 804f66a + 7cb07c0 commit 0d28dfc

File tree

5 files changed

+160
-51
lines changed

5 files changed

+160
-51
lines changed

docs/index.rst

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -715,29 +715,26 @@ Generic views
715715
The ``tagging.views`` module contains views to handle simple cases of
716716
common display logic related to tagging.
717717

718-
``tagging.views.tagged_object_list``
719-
------------------------------------
718+
``tagging.views.TaggedObjectList``
719+
----------------------------------
720720

721721
**Description:**
722722

723723
A view that displays a list of objects for a given model which have a
724724
given tag. This is a thin wrapper around the
725-
``django.views.generic.list_detail.object_list`` view, which takes a
725+
``django.views.generic.list.ListView`` view, which takes a
726726
model and a tag as its arguments (in addition to the other optional
727-
arguments supported by ``object_list``), building the appropriate
727+
arguments supported by ``ListView``), building the appropriate
728728
``QuerySet`` for you instead of expecting one to be passed in.
729729

730730
**Required arguments:**
731731

732-
* ``queryset_or_model``: A ``QuerySet`` or Django model class for the
733-
object which will be listed.
734-
735732
* ``tag``: The tag which objects of the given model must have in
736733
order to be listed.
737734

738735
**Optional arguments:**
739736

740-
Please refer to the `object_list documentation`_ for additional optional
737+
Please refer to the `ListView documentation`_ for additional optional
741738
arguments which may be given.
742739

743740
* ``related_tags``: If ``True``, a ``related_tags`` context variable
@@ -751,12 +748,12 @@ arguments which may be given.
751748

752749
**Template context:**
753750

754-
Please refer to the `object_list documentation`_ for additional
751+
Please refer to the `ListView documentation`_ for additional
755752
template context variables which may be provided.
756753

757754
* ``tag``: The ``Tag`` instance for the given tag.
758755

759-
.. _`object_list documentation`: http://docs.djangoproject.com/en/dev/ref/generic-views/#django-views-generic-list-detail-object-list
756+
.. _`ListView documentation`: https://docs.djangoproject.com/en/1.8/ref/class-based-views/generic-display/#listview
760757

761758
Example usage
762759
~~~~~~~~~~~~~
@@ -766,15 +763,13 @@ list items of a particular model class which have a given tag::
766763

767764
from django.conf.urls.defaults import *
768765

769-
from tagging.views import tagged_object_list
766+
from tagging.views import TaggedObjectList
770767

771768
from shop.apps.products.models import Widget
772769

773770
urlpatterns = patterns('',
774-
url(r'^widgets/tag/(?P<tag>[^/]+)/$',
775-
tagged_object_list,
776-
dict(queryset_or_model=Widget, paginate_by=10, allow_empty=True,
777-
template_object_name='widget'),
771+
url(r'^widgets/tag/(?P<tag>[^/]+(?u))/$',
772+
TaggedObjectList.as_view(model=Widget, paginate_by=10, allow_empty=True),
778773
name='widget_tag_detail'),
779774
)
780775

@@ -783,13 +778,10 @@ perform filtering of the objects which are listed::
783778

784779
from myapp.models import People
785780

786-
from tagging.views import tagged_object_list
781+
from tagging.views import TaggedObjectList
787782

788-
def tagged_people(request, country_code, tag):
783+
class TaggedPeopleFilteredList(TaggedObjectList):
789784
queryset = People.objects.filter(country__code=country_code)
790-
return tagged_object_list(request, queryset, tag, paginate_by=25,
791-
allow_empty=True, template_object_name='people')
792-
793785

794786
Template tags
795787
=============

tagging/tests/tests.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from django.utils import six
88
from django.db.models import Q
99
from django.test import TestCase
10+
from django.test.utils import override_settings
11+
from django.core.exceptions import ImproperlyConfigured
1012

1113
from tagging import settings
1214
from tagging.forms import TagField
@@ -1031,3 +1033,61 @@ def test_tag_d_validation(self):
10311033
self.assertRaises(
10321034
forms.ValidationError, t.clean,
10331035
'foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar')
1036+
1037+
1038+
#########
1039+
# Views #
1040+
#########
1041+
1042+
1043+
@override_settings(
1044+
ROOT_URLCONF='tagging.tests.urls',
1045+
TEMPLATE_LOADERS=(
1046+
'tagging.tests.utils.VoidLoader',
1047+
),
1048+
)
1049+
class TestTaggedObjectList(TestCase):
1050+
1051+
def setUp(self):
1052+
self.a1 = Article.objects.create(name='article 1')
1053+
self.a2 = Article.objects.create(name='article 2')
1054+
Tag.objects.update_tags(self.a1, 'static tag test')
1055+
Tag.objects.update_tags(self.a2, 'static test')
1056+
1057+
def get_view(self, url, queries=1, code=200,
1058+
expected_items=1,
1059+
friendly_context='article_list',
1060+
template='tests/article_list.html'):
1061+
with self.assertNumQueries(queries):
1062+
response = self.client.get(url)
1063+
self.assertEquals(response.status_code, code)
1064+
1065+
if code == 200:
1066+
self.assertTrue(isinstance(response.context['tag'], Tag))
1067+
self.assertEqual(len(response.context['object_list']),
1068+
expected_items)
1069+
self.assertEqual(response.context['object_list'],
1070+
response.context[friendly_context])
1071+
self.assertTemplateUsed(response, template)
1072+
return response
1073+
1074+
def test_view_static(self):
1075+
self.get_view('/static/', expected_items=2)
1076+
1077+
def test_view_dynamic(self):
1078+
self.get_view('/tag/', expected_items=1)
1079+
1080+
def test_view_related(self):
1081+
response = self.get_view('/static/related/',
1082+
queries=2, expected_items=2)
1083+
self.assertEquals(len(response.context['related_tags']), 2)
1084+
1085+
def test_view_no_queryset_no_model(self):
1086+
self.assertRaises(ImproperlyConfigured, self.get_view,
1087+
'/no-query-no-model/')
1088+
1089+
def test_view_no_tag(self):
1090+
self.assertRaises(AttributeError, self.get_view, '/no-tag/')
1091+
1092+
def test_view_404(self):
1093+
self.get_view('/unavailable/', code=404)

tagging/tests/urls.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Test urls for tagging."""
2+
from django.conf.urls import url
3+
4+
from tagging.views import TaggedObjectList
5+
from tagging.tests.models import Article
6+
7+
8+
class StaticTaggedObjectList(TaggedObjectList):
9+
tag = 'static'
10+
queryset = Article.objects.all()
11+
12+
13+
urlpatterns = [
14+
url(r'^static/$', StaticTaggedObjectList.as_view()),
15+
url(r'^static/related/$', StaticTaggedObjectList.as_view(
16+
related_tags=True)),
17+
url(r'^no-tag/$', TaggedObjectList.as_view(model=Article)),
18+
url(r'^no-query-no-model/$', TaggedObjectList.as_view()),
19+
url(r'^(?P<tag>[^/]+(?u))/$', TaggedObjectList.as_view(model=Article)),
20+
]

tagging/tests/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
Tests utils for tagging.
3+
"""
4+
5+
from django.template.loader import BaseLoader
6+
7+
class VoidLoader(BaseLoader):
8+
"""
9+
Template loader which is always returning
10+
an empty template.
11+
"""
12+
is_usable = True
13+
_accepts_engine_in_init = True
14+
15+
def load_template_source(self, template_name, template_dirs=None):
16+
return ('', 'voidloader:%s' % template_name)

tagging/views.py

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
Tagging related views.
33
"""
44
from django.http import Http404
5+
from django.views.generic.list import ListView
56
from django.utils.translation import ugettext as _
6-
from django.views.generic.list_detail import object_list
7+
from django.core.exceptions import ImproperlyConfigured
78

89
from tagging.models import Tag
910
from tagging.models import TaggedItem
1011
from tagging.utils import get_tag
12+
from tagging.utils import get_queryset_and_model
1113

1214

13-
def tagged_object_list(request, queryset_or_model=None, tag=None,
14-
related_tags=False, related_tag_counts=True, **kwargs):
15+
class TaggedObjectList(ListView):
1516
"""
1617
A thin wrapper around
17-
``django.views.generic.list_detail.object_list`` which creates a
18+
``django.views.generic.list.ListView`` which creates a
1819
``QuerySet`` containing instances of the given queryset or model
1920
tagged with the given tag.
2021
@@ -28,30 +29,50 @@ def tagged_object_list(request, queryset_or_model=None, tag=None,
2829
tag will have a ``count`` attribute indicating the number of items
2930
which have it in addition to the given tag.
3031
"""
31-
if queryset_or_model is None:
32-
try:
33-
queryset_or_model = kwargs.pop('queryset_or_model')
34-
except KeyError:
35-
raise AttributeError(
36-
_('tagged_object_list must be called '
37-
'with a queryset or a model.'))
38-
39-
if tag is None:
40-
try:
41-
tag = kwargs.pop('tag')
42-
except KeyError:
43-
raise AttributeError(
44-
_('tagged_object_list must be called with a tag.'))
45-
46-
tag_instance = get_tag(tag)
47-
if tag_instance is None:
48-
raise Http404(_('No Tag found matching "%s".') % tag)
49-
queryset = TaggedItem.objects.get_by_model(queryset_or_model, tag_instance)
50-
if 'extra_context' not in kwargs:
51-
kwargs['extra_context'] = {}
52-
kwargs['extra_context']['tag'] = tag_instance
53-
if related_tags:
54-
kwargs['extra_context']['related_tags'] = \
55-
Tag.objects.related_for_model(tag_instance, queryset_or_model,
56-
counts=related_tag_counts)
57-
return object_list(request, queryset, **kwargs)
32+
tag = None
33+
related_tags = False
34+
related_tag_counts = True
35+
36+
def get_tag(self):
37+
if self.tag is None:
38+
try:
39+
self.tag = self.kwargs.pop('tag')
40+
except KeyError:
41+
raise AttributeError(
42+
_('TaggedObjectList must be called with a tag.'))
43+
44+
tag_instance = get_tag(self.tag)
45+
if tag_instance is None:
46+
raise Http404(_('No Tag found matching "%s".') % self.tag)
47+
48+
return tag_instance
49+
50+
def get_queryset_or_model(self):
51+
if self.queryset is not None:
52+
return self.queryset
53+
elif self.model is not None:
54+
return self.model
55+
else:
56+
raise ImproperlyConfigured(
57+
"%(cls)s is missing a QuerySet. Define "
58+
"%(cls)s.model, %(cls)s.queryset, or override "
59+
"%(cls)s.get_queryset_or_model()." % {
60+
'cls': self.__class__.__name__
61+
}
62+
)
63+
64+
def get_queryset(self):
65+
self.queryset_or_model = self.get_queryset_or_model()
66+
self.tag_instance = self.get_tag()
67+
return TaggedItem.objects.get_by_model(
68+
self.queryset_or_model, self.tag_instance)
69+
70+
def get_context_data(self, **kwargs):
71+
context = super(TaggedObjectList, self).get_context_data(**kwargs)
72+
context['tag'] = self.tag_instance
73+
74+
if self.related_tags:
75+
queryset, model = get_queryset_and_model(self.queryset_or_model)
76+
context['related_tags'] = Tag.objects.related_for_model(
77+
self.tag_instance, model, counts=self.related_tag_counts)
78+
return context

0 commit comments

Comments
 (0)