From 0f2f24d2ce17017d8077bc5fc32081e7f54821a6 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 23 Sep 2012 19:41:21 -0500 Subject: [PATCH 1/9] Added customization of admin fieldsets --- categories/__init__.py | 6 +++--- doc_src/index.rst | 1 + doc_src/reference/settings.rst | 12 +++++++++++- example/simpletext/admin.py | 14 +++++++++----- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/categories/__init__.py b/categories/__init__.py index b5e5370..774978a 100644 --- a/categories/__init__.py +++ b/categories/__init__.py @@ -1,8 +1,8 @@ __version_info__ = { 'major': 1, - 'minor': 1, - 'micro': 4, - 'releaselevel': 'final', + 'minor': 2, + 'micro': 0, + 'releaselevel': 'beta', 'serial': 1 } diff --git a/doc_src/index.rst b/doc_src/index.rst index 33705cd..9c330ed 100644 --- a/doc_src/index.rst +++ b/doc_src/index.rst @@ -31,6 +31,7 @@ Contents usage registering_models adding_the_fields + admin_settings custom_categories reference/index diff --git a/doc_src/reference/settings.rst b/doc_src/reference/settings.rst index 78df52d..dd0ebee 100644 --- a/doc_src/reference/settings.rst +++ b/doc_src/reference/settings.rst @@ -13,7 +13,7 @@ The ``CATEGORIES_SETTINGS`` dictionary is where you can override the default set The default settings are: .. code-block:: python - + CATEGORIES_SETTINGS = { 'ALLOW_SLUG_CHANGE': False, 'CACHE_VIEW_LENGTH': 0, @@ -23,6 +23,7 @@ The default settings are: 'THUMBNAIL_UPLOAD_PATH': 'uploads/categories/thumbnails', 'THUMBNAIL_STORAGE': settings.DEFAULT_FILE_STORAGE, 'SLUG_TRANSLITERATOR': lambda x: x, + 'ADMIN_FIELDSETS': {} } @@ -119,3 +120,12 @@ JAVASCRIPT_URL **Default:** ``STATIC_URL or MEDIA_URL + 'js/'`` **Description:** Allows for customization of javascript placement. + +.. _ADMIN_FIELDSETS: + +ADMIN_FIELDSETS +=============== + +**Default:** ``{}`` + +**Description:** Allows for selective customization of the default behavior of adding the fields to the admin class. See :ref:`admin_settings` for more information. \ No newline at end of file diff --git a/example/simpletext/admin.py b/example/simpletext/admin.py index f36c1fd..7d5ea8f 100644 --- a/example/simpletext/admin.py +++ b/example/simpletext/admin.py @@ -3,9 +3,13 @@ from django.contrib import admin from categories.admin import CategoryBaseAdmin, CategoryBaseAdminForm -# class SimpleTextAdmin(admin.ModelAdmin): -# filter_horizontal = ['cats',] -# +class SimpleTextAdmin(admin.ModelAdmin): + fieldsets = ( + (None, { + 'fields': ('name', 'description', ) + }), + ) + class SimpleCategoryAdminForm(CategoryBaseAdminForm): class Meta: @@ -13,6 +17,6 @@ class SimpleCategoryAdminForm(CategoryBaseAdminForm): class SimpleCategoryAdmin(CategoryBaseAdmin): form = SimpleCategoryAdminForm - -admin.site.register(SimpleText) #, SimpleTextAdmin) + +admin.site.register(SimpleText, SimpleTextAdmin) admin.site.register(SimpleCategory, SimpleCategoryAdmin) \ No newline at end of file From 591680a24e8d9de657b6df83bcaf55b865f95edb Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 2 Sep 2012 16:02:00 +0200 Subject: [PATCH 2/9] german translation --- .../editor/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 443 bytes .../editor/locale/de/LC_MESSAGES/django.po | 40 +++++ categories/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 3595 bytes categories/locale/de/LC_MESSAGES/django.po | 154 ++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 categories/editor/locale/de/LC_MESSAGES/django.mo create mode 100644 categories/editor/locale/de/LC_MESSAGES/django.po create mode 100644 categories/locale/de/LC_MESSAGES/django.mo create mode 100644 categories/locale/de/LC_MESSAGES/django.po diff --git a/categories/editor/locale/de/LC_MESSAGES/django.mo b/categories/editor/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..c1b6e64d8c72ce9b3b39c19d08cc52647fe85604 GIT binary patch literal 443 zcma)%&q~8U5XLKd%F(lj6+9?9aT7hHTO(p^q0nL|(fgW=+b!vC*xmZ)=tKB=K8q6z z7CiaE$Ih_7VZQm=-}~&5cE|_hBl0f!jNBF)k=hJcwUEwZ$RElvWLvIF`HT)mrn0p* lO0eunhp1@QPBt*MZsi3t?N96Tbig)cer(ub&XSX;_YIn?fK>nh literal 0 HcmV?d00001 diff --git a/categories/editor/locale/de/LC_MESSAGES/django.po b/categories/editor/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..f985356 --- /dev/null +++ b/categories/editor/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,40 @@ +# German translation of django-categories +# Copyright (C) 2012 winniehell +# This file is distributed under the same license as django-categories. +# winniehell , 2012. +msgid "" +msgstr "" +"Project-Id-Version: django-categories 1.1.4\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-09-02 12:39+0200\n" +"PO-Revision-Date: 2012-09-01 02:38+0200\n" +"Last-Translator: winniehell \n" +"Language-Team: winniehell \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: tree_editor.py:167 +msgid "Database error" +msgstr "" + +#: tree_editor.py:206 +#, python-format +msgid "%(count)s %(name)s was changed successfully." +msgid_plural "%(count)s %(name)s were changed successfully." +msgstr[0] "" +msgstr[1] "" + +#: tree_editor.py:247 +#, python-format +msgid "%(total_count)s selected" +msgid_plural "All %(total_count)s selected" +msgstr[0] "" +msgstr[1] "" + +#: tree_editor.py:252 +#, python-format +msgid "0 of %(cnt)s selected" +msgstr "" diff --git a/categories/locale/de/LC_MESSAGES/django.mo b/categories/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..9cf93798ef68d476762cce69dc65e47afd073c60 GIT binary patch literal 3595 zcmbuB$&VXF6o(54+pvdye?g2gBhrpN5HWEWvW%S&k|9wr0mLCHyPw+?x2sKcwI{Z? zAn^}y-~wkZhyw@266H2R;>Lk1io^|sK!_VRey_V{Jd*?|OUv!Am#Vkbd-eL__HCai z96NA6h4cBFl-dpMx*2~seghZ5-@$|6bGInH6W;w)WIgqyMX}Or43wg*kWZN0f(;!Pd zwV8}fy^doJshc?RJ(E%=84R=y$tSd$xPCFwaYuU&&!p_VvC%wXAmo*g_feKI&CWR< z#=64;wtha%WwQ<|kkDu7#1xx&(lkgyj`6eQv`u5<*=e7+sT-O)@Itp^40q6wdIu{j>L5jI*_jS`KIf@xIL;mOI>IQ&vGyjh zyC^n&rkydY9rhBR?8MBDHzT-ADaYJ_>(JmiN}S=8^6>Q7IO_CF96Gk@U>IV|;l#0t z>@gn0=3Ltus|P$U!JM7T{VpDq<5#pDFUTMH-1s*8Rc5<&Y_4v0uGBw1lMkJTgpsx# z4%KpD*cc1qwTWqDcoh-^(+Ej?VBo6pJ$ODlH`Km&Cbw|Dq8hBP(cK23;(5PnxMVm) zsQ=&du1f}6{h3a>5%;e;)TCpFg&h;0GpSG24Jf@#6ar2S?X`MC)_26Noupi>aEgk` zP(}moktB$aWc2$PyRmBFDvh&fqY5)?@P_mtDaRqh2S_td#-|kXh~tVnEz6o%O7T*? zn`B5xDH#+|E_$euhF+>g6o~*yI*p`AJ1Bc?edVfWK`Y1IipsNs)im?hFUP8rSTFkg zc*sg}TqW(ZC=@cGDoj!93t>SS7(mH+)PItv{(a^H4e&%GHIUNTmi9uQ?JIrs>rG<)&VWRt;m80vxX z6k@BxW_PUU2MKjE+n1=zz(URyx`HQEfsQ@0QnG(Fb0JfhxlRuSvb8@T0oYHFmK*)S zQy=VbLUOZF};O4asBBF zjTmBUmY#9g@gRiuc z8bcEDrl1^O`zCi~ziL`6h{H-;Tb!ozu0KZCx-@xf$qNX5APftV?_pLo z)3T=WN*QV-4!-G9I)UA{k1w3H3r<#SYbQd|AQg~t4HUtfW#`EkGvv&h2%l>BO6uvj zjS6#{(^x9OQ4^~xUqS{;d(fg3P&le#7+N>wHubf&c?C1lA2}6q;zEn>gsH?*NK*=I zBjJjPc^zLOQOaDh5Yyr|zE}$r@@!GlnwnnzPh0Av<22BqHW|Cool, 2012. +msgid "" +msgstr "" +"Project-Id-Version: django-categories 1.1.4\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-09-02 15:59+0200\n" +"PO-Revision-Date: 2012-09-01 02:38+0200\n" +"Last-Translator: winniehell \n" +"Language-Team: winniehell \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: admin.py:50 +msgid "Meta Data" +msgstr "Meta-Daten" + +#: admin.py:55 +msgid "Advanced" +msgstr "Erweitert" + +#: base.py:41 +msgid "parent" +msgstr "oberkategorie" + +#: base.py:42 +msgid "name" +msgstr "name" + +#: base.py:43 +msgid "slug" +msgstr "slug" + +#: base.py:44 +msgid "active" +msgstr "aktiv" + +#: base.py:99 +msgid "The slug must be unique among the items at its level." +msgstr "Der Slug muss eindeutig innerhalb der Kategorien einer Ebene sein." + +#: base.py:109 +msgid "You can't set the parent of the item to itself." +msgstr "Eine Kategorie kann nicht Oberkategorie von sich selbst sein." + +#: base.py:112 +msgid "You can't set the parent of the item to a descendant." +msgstr "Die Oberkategorie kann keine untergeordnete Kategorie sein." + +#: base.py:143 +msgid "Deactivate selected categories and their children" +msgstr "Markierte Kategorien und ihre Unterkategorien deaktivieren" + +#: base.py:156 +msgid "Activate selected categories and their children" +msgstr "Markierte Kategorien und ihre Unterkategorien aktivieren" + +#: migration.py:21 migration.py:91 +msgid "%(dependency) must be installed for this command to work" +msgstr "" +"%(dependency) muss installiert sein, damit dieses Kommando funktioniert" + +#: migration.py:43 +msgid "Added ForeignKey %(field_name) to %(model_name)" +msgstr "Fremdschlüssel %(field_name) zu %(model_name) hinzugefügt" + +#: migration.py:49 +msgid "ForeignKey %(field_name) to %(model_name) already exists" +msgstr "Fremdschlüssel %(field_name) zu %(model_name) existiert bereits" + +#: migration.py:66 +msgid "Added Many2Many table between %(model_name) and %(category_table)" +msgstr "M:N-Beziehung zwischen %(model_name) und %(category_table) hinzugefügt" + +#: migration.py:72 +msgid "" +"Many2Many table between %(model_name) and %(category_table) already exists" +msgstr "" +"M:N-Beziehung zwischen %(model_name) und %(category_table) existiert bereits" + +#: migration.py:98 +msgid "Dropping ForeignKey %(field_name) from %(model_name)" +msgstr "Entferne Fremdschlüssel %(field_name) zu %(model_name)" + +#: migration.py:109 +msgid "Dropping Many2Many table between %(model_name) and %(category_table)" +msgstr "Entferne M:N-Beziehung zwischen %(model_name) und %(category_table)" + +#: models.py:91 models.py:122 +msgid "category" +msgstr "kategorie" + +#: models.py:92 +msgid "categories" +msgstr "kategorien" + +#: models.py:124 +msgid "content type" +msgstr "content type" + +#: models.py:125 +msgid "object id" +msgstr "objekt-ID" + +#: models.py:127 +msgid "relation type" +msgstr "relationstyp" + +#: models.py:131 +msgid "A generic text field to tag a relation, like 'leadphoto'." +msgstr "" +"Ein generisches Textfeld um eine Relation zu bezeichnen, z.B. 'leadphoto'" + +#: registration.py:45 +#, python-format +msgid "%(key) is not a model" +msgstr "%(key) ist kein Model" + +#: registration.py:54 registration.py:62 +msgid "%(settings) doesn't recognize the value of %(key)" +msgstr "%(settings) erkennt den Wert von %(key) nicht" + +#: settings.py:33 +msgid "%(transliterator) must be a callable or a string." +msgstr "%(transliterator) muss callable oder string sein" + +#: settings.py:39 +#, python-format +msgid "%(deprecated_setting) is deprecated; use %(replacement)s instead." +msgstr "" +"%(deprecated_setting) ist veraltet und wurde durch %(replacement)s ersetzt." + +#: views.py:73 +msgid "Category detail view %(view) must be called with a %(path_field)." +msgstr "" +"Kategorie-Detailansicht %(view) muss ein %(path_field) übergeben bekommen." + +#: views.py:80 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "Es wurde kein passendes Objekt für %(verbose_name)s gefunden" + +#: templates/admin/edit_inline/gen_coll_tabular.html:13 +msgid "Delete?" +msgstr "Löschen?" + +#: templates/admin/edit_inline/gen_coll_tabular.html:24 +msgid "View on site" +msgstr "Anzeigen" From 89c733330617c9964ac8e25469274a4dbcb3eda2 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 2 Sep 2012 16:03:58 +0200 Subject: [PATCH 3/9] i18n --- MANIFEST.in | 3 +++ categories/admin.py | 5 +++-- categories/base.py | 27 ++++++++++++++------------- categories/migration.py | 30 +++++++++++++++++++----------- categories/models.py | 11 ++++++----- categories/registration.py | 8 +++++--- categories/settings.py | 16 ++++++++++------ categories/views.py | 6 +++--- 8 files changed, 63 insertions(+), 43 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 65a5ba8..26b3af9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,4 +12,7 @@ recursive-include doc_src *.rst *.txt *.png *.css *.html *.js include doc_src/Makefile include doc_src/make.bat +recursive-include categories/locale *.mo *.po +recursive-include categories/editor/locale *.mo *.po + prune example diff --git a/categories/admin.py b/categories/admin.py index c19bba1..1255c91 100644 --- a/categories/admin.py +++ b/categories/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin from django import forms +from django.utils.translation import ugettext_lazy as _ from .genericcollection import GenericCollectionTabularInline from .settings import RELATION_MODELS, JAVASCRIPT_URL, REGISTER_ADMIN @@ -46,12 +47,12 @@ class CategoryAdmin(CategoryBaseAdmin): (None, { 'fields': ('parent', 'name', 'thumbnail', 'active') }), - ('Meta Data', { + (_('Meta Data'), { 'fields': ('alternate_title', 'alternate_url', 'description', 'meta_keywords', 'meta_extra'), 'classes': ('collapse',), }), - ('Advanced', { + (_('Advanced'), { 'fields': ('order', 'slug'), 'classes': ('collapse',), }), diff --git a/categories/base.py b/categories/base.py index 64e9400..3da9379 100644 --- a/categories/base.py +++ b/categories/base.py @@ -8,6 +8,7 @@ from django.db import models from django import forms from django.template.defaultfilters import slugify from django.utils.encoding import force_unicode +from django.utils.translation import ugettext as _ from mptt.models import MPTTModel from mptt.fields import TreeForeignKey @@ -36,11 +37,11 @@ class CategoryBase(MPTTModel): 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) + related_name='children', + verbose_name=_('parent')) + name = models.CharField(max_length=100, verbose_name=_('name')) + slug = models.SlugField(verbose_name=_('slug')) + active = models.BooleanField(default=True, verbose_name=_('active')) objects = CategoryManager() tree = TreeManager() @@ -100,8 +101,8 @@ class CategoryBaseAdminForm(forms.ModelForm): **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.") + 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 @@ -110,11 +111,11 @@ class CategoryBaseAdminForm(forms.ModelForm): if self.cleaned_data.get('parent', None) is None or self.instance.id is None: return self.cleaned_data elif self.cleaned_data['parent'].id == self.instance.id: - raise forms.ValidationError("You can't set the parent of the " - "item to itself.") + raise forms.ValidationError(_("You can't set the parent of the " + "item to itself.")) elif self.cleaned_data['parent'].id in decendant_ids: - raise forms.ValidationError("You can't set the parent of the " - "item to a descendant.") + raise forms.ValidationError(_("You can't set the parent of the " + "item to a descendant.")) return self.cleaned_data @@ -144,7 +145,7 @@ class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin): item.active = False item.save() item.children.all().update(active=False) - deactivate.short_description = "Deactivate selected categories and their children" + deactivate.short_description = _('Deactivate selected categories and their children') def activate(self, request, queryset): """ @@ -157,4 +158,4 @@ class CategoryBaseAdmin(TreeEditor, admin.ModelAdmin): item.active = True item.save() item.children.all().update(active=True) - activate.short_description = "Activate selected categories and their children" + activate.short_description = _('Activate selected categories and their children') diff --git a/categories/migration.py b/categories/migration.py index cb02f10..21ba971 100644 --- a/categories/migration.py +++ b/categories/migration.py @@ -1,5 +1,6 @@ from django.db import models, DatabaseError from django.core.exceptions import ImproperlyConfigured +from django.utils.translation import ugettext_lazy as _ def migrate_app(sender, app, created_models, verbosity, *args, **kwargs): @@ -17,7 +18,8 @@ def migrate_app(sender, app, created_models, verbosity, *args, **kwargs): try: from south.db import db except ImportError: - raise ImproperlyConfigured("South must be installed for this command to work") + raise ImproperlyConfigured(_('%(dependency) must be installed for this command to work') % + {'dependency' : 'South'}) # pull the information from the registry app_name = app.__name__.split('.')[-2] @@ -38,17 +40,19 @@ def migrate_app(sender, app, created_models, verbosity, *args, **kwargs): db.add_column(table_name, field_name, FIELD_REGISTRY[fld], keep_default=False) db.commit_transaction() if verbosity: - print "Added ForeignKey %s to %s" % (field_name, model_name) + print (_('Added ForeignKey %(field_name) to %(model_name)') % + {'field_name' : field_name, 'model_name' : model_name}) except DatabaseError, e: db.rollback_transaction() if "already exists" in str(e): if verbosity > 1: - print "ForeignKey %s to %s already exists" % (field_name, model_name) + print (_('ForeignKey %(field_name) to %(model_name) already exists') % + {'field_name' : field_name, 'model_name' : model_name}) else: sys.stderr = org_stderror raise e elif isinstance(FIELD_REGISTRY[fld], CategoryM2MField): - table_name = "%s_%s" % (mdl._meta.db_table, 'categories') + table_name = '%s_%s' % (mdl._meta.db_table, 'categories') try: db.start_transaction() db.create_table(table_name, ( @@ -59,12 +63,14 @@ def migrate_app(sender, app, created_models, verbosity, *args, **kwargs): db.create_unique(table_name, ['%s_id' % model_name, 'category_id']) db.commit_transaction() if verbosity: - print "Added Many2Many table between %s and %s" % (model_name, 'category') + print (_('Added Many2Many table between %(model_name) and %(category_table)') % + {'model_name' : model_name, 'category_table' : 'category'}) except DatabaseError, e: db.rollback_transaction() if "already exists" in str(e): if verbosity > 1: - print "Many2Many table between %s and %s already exists" % (model_name, 'category') + print (_('Many2Many table between %(model_name) and %(category_table) already exists') % + {'model_name' : model_name, 'category_table' : 'category'}) else: sys.stderr = org_stderror raise e @@ -82,13 +88,15 @@ def drop_field(app_name, model_name, field_name): try: from south.db import db except ImportError: - raise ImproperlyConfigured("South must be installed for this command to work") + raise ImproperlyConfigured(_('%(dependency) must be installed for this command to work') % + {'dependency' : 'South'}) mdl = models.get_model(app_name, model_name) - fld = "%s.%s.%s" % (app_name, model_name, field_name) + fld = '%s.%s.%s' % (app_name, model_name, field_name) if isinstance(FIELD_REGISTRY[fld], CategoryFKField): - print "Dropping ForeignKey %s from %s" % (field_name, model_name) + print (_('Dropping ForeignKey %(field_name) from %(model_name)') % + {'field_name' : field_name, 'model_name' : model_name}) try: db.start_transaction() table_name = mdl._meta.db_table @@ -98,8 +106,8 @@ def drop_field(app_name, model_name, field_name): db.rollback_transaction() raise e elif isinstance(FIELD_REGISTRY[fld], CategoryM2MField): - print "Dropping Many2Many table between %s and %s" % (model_name, 'category') - table_name = "%s_%s" % (mdl._meta.db_table, 'categories') + print (_('Dropping Many2Many table between %(model_name) and %(category_table)') % + {'model_name' : model_name, 'category_table' : 'category'}) try: db.start_transaction() db.delete_table(table_name, cascade=False) diff --git a/categories/models.py b/categories/models.py index ffa0bb3..4048218 100644 --- a/categories/models.py +++ b/categories/models.py @@ -88,7 +88,8 @@ class Category(CategoryBase): super(Category, self).save(*args, **kwargs) class Meta(CategoryBase.Meta): - verbose_name_plural = 'categories' + verbose_name = _('category') + verbose_name_plural = _('categories') class MPTTMeta: order_insertion_by = ('order', 'name') @@ -118,12 +119,12 @@ class CategoryRelationManager(models.Manager): class CategoryRelation(models.Model): """Related category item""" - category = models.ForeignKey(Category) + category = models.ForeignKey(Category, verbose_name=_('category')) content_type = models.ForeignKey( - ContentType, limit_choices_to=CATEGORY_RELATION_LIMITS) - object_id = models.PositiveIntegerField() + ContentType, limit_choices_to=CATEGORY_RELATION_LIMITS, verbose_name=_('content type')) + object_id = models.PositiveIntegerField(verbose_name=_('object id')) content_object = generic.GenericForeignKey('content_type', 'object_id') - relation_type = models.CharField(_("Relation Type"), + relation_type = models.CharField(verbose_name=_('relation type'), max_length="200", blank=True, null=True, diff --git a/categories/registration.py b/categories/registration.py index 05d865b..058e7c2 100644 --- a/categories/registration.py +++ b/categories/registration.py @@ -42,7 +42,7 @@ def _process_registry(registry, call_func): for key, value in registry.items(): model = get_model(*key.split('.')) if model is None: - raise ImproperlyConfigured('%s is not a model' % key) + raise ImproperlyConfigured(_('%(key) is not a model') % {'key' : key}) if isinstance(value, (tuple, list)): for item in value: if isinstance(item, basestring): @@ -51,11 +51,13 @@ def _process_registry(registry, call_func): field_name = item.pop('name') call_func(model, field_name, extra_params=item) else: - raise ImproperlyConfigured("CATEGORY_SETTINGS doesn't recognize the value of %s" % key) + raise ImproperlyConfigured(_("%(settings) doesn't recognize the value of %(key)") % + {'settings' : 'CATEGORY_SETTINGS', 'key' : key}) elif isinstance(value, basestring): call_func(model, value) elif isinstance(value, dict): field_name = value.pop('name') call_func(model, field_name, extra_params=value) else: - raise ImproperlyConfigured("CATEGORY_SETTINGS doesn't recognize the value of %s" % key) + raise ImproperlyConfigured(_("%(settings) doesn't recognize the value of %(key)") % + {'settings' : 'CATEGORY_SETTINGS', 'key' : key}) diff --git a/categories/settings.py b/categories/settings.py index a18a5c2..4d0679b 100644 --- a/categories/settings.py +++ b/categories/settings.py @@ -2,6 +2,7 @@ import warnings from django.conf import settings from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ DEFAULT_SETTINGS = { 'ALLOW_SLUG_CHANGE': False, @@ -29,26 +30,29 @@ if DEFAULT_SETTINGS['SLUG_TRANSLITERATOR']: DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'] = getattr(module, bits[-1]) else: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("SLUG_TRANSLITERATOR must be a callable or a string.") + raise ImproperlyConfigured(_('%(transliterator) must be a callable or a string.') % + {'transliterator' : 'SLUG_TRANSLITERATOR'}) else: DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'] = lambda x: x -ERR_MSG = "settings.%s is deprecated; use settings.CATEGORIES_SETTINGS instead." +def warn_deprecated(deprecated_setting, replacement): + warnings.warn(_('%(deprecated_setting) is deprecated; use %(replacement)s instead.') % + {'deprecated_setting' : deprecated_setting, 'replacement' : replacement}, DeprecationWarning) if hasattr(settings, 'CATEGORIES_ALLOW_SLUG_CHANGE'): - warnings.warn(ERR_MSG % 'CATEGORIES_ALLOW_SLUG_CHANGE', DeprecationWarning) + warn_deprecated('settings.CATEGORIES_ALLOW_SLUG_CHANGE', 'settings.CATEGORIES_SETTINGS') DEFAULT_SETTINGS["ALLOW_SLUG_CHANGE"] = getattr(settings, 'CATEGORIES_ALLOW_SLUG_CHANGE') if hasattr(settings, 'CATEGORIES_CACHE_VIEW_LENGTH'): - warnings.warn(ERR_MSG % "CATEGORIES_CACHE_VIEW_LENGTH", DeprecationWarning) + warn_deprecated('settings.CATEGORIES_CACHE_VIEW_LENGTH', 'settings.CATEGORIES_SETTINGS') DEFAULT_SETTINGS["CACHE_VIEW_LENGTH"] = getattr(settings, 'CATEGORIES_CACHE_VIEW_LENGTH') if hasattr(settings, 'CATEGORIES_THUMBNAIL_UPLOAD_PATH'): - warnings.warn(ERR_MSG % "CATEGORIES_THUMBNAIL_UPLOAD_PATH", DeprecationWarning) + warn_deprecated('settings.CATEGORIES_THUMBNAIL_UPLOAD_PATH', 'settings.CATEGORIES_SETTINGS') DEFAULT_SETTINGS["THUMBNAIL_UPLOAD_PATH"] = getattr(settings, 'CATEGORIES_THUMBNAIL_UPLOAD_PATH') if hasattr(settings, 'CATEGORIES_RELATION_MODELS'): - warnings.warn(ERR_MSG % "CATEGORIES_RELATION_MODELS", DeprecationWarning) + warn_deprecated('settings.CATEGORIES_RELATION_MODELS', 'settings.CATEGORIES_SETTINGS') DEFAULT_SETTINGS["RELATION_MODELS"] = getattr(settings, 'CATEGORIES_RELATION_MODELS') # Add all the keys/values to the module's namespace diff --git a/categories/views.py b/categories/views.py index 3606252..33fc1ca 100644 --- a/categories/views.py +++ b/categories/views.py @@ -70,14 +70,14 @@ if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): def get_object(self, **kwargs): if self.path_field not in self.kwargs: - raise AttributeError(u"Category detail view %s must be called with " - u"a %s." % self.__class__.__name__, self.path_field) + raise AttributeError(_('Category detail view %(view) must be called with a %(path_field).') % + {'view' : self.__class__.__name__, 'path_field' : self.path_field}) if self.queryset is None: 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") % + raise Http404(_('No %(verbose_name)s found matching the query') % {'verbose_name': queryset.model._meta.verbose_name}) def get_template_names(self): From 073b09a7b338fa19048509cdcdf6e4792cc18a92 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 23 Sep 2012 19:53:13 -0500 Subject: [PATCH 4/9] Added admin settings documentation --- doc_src/admin_settings.rst | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 doc_src/admin_settings.rst diff --git a/doc_src/admin_settings.rst b/doc_src/admin_settings.rst new file mode 100644 index 0000000..d7e512c --- /dev/null +++ b/doc_src/admin_settings.rst @@ -0,0 +1,49 @@ +.. _admin_settings: + +============================== +Adding the fields to the Admin +============================== + +By default, Django Categories adds the fields you configure to the model's Admin class. If your ModelAdmin class does not use the ``fieldsets`` attribute, the configured category fields are simply appended to the bottom the fields. If your ModelAdmin uses the ``fieldsets`` attribute, a new fieldset named ``Categories``, containing all the configured fields is appended to the fieldsets. You can override or alter this behavior with the :ref:`ADMIN_FIELDSETS` setting. + +ADMIN_FIELDSETS allows you to: + +* Prevent Django Categories from adding the fields or fieldsets to a model's ModelAdmin class. +* Change the name of the fieldset (from the default: "Categories") +* Change the placement of the fieldset (from the default: bottom) + +Preventing fields in the admin class +==================================== + +If you don't want Django Categories to add any fields to the admin class, simply use the following format:: + + CATEGORIES_SETTINGS = { + 'ADMIN_FIELDSETS': [ + 'app.model': None, + ] + } + +Changing the name of the field set +================================== + +To rename the field set, use the following format:: + + CATEGORIES_SETTINGS = { + 'ADMIN_FIELDSETS': [ + 'app.model': 'Taxonomy', + ] + } + +Putting the field set exactly where you want it +=============================================== + +For complete control over the field set, use the following format:: + + CATEGORIES_SETTINGS = { + 'ADMIN_FIELDSETS': [ + 'app.model': { + 'name': 'Categories', + 'index': 0 + }, + ] + } From 7232937014bce06434ee17c393e82a1639be2c8d Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 21 Jan 2013 07:00:03 -0600 Subject: [PATCH 5/9] Updating the admin template to support the latest django admin code --- .../admin/editor/tree_list_results.html | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/categories/editor/templates/admin/editor/tree_list_results.html b/categories/editor/templates/admin/editor/tree_list_results.html index e5f1784..849bbe7 100644 --- a/categories/editor/templates/admin/editor/tree_list_results.html +++ b/categories/editor/templates/admin/editor/tree_list_results.html @@ -8,10 +8,19 @@ -{% for header in result_headers %} -{% if header.sortable %}{% endif %} -{{ header.text|capfirst }} -{% if header.sortable %}{% endif %}{% endfor %} +{% for header in result_headers %}{% endfor %} From 0be7d5e41f375cee7cc4e23ffcbc380745bcb1b2 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 21 Jan 2013 08:14:05 -0600 Subject: [PATCH 6/9] Fixing a merge conflict --- README.rst | 9 +++ categories/settings.py | 24 +------ categories/views.py | 159 ++++++++++++++++++++--------------------- 3 files changed, 87 insertions(+), 105 deletions(-) diff --git a/README.rst b/README.rst index 0221f8d..287bed1 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,15 @@ Django Categories grew out of our need to provide a basic hierarchical taxonomy As a news site, our stories, photos, and other content get divided into "sections" and we wanted all the apps to use the same set of sections. As our needs grew, the Django Categories grew in the functionality it gave to category handling within web pages. +New in 1.2 +========== + +* Support for Django 1.5 +* Dropped support for Django 1.2 +* Dropped caching within the app +* Removed the old settings compatibility layer. *Must use new dictionary-based settings!* + + New in 1.1 ========== diff --git a/categories/settings.py b/categories/settings.py index 4d0679b..2d1589d 100644 --- a/categories/settings.py +++ b/categories/settings.py @@ -1,12 +1,9 @@ -import warnings - from django.conf import settings from django.db.models import Q from django.utils.translation import ugettext_lazy as _ DEFAULT_SETTINGS = { 'ALLOW_SLUG_CHANGE': False, - 'CACHE_VIEW_LENGTH': 600, 'RELATION_MODELS': [], 'M2M_REGISTRY': {}, 'FK_REGISTRY': {}, @@ -31,29 +28,10 @@ if DEFAULT_SETTINGS['SLUG_TRANSLITERATOR']: else: from django.core.exceptions import ImproperlyConfigured raise ImproperlyConfigured(_('%(transliterator) must be a callable or a string.') % - {'transliterator' : 'SLUG_TRANSLITERATOR'}) + {'transliterator': 'SLUG_TRANSLITERATOR'}) else: DEFAULT_SETTINGS['SLUG_TRANSLITERATOR'] = lambda x: x -def warn_deprecated(deprecated_setting, replacement): - warnings.warn(_('%(deprecated_setting) is deprecated; use %(replacement)s instead.') % - {'deprecated_setting' : deprecated_setting, 'replacement' : replacement}, DeprecationWarning) - -if hasattr(settings, 'CATEGORIES_ALLOW_SLUG_CHANGE'): - warn_deprecated('settings.CATEGORIES_ALLOW_SLUG_CHANGE', 'settings.CATEGORIES_SETTINGS') - DEFAULT_SETTINGS["ALLOW_SLUG_CHANGE"] = getattr(settings, 'CATEGORIES_ALLOW_SLUG_CHANGE') - -if hasattr(settings, 'CATEGORIES_CACHE_VIEW_LENGTH'): - warn_deprecated('settings.CATEGORIES_CACHE_VIEW_LENGTH', 'settings.CATEGORIES_SETTINGS') - DEFAULT_SETTINGS["CACHE_VIEW_LENGTH"] = getattr(settings, 'CATEGORIES_CACHE_VIEW_LENGTH') - -if hasattr(settings, 'CATEGORIES_THUMBNAIL_UPLOAD_PATH'): - warn_deprecated('settings.CATEGORIES_THUMBNAIL_UPLOAD_PATH', 'settings.CATEGORIES_SETTINGS') - DEFAULT_SETTINGS["THUMBNAIL_UPLOAD_PATH"] = getattr(settings, 'CATEGORIES_THUMBNAIL_UPLOAD_PATH') - -if hasattr(settings, 'CATEGORIES_RELATION_MODELS'): - warn_deprecated('settings.CATEGORIES_RELATION_MODELS', 'settings.CATEGORIES_SETTINGS') - DEFAULT_SETTINGS["RELATION_MODELS"] = getattr(settings, 'CATEGORIES_RELATION_MODELS') # Add all the keys/values to the module's namespace globals().update(DEFAULT_SETTINGS) diff --git a/categories/views.py b/categories/views.py index 33fc1ca..a42f071 100644 --- a/categories/views.py +++ b/categories/views.py @@ -1,17 +1,22 @@ -import django from django.shortcuts import get_object_or_404 from django.core.exceptions import ObjectDoesNotExist from django.template import RequestContext from django.http import HttpResponse, Http404 -from django.views.decorators.cache import cache_page from django.template.loader import select_template from django.utils.translation import ugettext_lazy as _ +try: + from django.views.generic import DetailView, ListView +except ImportError: + try: + from cbv import DetailView, ListView + except ImportError: + from django.core.exceptions import ImproperlyConfigured + raise ImproperlyConfigured("For older versions of Django, you need django-cbv.") + from .models import Category -from .settings import CACHE_VIEW_LENGTH -@cache_page(CACHE_VIEW_LENGTH) def category_detail(request, path, template_name='categories/category_detail.html', extra_context={}): path_items = path.strip('/').split('/') @@ -51,59 +56,78 @@ def get_category_for_path(path, queryset=Category.objects.all()): level=len(path_items) - 1) return queryset.get() -try: - import cbv - HAS_CBV = True -except ImportError: - HAS_CBV = False -if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): - if HAS_CBV: - from cbv import DetailView, ListView - else: - from django.views.generic import DetailView, ListView +class CategoryDetailView(DetailView): + model = Category + path_field = 'path' - class CategoryDetailView(DetailView): + def get_object(self, **kwargs): + if self.path_field not in self.kwargs: + 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() + 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}) - model = Category - path_field = 'path' + def get_template_names(self): + names = [] + path_items = self.kwargs[self.path_field].strip('/').split('/') + while path_items: + names.append('categories/%s.html' % '_'.join(path_items)) + path_items.pop() + names.extend(super(CategoryDetailView, self).get_template_names()) + return names - def get_object(self, **kwargs): - if self.path_field not in self.kwargs: - raise AttributeError(_('Category detail view %(view) must be called with a %(path_field).') % - {'view' : self.__class__.__name__, 'path_field' : self.path_field}) - if self.queryset is None: - queryset = self.get_queryset() - try: - return get_category_for_path(self.kwargs[self.path_field]) - except ObjectDoesNotExist: - raise Http404(_('No %(verbose_name)s found matching the query') % - {'verbose_name': queryset.model._meta.verbose_name}) - def get_template_names(self): - 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]) + + def get_template_names(self): + names = [] + opts = self.object._meta + path_items = self.kwargs[self.path_field].strip('/').split('/') + 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) + ) + path_items.pop() + 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): + queryset = super(CategoryRelatedList, self).get_queryset() + category = get_category_for_path(self.kwargs['category_path']) + return queryset.filter(category=category) + + def get_template_names(self): + names = [] + if hasattr(self.object_list, 'model'): + opts = self.object_list.model._meta path_items = self.kwargs[self.path_field].strip('/').split('/') - while path_items: - names.append('categories/%s.html' % '_'.join(path_items)) - path_items.pop() - 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]) - - def get_template_names(self): - names = [] - opts = self.object._meta - path_items = self.kwargs[self.path_field].strip('/').split('/') - 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, @@ -117,34 +141,5 @@ if ((django.VERSION[0] >= 1 and django.VERSION[1] >= 3) or HAS_CBV): 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): - queryset = super(CategoryRelatedList, self).get_queryset() - category = get_category_for_path(self.kwargs['category_path']) - return queryset.filter(category=category) - - def get_template_names(self): - names = [] - if hasattr(self.object_list, 'model'): - 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) - ) - path_items.pop() - 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 + names.extend(super(CategoryRelatedList, self).get_template_names()) + return names From 8ce56e8190b5a47019ed2436a7b2f88cf6de3ab7 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 20 Mar 2013 06:50:28 -0500 Subject: [PATCH 7/9] Fix for Django 1.5: {% url %} parameter needs to be quoted --- categories/templates/categories/ul_tree.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/categories/templates/categories/ul_tree.html b/categories/templates/categories/ul_tree.html index ef77d11..b6013de 100644 --- a/categories/templates/categories/ul_tree.html +++ b/categories/templates/categories/ul_tree.html @@ -1,5 +1,5 @@ {% load category_tags %}{% spaceless %} -
  • Top +
    • Top {% for node,structure in path|tree_info %} {% if structure.new_level %}
      • {% else %}
      • From c6f122d72433596b13bb027d86340b41cf0cc574 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 20 Mar 2013 06:52:09 -0500 Subject: [PATCH 8/9] Fixing a few minor Django 1.5 incompatibilities --- categories/urls.py | 16 +++++++++++++--- example/settings.py | 2 +- example/urls.py | 6 +++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/categories/urls.py b/categories/urls.py index a6aa6fd..333c6b7 100644 --- a/categories/urls.py +++ b/categories/urls.py @@ -1,13 +1,23 @@ -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url from .models import Category +try: + from django.views.generic import DetailView, ListView +except ImportError: + try: + from cbv import DetailView, ListView + except ImportError: + from django.core.exceptions import ImproperlyConfigured + raise ImproperlyConfigured("For older versions of Django, you need django-cbv.") + + categorytree_dict = { 'queryset': Category.objects.filter(level=0) } -urlpatterns = patterns('django.views.generic.list_detail', +urlpatterns = patterns('', url( - r'^$', 'object_list', categorytree_dict, name='categories_tree_list' + r'^$', ListView.as_view(**categorytree_dict), name='categories_tree_list' ), ) diff --git a/example/settings.py b/example/settings.py index 012dab6..75900f9 100644 --- a/example/settings.py +++ b/example/settings.py @@ -123,7 +123,7 @@ CATEGORIES_SETTINGS = { }, } -if django.VERSION[1] == 4: +if django.VERSION[1] >= 4: from settings14 import * if django.VERSION[1] == 3: from settings13 import * diff --git a/example/urls.py b/example/urls.py index c8e1a75..a15924d 100644 --- a/example/urls.py +++ b/example/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import * +from django.conf.urls import patterns, include # Uncomment the next two lines to enable the admin: from django.contrib import admin @@ -13,7 +13,7 @@ urlpatterns = patterns('', # Example: # (r'^sample/', include('sample.foo.urls')), - # Uncomment the admin/doc line below and add 'django.contrib.admindocs' + # Uncomment the admin/doc line below and add 'django.contrib.admindocs' # to INSTALLED_APPS to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), @@ -32,4 +32,4 @@ urlpatterns = patterns('', (r'^static/(?P.*)$', 'django.views.static.serve', {'document_root': os.path.join(ROOT_PATH, 'example', 'static')}), -) \ No newline at end of file +) From 63eefeeaf1bfc4dfe90903e03348ee78c617c247 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 20 Mar 2013 06:59:15 -0500 Subject: [PATCH 9/9] Version bump 1.2 --- categories/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/categories/__init__.py b/categories/__init__.py index 774978a..b64f1e4 100644 --- a/categories/__init__.py +++ b/categories/__init__.py @@ -2,7 +2,7 @@ __version_info__ = { 'major': 1, 'minor': 2, 'micro': 0, - 'releaselevel': 'beta', + 'releaselevel': 'final', 'serial': 1 } @@ -25,5 +25,5 @@ try: register_m2m) _process_registry(settings.FK_REGISTRY, register_fk) _process_registry(settings.M2M_REGISTRY, register_m2m) -except ImportError: +except: pass
+ {% if header.sortable %} + {% if header.sort_priority > 0 %} +
+ + {% if num_sorted_fields > 1 %}{{ header.sort_priority }}{% endif %} + +
+ {% endif %} + {% endif %} +
{% if header.sortable %}{{ header.text|capfirst }}{% else %}{{ header.text|capfirst }}{% endif %}
+
+