diff --git a/categories/admin.py b/categories/admin.py index 65081d4..e5807f8 100644 --- a/categories/admin.py +++ b/categories/admin.py @@ -32,7 +32,7 @@ if RELATION_MODELS: class CategoryAdminForm(CategoryBaseAdminForm): class Meta: model = Category - + def clean_alternate_title(self): if self.instance is None or not self.cleaned_data['alternate_title']: return self.cleaned_data['name'] @@ -48,7 +48,7 @@ class CategoryAdmin(CategoryBaseAdmin): 'fields': ('parent', 'name', 'thumbnail', 'active') }), ('Meta Data', { - 'fields': ('alternate_title', 'alternate_url', 'description', + 'fields': ('alternate_title', 'alternate_url', 'description', 'meta_keywords', 'meta_extra'), 'classes': ('collapse',), }), @@ -57,10 +57,10 @@ class CategoryAdmin(CategoryBaseAdmin): 'classes': ('collapse',), }), ) - + if RELATION_MODELS: - inlines = [InlineCategoryRelation,] - + inlines = [InlineCategoryRelation, ] + class Media: js = (JAVASCRIPT_URL + 'genericcollections.js',) diff --git a/categories/base.py b/categories/base.py index 4d829a1..941f009 100644 --- a/categories/base.py +++ b/categories/base.py @@ -16,6 +16,7 @@ from mptt.managers import TreeManager from .editor.tree_editor import TreeEditor from .settings import ALLOW_SLUG_CHANGE, SLUG_TRANSLITERATOR + class CategoryManager(models.Manager): """ A manager that adds an "active()" method for all active categories @@ -32,44 +33,44 @@ class CategoryBase(MPTTModel): This base model includes the absolute bare bones fields and methods. One could simply subclass this model and do nothing else and it should work. """ - parent = TreeForeignKey('self', - blank=True, - null=True, - related_name="children", + parent = TreeForeignKey('self', + blank=True, + null=True, + related_name="children", verbose_name='Parent') name = models.CharField(max_length=100) slug = models.SlugField() active = models.BooleanField(default=True) - + objects = CategoryManager() tree = TreeManager() - + def save(self, *args, **kwargs): """ While you can activate an item without activating its descendants, - It doesn't make sense that you can deactivate an item and have its + It doesn't make sense that you can deactivate an item and have its decendants remain active. """ if not self.slug: self.slug = slugify(SLUG_TRANSLITERATOR(self.name))[:50] - + super(CategoryBase, self).save(*args, **kwargs) - + if not self.active: for item in self.get_descendants(): if item.active != self.active: item.active = self.active item.save() - + def __unicode__(self): ancestors = self.get_ancestors() - return ' > '.join([force_unicode(i.name) for i in ancestors]+[self.name,]) - + return ' > '.join([force_unicode(i.name) for i in ancestors] + [self.name, ]) + class Meta: abstract = True unique_together = ('parent', 'name') ordering = ('tree_id', 'lft') - + class MPTTMeta: order_insertion_by = 'name' @@ -79,11 +80,11 @@ class CategoryBaseAdminForm(forms.ModelForm): if self.instance is None or not ALLOW_SLUG_CHANGE: self.cleaned_data['slug'] = slugify(self.cleaned_data['name']) return self.cleaned_data['slug'][:50] - + def clean(self): super(CategoryBaseAdminForm, self).clean() opts = self._meta - + # Validate slug is valid in that level kwargs = {} if self.cleaned_data.get('parent', None) is None: @@ -91,14 +92,14 @@ class CategoryBaseAdminForm(forms.ModelForm): else: kwargs['parent__pk'] = int(self.cleaned_data['parent'].id) this_level_slugs = [c['slug'] for c in opts.model.objects.filter( - **kwargs).values('id','slug' + **kwargs).values('id', 'slug' ) if c['id'] != self.instance.id] if self.cleaned_data['slug'] in this_level_slugs: raise forms.ValidationError("The slug must be unique among " "the items at its level.") - + # Validate Category Parent - # Make sure the category doesn't set itself or any of its children as + # Make sure the category doesn't set itself or any of its children as # its parent. decendant_ids = self.instance.get_descendants().values_list('id', flat=True) if self.cleaned_data.get('parent', None) is None or self.instance.id is None: @@ -117,35 +118,36 @@ class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin): list_display = ('name', 'active') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} - + actions = ['activate', 'deactivate'] + def get_actions(self, request): actions = super(CategoryBaseAdmin, self).get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions - + def deactivate(self, request, queryset): """ Set active to False for selected items """ selected_cats = self.model.objects.filter( pk__in=[int(x) for x in request.POST.getlist('_selected_action')]) - + for item in selected_cats: if item.active: item.active = False item.save() item.children.all().update(active=False) deactivate.short_description = "Deactivate selected categories and their children" - + def activate(self, request, queryset): """ Set active to True for selected items """ selected_cats = self.model.objects.filter( pk__in=[int(x) for x in request.POST.getlist('_selected_action')]) - + for item in selected_cats: item.active = True item.save() diff --git a/categories/editor/settings.py b/categories/editor/settings.py index ea68b7b..9f4658a 100644 --- a/categories/editor/settings.py +++ b/categories/editor/settings.py @@ -8,4 +8,4 @@ if STATIC_URL == None: STATIC_URL = settings.MEDIA_URL MEDIA_PATH = getattr(settings, 'EDITOR_MEDIA_PATH', '%seditor/' % STATIC_URL) -TREE_INITIAL_STATE = getattr(settings, 'EDITOR_TREE_INITIAL_STATE', 'collapsed') \ No newline at end of file +TREE_INITIAL_STATE = getattr(settings, 'EDITOR_TREE_INITIAL_STATE', 'collapsed') diff --git a/categories/editor/tree_editor.py b/categories/editor/tree_editor.py index e4dc4e9..bb43ac4 100644 --- a/categories/editor/tree_editor.py +++ b/categories/editor/tree_editor.py @@ -11,15 +11,16 @@ import django import settings + class TreeEditorQuerySet(QuerySet): """ The TreeEditorQuerySet is a special query set used only in the TreeEditor ChangeList page. The only difference to a regular QuerySet is that it will enforce: - + (a) The result is ordered in correct tree order so that the TreeAdmin works all right. - + (b) It ensures that all ancestors of selected items are included in the result set, so the resulting tree display actually makes sense. @@ -41,20 +42,20 @@ class TreeEditorQuerySet(QuerySet): p.id not in include_pages: ancestor_id_list = p.get_ancestors().values_list('id', flat=True) include_pages.update(ancestor_id_list) - + if include_pages: qs = qs | self.model._default_manager.filter(id__in=include_pages) - + qs = qs.distinct() - + for obj in super(TreeEditorQuerySet, qs).iterator(): yield obj - + # Although slicing isn't nice in a tree, it is used in the deletion action # in the admin changelist. This causes github issue #25 # def __getitem__(self, index): # return self # Don't even try to slice - + def get(self, *args, **kwargs): """ Quick and dirty hack to fix change_view and delete_view; they use @@ -65,54 +66,56 @@ class TreeEditorQuerySet(QuerySet): """ return self.model._default_manager.get(*args, **kwargs) + class TreeChangeList(ChangeList): def _get_default_ordering(self): if django.VERSION[1] < 4: - return '', '' #('tree_id', 'lft') + return '', '' # ('tree_id', 'lft') else: return [] - + def get_ordering(self, request=None, queryset=None): if django.VERSION[1] < 4: - return '', '' #('tree_id', 'lft') + return '', '' # ('tree_id', 'lft') else: return [] - + def get_query_set(self, *args, **kwargs): qs = super(TreeChangeList, self).get_query_set(*args, **kwargs).order_by('tree_id', 'lft') return qs + class TreeEditor(admin.ModelAdmin): - list_per_page = 999999999 # We can't have pagination - list_max_show_all = 200 # new in django 1.4 - + list_per_page = 999999999 # We can't have pagination + list_max_show_all = 200 # new in django 1.4 + class Media: - css = {'all':(settings.MEDIA_PATH + "jquery.treeTable.css",)} + css = {'all': (settings.MEDIA_PATH + "jquery.treeTable.css", )} js = [] - - js.extend((settings.MEDIA_PATH + "jquery.treeTable.js",)) - + + js.extend((settings.MEDIA_PATH + "jquery.treeTable.js", )) + def __init__(self, *args, **kwargs): super(TreeEditor, self).__init__(*args, **kwargs) - + self.list_display = list(self.list_display) - + if 'action_checkbox' in self.list_display: self.list_display.remove('action_checkbox') - + opts = self.model._meta self.change_list_template = [ 'admin/%s/%s/editor/tree_editor.html' % (opts.app_label, opts.object_name.lower()), 'admin/%s/editor/tree_editor.html' % opts.app_label, 'admin/editor/tree_editor.html', ] - + def get_changelist(self, request, **kwargs): """ Returns the ChangeList class for use on the changelist page. """ return TreeChangeList - + def old_changelist_view(self, request, extra_context=None): "The 'change list' admin view for this model." from django.contrib.admin.views.main import ERROR_FLAG @@ -137,15 +140,15 @@ class TreeEditor(admin.ModelAdmin): try: if django.VERSION[1] < 4: - params = (request, self.model, list_display, - self.list_display_links, self.list_filter, self.date_hierarchy, - self.search_fields, self.list_select_related, - self.list_per_page,self.list_editable, self) + params = (request, self.model, list_display, + self.list_display_links, self.list_filter, self.date_hierarchy, + self.search_fields, self.list_select_related, + self.list_per_page, self.list_editable, self) else: - params = (request, self.model, list_display, - self.list_display_links, self.list_filter, self.date_hierarchy, - self.search_fields, self.list_select_related, - self.list_per_page, self.list_max_show_all, + params = (request, self.model, list_display, + self.list_display_links, self.list_filter, self.date_hierarchy, + self.search_fields, self.list_select_related, + self.list_per_page, self.list_max_show_all, self.list_editable, self) cl = TreeChangeList(*params) except IncorrectLookupParameters: @@ -238,7 +241,7 @@ class TreeEditor(admin.ModelAdmin): else: selection_note_all = ungettext('%(total_count)s selected', 'All %(total_count)s selected', cl.result_count) - + context.update({ 'module_name': force_unicode(opts.verbose_name_plural), 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, @@ -253,7 +256,7 @@ class TreeEditor(admin.ModelAdmin): 'admin/%s/change_list.html' % app_label, 'admin/change_list.html' ], context, context_instance=context_instance) - + def changelist_view(self, request, extra_context=None, *args, **kwargs): """ Handle the changelist view, the django view for the model instances @@ -267,7 +270,7 @@ class TreeEditor(admin.ModelAdmin): request, extra_context, *args, **kwargs) else: return self.old_changelist_view(request, extra_context) - + def queryset(self, request): """ Returns a QuerySet of all model instances that can be edited by the diff --git a/categories/editor/utils.py b/categories/editor/utils.py index 1a6c877..1200569 100644 --- a/categories/editor/utils.py +++ b/categories/editor/utils.py @@ -3,8 +3,12 @@ Provides compatibility with Django 1.1 Copied from django.contrib.admin.util """ +from django.forms.forms import pretty_name from django.db import models +from django.db.models.related import RelatedObject from django.utils.encoding import force_unicode, smart_unicode, smart_str +from django.utils import formats + def lookup_field(name, obj, model_admin=None): opts = obj._meta @@ -81,10 +85,11 @@ def label_for_field(name, model, model_admin=None, return_attr=False): else: return label + def display_for_field(value, field): from django.contrib.admin.templatetags.admin_list import _boolean_icon from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE - + if field.flatchoices: return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE) # NullBooleanField needs special-case null-handling, so it comes diff --git a/categories/fields.py b/categories/fields.py index 6c815a9..9d0db82 100644 --- a/categories/fields.py +++ b/categories/fields.py @@ -2,6 +2,7 @@ from django.db.models import ForeignKey, ManyToManyField from .models import Category + class CategoryM2MField(ManyToManyField): def __init__(self, **kwargs): if 'to' in kwargs: diff --git a/categories/management/commands/add_category_fields.py b/categories/management/commands/add_category_fields.py index 0d247d6..73516ba 100644 --- a/categories/management/commands/add_category_fields.py +++ b/categories/management/commands/add_category_fields.py @@ -1,4 +1,5 @@ -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand + class Command(BaseCommand): """ @@ -8,7 +9,7 @@ class Command(BaseCommand): args = "[appname ...]" can_import_settings = True requires_model_validation = False - + def handle(self, *args, **options): """ Alter the tables @@ -17,7 +18,7 @@ class Command(BaseCommand): from south.db import db except ImportError: raise ImproperlyConfigured("South must be installed for this command to work") - + from categories.migration import migrate_app from categories import model_registry if args: diff --git a/categories/management/commands/drop_category_field.py b/categories/management/commands/drop_category_field.py index 8108e20..3163e01 100644 --- a/categories/management/commands/drop_category_field.py +++ b/categories/management/commands/drop_category_field.py @@ -1,4 +1,5 @@ -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand + class Command(BaseCommand): """ @@ -8,7 +9,7 @@ class Command(BaseCommand): args = "appname modelname fieldname" can_import_settings = True requires_model_validation = False - + def handle(self, *args, **options): """ Alter the tables @@ -17,10 +18,9 @@ class Command(BaseCommand): from south.db import db except ImportError: raise ImproperlyConfigured("South must be installed for this command to work") - + from categories.migration import drop_field - from categories import model_registry if len(args) != 3: print "You must specify an Application name, a Model name and a Field name" - + drop_field(*args) diff --git a/categories/management/commands/import_categories.py b/categories/management/commands/import_categories.py index b417581..b35c61c 100644 --- a/categories/management/commands/import_categories.py +++ b/categories/management/commands/import_categories.py @@ -5,18 +5,19 @@ from django.db import transaction from categories.models import Category from categories.settings import SLUG_TRANSLITERATOR + class Command(BaseCommand): """Import category trees from a file.""" - + help = "Imports category tree(s) from a file. Sub categories must be indented by the same multiple of spaces or tabs." args = "file_path [file_path ...]" - + def get_indent(self, string): """ Look through the string and count the spaces """ indent_amt = 0 - + if string[0] == '\t': return '\t' for char in string: @@ -24,7 +25,7 @@ class Command(BaseCommand): indent_amt += 1 else: return ' ' * indent_amt - + @transaction.commit_on_success def make_category(self, string, parent=None, order=1): """ @@ -42,20 +43,20 @@ class Command(BaseCommand): parent.rght = cat.rght + 1 parent.save() return cat - + def parse_lines(self, lines): """ Do the work of parsing each line """ indent = '' level = 0 - + if lines[0][0] == ' ' or lines[0][0] == '\t': raise CommandError("The first line in the file cannot start with a space or tab.") - + # This keeps track of the current parents at a given level current_parents = {0: None} - + for line in lines: if len(line) == 0: continue @@ -65,18 +66,18 @@ class Command(BaseCommand): elif not line[0] in indent: raise CommandError("You can't mix spaces and tabs for indents") level = line.count(indent) - current_parents[level] = self.make_category(line, parent=current_parents[level-1]) + current_parents[level] = self.make_category(line, parent=current_parents[level - 1]) else: # We are back to a zero level, so reset the whole thing current_parents = {0: self.make_category(line)} current_parents[0]._tree_manager.rebuild() - + def handle(self, *file_paths, **options): """ Handle the basic import """ import os - + for file_path in file_paths: if not os.path.isfile(file_path): print "File %s not found." % file_path @@ -84,6 +85,5 @@ class Command(BaseCommand): f = file(file_path, 'r') data = f.readlines() f.close() - + self.parse_lines(data) - \ No newline at end of file diff --git a/categories/models.py b/categories/models.py index 60bc7e4..50a28ea 100644 --- a/categories/models.py +++ b/categories/models.py @@ -7,16 +7,17 @@ from django.core.files.storage import get_storage_class from django.utils.translation import ugettext_lazy as _ -from .settings import (RELATION_MODELS, RELATIONS, THUMBNAIL_UPLOAD_PATH, +from .settings import (RELATION_MODELS, RELATIONS, THUMBNAIL_UPLOAD_PATH, THUMBNAIL_STORAGE) from .base import CategoryBase STORAGE = get_storage_class(THUMBNAIL_STORAGE) + class Category(CategoryBase): thumbnail = models.FileField( - upload_to=THUMBNAIL_UPLOAD_PATH, + upload_to=THUMBNAIL_UPLOAD_PATH, null=True, blank=True, storage=STORAGE(),) thumbnail_width = models.IntegerField(blank=True, null=True) @@ -28,8 +29,8 @@ class Category(CategoryBase): max_length=100, help_text="An alternative title to use on pages with this category.") alternate_url = models.CharField( - blank=True, - max_length=200, + blank=True, + max_length=200, help_text="An alternative URL to use instead of the one derived from " "the category hierarchy.") description = models.TextField(blank=True, null=True) @@ -43,20 +44,19 @@ class Category(CategoryBase): default="", help_text="(Advanced) Any additional HTML to be placed verbatim " "in the <head>") - - + @property def short_title(self): return self.name - + def get_absolute_url(self): """Return a path""" if self.alternate_url: return self.alternate_url prefix = reverse('categories_tree_list') - ancestors = list(self.get_ancestors()) + [self,] + ancestors = list(self.get_ancestors()) + [self, ] return prefix + '/'.join([force_unicode(i.slug) for i in ancestors]) + '/' - + if RELATION_MODELS: def get_related_content_type(self, content_type): """ @@ -64,13 +64,13 @@ class Category(CategoryBase): """ return self.categoryrelation_set.filter( content_type__name=content_type) - + def get_relation_type(self, relation_type): """ Get all relations of the specified relation type """ return self.categoryrelation_set.filter(relation_type=relation_type) - + def save(self, *args, **kwargs): if self.thumbnail: from django.core.files.images import get_image_dimensions @@ -81,23 +81,25 @@ class Category(CategoryBase): width, height = get_image_dimensions(self.thumbnail.file, close=True) else: width, height = None, None - + self.thumbnail_width = width self.thumbnail_height = height - + super(Category, self).save(*args, **kwargs) - + class Meta(CategoryBase.Meta): verbose_name_plural = 'categories' - + class MPTTMeta: order_insertion_by = ('order', 'name') + if RELATIONS: - CATEGORY_RELATION_LIMITS = reduce(lambda x,y: x|y, RELATIONS) + CATEGORY_RELATION_LIMITS = reduce(lambda x, y: x | y, RELATIONS) else: CATEGORY_RELATION_LIMITS = [] + class CategoryRelationManager(models.Manager): def get_content_type(self, content_type): """ @@ -105,7 +107,7 @@ class CategoryRelationManager(models.Manager): """ qs = self.get_query_set() return qs.filter(content_type__name=content_type) - + def get_relation_type(self, relation_type): """ Get all the items of the given relationship type related to this item. @@ -113,6 +115,7 @@ class CategoryRelationManager(models.Manager): qs = self.get_query_set() return qs.filter(relation_type=relation_type) + class CategoryRelation(models.Model): """Related category item""" category = models.ForeignKey(Category) @@ -120,13 +123,13 @@ class CategoryRelation(models.Model): ContentType, limit_choices_to=CATEGORY_RELATION_LIMITS) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') - relation_type = models.CharField(_("Relation Type"), - max_length="200", - blank=True, + relation_type = models.CharField(_("Relation Type"), + max_length="200", + blank=True, null=True, help_text=_("A generic text field to tag a relation, like 'leadphoto'.")) - + objects = CategoryRelationManager() - + def __unicode__(self): return u"CategoryRelation" diff --git a/categories/urls.py b/categories/urls.py index e954df7..a6aa6fd 100644 --- a/categories/urls.py +++ b/categories/urls.py @@ -13,4 +13,4 @@ urlpatterns = patterns('django.views.generic.list_detail', urlpatterns += patterns('categories.views', url(r'^(?P.+)/$', 'category_detail', name='categories_category'), -) \ No newline at end of file +) diff --git a/categories/views.py b/categories/views.py index b17329f..3606252 100644 --- a/categories/views.py +++ b/categories/views.py @@ -10,20 +10,21 @@ from django.utils.translation import ugettext_lazy as _ from .models import Category from .settings import CACHE_VIEW_LENGTH + @cache_page(CACHE_VIEW_LENGTH) -def category_detail(request, path, +def category_detail(request, path, template_name='categories/category_detail.html', extra_context={}): path_items = path.strip('/').split('/') if len(path_items) >= 2: category = get_object_or_404(Category, - slug__iexact = path_items[-1], - level = len(path_items)-1, + slug__iexact=path_items[-1], + level=len(path_items) - 1, parent__slug__iexact=path_items[-2]) else: category = get_object_or_404(Category, - slug__iexact = path_items[-1], - level = len(path_items)-1) - + slug__iexact=path_items[-1], + level=len(path_items) - 1) + templates = [] while path_items: templates.append('categories/%s.html' % '_'.join(path_items)) @@ -31,7 +32,7 @@ def category_detail(request, path, templates.append(template_name) context = RequestContext(request) - context.update({'category':category}) + context.update({'category': category}) if extra_context: context.update(extra_context) return HttpResponse(select_template(templates).render(context)) @@ -41,13 +42,13 @@ def get_category_for_path(path, queryset=Category.objects.all()): path_items = path.strip('/').split('/') if len(path_items) >= 2: queryset = queryset.filter( - slug__iexact = path_items[-1], - level = len(path_items)-1, + slug__iexact=path_items[-1], + level=len(path_items) - 1, parent__slug__iexact=path_items[-2]) else: queryset = queryset.filter( - slug__iexact = path_items[-1], - level = len(path_items)-1) + slug__iexact=path_items[-1], + level=len(path_items) - 1) return queryset.get() try: @@ -72,13 +73,13 @@ if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): raise AttributeError(u"Category detail view %s must be called with " u"a %s." % self.__class__.__name__, self.path_field) if self.queryset is None: - queryset = self.get_queryset() + queryset = self.get_queryset() try: return get_category_for_path(self.kwargs[self.path_field]) except ObjectDoesNotExist: raise Http404(_(u"No %(verbose_name)s found matching the query") % {'verbose_name': queryset.model._meta.verbose_name}) - + def get_template_names(self): names = [] path_items = self.kwargs[self.path_field].strip('/').split('/') @@ -88,16 +89,14 @@ if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): names.extend(super(CategoryDetailView, self).get_template_names()) return names - class CategoryRelatedDetail(DetailView): - path_field = 'category_path' object_name_field = None - + def get_object(self, **kwargs): queryset = super(CategoryRelatedDetail, self).get_queryset() category = get_category_for_path(self.kwargs[self.path_field]) - return queryset.get(category=category,slug=self.kwargs[self.slug_field]) + return queryset.get(category=category, slug=self.kwargs[self.slug_field]) def get_template_names(self): names = [] @@ -106,22 +105,22 @@ if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): if self.object_name_field: path_items.append(getattr(self.object, self.object_name_field)) while path_items: - names.append( '%s/category_%s_%s%s.html' % (opts.app_label, - '_'.join(path_items), - opts.object_name.lower(), - self.template_name_suffix) - ) + names.append('%s/category_%s_%s%s.html' % ( + opts.app_label, + '_'.join(path_items), + opts.object_name.lower(), + self.template_name_suffix) + ) path_items.pop() - names.append('%s/category_%s%s.html' % (opts.app_label, - opts.object_name.lower(), - self.template_name_suffix) - ) + names.append('%s/category_%s%s.html' % ( + opts.app_label, + opts.object_name.lower(), + self.template_name_suffix) + ) names.extend(super(CategoryRelatedDetail, self).get_template_names()) return names - class CategoryRelatedList(ListView): - path_field = 'category_path' def get_queryset(self): @@ -135,15 +134,17 @@ if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): opts = self.object_list.model._meta path_items = self.kwargs[self.path_field].strip('/').split('/') while path_items: - names.append( '%s/category_%s_%s%s.html' % (opts.app_label, - '_'.join(path_items), - opts.object_name.lower(), - self.template_name_suffix) - ) + names.append('%s/category_%s_%s%s.html' % ( + opts.app_label, + '_'.join(path_items), + opts.object_name.lower(), + self.template_name_suffix) + ) path_items.pop() - names.append('%s/category_%s%s.html' % (opts.app_label, - opts.object_name.lower(), - self.template_name_suffix) - ) + names.append('%s/category_%s%s.html' % ( + opts.app_label, + opts.object_name.lower(), + self.template_name_suffix) + ) names.extend(super(CategoryRelatedList, self).get_template_names()) return names