├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST ├── README.markdown ├── breadcrumbs ├── __init__.py ├── breadcrumbs.py ├── fixtures │ └── sample_flatpages_for_breadcrumbs.json ├── middleware.py ├── models.py ├── tests │ ├── __init__.py │ ├── breadcrumbs_tests.py │ ├── flatpages_tests.py │ ├── singleton_tests.py │ ├── templates │ │ ├── 404.html │ │ ├── base.html │ │ ├── flatpages │ │ │ └── default.html │ │ └── page1.html │ ├── urls.py │ └── views.py ├── urls.py ├── utils.py └── views.py ├── breadcrumbs_sample ├── __init__.py ├── manage.py ├── settings.py ├── test.db ├── urls.py └── webui │ ├── __init__.py │ ├── models.py │ ├── templates │ ├── flatpages │ │ └── default.html │ └── home.html │ ├── tests.py │ └── views.py ├── dist ├── django-breadcrumbs-1.1.0.tar.gz └── django-breadcrumbs-1.1.2.tar.gz ├── flatpages.json ├── requirements.txt ├── runtests.py ├── runtests.sh ├── sample_d14 ├── dev.db ├── manage.py ├── requirements.txt ├── sample_d14 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── webui │ ├── __init__.py │ ├── models.py │ ├── templates │ ├── flatpages │ │ └── default.html │ └── home.html │ ├── tests.py │ └── views.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.sw* 3 | build 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | env: 6 | - DJANGO=1.4.2 7 | - DJANGO=1.3.4 8 | install: 9 | - pip install -q Django==$DJANGO --use-mirrors 10 | - pip install -q . --use-mirrors 11 | before_script: 12 | - mysql -e 'create database sentry;' 13 | - psql -c 'create database sentry;' -U postgres 14 | script: 15 | - python runtests.py 16 | notifications: 17 | email: 18 | recipients: 19 | - philipe.rp@gmail.com 20 | on_success: change 21 | on_failure: change 22 | branches: 23 | only: 24 | - master 25 | - tests 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | NEW BSD LICENSE: http://www.opensource.org/licenses/bsd-license.php 2 | 3 | Copyright (c) 2009, Felipe R. Prenholato 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the Felipe R. Prenholato nor the names of its 17 | contributors may be used to endorse or promote products derived from this 18 | software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | setup.py 2 | breadcrumbs/__init__.py 3 | breadcrumbs/breadcrumbs.py 4 | breadcrumbs/middleware.py 5 | breadcrumbs/models.py 6 | breadcrumbs/tests.py 7 | breadcrumbs/utils.py 8 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Django Breadcrumbs 2 | 3 | 4 | 5 | *django-breadcrumbs* is a breadcrumb system to Django framework that allow you to add custom breadcrumbs for simple views, generic views and support Django FlatPages app. 6 | 7 | It works as a pluggable middleware that add a breadcrumbs callable/iterable in your request object, allowing you to set breadcrumbs (one or more) in each view accepting objects, lists or tuples added from request.breadcrumbs and is iterable, easy to use in templates providing a object with name and url attributes. 8 | 9 | # Django versions support. 10 | 11 | Breadcrumbs support 1.3.x and 1.4.x releases of Django. 12 | Django 1.2.x support was dropped in django-breadcrumbs-1.1.3. 13 | 14 | My support will follow Django policy. 15 | 16 | #1 - Install django-breadcrumbs 17 | 18 | Add **breadcrumbs.middleware.BreadcrumbsMiddleware** to your **MIDDLEWARE_CLASSES** and **breadcrumbs** to your **INSTALLED_APPS**. 19 | 20 | The middleware store breadcrumbs in request, and the app is needed to enable Django signals in breadcrumbs app. 21 | 22 | Also, if you did't put request context processor on yours TEMPLATE_CONTEXT_PROCESSORS, add it, ex: 23 | 24 | TEMPLATE_CONTEXT_PROCESSORS = ( 25 | 'django.contrib.auth.context_processors.auth', 26 | 'django.core.context_processors.debug', 27 | 'django.core.context_processors.i18n', 28 | 'django.core.context_processors.media', 29 | 'django.core.context_processors.static', 30 | 'django.core.context_processors.tz', 31 | 'django.contrib.messages.context_processors.messages', 32 | 'django.core.context_processors.request' 33 | ) 34 | 35 | 36 | 37 | #2 - Adding breadcrumbs 38 | 39 | To add breadcrumbs you just need to call **request.breadcrumbs('Title',url)**, ex: 40 | 41 | def about(request): 42 | request.breadcrumbs(_("About"),request.path_info) 43 | ... 44 | 45 | def generic_crud_view(request,model,action): 46 | """ 47 | model = model name 48 | action = action name 49 | """ 50 | 51 | request.breadcrumbs([ 52 | (_(model.title()),'/crud/%s' % model), 53 | (_('%s %s') % (action.title(),model.title()),'/crud/%s/%s' % (model,action)), 54 | ]) 55 | 56 | ... 57 | 58 | All modes of add a breadcrumb: 59 | 60 | # one by one 61 | request.breadcrumbs( name, url ) 62 | 63 | # various tuples/lists 64 | request.breadcrumbs( ( (name1, url1), (name2, url2), (name3, url3), ...,) ) 65 | request.breadcrumbs( [ [name1, url1], [name2, url2], [name3, url3], ...] ) 66 | 67 | # objects with attributes name and url in list / tuple format: 68 | request.breadcrumbs( ( obj1, obj2, obj3, obj4, ......) ) 69 | request.breadcrumbs( [ obj1, obj2, obj3, obj4, ......] ) 70 | 71 | *Note: You can use request.breadcrumbs safely in any middleware after BreadcrumbsMiddleware or any place where you have request object after BreadcrumbsMiddleware are processed* 72 | 73 | #3 - Enable FlatPages + Breadcrumbs 74 | 75 | FlatPages is a app that allow user create urls with static content and a title. But create breadcrumbs for this kind of 'unknow' url path isn't fun at all, so I modified FlatpageFallbackMiddleware to fill breadcrumbs for each flat page in path. 76 | 77 | Is really easy to use, just add **breadcrumbs.middleware.FlatpageFallbackMiddleware** in your **MIDDLEWARE_CLASSES** after BreadcrumbsMiddleware and remove Django FlatpageFallbackMiddleware. Now you flat pages will be in breadcrumbs too. 78 | 79 | FlatpageFallbackMiddleware will call **breadcrumbs.views.flatpage**, that as bonus, cache results of FlatPage models, avoiding DB in every request, and in every part of breadcrumb. 80 | 81 | ## Flatpages in urls.py. 82 | 83 | Django also supports Flatpages in urls.py, as doc in http://goo.gl/iCvf3 show. To use this way, do something like: 84 | 85 | urlpatterns = patterns('', 86 | (r'^pages/', include('breadcrumbs.urls')), 87 | ) 88 | 89 | urlpatterns += patterns('breadcrumbs.views', 90 | (r'^pages2/(?P.*)$', 'flatpage'), 91 | ) 92 | 93 | urlpatterns += patterns('breadcrumbs.views', 94 | url(r'^license/$', 'flatpage', {'url': '/flat04/'}, name='license'), 95 | ) 96 | 97 | 98 | #4 - Using in templates 99 | 100 | To use breadcrumbs in template, only that you need is iterate over breadcrumbs, example: 101 | 102 | {% for breadcrumb in request.breadcrumbs %} 103 | {{ breadcrumb.name }}{% if not forloop.last %} » {% endif %} 104 | {% endfor %} 105 | 106 | #5 - Options 107 | 108 | django-breadcrumbs have a single option to set in your settings.py: 109 | 110 | BREADCRUMBS_AUTO_HOME: defaults to False, If True, breadcrumbs add as first Breadcrumb in list (_("Home"),u"/") 111 | BREADCRUMBS_HOME_TITLE: defaults to _(u'Home') 112 | 113 | -------------------------------------------------------------------------------- /breadcrumbs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '1.1.4a1' 3 | 4 | from .breadcrumbs import Breadcrumb 5 | -------------------------------------------------------------------------------- /breadcrumbs/breadcrumbs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Classes to add request.breadcrumbs as one class to have a list of breadcrumbs 4 | TODO: maybe is better to move to contrib/breadcrumbs 5 | """ 6 | 7 | from django.conf import settings 8 | from django.utils.safestring import mark_safe 9 | from django.utils.translation import ugettext as _ 10 | try: 11 | from django.utils import six 12 | except ImportError: 13 | import six 14 | 15 | 16 | class BreadcrumbsInvalidFormat(Exception): 17 | """ 18 | Simple exception that can be extended 19 | """ 20 | pass 21 | 22 | 23 | class BreadcrumbsNotSet(Exception): 24 | """ 25 | Raised in utils.breadcrumbs_for_flatpages when we not have breadcrumbs in 26 | request. 27 | """ 28 | pass 29 | 30 | 31 | class Breadcrumb(object): 32 | """ 33 | Breadcrumb can have methods to customize breadcrumb object, Breadcrumbs 34 | class send to us name and url. 35 | """ 36 | def __init__(self, name, url): 37 | # HERE 38 | # 39 | # If I don't use force_unicode, always runs ok, but have problems on 40 | # template with unicode text 41 | self.name = name 42 | self.url = url 43 | 44 | def __str__(self): 45 | return self.__unicode__() 46 | 47 | def __unicode__(self): 48 | return u"%s,%s" % (self.name, self.url) 49 | 50 | def __repr__(self): 51 | return u"Breadcrumb <%s,%s>" % (self.name, self.url) 52 | 53 | class Breadcrumbs(object): 54 | """ 55 | Breadcrumbs maintain a list of breadcrumbs that you can get interating with 56 | class or with get_breadcrumbs(). 57 | """ 58 | 59 | def __call__(self, *args, **kwargs): 60 | if not len(args) and not len(kwargs): 61 | return self 62 | return self._add(*args, **kwargs) 63 | 64 | def __fill_home(self): 65 | # fill home if settings.BREADCRUMBS_AUTO_HOME is True 66 | if self.__autohome and len(self.__bds) == 0: 67 | home_title = getattr(settings, 'BREADCRUMBS_HOME_TITLE', _(u'Home')) 68 | self.__fill_bds((home_title, u"/")) 69 | 70 | def _clean(self): 71 | self.__bds = [] 72 | self.__autohome = getattr(settings, 'BREADCRUMBS_AUTO_HOME', False) 73 | self.__urls = [] 74 | self.__fill_home() 75 | 76 | def _add(self, *a, **kw): 77 | 78 | # match **{'name': name, 'url': url} 79 | if kw.get('name') and kw.get('url'): 80 | self.__validate((kw['name'], kw['url']), 0) 81 | self.__fill_bds((kw['name'], kw['url'])) 82 | # match Breadcrumbs( 'name', 'url' ) 83 | if len(a) == 2 and type(a[0]) not in (list, tuple): 84 | if(self.__validate(a, 0)): 85 | self.__fill_bds(a) 86 | # match ( ( 'name', 'url'), ..) and samething with list 87 | elif len(a) == 1 and type(a[0]) in (list, tuple) \ 88 | and len(a[0]) > 0: 89 | for i, arg in enumerate(a[0]): 90 | if isinstance(arg, dict): 91 | self._add(**arg) 92 | elif self.__validate(arg, i): 93 | self.__fill_bds(arg) 94 | # try to ( obj1, obj2, ... ) and samething with list 95 | else: 96 | for arg in a: 97 | if type(arg) in (list, tuple): 98 | self._add(arg) 99 | elif isinstance(arg, dict): 100 | self._add(**arg) 101 | else: 102 | raise BreadcrumbsInvalidFormat(_("We accept lists of " 103 | "tuples, lists of dicts, or two args as name and url, " 104 | "not '%s'") % a) 105 | 106 | 107 | def __init__(self, *a, **kw): 108 | """ 109 | Call validate and if ok, call fill bd 110 | """ 111 | super(Breadcrumbs, self).__init__(*a, **kw) 112 | self._clean() 113 | if a or kw: 114 | self._add(*a, **kw) 115 | 116 | 117 | def __validate(self, obj, index): 118 | """ 119 | check for object type and return a string as name for each item of a 120 | list or tuple with items, if error was found raise 121 | BreadcrumbsInvalidFormat 122 | """ 123 | # for list or tuple 124 | if type(obj) in (list, tuple): 125 | if len(obj) == 2: 126 | if (not obj[0] and not obj[1]) or \ 127 | not isinstance(obj[0], six.string_types) and \ 128 | not isinstance(obj[1], six.string_types): 129 | raise BreadcrumbsInvalidFormat(u"Invalid format for \ 130 | breadcrumb %s in %s" % (index, type(obj).__name__)) 131 | if len(obj) != 2: 132 | raise BreadcrumbsInvalidFormat( 133 | u"Wrong itens number in breadcrumb %s in %s. \ 134 | You need to send as example (name,url)" % \ 135 | (index, type(obj).__name__) 136 | ) 137 | # for objects and dicts 138 | else: 139 | if isinstance(obj, dict) and obj.get('name') and obj.get('url'): 140 | obj = Breadcrumb(obj['name'], obj['url']) 141 | if not hasattr(obj, 'name') and not hasattr(obj, 'url'): 142 | raise BreadcrumbsInvalidFormat(u"You need to use a tuple like " 143 | "(name, url) or dict or one object with name and url " 144 | "attributes for breadcrumb.") 145 | return True 146 | 147 | def __fill_bds(self, bd): 148 | """ 149 | simple interface to add Breadcrumb to bds 150 | """ 151 | if hasattr(bd, 'name') and hasattr(bd, 'url'): 152 | bd = Breadcrumb(bd.name, bd.url) 153 | else: 154 | bd = Breadcrumb(*bd) 155 | if bd.url not in self.__urls: 156 | self.__bds.append(bd) 157 | self.__urls.append(bd.url) 158 | 159 | def __len__(self): 160 | return len(self.__bds) 161 | 162 | def __iter__(self): 163 | return iter(self.__bds) 164 | 165 | def __getitem__(self, key): 166 | return self.__bds[key] 167 | 168 | def __repr__(self): 169 | return self.__unicode__() 170 | 171 | def __str__(self): 172 | return self.__unicode__() 173 | 174 | def __unicode__(self): 175 | return u"Breadcrumbs <%s>" % u", ".join([mark_safe(item.name) for item \ 176 | in self[:10]] + [u' ...']) 177 | 178 | def all(self): 179 | return self.__bds 180 | 181 | # vim: ts=4 sts=4 sw=4 et ai 182 | -------------------------------------------------------------------------------- /breadcrumbs/fixtures/sample_flatpages_for_breadcrumbs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "flatpages.flatpage", 5 | "fields": { 6 | "registration_required": false, 7 | "title": "Flat Page 1", 8 | "url": "/flat01/", 9 | "template_name": "", 10 | "sites": [ 11 | 1 12 | ], 13 | "content": "This is flat 1", 14 | "enable_comments": false 15 | } 16 | }, 17 | { 18 | "pk": 2, 19 | "model": "flatpages.flatpage", 20 | "fields": { 21 | "registration_required": false, 22 | "title": "Flat page 2", 23 | "url": "/flat01/flat02/", 24 | "template_name": "", 25 | "sites": [ 26 | 1 27 | ], 28 | "content": "This is flat 2 under flat 1", 29 | "enable_comments": false 30 | } 31 | }, 32 | { 33 | "pk": 3, 34 | "model": "flatpages.flatpage", 35 | "fields": { 36 | "registration_required": false, 37 | "title": "Flat page 3", 38 | "url": "/flat01/flat02/flat03/", 39 | "template_name": "", 40 | "sites": [ 41 | 1 42 | ], 43 | "content": "This is flat 3 under flat 2 that is under flat 1", 44 | "enable_comments": false 45 | } 46 | }, 47 | { 48 | "pk": 4, 49 | "model": "flatpages.flatpage", 50 | "fields": { 51 | "registration_required": false, 52 | "title": "Flat Page 4", 53 | "url": "/flat04/", 54 | "template_name": "", 55 | "sites": [ 56 | 1 57 | ], 58 | "content": "This is flat 4 :)", 59 | "enable_comments": false 60 | } 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /breadcrumbs/middleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf import settings 3 | from django.http import Http404 4 | from .breadcrumbs import Breadcrumbs 5 | 6 | try: 7 | from django.utils.deprecation import MiddlewareMixin 8 | except ImportError: # Django < 1.10 9 | # Works perfectly for everyone using MIDDLEWARE_CLASSES 10 | MiddlewareMixin = object 11 | 12 | 13 | class BreadcrumbsMiddleware(MiddlewareMixin): 14 | def process_request(self, request): 15 | if not hasattr(request, 'breadcrumbs'): 16 | request.breadcrumbs = Breadcrumbs() 17 | 18 | 19 | class FlatpageFallbackMiddleware(MiddlewareMixin): 20 | def process_response(self, request, response): 21 | # do nothing if flatpages middleware isn't enabled, also if response 22 | # code isn't 404. 23 | if response.status_code != 404: 24 | return response 25 | try: 26 | from .views import flatpage 27 | return flatpage(request, request.path_info) 28 | # Return the original response if any errors happened. Because this 29 | # is a middleware, we can't assume the errors will be caught elsewhere. 30 | except Http404: 31 | return response 32 | except: 33 | if settings.DEBUG: 34 | raise 35 | return response 36 | -------------------------------------------------------------------------------- /breadcrumbs/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib.flatpages.models import FlatPage 3 | from django.core.cache import cache 4 | from django.db.models.signals import post_save 5 | from .utils import make_flatpages_cache_key 6 | 7 | 8 | def clean_flatpages_cache(sender, **kw): 9 | """ 10 | Invalidate flatpages cache, because some flatpage was saved! 11 | """ 12 | cache.delete(make_flatpages_cache_key()) 13 | 14 | post_save.connect(clean_flatpages_cache, sender=FlatPage) 15 | -------------------------------------------------------------------------------- /breadcrumbs/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from singleton_tests import * 3 | from breadcrumbs_tests import * 4 | from flatpages_tests import * 5 | -------------------------------------------------------------------------------- /breadcrumbs/tests/breadcrumbs_tests.py: -------------------------------------------------------------------------------- 1 | # # coding: utf-8 2 | import os 3 | from django.conf import settings 4 | from django.test import TestCase 5 | from django.utils.datastructures import SortedDict 6 | 7 | from breadcrumbs.breadcrumbs import Breadcrumb, Breadcrumbs 8 | 9 | 10 | class BreadcrumbsTest(TestCase): 11 | urls = 'breadcrumbs.tests.urls' 12 | 13 | def setUp(self): 14 | self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES 15 | breadcrumbs_middleware_class = 'breadcrumbs.middleware.BreadcrumbsMiddleware' 16 | if breadcrumbs_middleware_class not in settings.MIDDLEWARE_CLASSES: 17 | settings.MIDDLEWARE_CLASSES += (breadcrumbs_middleware_class,) 18 | 19 | self.old_TEMPLATE_CONTEXT_PROCESSORS = settings.TEMPLATE_CONTEXT_PROCESSORS 20 | request_processor = 'django.core.context_processors.request' 21 | if request_processor not in settings.TEMPLATE_CONTEXT_PROCESSORS: 22 | settings.TEMPLATE_CONTEXT_PROCESSORS += (request_processor,) 23 | 24 | self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS 25 | settings.TEMPLATE_DIRS = ( 26 | os.path.join( 27 | os.path.dirname(__file__), 28 | 'templates' 29 | ), 30 | ) 31 | 32 | # now we start singleton. singleton are tested on singleton_tests.py 33 | self.breadcrumbs = Breadcrumbs() 34 | 35 | # set some common ta to use 36 | SD = SortedDict 37 | self.data = [ 38 | SD([('name', 'Page1'), ('url', '/page1/')]), 39 | SD([('name', 'Page2'), ('url', '/page2/')]), 40 | SD([('name', 'Page3'), ('url', '/page2/page3/')]), 41 | SD([('name', 'Page4'), ('url', '/page4/')]), 42 | SD([('name', 'Page5'), ('url', '/page5/')]), 43 | ] 44 | 45 | def tearDown(self): 46 | settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES 47 | settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS 48 | settings.TEMPLATE_CONTEXT_PROCESSORS = self.old_TEMPLATE_CONTEXT_PROCESSORS 49 | 50 | # kill singleton 51 | self.breadcrumbs._drop_it() 52 | del self.data 53 | 54 | def test_breadcrumb_class(self): 55 | b = Breadcrumb(**self.data[0]) 56 | self.assertEqual(b.name, self.data[0]['name']) 57 | self.assertEqual(b.url, self.data[0]['url']) 58 | 59 | def test_breadcrumbs_singleton(self): 60 | brd = Breadcrumbs() 61 | brd(**self.data[0]) 62 | brd2 = Breadcrumbs() 63 | brd2(**self.data[1]) 64 | # test 3 instances to see if singleton really works 65 | self.assertEqual(self.breadcrumbs[0].__dict__, 66 | Breadcrumb(**self.data[0]).__dict__) 67 | self.assertEqual(self.breadcrumbs[1].__dict__, 68 | Breadcrumb(**self.data[1]).__dict__) 69 | self.assertEqual(brd[1].__dict__, Breadcrumbs()[1].__dict__) 70 | 71 | def test_breadcrumbs_params_and_iteration(self): 72 | 73 | b = self.breadcrumbs 74 | 75 | b(self.data[0]['name'], self.data[0]['url']) 76 | b(*self.data[1].values()) 77 | b(**self.data[2]) 78 | b(self.data[3:5]) 79 | for i, bd in enumerate(b): 80 | self.assertEqual(bd.__dict__, Breadcrumb(**self.data[i]).__dict__) 81 | 82 | 83 | def test_request_breadcrumbs(self): 84 | response = self.client.get('/page1/') 85 | self.assertEqual(response.status_code, 200) 86 | self.assertContains(response, 87 | '') 88 | -------------------------------------------------------------------------------- /breadcrumbs/tests/flatpages_tests.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | from django.conf import settings 4 | from django.contrib.flatpages.models import FlatPage 5 | from django.test import TestCase 6 | from django.utils.datastructures import SortedDict 7 | from breadcrumbs.breadcrumbs import Breadcrumb, Breadcrumbs 8 | 9 | 10 | class FlatpagesTest(TestCase): 11 | fixtures = ['sample_flatpages_for_breadcrumbs.json'] 12 | 13 | def setUp(self): 14 | breadcrumbs_middleware_class = 'breadcrumbs.middleware.BreadcrumbsMiddleware' 15 | flatpages_middleware_class = 'breadcrumbs.middleware.FlatpageFallbackMiddleware' 16 | 17 | # remove breadcrumbs middlewares to assert that we set correct 18 | # order 19 | self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES 20 | settings.MIDDLEWARE_CLASSES = [mid for mid \ 21 | in self.old_MIDDLEWARE_CLASSES if mid not in \ 22 | (breadcrumbs_middleware_class, flatpages_middleware_class)] 23 | settings.MIDDLEWARE_CLASSES += [ 24 | breadcrumbs_middleware_class, 25 | flatpages_middleware_class 26 | ] 27 | self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS 28 | settings.TEMPLATE_DIRS = ( 29 | os.path.join( 30 | os.path.dirname(__file__), 31 | 'templates' 32 | ), 33 | ) 34 | # now we start singleton. singleton are tested on singleton_tests.py 35 | self.breadcrumbs = Breadcrumbs() 36 | 37 | def tearDown(self): 38 | settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES 39 | 40 | # kill singleton 41 | self.breadcrumbs._drop_it() 42 | 43 | def test_flatpages_fixture_loaded(self): 44 | flat1 = FlatPage.objects.get(pk=1) 45 | self.assertEqual(flat1.title, u"Flat Page 1") 46 | self.assertEqual(flat1.content, u"This is flat 1") 47 | flat2 = FlatPage.objects.get(pk=2) 48 | self.assertEqual(flat2.title, u"Flat page 2") 49 | self.assertEqual(flat2.content, u"This is flat 2 under flat 1") 50 | 51 | def test_404_flatpage(self): 52 | response = self.client.get('/404_not_found/') 53 | self.assertEqual(response.status_code, 404) 54 | # self.assertContains(response, "

Isn't it flat!

") 55 | 56 | def test_fallback_flatpage(self): 57 | response = self.client.get('/flat01/') 58 | self.assertEqual(response.status_code, 200) 59 | self.assertContains(response, 60 | '') 61 | 62 | response = self.client.get('/flat01/flat02/') 63 | self.assertEqual(response.status_code, 200) 64 | self.assertContains(response, 65 | '') 66 | -------------------------------------------------------------------------------- /breadcrumbs/tests/singleton_tests.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from django.test import TestCase 4 | 5 | from breadcrumbs.breadcrumbs import Singleton 6 | 7 | 8 | class Foo(Singleton): 9 | """ 10 | Class used in singleton tests 11 | """ 12 | pass 13 | 14 | 15 | class SingletonTest(TestCase): 16 | 17 | def test_singleton(self): 18 | """ 19 | Test singleton implementation with values 20 | """ 21 | a = Foo() 22 | a.attr_1 = 1 23 | 24 | b = Foo() 25 | 26 | self.assertEqual(b.attr_1, 1) 27 | self.assertTrue(a is b, "'a' isn't 'b', Singleton not works") 28 | 29 | def test_singleton_destruction(self): 30 | """ 31 | Test singleton imsinplementation with values and than destroy it 32 | """ 33 | a = Foo() 34 | id_a = id(a) 35 | a.attr_1 = 1 36 | 37 | b = Foo() 38 | id_b = id(b) 39 | 40 | self.assertEqual(id_a, id_b) 41 | self.assertEqual(b.attr_1, 1) 42 | self.assertTrue(a is b, "'a' isn't 'b', Singleton not works") 43 | 44 | a._drop_it() 45 | 46 | c = Foo() 47 | id_c = id(c) 48 | self.assertNotEqual(id_a, id_c) 49 | self.assertNotEqual(getattr(c,'attr_1',None), 1) 50 | 51 | -------------------------------------------------------------------------------- /breadcrumbs/tests/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Not Found 5 | 6 | 7 | 404 - Not found 8 | 9 | 10 | -------------------------------------------------------------------------------- /breadcrumbs/tests/templates/base.html: -------------------------------------------------------------------------------- 1 | {% spaceless %} 2 | 3 | 4 | {{ flatpage.title }} 5 | 6 | 7 | {% block breadcrumb %} 8 | 13 | {% endblock breadcrumb %} 14 | {% block body %} 15 | {% endblock body %} 16 | 17 | {% endspaceless %} 18 | -------------------------------------------------------------------------------- /breadcrumbs/tests/templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 |

{{ flatpage.content }}

4 | {% endblock body %} 5 | -------------------------------------------------------------------------------- /breadcrumbs/tests/templates/page1.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 | Page 1 :) 4 | {% endblock body %} 5 | -------------------------------------------------------------------------------- /breadcrumbs/tests/urls.py: -------------------------------------------------------------------------------- 1 | import django 2 | if django.get_version().startswith('1.4'): 3 | from django.conf.urls import patterns 4 | else: 5 | from django.conf.urls.defaults import patterns 6 | from .views import page1 7 | # special urls for flatpage test cases 8 | urlpatterns = patterns('', 9 | (r'^page1/', page1), 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /breadcrumbs/tests/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.shortcuts import render_to_response 4 | from django.template import RequestContext 5 | 6 | def page1(request): 7 | request.breadcrumbs("Page 1", request.get_full_path()) 8 | return render_to_response('page1.html', {}, 9 | context_instance=RequestContext(request)) 10 | -------------------------------------------------------------------------------- /breadcrumbs/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns 2 | 3 | urlpatterns = patterns('breadcrumbs.views', 4 | (r'^(?P.*)$', 'flatpage'), 5 | ) 6 | -------------------------------------------------------------------------------- /breadcrumbs/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib.flatpages.models import FlatPage 3 | from django.http import Http404 4 | from .breadcrumbs import Breadcrumbs, BreadcrumbsNotSet 5 | from django.conf import settings 6 | from django.core.cache import cache 7 | 8 | 9 | def make_flatpages_cache_key(): 10 | """ 11 | Create a cache key based on some basic data, respecting defined site. 12 | """ 13 | key = "flatpages_cache_%s-%s" % (hash(settings.SITE_ID), 14 | hash(settings.SECRET_KEY)) 15 | 16 | return key 17 | 18 | 19 | def get_flapage_from_cache(url): 20 | """ 21 | Try get flatpage from cache entry with all flatpages by url. 22 | If not found, create cache and return flatpage from db. 23 | 24 | This probably avoid some hits on DB. 25 | """ 26 | site_id = settings.SITE_ID 27 | cache_key = make_flatpages_cache_key() 28 | flatpages = cache.get(cache_key) 29 | if flatpages and url in flatpages: 30 | return flatpages[url] 31 | 32 | # flatpages cache not exist or flatpage not found. 33 | 34 | # 1. get all flatpages. 35 | flatpages = dict([(f.url, f) for f in 36 | FlatPage.objects.filter(sites__id__exact=site_id).order_by('url')]) 37 | 38 | # 2. if url not in flatpages, raise Http404 39 | if url not in flatpages: 40 | raise Http404 41 | 42 | # 3. if url in flatpages, recreate cache and return flatpage 43 | cache.delete(cache_key) 44 | cache.add(cache_key, flatpages) 45 | return flatpages[url] 46 | 47 | 48 | def breadcrumbs_for_flatpages(request, flatpage): 49 | """ given request and flatpage instance create breadcrumbs for all flat 50 | pages """ 51 | if not hasattr(request, 'breadcrumbs') or \ 52 | not isinstance(request.breadcrumbs, Breadcrumbs): 53 | raise BreadcrumbsNotSet(u"You need to setup breadcrumbs to use this " 54 | u"function.") 55 | 56 | if not isinstance(flatpage, FlatPage) or not hasattr(flatpage, 'id'): 57 | raise TypeError(u"flatpage argument isn't a FlatPage instance or not " 58 | u"have id.") 59 | 60 | # URL for a flatpage can be composed of other flatpages, ex: 61 | # 62 | # We have: 63 | # flatpage01 = /flat01/ 64 | # flatpage02 = /flat01/flat02/ 65 | # flatpage03 = /flat01/flat02/flat03/ 66 | # 67 | # In breadcrumbs we want to know each title of each page, so we split url 68 | # in parts, and try to get flatpage title. 69 | # 70 | # However, you can define something like that in your urls.py: 71 | # (r'^pages/', include('breadcrumbs.urls')), 72 | # And, we will never know what is /pages/, so we ignore it for now. 73 | paths = [] 74 | for part in request.path_info.split(u"/"): 75 | # When split we have u"" for slashes 76 | if len(part) == 0: 77 | continue 78 | # Add slash agai 79 | if not part.startswith(u"/"): 80 | part = u"/" + part 81 | if not part.endswith(u"/"): 82 | part = part + u"/" 83 | # If we have something on paths, url for flatpage is composed of what we 84 | # have in path + part. Note that strings in path not have last slash, but 85 | # part have. 86 | if len(paths) > 0: 87 | url = u"".join(paths + [part]) 88 | else: 89 | url = part 90 | # if part of url is same url of flatpage instance, we don't hit 91 | # database again, we get page from FlatPage instance. 92 | # If part of url isn't same url of flatpage instance, we try to get it. 93 | # If page doesn't exist, we just continue to next part. 94 | if url == flatpage.url: 95 | request.breadcrumbs(flatpage.title, flatpage.url) 96 | else: 97 | try: 98 | f = FlatPage.objects.get(url=url, sites=settings.SITE_ID) 99 | except FlatPage.DoesNotExist: 100 | # TODO: this part can be a view, maybe is a good idea get that 101 | # view and check for viewfunc.breadcrumb_title or 102 | # viewclass.breadcrumb_title attributes. 103 | continue 104 | else: 105 | request.breadcrumbs(f.title, f.url) 106 | # add last part of path in paths with one slash 107 | paths.append(u"/" + url[1:-1].rpartition(u"/")[-1]) 108 | -------------------------------------------------------------------------------- /breadcrumbs/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.flatpages.views import render_flatpage 3 | from django.http import Http404, HttpResponsePermanentRedirect 4 | from .utils import breadcrumbs_for_flatpages, get_flapage_from_cache 5 | 6 | 7 | def flatpage(request, url): 8 | """ 9 | Public interface to the flat page view. 10 | 11 | Models: `flatpages.flatpages` 12 | Templates: Uses the template defined by the ``template_name`` field, 13 | or `flatpages/default.html` if template_name is not defined. 14 | Context: 15 | flatpage 16 | `flatpages.flatpages` object 17 | """ 18 | 19 | if not url.startswith('/'): 20 | url = '/' + url 21 | try: 22 | # try load flatpage from cache, else, update cache and get from DB 23 | f = get_flapage_from_cache(url) 24 | except Http404: 25 | if not url.endswith('/') and settings.APPEND_SLASH: 26 | url += '/' 27 | f = get_flapage_from_cache(url) 28 | return HttpResponsePermanentRedirect('%s/' % request.path) 29 | else: 30 | raise 31 | 32 | # create breadcrumbs 33 | breadcrumbs_for_flatpages(request, f) 34 | 35 | return render_flatpage(request, f) 36 | -------------------------------------------------------------------------------- /breadcrumbs_sample/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/breadcrumbs_sample/__init__.py -------------------------------------------------------------------------------- /breadcrumbs_sample/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /breadcrumbs_sample/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for breadcrumbs_sample project. 2 | import os 3 | PROJECT_ROOT= os.path.dirname(__file__) 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | ADMINS = ( 9 | # ('Your Name', 'your_email@domain.com'), 10 | ) 11 | 12 | MANAGERS = ADMINS 13 | 14 | DATABASES = { 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 17 | 'NAME': '%s/test.db' % PROJECT_ROOT, # Or path to database file if using sqlite3. 18 | 'USER': '', # Not used with sqlite3. 19 | 'PASSWORD': '', # Not used with sqlite3. 20 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 21 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 22 | } 23 | } 24 | 25 | # Local time zone for this installation. Choices can be found here: 26 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 27 | # although not all choices may be available on all operating systems. 28 | # On Unix systems, a value of None will cause Django to use the same 29 | # timezone as the operating system. 30 | # If running in a Windows environment this must be set to the same as your 31 | # system time zone. 32 | TIME_ZONE = 'America/Chicago' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # If you set this to False, Django will not format dates, numbers and 45 | # calendars according to the current locale 46 | USE_L10N = True 47 | 48 | # Absolute path to the directory that holds media. 49 | # Example: "/home/media/media.lawrence.com/" 50 | MEDIA_ROOT = '' 51 | 52 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 53 | # trailing slash if there is a path component (optional in other cases). 54 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 55 | MEDIA_URL = '' 56 | 57 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 58 | # trailing slash. 59 | # Examples: "http://foo.com/media/", "/media/". 60 | ADMIN_MEDIA_PREFIX = '/media/' 61 | 62 | # Make this unique, and don't share it with anybody. 63 | SECRET_KEY = 'fh+tvwi6aw(z_of+f!=1heme@o+r^=^=c&b%hh7r+$x+e&3pj7' 64 | 65 | # List of callables that know how to import templates from various sources. 66 | TEMPLATE_LOADERS = ( 67 | 'django.template.loaders.filesystem.Loader', 68 | 'django.template.loaders.app_directories.Loader', 69 | # 'django.template.loaders.eggs.Loader', 70 | ) 71 | 72 | TEMPLATE_CONTEXT_PROCESSORS = ( 73 | "django.contrib.auth.context_processors.auth", 74 | "django.core.context_processors.debug", 75 | "django.core.context_processors.i18n", 76 | "django.core.context_processors.media", 77 | "django.contrib.messages.context_processors.messages", 78 | 'django.core.context_processors.request', 79 | ) 80 | 81 | MIDDLEWARE_CLASSES = ( 82 | 'django.middleware.common.CommonMiddleware', 83 | 'django.contrib.sessions.middleware.SessionMiddleware', 84 | 'django.middleware.csrf.CsrfViewMiddleware', 85 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 86 | 'django.contrib.messages.middleware.MessageMiddleware', 87 | 'breadcrumbs.middleware.BreadcrumbsMiddleware', 88 | 'breadcrumbs.middleware.FlatpageFallbackMiddleware', 89 | ) 90 | 91 | ROOT_URLCONF = 'breadcrumbs_sample.urls' 92 | 93 | TEMPLATE_DIRS = ( 94 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 95 | # Always use forward slashes, even on Windows. 96 | # Don't forget to use absolute paths, not relative paths. 97 | ) 98 | 99 | INSTALLED_APPS = ( 100 | 'django.contrib.auth', 101 | 'django.contrib.contenttypes', 102 | 'django.contrib.sessions', 103 | 'django.contrib.sites', 104 | 'django.contrib.messages', 105 | 'django.contrib.flatpages', 106 | # Uncomment the next line to enable the admin: 107 | 'django.contrib.admin', 108 | 'breadcrumbs_sample.webui' 109 | ) 110 | 111 | BREADCRUMBS_AUTO_HOME = True 112 | -------------------------------------------------------------------------------- /breadcrumbs_sample/test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/breadcrumbs_sample/test.db -------------------------------------------------------------------------------- /breadcrumbs_sample/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Example: 9 | # (r'^breadcrumbs_sample/', include('breadcrumbs_sample.foo.urls')), 10 | 11 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 12 | # to INSTALLED_APPS to enable admin documentation: 13 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | (r'^admin/', include(admin.site.urls)), 17 | (r'^$','webui.views.home'), 18 | (r'^someview/$','webui.views.someview'), 19 | ) 20 | -------------------------------------------------------------------------------- /breadcrumbs_sample/webui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/breadcrumbs_sample/webui/__init__.py -------------------------------------------------------------------------------- /breadcrumbs_sample/webui/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /breadcrumbs_sample/webui/templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "home.html" %} 2 | -------------------------------------------------------------------------------- /breadcrumbs_sample/webui/templates/home.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | Breadcrumbs test page | {% for b in request.breadcrumbs %}{{ b.name }}{% if not forloop.last %} > {% endif %}{% endfor %} 8 | 9 | 10 | 11 | {{ request.breadcrumbs }} 12 |

Breadcrumb: {% for b in request.breadcrumbs %}{{ b.name }}{% if not forloop.last %} / {% endif %}{% endfor %}

13 |

Links: Home | Breadcrumb in view | Flatpages 14 |

Content:
15 | {% if text %}{{ text }}{% endif %} 16 | {% if flatpage %}{{ flatpage.content }}{% endif %} 17 | 18 | 19 | -------------------------------------------------------------------------------- /breadcrumbs_sample/webui/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 | -------------------------------------------------------------------------------- /breadcrumbs_sample/webui/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | 3 | from django.shortcuts import render_to_response 4 | from django.template.context import RequestContext 5 | 6 | 7 | def home(request): 8 | print request.breadcrumbs 9 | return render_to_response('home.html', 10 | {'text': 'Hello, this is home!'}, 11 | context_instance=RequestContext(request)) 12 | 13 | 14 | def someview(request): 15 | request.breadcrumbs('just a view to show some url', request.path) 16 | 17 | return render_to_response('home.html', 18 | {'text': 'Hello, this is some second view'}, 19 | context_instance=RequestContext(request)) 20 | -------------------------------------------------------------------------------- /dist/django-breadcrumbs-1.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/dist/django-breadcrumbs-1.1.0.tar.gz -------------------------------------------------------------------------------- /dist/django-breadcrumbs-1.1.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/dist/django-breadcrumbs-1.1.2.tar.gz -------------------------------------------------------------------------------- /flatpages.json: -------------------------------------------------------------------------------- 1 | [{"pk": 1, "model": "flatpages.flatpage", "fields": {"registration_required": false, "title": "Flat page 1", "url": "/flat01/", "template_name": "", "sites": [1], "content": "Flat page 1\r\n\r\n\r\nFlat page links: Flat 1 | Flat 2 | Flat 3", "enable_comments": false}}, {"pk": 2, "model": "flatpages.flatpage", "fields": {"registration_required": false, "title": "Flat page 2", "url": "/flat01/flat02/", "template_name": "", "sites": [1], "content": "Flag page 2\r\n\r\n\r\nFlat page links: Flat 1 | Flat 2 | Flat 3", "enable_comments": false}}, {"pk": 3, "model": "flatpages.flatpage", "fields": {"registration_required": false, "title": "Flat page 3", "url": "/flat01/flat02/flat03/", "template_name": "", "sites": [1], "content": "Flat page 3\r\n\r\nFlat page links: Flat 1 | Flat 2 | Flat 3", "enable_comments": false}}] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=1.3.4 2 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys 4 | 5 | from django.conf import settings 6 | 7 | 8 | if not settings.configured: 9 | settings_dict = dict( 10 | SITE_ID=1, 11 | ROOT_URLCONF='breadcrumbs.tests.urls', 12 | INSTALLED_APPS=( 13 | 'django.contrib.contenttypes', 14 | 'django.contrib.sites', 15 | 'django.contrib.flatpages', 16 | 'breadcrumbs', 17 | ), 18 | DATABASES={ 19 | "default": { 20 | "ENGINE": "django.db.backends.sqlite3" 21 | } 22 | }, 23 | ) 24 | 25 | settings.configure(**settings_dict) 26 | 27 | 28 | def runtests(*test_args): 29 | 30 | if not test_args: 31 | test_args = ['breadcrumbs'] 32 | 33 | # try to set more used args to django test 34 | test_kwargs = { 35 | 'verbosity': 1, 36 | 'noinput': False, 37 | 'failfast': False, 38 | } 39 | for i,arg in enumerate(sys.argv): 40 | if arg.startswith('-v'): 41 | _value = arg.replace('-v','') 42 | if len(_value): 43 | test_kwargs['verbosity'] = int(_value) 44 | else: 45 | test_kwargs['verbosity'] = int(sys.argv[i+1]) 46 | if arg == '--noinput': 47 | test_kwargs['noinput'] = True 48 | if arg == '--failfast': 49 | test_kwargs['failfast'] =True 50 | 51 | parent = os.path.dirname(os.path.abspath(__file__)) 52 | sys.path.insert(0, parent) 53 | 54 | from django.test.simple import DjangoTestSuiteRunner 55 | failures = DjangoTestSuiteRunner( 56 | interactive=True, **test_kwargs).run_tests(test_args) 57 | sys.exit(failures) 58 | 59 | 60 | if __name__ == '__main__': 61 | runtests() 62 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This is a helper script to test breadcrumbs against many environ versions." 4 | echo "This script test against Django 1.3.x and 1.4.x using sqlite. It depends of pip." 5 | 6 | OLD_DJANGO="$(pip freeze | grep Django)" 7 | 8 | test_13() { 9 | echo "Testing against Django 1.3.x ..." 10 | pip install -q -U --use-mirrors Django\<1.4.0 11 | python runtests.py --failfast --noinput -v0 && return 0 || return 1 12 | } 13 | 14 | test_14() { 15 | echo "Testing against Django 1.4.x ..." 16 | pip install -q -U --use-mirrors Django\<1.5.0 17 | python runtests.py --failfast --noinput -v0 && return 0 || return 1 18 | } 19 | 20 | test_13 && test_14 21 | 22 | result=$? 23 | 24 | if [[ "$OLD_DJANGO" ]]; 25 | then 26 | pip install -q -U --use-mirrors "$OLD_DJANGO" 27 | echo "$OLD_DJANGO reinstalled" 28 | else 29 | pip uninstall Django 30 | fi 31 | 32 | exit $result 33 | -------------------------------------------------------------------------------- /sample_d14/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/sample_d14/dev.db -------------------------------------------------------------------------------- /sample_d14/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_d14.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /sample_d14/requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=1.4.1 2 | pylibmc>=1.2.3 3 | -------------------------------------------------------------------------------- /sample_d14/sample_d14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/sample_d14/sample_d14/__init__.py -------------------------------------------------------------------------------- /sample_d14/sample_d14/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for sample_d14 project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 15 | 'NAME': 'dev.db', # Or path to database file if using sqlite3. 16 | 'USER': '', # Not used with sqlite3. 17 | 'PASSWORD': '', # Not used with sqlite3. 18 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 19 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 20 | } 21 | } 22 | 23 | # Local time zone for this installation. Choices can be found here: 24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 25 | # although not all choices may be available on all operating systems. 26 | # In a Windows environment this must be set to your system time zone. 27 | TIME_ZONE = 'America/Chicago' 28 | 29 | # Language code for this installation. All choices can be found here: 30 | # http://www.i18nguy.com/unicode/language-identifiers.html 31 | LANGUAGE_CODE = 'en-us' 32 | 33 | SITE_ID = 1 34 | 35 | # If you set this to False, Django will make some optimizations so as not 36 | # to load the internationalization machinery. 37 | USE_I18N = True 38 | 39 | # If you set this to False, Django will not format dates, numbers and 40 | # calendars according to the current locale. 41 | USE_L10N = True 42 | 43 | # If you set this to False, Django will not use timezone-aware datetimes. 44 | USE_TZ = True 45 | 46 | # Absolute filesystem path to the directory that will hold user-uploaded files. 47 | # Example: "/home/media/media.lawrence.com/media/" 48 | MEDIA_ROOT = '' 49 | 50 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 51 | # trailing slash. 52 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 53 | MEDIA_URL = '' 54 | 55 | # Absolute path to the directory static files should be collected to. 56 | # Don't put anything in this directory yourself; store your static files 57 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 58 | # Example: "/home/media/media.lawrence.com/static/" 59 | STATIC_ROOT = '' 60 | 61 | # URL prefix for static files. 62 | # Example: "http://media.lawrence.com/static/" 63 | STATIC_URL = '/static/' 64 | 65 | # Additional locations of static files 66 | STATICFILES_DIRS = ( 67 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 68 | # Always use forward slashes, even on Windows. 69 | # Don't forget to use absolute paths, not relative paths. 70 | ) 71 | 72 | # List of finder classes that know how to find static files in 73 | # various locations. 74 | STATICFILES_FINDERS = ( 75 | 'django.contrib.staticfiles.finders.FileSystemFinder', 76 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 77 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 78 | ) 79 | 80 | # Make this unique, and don't share it with anybody. 81 | SECRET_KEY = '14i6-n-!v*w07gyx4is-j3z)ou2^%8u+i5t0(3$^wyme1odalz' 82 | 83 | # List of callables that know how to import templates from various sources. 84 | TEMPLATE_LOADERS = ( 85 | 'django.template.loaders.filesystem.Loader', 86 | 'django.template.loaders.app_directories.Loader', 87 | # 'django.template.loaders.eggs.Loader', 88 | ) 89 | 90 | MIDDLEWARE_CLASSES = ( 91 | 'django.middleware.common.CommonMiddleware', 92 | 'django.contrib.sessions.middleware.SessionMiddleware', 93 | 'django.middleware.csrf.CsrfViewMiddleware', 94 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 95 | 'django.contrib.messages.middleware.MessageMiddleware', 96 | # Uncomment the next line for simple clickjacking protection: 97 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 98 | 'breadcrumbs.middleware.BreadcrumbsMiddleware', 99 | 'breadcrumbs.middleware.FlatpageFallbackMiddleware', 100 | ) 101 | 102 | TEMPLATE_CONTEXT_PROCESSORS = ( 103 | 'django.contrib.auth.context_processors.auth', 104 | 'django.core.context_processors.debug', 105 | 'django.core.context_processors.i18n', 106 | 'django.core.context_processors.media', 107 | 'django.core.context_processors.static', 108 | 'django.core.context_processors.tz', 109 | 'django.contrib.messages.context_processors.messages', 110 | 'django.core.context_processors.request' 111 | ) 112 | 113 | ROOT_URLCONF = 'sample_d14.urls' 114 | 115 | # Python dotted path to the WSGI application used by Django's runserver. 116 | WSGI_APPLICATION = 'sample_d14.wsgi.application' 117 | 118 | TEMPLATE_DIRS = ( 119 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 120 | # Always use forward slashes, even on Windows. 121 | # Don't forget to use absolute paths, not relative paths. 122 | ) 123 | 124 | INSTALLED_APPS = ( 125 | 'django.contrib.auth', 126 | 'django.contrib.contenttypes', 127 | 'django.contrib.sessions', 128 | 'django.contrib.sites', 129 | 'django.contrib.messages', 130 | 'django.contrib.staticfiles', 131 | 'django.contrib.flatpages', 132 | # Uncomment the next line to enable the admin: 133 | 'django.contrib.admin', 134 | # Uncomment the next line to enable admin documentation: 135 | # 'django.contrib.admindocs', 136 | 'webui', 137 | 'breadcrumbs', 138 | ) 139 | 140 | BREADCRUMBS_AUTO_HOME = True 141 | 142 | CACHES = { 143 | 'default': { 144 | 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 145 | 'LOCATION': '127.0.0.1:11211', 146 | } 147 | } 148 | 149 | # A sample logging configuration. The only tangible logging 150 | # performed by this configuration is to send an email to 151 | # the site admins on every HTTP 500 error when DEBUG=False. 152 | # See http://docs.djangoproject.com/en/dev/topics/logging for 153 | # more details on how to customize your logging configuration. 154 | LOGGING = { 155 | 'version': 1, 156 | 'disable_existing_loggers': False, 157 | 'filters': { 158 | 'require_debug_false': { 159 | '()': 'django.utils.log.RequireDebugFalse' 160 | } 161 | }, 162 | 'handlers': { 163 | 'mail_admins': { 164 | 'level': 'ERROR', 165 | 'filters': ['require_debug_false'], 166 | 'class': 'django.utils.log.AdminEmailHandler' 167 | } 168 | }, 169 | 'loggers': { 170 | 'django.request': { 171 | 'handlers': ['mail_admins'], 172 | 'level': 'ERROR', 173 | 'propagate': True, 174 | }, 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /sample_d14/sample_d14/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Examples: 9 | # url(r'^$', 'sample_d14.views.home', name='home'), 10 | # url(r'^sample_d14/', include('sample_d14.foo.urls')), 11 | 12 | # Uncomment the admin/doc line below to enable admin documentation: 13 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | url(r'^admin/', include(admin.site.urls)), 17 | url(r'^$', 'webui.views.home', name='home'), 18 | url(r'^someview/$', 'webui.views.someview', name='someview'), 19 | (r'^pages/', include('breadcrumbs.urls')), 20 | ) 21 | 22 | urlpatterns += patterns('breadcrumbs.views', 23 | (r'^pages2/(?P.*)$', 'flatpage'), 24 | url(r'^license/$', 'flatpage', {'url': '/flat04/'}, name='license'), 25 | ) 26 | -------------------------------------------------------------------------------- /sample_d14/sample_d14/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sample_d14 project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_d14.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /sample_d14/webui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipe-prenholato/django-breadcrumbs/2ff5104733099a9a4d2f5156c1453e231b164b25/sample_d14/webui/__init__.py -------------------------------------------------------------------------------- /sample_d14/webui/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /sample_d14/webui/templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "home.html" %} 2 | -------------------------------------------------------------------------------- /sample_d14/webui/templates/home.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | Breadcrumbs test page | {% for b in request.breadcrumbs %}{{ b.name }}{% if not forloop.last %} > {% endif %}{% endfor %} 8 | 9 | 10 | 11 | {{ request.breadcrumbs }} 12 |

Breadcrumb: {% for b in request.breadcrumbs %}{{ b.name }}{% if not forloop.last %} / {% endif %}{% endfor %}

13 |

Links: Home | Breadcrumb in view | Flatpages 14 |

Content:
15 | {% if text %}{{ text }}{% endif %} 16 | {% if flatpage %}{{ flatpage.content }}{% endif %} 17 | 18 | 19 | -------------------------------------------------------------------------------- /sample_d14/webui/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /sample_d14/webui/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render_to_response 2 | from django.template.context import RequestContext 3 | 4 | 5 | def home(request): 6 | return render_to_response('home.html', 7 | {'text': 'Hello, this is home!'}, 8 | context_instance=RequestContext(request)) 9 | 10 | 11 | def someview(request): 12 | request.breadcrumbs('just a view to show some url', request.path) 13 | 14 | return render_to_response('home.html', 15 | {'text': 'Hello, this is some second view'}, 16 | context_instance=RequestContext(request)) 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | setup( 3 | name="django-breadcrumbs", 4 | version="1.1.4a1", 5 | packages=find_packages(exclude=('breadcrumbs_sample*', 'sample_d14*')), 6 | author="Felipe 'chronos' Prenholato", 7 | author_email="philipe.rp@gmail.com", 8 | maintainer="Felipe 'chronos' Prenholato", 9 | maintainer_email="philipe.rp@gmail.com", 10 | url="http://github.com/chronossc/django-breadcrumbs", 11 | license='NEW BSD LICENSE: http://www.opensource.org/licenses/bsd-license.php', 12 | description="Easy to use generic breadcrumbs system for Django framework.", 13 | long_description="*django-breadcrumbs* is a breadcrumb system to Django " 14 | "framework that allow you to add custom breadcrumbs for simple views, " 15 | "generic views and support Django FlatPages app. It works as a " 16 | "pluggable middleware that add a breadcrumbs callable/iterable in your " 17 | "request object, allowing you to set breadcrumbs (one or more) in " 18 | "each view accepting objects, lists or tuples added from " 19 | "request.breadcrumbs and is iterable, easy to use in templates " 20 | "providing a object with name and url attributes.", 21 | classifiers=[ 22 | "Development Status :: 5 - Production/Stable", 23 | "Environment :: Web Environment", 24 | "Framework :: Django", 25 | "Intended Audience :: Developers", 26 | "License :: OSI Approved :: BSD License", 27 | "Operating System :: OS Independent", 28 | "Programming Language :: Python", 29 | ], 30 | install_requires=["Django>=1.3.4", "six"], 31 | ) 32 | --------------------------------------------------------------------------------