├── LICENSE ├── README ├── setup.py └── taxonomy ├── __init__.py ├── admin.py ├── models.py ├── templatetags ├── __init__.py └── taxonomy_tags.py ├── tests.py └── views.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Brian K. Jones (bkjones@gmail.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the Brian K. Jones, nor the names of its 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 20 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 24 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | django-taxonomy is an app that is completely self-contained, following 2 | the 'loosely-coupled' mantra that most of the Django community puts forth. 3 | 4 | With this app installed, you not only create categories, you create the whole 5 | notion of categories in your application, *then* create categories. If your 6 | app evolves and needs tags later, don't install a "tags" app, just create the 7 | notion of "tags" right there on the spot using django-taxonomy! Wanna create 8 | labels? What are those?! I don't know, but if it's a way to classify 9 | information in your app, I hope django-taxonomy can handle it. 10 | 11 | To see some background on why/how this was created, please see this blog post, 12 | which discusses it in gory detail. 13 | http://protocolostomy.com/2009/08/21/lessons-learned-while-creating-a-generic-taxonomy-app-for-django/ 14 | 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from distutils.core import setup 4 | 5 | setup( 6 | name='django-taxonomy', 7 | version='0.0.1', 8 | description='A flexible taxonomy approach for Django.', 9 | author='Brian K. Jones', 10 | author_email='bkjones@gmail.com', 11 | url='http://github.com/bkjones/django-taxonomy', 12 | packages=[ 13 | 'taxonomy', 14 | 'taxonomy.templatetags', 15 | ], 16 | classifiers=[ 17 | 'Development Status :: 4 - Beta', 18 | 'Environment :: Web Environment', 19 | 'Framework :: Django', 20 | 'Intended Audience :: Developers', 21 | 'License :: OSI Approved :: BSD License' 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python', 24 | 'Topic :: Utilities' 25 | ], 26 | ) -------------------------------------------------------------------------------- /taxonomy/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Brian K. Jones' 2 | __version__ = (0, 0, 1) 3 | -------------------------------------------------------------------------------- /taxonomy/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from taxonomy.models import Taxonomy, TaxonomyTerm, TaxonomyMap 3 | 4 | class TaxonomyAdmin(admin.ModelAdmin): 5 | pass 6 | 7 | class TaxonomyTermAdmin(admin.ModelAdmin): 8 | pass 9 | 10 | class TaxonomyMapAdmin(admin.ModelAdmin): 11 | pass 12 | 13 | 14 | admin.site.register(Taxonomy, TaxonomyAdmin) 15 | admin.site.register(TaxonomyTerm, TaxonomyTermAdmin) 16 | admin.site.register(TaxonomyMap, TaxonomyMapAdmin) 17 | -------------------------------------------------------------------------------- /taxonomy/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.contenttypes import generic 3 | from django.contrib.contenttypes.models import ContentType 4 | 5 | ### 6 | ### Managers 7 | ### 8 | class TaxonomyManager(models.Manager): 9 | def get_for_object(self, obj): 10 | """ 11 | Get all taxonomy type-term pairings for an instance of a content object. 12 | """ 13 | ctype = ContentType.objects.get_for_model(obj) 14 | return self.filter(content_type__pk=ctype.pk, 15 | object_id=obj.pk) 16 | 17 | 18 | ### 19 | ### Models 20 | ### 21 | 22 | class Taxonomy(models.Model): 23 | """A facility for creating custom content classification types""" 24 | type = models.CharField(max_length=50, unique=True) 25 | 26 | class Meta: 27 | verbose_name = "taxonomy" 28 | verbose_name_plural = "taxonomies" 29 | 30 | def __unicode__(self): 31 | return self.type 32 | 33 | class TaxonomyTerm(models.Model): 34 | """Terms are associated with a specific Taxonomy, and should be generically usable with any contenttype""" 35 | type = models.ForeignKey(Taxonomy) 36 | term = models.CharField(max_length=50) 37 | parent = models.ForeignKey('self', null=True,blank=True) 38 | 39 | class Meta: 40 | unique_together = ('type', 'term') 41 | 42 | def __unicode__(self): 43 | return self.term 44 | 45 | class TaxonomyMap(models.Model): 46 | """Mappings between content and any taxonomy types/terms used to classify it""" 47 | term = models.ForeignKey(TaxonomyTerm, db_index=True) 48 | type = models.ForeignKey(Taxonomy, db_index=True) 49 | content_type = models.ForeignKey(ContentType, verbose_name='content type', db_index=True) 50 | object_id = models.PositiveIntegerField(db_index=True) 51 | object = generic.GenericForeignKey('content_type', 'object_id') 52 | 53 | objects = TaxonomyManager() 54 | 55 | class Meta: 56 | unique_together = ('term', 'type', 'content_type', 'object_id') 57 | 58 | def __unicode__(self): 59 | return u'%s [%s]' % (self.term, self.type) 60 | 61 | 62 | -------------------------------------------------------------------------------- /taxonomy/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkjones/django-taxonomy/ec6ed2fcadadcb12c256a34183aa6746a2045b81/taxonomy/templatetags/__init__.py -------------------------------------------------------------------------------- /taxonomy/templatetags/taxonomy_tags.py: -------------------------------------------------------------------------------- 1 | from django.db.models import get_model 2 | from django.template import Library, Node, TemplateSyntaxError, Variable, resolve_variable 3 | 4 | from taxonomy.models import TaxonomyMap 5 | 6 | register = Library() 7 | 8 | class TaxonomyForObjectNode(Node): 9 | def __init__(self, obj, context_var): 10 | self.obj = Variable(obj) 11 | self.context_var = context_var 12 | 13 | def render(self, context): 14 | context[self.context_var] = \ 15 | TaxonomyMap.objects.get_for_object(self.obj.resolve(context)) 16 | return '' 17 | 18 | def do_taxonomy_for_object(parser, token): 19 | """ 20 | Retrieves a list of ``TaxonomyMap`` objects and stores them in a context variable. 21 | 22 | Usage:: 23 | 24 | {% taxonomy_for_object [object] as [varname] %} 25 | 26 | Example:: 27 | 28 | {% taxonomy_for_object foo_object as taxonomy_list %} 29 | """ 30 | bits = token.contents.split() 31 | if len(bits) != 4: 32 | raise TemplateSyntaxError(('%s taxonomy requires exactly three arguments') % bits[0]) 33 | if bits[2] != 'as': 34 | raise TemplateSyntaxError(("second argument to %s taxonomy must be 'as'") % bits[0]) 35 | return TaxonomyForObjectNode(bits[1], bits[3]) 36 | 37 | register.tag('taxonomy_for_object', do_taxonomy_for_object) 38 | -------------------------------------------------------------------------------- /taxonomy/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | -------------------------------------------------------------------------------- /taxonomy/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | --------------------------------------------------------------------------------