diff --git a/README.rst b/README.rst index 59d9bcd8..0e964313 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,28 @@ 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.0 +========== + +**Abstract Base Class for generic hierarchical category models** + When you want a multiple types of categories and don't want them all part of the same model, you can now easily create new models by subclassing ``CategoryBase``. You can also add additional metadata as necessary. + + Your model's can subclass ``CategoryBaseAdminForm`` and ``CategoryBaseAdmin`` to get the hierarchical management in the admin. + + See the docs for more information. + +**Increased the default caching time on views** + The default setting for ``CACHE_VIEW_LENGTH`` was ``0``, which means it would tell the browser to *never* cache the page. It is now ``600``, which is the default for `CACHE_MIDDLEWARE_SECONDS `_ + +**Updated for use with Django-MPTT 0.5** + Just a few tweaks. + +**Initial compatibility with Django 1.4** + More is coming, but at least it works. + +**Slug transliteration for non-ASCII characters** + A new setting, ``SLUG_TRANSLITERATOR``, allows you to specify a function for converting the non-ASCII characters to ASCII characters before the slugification. Works great with `Unidecode `_. + Updated in 0.8.8 ================ diff --git a/doc_src/_static/default.css b/doc_src/_static/default.css index 934f3160..48cf53fa 100644 --- a/doc_src/_static/default.css +++ b/doc_src/_static/default.css @@ -532,6 +532,10 @@ pre { padding: 10px; } +td.linenos { + width: 2em; +} + td.linenos pre { padding: 5px 0px; border: 0; @@ -539,14 +543,23 @@ td.linenos pre { color: #aaa; } +td.code { + +} + table.highlighttable { margin-left: 0.5em; + width: 100%; } table.highlighttable td { padding: 0 0.5em 0 0.5em; } - +table.highlighttable td.linenos { + text-align: right; + width: 1.5em; + padding-right: 0; +} tt { font-family:"Bitstream Vera Sans Mono",Monaco,"Lucida Console",Courier,Consolas,monospace; diff --git a/doc_src/code_examples/custom_categories1.py b/doc_src/code_examples/custom_categories1.py new file mode 100644 index 00000000..72eb2ca9 --- /dev/null +++ b/doc_src/code_examples/custom_categories1.py @@ -0,0 +1,9 @@ +from categories.models import CategoryBase + +class SimpleCategory(CategoryBase): + """ + A simple of catgorizing example + """ + + class Meta: + verbose_name_plural = 'simple categories' diff --git a/doc_src/code_examples/custom_categories2.py b/doc_src/code_examples/custom_categories2.py new file mode 100644 index 00000000..1aff066a --- /dev/null +++ b/doc_src/code_examples/custom_categories2.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from categories.admin import CategoryBaseAdmin, CategoryBaseAdminForm + +from .models import SimpleCategory + +class SimpleCategoryAdminForm(CategoryBaseAdminForm): + class Meta: + model = SimpleCategory + +class SimpleCategoryAdmin(CategoryBaseAdmin): + form = SimpleCategoryAdminForm + +admin.site.register(SimpleCategory, SimpleCategoryAdmin) \ No newline at end of file diff --git a/doc_src/code_examples/custom_categories3.py b/doc_src/code_examples/custom_categories3.py new file mode 100644 index 00000000..57907e2f --- /dev/null +++ b/doc_src/code_examples/custom_categories3.py @@ -0,0 +1,29 @@ +class Category(CategoryBase): + thumbnail = models.FileField( + upload_to=THUMBNAIL_UPLOAD_PATH, + null=True, blank=True, + storage=STORAGE(),) + thumbnail_width = models.IntegerField(blank=True, null=True) + thumbnail_height = models.IntegerField(blank=True, null=True) + order = models.IntegerField(default=0) + alternate_title = models.CharField( + blank=True, + default="", + max_length=100, + help_text="An alternative title to use on pages with this category.") + alternate_url = models.CharField( + 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) + meta_keywords = models.CharField( + blank=True, + default="", + max_length=255, + help_text="Comma-separated keywords for search engines.") + meta_extra = models.TextField( + blank=True, + default="", + help_text="(Advanced) Any additional HTML to be placed verbatim " + "in the <head>") \ No newline at end of file diff --git a/doc_src/code_examples/custom_categories4.py b/doc_src/code_examples/custom_categories4.py new file mode 100644 index 00000000..4a4fa65f --- /dev/null +++ b/doc_src/code_examples/custom_categories4.py @@ -0,0 +1,15 @@ +def save(self, *args, **kwargs): + if self.thumbnail: + from django.core.files.images import get_image_dimensions + import django + if django.VERSION[1] < 2: + width, height = get_image_dimensions(self.thumbnail.file) + else: + 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) \ No newline at end of file diff --git a/doc_src/code_examples/custom_categories5.py b/doc_src/code_examples/custom_categories5.py new file mode 100644 index 00000000..2dfc16fe --- /dev/null +++ b/doc_src/code_examples/custom_categories5.py @@ -0,0 +1,5 @@ +class Meta(CategoryBase.Meta): + verbose_name_plural = 'categories' + +class MPTTMeta: + order_insertion_by = ('order', 'name') diff --git a/doc_src/code_examples/custom_categories6.py b/doc_src/code_examples/custom_categories6.py new file mode 100644 index 00000000..c649ab77 --- /dev/null +++ b/doc_src/code_examples/custom_categories6.py @@ -0,0 +1,9 @@ +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'] + else: + return self.cleaned_data['alternate_title'] \ No newline at end of file diff --git a/doc_src/code_examples/custom_categories7.py b/doc_src/code_examples/custom_categories7.py new file mode 100644 index 00000000..dc484993 --- /dev/null +++ b/doc_src/code_examples/custom_categories7.py @@ -0,0 +1,17 @@ +class CategoryAdmin(CategoryBaseAdmin): + form = CategoryAdminForm + list_display = ('name', 'alternate_title', 'active') + fieldsets = ( + (None, { + 'fields': ('parent', 'name', 'thumbnail', 'active') + }), + ('Meta Data', { + 'fields': ('alternate_title', 'alternate_url', 'description', + 'meta_keywords', 'meta_extra'), + 'classes': ('collapse',), + }), + ('Advanced', { + 'fields': ('order', 'slug'), + 'classes': ('collapse',), + }), + ) \ No newline at end of file diff --git a/doc_src/custom_categories.rst b/doc_src/custom_categories.rst new file mode 100644 index 00000000..01ea95e5 --- /dev/null +++ b/doc_src/custom_categories.rst @@ -0,0 +1,58 @@ +.. _creating_custom_categories: + +========================== +Creating Custom Categories +========================== + +Django Categories isn't just for using a single category model. It allows you to create your own custom category-like models with as little or much customization as you need. + +Name only +========= + +For many cases, you want a simple user-managed lookup table. You can do this with just a little bit of code. The resulting model will include name, slug and active fields and a hierarchical admin. + +#. Create a model that subclasses :py:class:`CategoryBase` + + .. literalinclude:: code_examples/custom_categories1.py + :linenos: + +#. For the Django admin, create a subclass of :py:class:`CategoryBaseAdminForm`. Create an internal class called :py:class:`Meta` and assign the attribute ``model`` to your model (see line 9 in the example). + + .. literalinclude:: code_examples/custom_categories2.py + :linenos: + +#. Create a subclass of CategoryBaseAdmin and assign ``form`` attribute the class created above (see line 12 in the above example). + +#. Register your model and custom model admin class. + +Name and other data +=================== + +Sometimes you need more functionality, such as extra metadata and custom functions. The :py:class:`Category` model in this package does this. + +#. Create a model that subclasses :py:class:`CategoryBase` as above. + +#. Add new fields to the model. The :py:class:`Category` model adds these extra fields. + + .. literalinclude:: code_examples/custom_categories3.py + :linenos: + +#. Add new methods to the model. For example, the :py:class:`Category` model adds several new methods, including overriding the :py:meth:`save` method. + + .. literalinclude:: code_examples/custom_categories4.py + :linenos: + +#. Alter :py:class:`Meta` or :py:class:`MPTTMeta` class. Either of these inner classes can be overridden, however your :py:class:`Meta` class should inherit :py:class:`CategoryBase.Meta`. Options for :py:class:`Meta` are in the `Django-MPTT docs `_. + + .. literalinclude:: code_examples/custom_categories5.py + :linenos: + +#. For the admin, you must create a form that subclasses :py:class:`CategoryBaseAdminForm` and at least sets the ``Meta.model`` attribute. You can also alter the form fields and cleaning methods, as :py:class:`Category` does. + + .. literalinclude:: code_examples/custom_categories6.py + :linenos: + +#. Next you must subclass :py:class:`CategoryBaseAdmin` and assign the ``form`` attribute the form class created above. You can alter any other attributes as necessary. + + .. literalinclude:: code_examples/custom_categories7.py + :linenos: diff --git a/doc_src/getting_started.rst b/doc_src/getting_started.rst index 954a0635..48856283 100644 --- a/doc_src/getting_started.rst +++ b/doc_src/getting_started.rst @@ -4,7 +4,7 @@ Getting Started You can use Django Categories in two ways: -1. As storage for one tree of categories, e.g.:: +1. As storage for one tree of categories, using the :py:class:`Category` model:: Top Category 1 Subcategory 1-1 @@ -13,24 +13,29 @@ You can use Django Categories in two ways: Top Category 2 Subcategory 2-1 -2. As a storage of several trees of categories, e.g.:: - - Model 1 - Category 1 - Subcategory 1-1 - Subcategory 1-2 - subcategory 1-2-1 - Category 2 - Subcategory 2-1 - Model 2 - Category 3 - Subcategory 3-1 - Subcategory 3-2 - subcategory 3-2-1 - Category 4 - Subcategory 4-1 - -You can't do it as both at the same time, though. +2. As the basis for several custom categories (see :ref:`creating_custom_categories`), e.g. a **MusicGenre** model + + :: + + MusicGenre 1 + MusicSubGenre 1-1 + MusicSubGenre 1-2 + MusicSubGenre 1-2-1 + MusicGenre 2 + MusicSubGenre 2-1 + + and a **Subject** model + + :: + + Subject 1 + Discipline 1-1 + Discipline 1-2 + SubDiscipline 1-2-1 + Subject 2 + Discipline 2-1 + + Connecting your model with Django-Categories ============================================ @@ -42,7 +47,7 @@ For 3rd-party apps or even your own apps that you don't wish to add Django-Categ .. _hard_coded_connection: Hard Coded Connection -********************* +--------------------- Hard coded connections are done in the exact same way you handle any other foreign key or many-to-many relations to a model. @@ -60,10 +65,10 @@ Don't forget to add the field or fields to your ``ModelAdmin`` class as well. .. _lazy_connection: Lazy Connection -*************** +--------------- Lazy connections are done through configuring Django Categories in the project's ``settings.py`` file. When the project starts up, the configured fields are dynamically added to the configured models and admin. If you do this before you have created the database (before you ran ``manage.py syncdb``), the fields will also be in the tables. If you do this after you have already created all the tables, you can run ``manage.py add_category_fields`` to create the fields (this requires Django South to be installed). -You add a many-to-one or many-to-many relationship with Django Categories using the ``CATEGORIES_SETTINGS['FK_REGISTRY']`` and ``CATEGORIES_SETTINGS['M2M_REGISTRY']`` settings respectively. For more information see :ref:`registering_models`\ . +You add a many-to-one or many-to-many relationship with Django Categories using the :ref:`FK_REGISTRY` and :ref:`M2M_REGISTRY` settings respectively. For more information see :ref:`registering_models`\ . diff --git a/doc_src/index.rst b/doc_src/index.rst index d86c1866..486bae99 100644 --- a/doc_src/index.rst +++ b/doc_src/index.rst @@ -2,7 +2,35 @@ Django Categories v |version| ============================= -Contents: +Django Categories grew out of our need to provide a basic hierarchical taxonomy management system that multiple applications could use independently or in concert. + +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.0 +========== + +**Abstract Base Class for generic hierarchical category models** + When you want a multiple types of categories and don't want them all part of the same model, you can now easily create new models by subclassing ``CategoryBase``. You can also add additional metadata as necessary. + + Your model's can subclass ``CategoryBaseAdminForm`` and ``CategoryBaseAdmin`` to get the hierarchical management in the admin. + + See the docs for more information. + +**Increased the default caching time on views** + The default setting for ``CACHE_VIEW_LENGTH`` was ``0``, which means it would tell the browser to *never* cache the page. It is now ``600``, which is the default for `CACHE_MIDDLEWARE_SECONDS `_ + +**Updated for use with Django-MPTT 0.5** + Just a few tweaks. + +**Initial compatibility with Django 1.4** + More is coming, but at least it works. + +**Slug transliteration for non-ASCII characters** + A new setting, ``SLUG_TRANSLITERATOR``, allows you to specify a function for converting the non-ASCII characters to ASCII characters before the slugification. Works great with `Unidecode `_. + + +Contents +======== .. toctree:: :maxdepth: 2 @@ -13,6 +41,7 @@ Contents: usage registering_models adding_the_fields + custom_categories reference/index Indices and tables diff --git a/doc_src/installation.rst b/doc_src/installation.rst index fe0be2d6..1cff18a6 100644 --- a/doc_src/installation.rst +++ b/doc_src/installation.rst @@ -2,11 +2,14 @@ Installation ============ +To use the Category model +========================= + 1. Install django-categories:: pip install django-categories -2. Add ``"categories"`` and ``"editor"`` to your ``INSTALLED_APPS`` list in your project's ``settings.py`` file. +2. Add ``"categories"`` and ``"categories.editor"`` to your ``INSTALLED_APPS`` list in your project's ``settings.py`` file. .. code-block:: python @@ -17,3 +20,24 @@ Installation ] 3. Run ``./manage.py syncdb`` (or ``./manage.py migrate categories`` if you are using `South `_) + + +To only subclass CategoryBase +============================= + +If you are going to create your own models using :py:class:`CategoryBase`, (see :ref:`creating_custom_categories`) you'll need a few different steps. + +1. Install django-categories:: + + pip install django-categories + +2. Add ``"categories.editor"`` to your ``INSTALLED_APPS`` list in your project's ``settings.py`` file. + + .. code-block:: python + + INSTALLED_APPS = [ + # ... + "categories.editor", + ] + +3. Create your own models. diff --git a/doc_src/reference/models.rst b/doc_src/reference/models.rst index 2f66ba8d..2ec54443 100644 --- a/doc_src/reference/models.rst +++ b/doc_src/reference/models.rst @@ -2,41 +2,114 @@ Models ====== -Category -======== - -**parent** - The category's parent category. Leave this blank for an Category Tree. - -**name** - The name of the category. +CategoryBase +============ -**thumbnail** - An optional thumbnail, that is uploaded to ``CATEGORY_SETTINGS['THUMBNAIL_UPLOAD_PATH']`` via ``CATEGORY_SETTINGS['THUMBNAIL_STORAGE']``\ . +.. py:class:: CategoryBase -**thumbnail_width** - The thumbnail width. + .. py:attribute:: parent + + :py:class:`TreeForeignKey` ``(self)`` + + The category's parent category. Leave this blank for an root category. + + .. py:attribute:: name + + **Required** ``CharField(100)`` + + The name of the category. + + .. py:attribute:: slug + + **Required** ``SlugField`` + + URL-friendly title. It is automatically generated from the title. + + .. py:attribute:: active + + **Required** ``BooleanField`` *default:* ``True`` + + Is this item active. If it is inactive, all children are set to inactive as well. + + .. py:attribute:: objects + + ``CategoryManager`` + + An object manager that adds an ``active`` method for only selecting items whose ``active`` attribute is ``True``. + + .. py:attribute:: tree + + ``TreeManager`` + + A Django-MPTT `TreeManager `_ instance. -**thumbnail_height** - The thumbnail height. - -**order** - The order of this category in the listing +Category +======== -**slug** - A slug created from the name field +.. py:class:: Category + + Category is a subclass of :py:class:`CategoryBase` and includes all its attributes. + + .. py:attribute:: thumbnail + + ``FileField`` + + An optional thumbnail, that is uploaded to :ref:`thumbnail_upload_path` via :ref:`THUMBNAIL_STORAGE`. + + .. note:: Why isn't this an ``ImageField``? + + For ``ImageField``\ s, Django checks the file system for the existance of the files to handle the height and width. In many cases this can lead to problems and impact performance. + + For these reasons, a ``FileField`` that manually manages the width and height was chosen. -**alternate_title** - An alternative title to use on pages with this category. + .. py:attribute:: thumbnail_width + + ``IntegerField`` + + The thumbnail width. Automatically set on save if a thumbnail is uploaded. + + .. py:attribute:: thumbnail_height + + ``IntegerField`` + + The thumbnail height. Automatically set on save if a thumbnail is uploaded. -**alternate_url** - An alternative URL to use instead of the one derived from the category hierarchy. + .. py:attribute:: order + + **Required** ``IntegerField`` *default:* 0 + + A manually-managed order of this category in the listing. Items with the same order are sorted alphabetically. -**description** - An optional longer description of the category. + .. py:attribute:: alternate_title + + ``CharField(100)`` + + An alternative title to use on pages with this category. + + .. py:attribute:: alternate_url + + ``CharField(200)`` + + An alternative URL to use instead of the one derived from the category hierarchy. + + .. note:: Why isn't this a ``URLField``? + + For ``URLField``\ s, Django checks that the URL includes ``http://`` and the site name. This makes it impossible to use relative URLs in that field. -**meta_keywords** - Comma-separated keywords for search engines. + .. py:attribute:: description + + ``TextField`` + + An optional longer description of the category. Very useful on category landing pages. -**meta_extra** - (Advanced) Any additional HTML to be placed verbatim in the ```` + .. py:attribute:: meta_keywords + + ``CharField(255)`` + + Comma-separated keywords for search engines. + + .. py:attribute:: meta_extra + + ``TextField`` + + (Advanced) Any additional HTML to be placed verbatim in the ```` of the page. diff --git a/doc_src/reference/settings.rst b/doc_src/reference/settings.rst index a6c5a2ce..d72456f8 100644 --- a/doc_src/reference/settings.rst +++ b/doc_src/reference/settings.rst @@ -6,6 +6,10 @@ Settings The ``CATEGORIES_SETTINGS`` dictionary is where you can override the default settings. You don't have to include all the settings; only the ones which you want to override. +.. contents:: + :local: + + The default settings are: .. code-block:: python @@ -20,7 +24,10 @@ The default settings are: 'THUMBNAIL_STORAGE': settings.DEFAULT_FILE_STORAGE, 'SLUG_TRANSLITERATOR': lambda x: x, } - + + +.. _ALLOW_SLUG_CHANGE: + ALLOW_SLUG_CHANGE ================= @@ -28,6 +35,8 @@ ALLOW_SLUG_CHANGE **Description:** Changing the slug for a category can have serious consequences if it is used as part of a URL. Setting this to ``True`` will allow users to change the slug of a category. +.. _SLUG_TRANSLITERATOR: + SLUG_TRANSLITERATOR =================== @@ -37,6 +46,9 @@ SLUG_TRANSLITERATOR A great tool for this is `Unidecode `_. Use it by setting ``SLUG_TRANSLITERATOR`` to ``'unidecode.unidecode``. + +.. _CACHE_VIEW_LENGTH: + CACHE_VIEW_LENGTH ================= @@ -44,6 +56,8 @@ CACHE_VIEW_LENGTH **Description:** This setting will be deprecated soon, but in the mean time, it allows you to specify the amount of time each view result is cached. +.. _RELATION_MODELS: + RELATION_MODELS =============== @@ -51,6 +65,8 @@ RELATION_MODELS **Description:** Relation models is a set of models that a user can associate with this category. You specify models using ``'app_name.modelname'`` syntax. +.. _M2M_REGISTRY: + M2M_REGISTRY ============ @@ -58,6 +74,8 @@ M2M_REGISTRY **Description:** A dictionary where the keys are in ``'app_name.model_name'`` syntax, and the values are a string, dict, or tuple of dicts. See :ref:`registering_models`\ . +.. _FK_REGISTRY: + FK_REGISTRY ============ @@ -65,6 +83,8 @@ FK_REGISTRY **Description:** A dictionary where the keys are in ``'app_name.model_name'`` syntax, and the values are a string, dict, or tuple of dicts. See :ref:`registering_models`\ . +.. _THUMBNAIL_UPLOAD_PATH: + THUMBNAIL_UPLOAD_PATH ===================== @@ -72,6 +92,8 @@ THUMBNAIL_UPLOAD_PATH **Description:** Where thumbnails for the categories will be saved. +.. _THUMBNAIL_STORAGE: + THUMBNAIL_STORAGE ================= @@ -79,6 +101,8 @@ THUMBNAIL_STORAGE **Description:** How to store the thumbnails. Allows for external storage engines like S3. +.. _JAVASCRIPT_URL: + JAVASCRIPT_URL ============== diff --git a/docs/_sources/custom_categories.txt b/docs/_sources/custom_categories.txt new file mode 100644 index 00000000..01ea95e5 --- /dev/null +++ b/docs/_sources/custom_categories.txt @@ -0,0 +1,58 @@ +.. _creating_custom_categories: + +========================== +Creating Custom Categories +========================== + +Django Categories isn't just for using a single category model. It allows you to create your own custom category-like models with as little or much customization as you need. + +Name only +========= + +For many cases, you want a simple user-managed lookup table. You can do this with just a little bit of code. The resulting model will include name, slug and active fields and a hierarchical admin. + +#. Create a model that subclasses :py:class:`CategoryBase` + + .. literalinclude:: code_examples/custom_categories1.py + :linenos: + +#. For the Django admin, create a subclass of :py:class:`CategoryBaseAdminForm`. Create an internal class called :py:class:`Meta` and assign the attribute ``model`` to your model (see line 9 in the example). + + .. literalinclude:: code_examples/custom_categories2.py + :linenos: + +#. Create a subclass of CategoryBaseAdmin and assign ``form`` attribute the class created above (see line 12 in the above example). + +#. Register your model and custom model admin class. + +Name and other data +=================== + +Sometimes you need more functionality, such as extra metadata and custom functions. The :py:class:`Category` model in this package does this. + +#. Create a model that subclasses :py:class:`CategoryBase` as above. + +#. Add new fields to the model. The :py:class:`Category` model adds these extra fields. + + .. literalinclude:: code_examples/custom_categories3.py + :linenos: + +#. Add new methods to the model. For example, the :py:class:`Category` model adds several new methods, including overriding the :py:meth:`save` method. + + .. literalinclude:: code_examples/custom_categories4.py + :linenos: + +#. Alter :py:class:`Meta` or :py:class:`MPTTMeta` class. Either of these inner classes can be overridden, however your :py:class:`Meta` class should inherit :py:class:`CategoryBase.Meta`. Options for :py:class:`Meta` are in the `Django-MPTT docs `_. + + .. literalinclude:: code_examples/custom_categories5.py + :linenos: + +#. For the admin, you must create a form that subclasses :py:class:`CategoryBaseAdminForm` and at least sets the ``Meta.model`` attribute. You can also alter the form fields and cleaning methods, as :py:class:`Category` does. + + .. literalinclude:: code_examples/custom_categories6.py + :linenos: + +#. Next you must subclass :py:class:`CategoryBaseAdmin` and assign the ``form`` attribute the form class created above. You can alter any other attributes as necessary. + + .. literalinclude:: code_examples/custom_categories7.py + :linenos: diff --git a/docs/_sources/getting_started.txt b/docs/_sources/getting_started.txt index 954a0635..48856283 100644 --- a/docs/_sources/getting_started.txt +++ b/docs/_sources/getting_started.txt @@ -4,7 +4,7 @@ Getting Started You can use Django Categories in two ways: -1. As storage for one tree of categories, e.g.:: +1. As storage for one tree of categories, using the :py:class:`Category` model:: Top Category 1 Subcategory 1-1 @@ -13,24 +13,29 @@ You can use Django Categories in two ways: Top Category 2 Subcategory 2-1 -2. As a storage of several trees of categories, e.g.:: - - Model 1 - Category 1 - Subcategory 1-1 - Subcategory 1-2 - subcategory 1-2-1 - Category 2 - Subcategory 2-1 - Model 2 - Category 3 - Subcategory 3-1 - Subcategory 3-2 - subcategory 3-2-1 - Category 4 - Subcategory 4-1 - -You can't do it as both at the same time, though. +2. As the basis for several custom categories (see :ref:`creating_custom_categories`), e.g. a **MusicGenre** model + + :: + + MusicGenre 1 + MusicSubGenre 1-1 + MusicSubGenre 1-2 + MusicSubGenre 1-2-1 + MusicGenre 2 + MusicSubGenre 2-1 + + and a **Subject** model + + :: + + Subject 1 + Discipline 1-1 + Discipline 1-2 + SubDiscipline 1-2-1 + Subject 2 + Discipline 2-1 + + Connecting your model with Django-Categories ============================================ @@ -42,7 +47,7 @@ For 3rd-party apps or even your own apps that you don't wish to add Django-Categ .. _hard_coded_connection: Hard Coded Connection -********************* +--------------------- Hard coded connections are done in the exact same way you handle any other foreign key or many-to-many relations to a model. @@ -60,10 +65,10 @@ Don't forget to add the field or fields to your ``ModelAdmin`` class as well. .. _lazy_connection: Lazy Connection -*************** +--------------- Lazy connections are done through configuring Django Categories in the project's ``settings.py`` file. When the project starts up, the configured fields are dynamically added to the configured models and admin. If you do this before you have created the database (before you ran ``manage.py syncdb``), the fields will also be in the tables. If you do this after you have already created all the tables, you can run ``manage.py add_category_fields`` to create the fields (this requires Django South to be installed). -You add a many-to-one or many-to-many relationship with Django Categories using the ``CATEGORIES_SETTINGS['FK_REGISTRY']`` and ``CATEGORIES_SETTINGS['M2M_REGISTRY']`` settings respectively. For more information see :ref:`registering_models`\ . +You add a many-to-one or many-to-many relationship with Django Categories using the :ref:`FK_REGISTRY` and :ref:`M2M_REGISTRY` settings respectively. For more information see :ref:`registering_models`\ . diff --git a/docs/_sources/index.txt b/docs/_sources/index.txt index d86c1866..486bae99 100644 --- a/docs/_sources/index.txt +++ b/docs/_sources/index.txt @@ -2,7 +2,35 @@ Django Categories v |version| ============================= -Contents: +Django Categories grew out of our need to provide a basic hierarchical taxonomy management system that multiple applications could use independently or in concert. + +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.0 +========== + +**Abstract Base Class for generic hierarchical category models** + When you want a multiple types of categories and don't want them all part of the same model, you can now easily create new models by subclassing ``CategoryBase``. You can also add additional metadata as necessary. + + Your model's can subclass ``CategoryBaseAdminForm`` and ``CategoryBaseAdmin`` to get the hierarchical management in the admin. + + See the docs for more information. + +**Increased the default caching time on views** + The default setting for ``CACHE_VIEW_LENGTH`` was ``0``, which means it would tell the browser to *never* cache the page. It is now ``600``, which is the default for `CACHE_MIDDLEWARE_SECONDS `_ + +**Updated for use with Django-MPTT 0.5** + Just a few tweaks. + +**Initial compatibility with Django 1.4** + More is coming, but at least it works. + +**Slug transliteration for non-ASCII characters** + A new setting, ``SLUG_TRANSLITERATOR``, allows you to specify a function for converting the non-ASCII characters to ASCII characters before the slugification. Works great with `Unidecode `_. + + +Contents +======== .. toctree:: :maxdepth: 2 @@ -13,6 +41,7 @@ Contents: usage registering_models adding_the_fields + custom_categories reference/index Indices and tables diff --git a/docs/_sources/installation.txt b/docs/_sources/installation.txt index fe0be2d6..1cff18a6 100644 --- a/docs/_sources/installation.txt +++ b/docs/_sources/installation.txt @@ -2,11 +2,14 @@ Installation ============ +To use the Category model +========================= + 1. Install django-categories:: pip install django-categories -2. Add ``"categories"`` and ``"editor"`` to your ``INSTALLED_APPS`` list in your project's ``settings.py`` file. +2. Add ``"categories"`` and ``"categories.editor"`` to your ``INSTALLED_APPS`` list in your project's ``settings.py`` file. .. code-block:: python @@ -17,3 +20,24 @@ Installation ] 3. Run ``./manage.py syncdb`` (or ``./manage.py migrate categories`` if you are using `South `_) + + +To only subclass CategoryBase +============================= + +If you are going to create your own models using :py:class:`CategoryBase`, (see :ref:`creating_custom_categories`) you'll need a few different steps. + +1. Install django-categories:: + + pip install django-categories + +2. Add ``"categories.editor"`` to your ``INSTALLED_APPS`` list in your project's ``settings.py`` file. + + .. code-block:: python + + INSTALLED_APPS = [ + # ... + "categories.editor", + ] + +3. Create your own models. diff --git a/docs/_sources/reference/models.txt b/docs/_sources/reference/models.txt index 2f66ba8d..2ec54443 100644 --- a/docs/_sources/reference/models.txt +++ b/docs/_sources/reference/models.txt @@ -2,41 +2,114 @@ Models ====== -Category -======== - -**parent** - The category's parent category. Leave this blank for an Category Tree. - -**name** - The name of the category. +CategoryBase +============ -**thumbnail** - An optional thumbnail, that is uploaded to ``CATEGORY_SETTINGS['THUMBNAIL_UPLOAD_PATH']`` via ``CATEGORY_SETTINGS['THUMBNAIL_STORAGE']``\ . +.. py:class:: CategoryBase -**thumbnail_width** - The thumbnail width. + .. py:attribute:: parent + + :py:class:`TreeForeignKey` ``(self)`` + + The category's parent category. Leave this blank for an root category. + + .. py:attribute:: name + + **Required** ``CharField(100)`` + + The name of the category. + + .. py:attribute:: slug + + **Required** ``SlugField`` + + URL-friendly title. It is automatically generated from the title. + + .. py:attribute:: active + + **Required** ``BooleanField`` *default:* ``True`` + + Is this item active. If it is inactive, all children are set to inactive as well. + + .. py:attribute:: objects + + ``CategoryManager`` + + An object manager that adds an ``active`` method for only selecting items whose ``active`` attribute is ``True``. + + .. py:attribute:: tree + + ``TreeManager`` + + A Django-MPTT `TreeManager `_ instance. -**thumbnail_height** - The thumbnail height. - -**order** - The order of this category in the listing +Category +======== -**slug** - A slug created from the name field +.. py:class:: Category + + Category is a subclass of :py:class:`CategoryBase` and includes all its attributes. + + .. py:attribute:: thumbnail + + ``FileField`` + + An optional thumbnail, that is uploaded to :ref:`thumbnail_upload_path` via :ref:`THUMBNAIL_STORAGE`. + + .. note:: Why isn't this an ``ImageField``? + + For ``ImageField``\ s, Django checks the file system for the existance of the files to handle the height and width. In many cases this can lead to problems and impact performance. + + For these reasons, a ``FileField`` that manually manages the width and height was chosen. -**alternate_title** - An alternative title to use on pages with this category. + .. py:attribute:: thumbnail_width + + ``IntegerField`` + + The thumbnail width. Automatically set on save if a thumbnail is uploaded. + + .. py:attribute:: thumbnail_height + + ``IntegerField`` + + The thumbnail height. Automatically set on save if a thumbnail is uploaded. -**alternate_url** - An alternative URL to use instead of the one derived from the category hierarchy. + .. py:attribute:: order + + **Required** ``IntegerField`` *default:* 0 + + A manually-managed order of this category in the listing. Items with the same order are sorted alphabetically. -**description** - An optional longer description of the category. + .. py:attribute:: alternate_title + + ``CharField(100)`` + + An alternative title to use on pages with this category. + + .. py:attribute:: alternate_url + + ``CharField(200)`` + + An alternative URL to use instead of the one derived from the category hierarchy. + + .. note:: Why isn't this a ``URLField``? + + For ``URLField``\ s, Django checks that the URL includes ``http://`` and the site name. This makes it impossible to use relative URLs in that field. -**meta_keywords** - Comma-separated keywords for search engines. + .. py:attribute:: description + + ``TextField`` + + An optional longer description of the category. Very useful on category landing pages. -**meta_extra** - (Advanced) Any additional HTML to be placed verbatim in the ```` + .. py:attribute:: meta_keywords + + ``CharField(255)`` + + Comma-separated keywords for search engines. + + .. py:attribute:: meta_extra + + ``TextField`` + + (Advanced) Any additional HTML to be placed verbatim in the ```` of the page. diff --git a/docs/_sources/reference/settings.txt b/docs/_sources/reference/settings.txt index 94f1ac31..d72456f8 100644 --- a/docs/_sources/reference/settings.txt +++ b/docs/_sources/reference/settings.txt @@ -6,6 +6,10 @@ Settings The ``CATEGORIES_SETTINGS`` dictionary is where you can override the default settings. You don't have to include all the settings; only the ones which you want to override. +.. contents:: + :local: + + The default settings are: .. code-block:: python @@ -18,8 +22,12 @@ The default settings are: 'FK_REGISTRY': {}, 'THUMBNAIL_UPLOAD_PATH': 'uploads/categories/thumbnails', 'THUMBNAIL_STORAGE': settings.DEFAULT_FILE_STORAGE, + 'SLUG_TRANSLITERATOR': lambda x: x, } - + + +.. _ALLOW_SLUG_CHANGE: + ALLOW_SLUG_CHANGE ================= @@ -27,6 +35,20 @@ ALLOW_SLUG_CHANGE **Description:** Changing the slug for a category can have serious consequences if it is used as part of a URL. Setting this to ``True`` will allow users to change the slug of a category. +.. _SLUG_TRANSLITERATOR: + +SLUG_TRANSLITERATOR +=================== + +**Default:** ``lambda x: x`` + +**Description:** Allows the specification of a function to convert non-ASCII characters in the potential slug to ASCII characters. Allows specifying a ``callable()`` or a string in the form of ``'path.to.module.function'``. + +A great tool for this is `Unidecode `_. Use it by setting ``SLUG_TRANSLITERATOR`` to ``'unidecode.unidecode``. + + +.. _CACHE_VIEW_LENGTH: + CACHE_VIEW_LENGTH ================= @@ -34,6 +56,8 @@ CACHE_VIEW_LENGTH **Description:** This setting will be deprecated soon, but in the mean time, it allows you to specify the amount of time each view result is cached. +.. _RELATION_MODELS: + RELATION_MODELS =============== @@ -41,6 +65,8 @@ RELATION_MODELS **Description:** Relation models is a set of models that a user can associate with this category. You specify models using ``'app_name.modelname'`` syntax. +.. _M2M_REGISTRY: + M2M_REGISTRY ============ @@ -48,6 +74,8 @@ M2M_REGISTRY **Description:** A dictionary where the keys are in ``'app_name.model_name'`` syntax, and the values are a string, dict, or tuple of dicts. See :ref:`registering_models`\ . +.. _FK_REGISTRY: + FK_REGISTRY ============ @@ -55,6 +83,8 @@ FK_REGISTRY **Description:** A dictionary where the keys are in ``'app_name.model_name'`` syntax, and the values are a string, dict, or tuple of dicts. See :ref:`registering_models`\ . +.. _THUMBNAIL_UPLOAD_PATH: + THUMBNAIL_UPLOAD_PATH ===================== @@ -62,6 +92,8 @@ THUMBNAIL_UPLOAD_PATH **Description:** Where thumbnails for the categories will be saved. +.. _THUMBNAIL_STORAGE: + THUMBNAIL_STORAGE ================= @@ -69,6 +101,8 @@ THUMBNAIL_STORAGE **Description:** How to store the thumbnails. Allows for external storage engines like S3. +.. _JAVASCRIPT_URL: + JAVASCRIPT_URL ============== diff --git a/docs/_static/basic.css b/docs/_static/basic.css index f0379f35..925f7a53 100644 --- a/docs/_static/basic.css +++ b/docs/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -79,14 +79,6 @@ div.sphinxsidebar input { font-size: 1em; } -div.sphinxsidebar input[type="text"] { - width: 170px; -} - -div.sphinxsidebar input[type="submit"] { - width: 30px; -} - img { border: 0; } @@ -244,6 +236,7 @@ img.align-center, .figure.align-center, object.align-center { } .align-center { + clear: both; text-align: center; } @@ -420,7 +413,7 @@ dl.glossary dt { } .footnote:target { - background-color: #ffa; + background-color: #ffa } .line-block { @@ -447,16 +440,10 @@ dl.glossary dt { font-style: oblique; } -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - /* -- code displays --------------------------------------------------------- */ pre { overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ } td.linenos pre { @@ -537,4 +524,4 @@ span.eqno { #top-link { display: none; } -} \ No newline at end of file +} diff --git a/docs/_static/default.css b/docs/_static/default.css index 934f3160..48cf53fa 100644 --- a/docs/_static/default.css +++ b/docs/_static/default.css @@ -532,6 +532,10 @@ pre { padding: 10px; } +td.linenos { + width: 2em; +} + td.linenos pre { padding: 5px 0px; border: 0; @@ -539,14 +543,23 @@ td.linenos pre { color: #aaa; } +td.code { + +} + table.highlighttable { margin-left: 0.5em; + width: 100%; } table.highlighttable td { padding: 0 0.5em 0 0.5em; } - +table.highlighttable td.linenos { + text-align: right; + width: 1.5em; + padding-right: 0; +} tt { font-family:"Bitstream Vera Sans Mono",Monaco,"Lucida Console",Courier,Consolas,monospace; diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js index d4619fdf..eeea95ea 100644 --- a/docs/_static/doctools.js +++ b/docs/_static/doctools.js @@ -2,9 +2,9 @@ * doctools.js * ~~~~~~~~~~~ * - * Sphinx JavaScript utilities for all documentation. + * Sphinx JavaScript utilties for all documentation. * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -185,9 +185,9 @@ var Documentation = { body.highlightText(this.toLowerCase(), 'highlighted'); }); }, 10); - $('') - .appendTo($('#searchbox')); + $('') + .appendTo($('.sidebar .this-page-menu')); } }, @@ -213,7 +213,7 @@ var Documentation = { * helper function to hide the search marks again */ hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); + $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); $('span.highlighted').removeClass('highlighted'); }, diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js index 663be4c9..5cbfe004 100644 --- a/docs/_static/searchtools.js +++ b/docs/_static/searchtools.js @@ -1,10 +1,10 @@ /* - * searchtools.js_t - * ~~~~~~~~~~~~~~~~ + * searchtools.js + * ~~~~~~~~~~~~~~ * * Sphinx JavaScript utilties for the full-text search. * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -36,11 +36,10 @@ jQuery.makeSearchSummary = function(text, keywords, hlwords) { return rv; } - /** * Porter Stemmer */ -var Stemmer = function() { +var PorterStemmer = function() { var step2list = { ational: 'ate', @@ -301,20 +300,20 @@ var Search = { }, query : function(query) { - var stopwords = ["and","then","into","it","as","are","in","if","for","no","there","their","was","is","be","to","that","but","they","not","such","with","by","a","on","these","of","will","this","near","the","or","at"]; - - // Stem the searchterms and add them to the correct list - var stemmer = new Stemmer(); + var stopwords = ['and', 'then', 'into', 'it', 'as', 'are', 'in', + 'if', 'for', 'no', 'there', 'their', 'was', 'is', + 'be', 'to', 'that', 'but', 'they', 'not', 'such', + 'with', 'by', 'a', 'on', 'these', 'of', 'will', + 'this', 'near', 'the', 'or', 'at']; + + // stem the searchterms and add them to the correct list + var stemmer = new PorterStemmer(); var searchterms = []; var excluded = []; var hlterms = []; var tmp = query.split(/\s+/); - var objectterms = []; + var object = (tmp.length == 1) ? tmp[0].toLowerCase() : null; for (var i = 0; i < tmp.length; i++) { - if (tmp[i] != "") { - objectterms.push(tmp[i].toLowerCase()); - } - if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) || tmp[i] == "") { // skip this "word" @@ -345,6 +344,9 @@ var Search = { var filenames = this._index.filenames; var titles = this._index.titles; var terms = this._index.terms; + var objects = this._index.objects; + var objtypes = this._index.objtypes; + var objnames = this._index.objnames; var fileMap = {}; var files = null; // different result priorities @@ -355,19 +357,40 @@ var Search = { $('#search-progress').empty(); // lookup as object - for (var i = 0; i < objectterms.length; i++) { - var others = [].concat(objectterms.slice(0,i), - objectterms.slice(i+1, objectterms.length)) - var results = this.performObjectSearch(objectterms[i], others); - // Assume first word is most likely to be the object, - // other words more likely to be in description. - // Therefore put matches for earlier words first. - // (Results are eventually used in reverse order). - objectResults = results[0].concat(objectResults); - importantResults = results[1].concat(importantResults); - unimportantResults = results[2].concat(unimportantResults); + if (object != null) { + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + if (fullname.toLowerCase().indexOf(object) > -1) { + match = objects[prefix][name]; + descr = objnames[match[1]] + _(', in ') + titles[match[0]]; + // XXX the generated anchors are not generally correct + // XXX there may be custom prefixes + result = [filenames[match[0]], fullname, '#'+fullname, descr]; + switch (match[2]) { + case 1: objectResults.push(result); break; + case 0: importantResults.push(result); break; + case 2: unimportantResults.push(result); break; + } + } + } + } } + // sort results descending + objectResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + importantResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + unimportantResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + // perform the search on the required terms for (var i = 0; i < searchterms.length; i++) { var word = searchterms[i]; @@ -466,7 +489,7 @@ var Search = { listItem.slideDown(5, function() { displayNextItem(); }); - }, "text"); + }); } else { // no source available, just display title Search.output.append(listItem); @@ -487,74 +510,9 @@ var Search = { } } displayNextItem(); - }, - - performObjectSearch : function(object, otherterms) { - var filenames = this._index.filenames; - var objects = this._index.objects; - var objnames = this._index.objnames; - var titles = this._index.titles; - - var importantResults = []; - var objectResults = []; - var unimportantResults = []; - - for (var prefix in objects) { - for (var name in objects[prefix]) { - var fullname = (prefix ? prefix + '.' : '') + name; - if (fullname.toLowerCase().indexOf(object) > -1) { - var match = objects[prefix][name]; - var objname = objnames[match[1]][2]; - var title = titles[match[0]]; - // If more than one term searched for, we require other words to be - // found in the name/title/description - if (otherterms.length > 0) { - var haystack = (prefix + ' ' + name + ' ' + - objname + ' ' + title).toLowerCase(); - var allfound = true; - for (var i = 0; i < otherterms.length; i++) { - if (haystack.indexOf(otherterms[i]) == -1) { - allfound = false; - break; - } - } - if (!allfound) { - continue; - } - } - var descr = objname + _(', in ') + title; - anchor = match[3]; - if (anchor == '') - anchor = fullname; - else if (anchor == '-') - anchor = objnames[match[1]][1] + '-' + fullname; - result = [filenames[match[0]], fullname, '#'+anchor, descr]; - switch (match[2]) { - case 1: objectResults.push(result); break; - case 0: importantResults.push(result); break; - case 2: unimportantResults.push(result); break; - } - } - } - } - - // sort results descending - objectResults.sort(function(a, b) { - return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); - }); - - importantResults.sort(function(a, b) { - return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); - }); - - unimportantResults.sort(function(a, b) { - return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); - }); - - return [importantResults, objectResults, unimportantResults] } } $(document).ready(function() { Search.init(); -}); \ No newline at end of file +}); diff --git a/docs/_static/sidebar.js b/docs/_static/sidebar.js index a45e1926..73185171 100644 --- a/docs/_static/sidebar.js +++ b/docs/_static/sidebar.js @@ -16,7 +16,7 @@ * Once the browser is closed the cookie is deleted and the position * reset to the default (expanded). * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -29,9 +29,6 @@ $(function() { var sidebar = $('.sphinxsidebar'); var sidebarwrapper = $('.sphinxsidebarwrapper'); - // for some reason, the document has no sidebar; do not run into errors - if (!sidebar.length) return; - // original margin-left of the bodywrapper and width of the sidebar // with the sidebar expanded var bw_margin_expanded = bodywrapper.css('margin-left'); diff --git a/docs/_static/underscore.js b/docs/_static/underscore.js index 5d899143..9146e086 100644 --- a/docs/_static/underscore.js +++ b/docs/_static/underscore.js @@ -1,10 +1,3 @@ -// Underscore.js 0.5.5 -// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the terms of the MIT license. -// Portions of Underscore are inspired by or borrowed from Prototype.js, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore/ (function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e - - Adding the fields to the database — Django Categories 0.8.8 documentation - + Adding the fields to the database — Django Categories v1.0b1 documentation - - - + +
-

Django Categories 0.8.8 documentation

+

Django Categories v1.0b1 documentation

diff --git a/docs/custom_categories.html b/docs/custom_categories.html new file mode 100644 index 00000000..726201a8 --- /dev/null +++ b/docs/custom_categories.html @@ -0,0 +1,351 @@ + + + + + + + + Creating Custom Categories — Django Categories v1.0b1 documentation + + + + + + + + + + + +
+

Django Categories v1.0b1 documentation

+
+ + +
+
+ + + +

This Page

+ + + +
+
+ + + +
+
+
+
+ +
+

Creating Custom Categories

+

Django Categories isn’t just for using a single category model. It allows you to create your own custom category-like models with as little or much customization as you need.

+
+

Name only

+

For many cases, you want a simple user-managed lookup table. You can do this with just a little bit of code. The resulting model will include name, slug and active fields and a hierarchical admin.

+
    +
  1. Create a model that subclasses CategoryBase

    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    from categories.models import CategoryBase
    +
    +class SimpleCategory(CategoryBase):
    +    """
    +    A simple of catgorizing example
    +    """
    +    
    +    class Meta:
    +        verbose_name_plural = 'simple categories'
    +
    +
    +
  2. +
  3. For the Django admin, create a subclass of CategoryBaseAdminForm. Create an internal class called Meta and assign the attribute model to your model (see line 9 in the example).

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    from django.contrib import admin
    +
    +from categories.admin import CategoryBaseAdmin, CategoryBaseAdminForm
    +
    +from .models import SimpleCategory
    +
    +class SimpleCategoryAdminForm(CategoryBaseAdminForm):
    +    class Meta:
    +        model = SimpleCategory
    +
    +class SimpleCategoryAdmin(CategoryBaseAdmin):
    +    form = SimpleCategoryAdminForm
    +
    +admin.site.register(SimpleCategory, SimpleCategoryAdmin)
    +
    +
    +
  4. +
  5. Create a subclass of CategoryBaseAdmin and assign form attribute the class created above (see line 12 in the above example).

    +
  6. +
  7. Register your model and custom model admin class.

    +
  8. +
+
+
+

Name and other data

+

Sometimes you need more functionality, such as extra metadata and custom functions. The Category model in this package does this.

+
    +
  1. Create a model that subclasses CategoryBase as above.

    +
  2. +
  3. Add new fields to the model. The Category model adds these extra fields.

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    class Category(CategoryBase):
    +    thumbnail = models.FileField(
    +        upload_to=THUMBNAIL_UPLOAD_PATH, 
    +        null=True, blank=True,
    +        storage=STORAGE(),)
    +    thumbnail_width = models.IntegerField(blank=True, null=True)
    +    thumbnail_height = models.IntegerField(blank=True, null=True)
    +    order = models.IntegerField(default=0)
    +    alternate_title = models.CharField(
    +        blank=True,
    +        default="",
    +        max_length=100,
    +        help_text="An alternative title to use on pages with this category.")
    +    alternate_url = models.CharField(
    +        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)
    +    meta_keywords = models.CharField(
    +        blank=True,
    +        default="",
    +        max_length=255,
    +        help_text="Comma-separated keywords for search engines.")
    +    meta_extra = models.TextField(
    +        blank=True,
    +        default="",
    +        help_text="(Advanced) Any additional HTML to be placed verbatim "
    +                  "in the &lt;head&gt;")
    +
    +
    +
  4. +
  5. Add new methods to the model. For example, the Category model adds several new methods, including overriding the save() method.

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    +15
    def save(self, *args, **kwargs):
    +    if self.thumbnail:
    +        from django.core.files.images import get_image_dimensions
    +        import django
    +        if django.VERSION[1] < 2:
    +            width, height = get_image_dimensions(self.thumbnail.file)
    +        else:
    +            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)
    +
    +
    +
  6. +
  7. Alter Meta or MPTTMeta class. Either of these inner classes can be overridden, however your Meta class should inherit CategoryBase.Meta. Options for Meta are in the Django-MPTT docs.

    +
    1
    +2
    +3
    +4
    +5
    class Meta(CategoryBase.Meta):
    +    verbose_name_plural = 'categories'
    +
    +class MPTTMeta:
    +    order_insertion_by = ('order', 'name')
    +
    +
    +
  8. +
  9. For the admin, you must create a form that subclasses CategoryBaseAdminForm and at least sets the Meta.model attribute. You can also alter the form fields and cleaning methods, as Category does.

    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    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']
    +        else:
    +            return self.cleaned_data['alternate_title']
    +
    +
    +
  10. +
  11. Next you must subclass CategoryBaseAdmin and assign the form attribute the form class created above. You can alter any other attributes as necessary.

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    class CategoryAdmin(CategoryBaseAdmin):
    +    form = CategoryAdminForm
    +    list_display = ('name', 'alternate_title', 'active')
    +    fieldsets = (
    +        (None, {
    +            'fields': ('parent', 'name', 'thumbnail', 'active')
    +        }),
    +        ('Meta Data', {
    +            'fields': ('alternate_title', 'alternate_url', 'description', 
    +                        'meta_keywords', 'meta_extra'),
    +            'classes': ('collapse',),
    +        }),
    +        ('Advanced', {
    +            'fields': ('order', 'slug'),
    +            'classes': ('collapse',),
    +        }),
    +    )
    +
    +
    +
  12. +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/genindex.html b/docs/genindex.html index 948809c4..e4ec556f 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -1,23 +1,18 @@ - - - - Index — Django Categories 0.8.8 documentation - + Index — Django Categories v1.0b1 documentation - - +
-

Django Categories 0.8.8 documentation

+

Django Categories v1.0b1 documentation

diff --git a/docs/index.html b/docs/index.html index 962b8196..aec13330 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,20 +2,17 @@ - - Django Categories v 0.8 — Django Categories 0.8.8 documentation - + Django Categories v 1.0 — Django Categories v1.0b1 documentation - - +
-

Django Categories 0.8.8 documentation

+

Django Categories v1.0b1 documentation