opts = form._meta
if issubclass(form, EntityForm) and opts.model:
proxy_fields = proxy_fields_for_entity_model(opts.model).keys()
+
+ # Store readonly fields iff they have been declared.
+ if 'readonly_fields' in attrs or not hasattr(new_class, '_real_readonly_fields'):
+ new_class._real_readonly_fields = new_class.readonly_fields
+
readonly_fields = new_class.readonly_fields
- new_class._real_readonly_fields = readonly_fields
- new_class.readonly_fields = list(readonly_fields) + proxy_fields
+ new_class.readonly_fields = list(set(readonly_fields) | set(proxy_fields))
# Additional HACKS to handle raw_id_fields and other attributes that the admin
# uses model._meta.get_field to validate.
from django.contrib import admin
-from philo.admin.base import EntityAdmin, TreeEntityAdmin
+from philo.admin.base import EntityAdmin, TreeEntityAdmin, COLLAPSE_CLASSES
from philo.models import Node, Redirect, File
class NodeAdmin(TreeEntityAdmin):
list_display = ('slug', 'view', 'accepts_subpath')
+ related_field_lookups = {
+ 'fk': [],
+ 'm2m': [],
+ 'generic': [['view_content_type', 'view_object_id']]
+ }
def accepts_subpath(self, obj):
return obj.accepts_subpath
class RedirectAdmin(ViewAdmin):
fieldsets = (
(None, {
- 'fields': ('target', 'status_code')
+ 'fields': ('target_node', 'url_or_subpath', 'status_code')
}),
+ ('Advanced', {
+ 'fields': ('reversing_parameters',),
+ 'classes': COLLAPSE_CLASSES
+ })
)
- list_display = ('target', 'status_code')
+ list_display = ('target_url', 'status_code', 'target_node', 'url_or_subpath')
list_filter = ('status_code',)
+ raw_id_fields = ['target_node']
+ related_field_lookups = {
+ 'fk': ['target_node']
+ }
class FileAdmin(ViewAdmin):
)
if self.feeds_enabled:
urlpatterns += patterns('',
- url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)/%s/' % (self.tag_permalink_base, self.feed_suffix), self.feed_view(self.get_entries_by_tag, 'entries_by_tag_feed'), name='entries_by_tag_feed'),
+ url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)/%s$' % (self.tag_permalink_base, self.feed_suffix), self.feed_view(self.get_entries_by_tag, 'entries_by_tag_feed'), name='entries_by_tag_feed'),
)
urlpatterns += patterns('',
- url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)/' % self.tag_permalink_base, self.page_view(self.get_entries_by_tag, self.tag_page), name='entries_by_tag')
+ url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, self.page_view(self.get_entries_by_tag, self.tag_page), name='entries_by_tag')
)
if self.tag_archive_page:
urlpatterns += patterns('',
- url((r'^(?:%s)/?$' % self.tag_permalink_base), self.tag_archive_view, 'tag_archive')
+ url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
)
if self.entry_archive_page:
if self.entry_permalink_style in 'DMY':
urlpatterns += patterns('',
- url(r'^(?P<year>\d{4})/', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_year')))
+ url(r'^(?P<year>\d{4})', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_year')))
)
if self.entry_permalink_style in 'DM':
urlpatterns += patterns('',
- url(r'^(?P<year>\d{4})/(?P<month>\d{2})/?$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_month'))),
+ url(r'^(?P<year>\d{4})/(?P<month>\d{2})$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_month'))),
)
if self.entry_permalink_style == 'D':
urlpatterns += patterns('',
- url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/?$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_day')))
+ url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_day')))
)
if self.entry_permalink_style == 'D':
urlpatterns += patterns('',
- url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/?$', self.entry_view)
+ url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
)
elif self.entry_permalink_style == 'M':
urlpatterns += patterns('',
- url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)/?$', self.entry_view)
+ url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
)
elif self.entry_permalink_style == 'Y':
urlpatterns += patterns('',
- url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)/?$', self.entry_view)
+ url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
)
elif self.entry_permalink_style == 'B':
urlpatterns += patterns('',
- url((r'^(?:%s)/(?P<slug>[-\w]+)/?$' % self.entry_permalink_base), self.entry_view)
+ url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
)
else:
urlpatterns = patterns('',
- url(r'^(?P<slug>[-\w]+)/?$', self.entry_view)
+ url(r'^(?P<slug>[-\w]+)$', self.entry_view)
)
return urlpatterns
def urlpatterns(self):
urlpatterns = patterns('',
url(r'^', include(self.feed_patterns(self.get_all_articles, self.index_page, 'index'))),
- url(r'^(?:%s)/(?P<numbering>.+)/' % self.issue_permalink_base, include(self.feed_patterns(self.get_articles_by_issue, self.issue_page, 'issue')))
+ url(r'^%s/(?P<numbering>.+)' % self.issue_permalink_base, include(self.feed_patterns(self.get_articles_by_issue, self.issue_page, 'issue')))
)
if self.issue_archive_page:
urlpatterns += patterns('',
- url(r'^(?:%s)/$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
+ url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
)
if self.article_archive_page:
urlpatterns += patterns('',
- url(r'^(?:%s)/' % self.article_permalink_base, include(self.feed_patterns(self.get_all_articles, self.article_archive_page, 'articles')))
+ url(r'^%s' % self.article_permalink_base, include(self.feed_patterns(self.get_all_articles, self.article_archive_page, 'articles')))
)
if self.article_permalink_style in 'DMY':
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<year>\d{4})/' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_year')))
+ url(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_year')))
)
if self.article_permalink_style in 'DM':
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<year>\d{4})/(?P<month>\d{2})/' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_month')))
+ url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_month')))
)
if self.article_permalink_style == 'D':
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_day')))
+ url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_day')))
)
if self.article_permalink_style == 'Y':
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<year>\d{4})/(?P<slug>[\w-]+)/$' % self.article_permalink_base, self.article_view)
+ url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
)
elif self.article_permalink_style == 'M':
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)/$' % self.article_permalink_base, self.article_view)
+ url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
)
elif self.article_permalink_style == 'D':
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w-]+)/$' % self.article_permalink_base, self.article_view)
+ url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
)
else:
urlpatterns += patterns('',
- url(r'^(?:%s)/(?P<slug>[-\w]+)/?$' % self.article_permalink_base, self.article_view)
+ url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
)
return urlpatterns
from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
from django.conf.urls.defaults import url, patterns
-from django.contrib.sites.models import Site
-from django.core.urlresolvers import reverse
from django.http import HttpResponse
from philo.utils import paginate
else:
feed_type = 'atom'
- current_site = Site.objects.get_current()
- #Could this be done with request.path instead somehow?
feed_kwargs = {
- 'link': 'http://%s/%s/%s/' % (current_site.domain, request.node.get_absolute_url().strip('/'), reverse(reverse_name, urlconf=self, kwargs=kwargs).strip('/'))
+ 'link': request.node.construct_url(subpath=self.reverse(reverse_name, kwargs=kwargs), request=request, with_domain=True)
}
feed = self.get_feed(feed_type, extra_context, feed_kwargs)
for obj in objects:
kwargs = {
- 'link': 'http://%s/%s/%s/' % (current_site.domain, request.node.get_absolute_url().strip('/'), self.get_subpath(obj).strip('/'))
+ 'link': request.node.construct_url(subpath=self.reverse(obj=obj), request=request, with_domain=True)
}
self.add_item(feed, obj, kwargs=kwargs)
if self.feeds_enabled:
feed_name = '%s_feed' % base_name
urlpatterns = patterns('',
- url(r'^%s/$' % self.feed_suffix, self.feed_view(object_fetcher, feed_name), name=feed_name),
+ url(r'^%s$' % self.feed_suffix, self.feed_view(object_fetcher, feed_name), name=feed_name),
) + urlpatterns
return urlpatterns
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Navigation'
+ db.create_table('shipherd_navigation', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('node', self.gf('django.db.models.fields.related.ForeignKey')(related_name='navigation_set', to=orm['philo.Node'])),
+ ('key', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('depth', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=3)),
+ ))
+ db.send_create_signal('shipherd', ['Navigation'])
+
+ # Adding unique constraint on 'Navigation', fields ['node', 'key']
+ db.create_unique('shipherd_navigation', ['node_id', 'key'])
+
+ # Adding model 'NavigationItem'
+ db.create_table('shipherd_navigationitem', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['shipherd.NavigationItem'])),
+ ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
+ ('lft', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
+ ('rght', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
+ ('tree_id', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
+ ('level', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
+ ('navigation', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='roots', null=True, to=orm['shipherd.Navigation'])),
+ ('text', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ('target_node', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='navigation_items', null=True, to=orm['philo.Node'])),
+ ('url_or_subpath', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)),
+ ('reversing_parameters', self.gf('philo.models.fields.JSONField')(blank=True)),
+ ('order', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=0)),
+ ))
+ db.send_create_signal('shipherd', ['NavigationItem'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Navigation'
+ db.delete_table('shipherd_navigation')
+
+ # Removing unique constraint on 'Navigation', fields ['node', 'key']
+ db.delete_unique('shipherd_navigation', ['node_id', 'key'])
+
+ # Deleting model 'NavigationItem'
+ db.delete_table('shipherd_navigationitem')
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'shipherd.navigation': {
+ 'Meta': {'unique_together': "(('node', 'key'),)", 'object_name': 'Navigation'},
+ 'depth': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '3'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'navigation_set'", 'to': "orm['philo.Node']"})
+ },
+ 'shipherd.navigationitem': {
+ 'Meta': {'object_name': 'NavigationItem'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'navigation': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'roots'", 'null': 'True', 'to': "orm['shipherd.Navigation']"}),
+ 'order': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['shipherd.NavigationItem']"}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'navigation_items'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'text': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['shipherd']
from django.core.validators import RegexValidator, MinValueValidator
from django.db import models
from django.forms.models import model_to_dict
-from philo.models import TreeEntity, JSONField, Node, TreeManager, Entity
+from philo.models import TreeEntity, Node, TreeManager, Entity, TargetURLModel
from philo.validators import RedirectValidator
from UserDict import DictMixin
# A distinct query is not strictly necessary. TODO: benchmark the efficiency
# with/without distinct.
- targets = list(Node.objects.filter(navigation_items__in=items).distinct())
+ targets = list(Node.objects.filter(shipherd_navigationitem_related__in=items).distinct())
for cache in caches:
for item in cache['items']:
return NavigationCacheQuerySet(self.model, using=self._db)
-class NavigationItem(TreeEntity):
+class NavigationItem(TreeEntity, TargetURLModel):
objects = NavigationItemManager()
navigation = models.ForeignKey(Navigation, blank=True, null=True, related_name='roots', help_text="Be a root in this navigation tree.")
text = models.CharField(max_length=50)
- target_node = models.ForeignKey(Node, blank=True, null=True, related_name='navigation_items', help_text="Point to this node's url.")
- url_or_subpath = models.CharField(max_length=200, validators=[RedirectValidator()], blank=True, help_text="Point to this url or, if a node is defined and accepts subpaths, this subpath of the node.")
- reversing_parameters = JSONField(blank=True, help_text="If reversing parameters are defined, url_or_subpath will instead be interpreted as the view name to be reversed.")
-
order = models.PositiveSmallIntegerField(default=0)
def __init__(self, *args, **kwargs):
return self.get_path(field='text', pathsep=u' › ')
def clean(self):
- # Should this be enforced? Not enforcing it would allow creation of "headers" in the navbar.
- if not self.target_node and not self.url_or_subpath:
- raise ValidationError("Either a target node or a url must be defined.")
-
- if self.reversing_parameters and (not self.url_or_subpath or not self.target_node):
- raise ValidationError("Reversing parameters require a view name and a target node.")
-
- try:
- self.get_target_url()
- except NoReverseMatch, e:
- raise ValidationError(e.message)
-
+ super(NavigationItem, self).clean()
if bool(self.parent) == bool(self.navigation):
raise ValidationError("Exactly one of `parent` and `navigation` must be defined.")
- def get_target_url(self):
- node = self.target_node
- if node is not None and node.accepts_subpath and self.url_or_subpath:
- if self.reversing_parameters is not None:
- view_name = self.url_or_subpath
- params = self.reversing_parameters
- args = isinstance(params, list) and params or None
- kwargs = isinstance(params, dict) and params or None
- return node.view.reverse(view_name, args=args, kwargs=kwargs, node=node)
- else:
- subpath = self.url_or_subpath
- while subpath and subpath[0] == '/':
- subpath = subpath[1:]
- return '%s%s' % (node.get_absolute_url(), subpath)
- elif node is not None:
- return node.get_absolute_url()
- else:
- return self.url_or_subpath
- target_url = property(get_target_url)
-
def is_active(self, request):
if self.target_url == request.path:
# Handle the `default` case where the target_url and requested path
@register.filter
-def has_navigation(node): # optional arg for a key?
+def has_navigation(node, key=None):
+ nav = node.navigation
+ if key is not None:
+ if key in nav and bool(node.navigation[key]):
+ return True
+ elif key not in node.navigation:
+ return False
return bool(node.navigation)
@property
def urlpatterns(self):
urlpatterns = patterns('',
- url(r'^login/$', self.login, name='login'),
- url(r'^logout/$', self.logout, name='logout'),
+ url(r'^login$', self.login, name='login'),
+ url(r'^logout$', self.logout, name='logout'),
- url(r'^password/reset/$', csrf_protect(self.password_reset), name='password_reset'),
- url(r'^password/reset/(?P<uidb36>\w+)/(?P<token>[^/]+)/$', self.password_reset_confirm, name='password_reset_confirm'),
+ url(r'^password/reset$', csrf_protect(self.password_reset), name='password_reset'),
+ url(r'^password/reset/(?P<uidb36>\w+)/(?P<token>[^/]+)$', self.password_reset_confirm, name='password_reset_confirm'),
- url(r'^register/$', csrf_protect(self.register), name='register'),
- url(r'^register/(?P<uidb36>\w+)/(?P<token>[^/]+)/$', self.register_confirm, name='register_confirm')
+ url(r'^register$', csrf_protect(self.register), name='register'),
+ url(r'^register/(?P<uidb36>\w+)/(?P<token>[^/]+)$', self.register_confirm, name='register_confirm')
)
if self.password_change_page:
urlpatterns += patterns('',
- url(r'^password/change/$', csrf_protect(self.login_required(self.password_change)), name='password_change'),
+ url(r'^password/change$', csrf_protect(self.login_required(self.password_change)), name='password_change'),
)
return urlpatterns
def make_confirmation_link(self, confirmation_view, token_generator, user, node, token_args=None, reverse_kwargs=None):
- current_site = Site.objects.get_current()
token = token_generator.make_token(user, *(token_args or []))
kwargs = {
'uidb36': int_to_base36(user.id),
'token': token
}
kwargs.update(reverse_kwargs or {})
- return 'http://%s%s' % (current_site.domain, self.reverse(confirmation_view, kwargs=kwargs, node=node))
+ return node.construct_url(subpath=self.reverse(confirmation_view, kwargs=kwargs), with_domain=True)
def display_login_page(self, request, message, extra_context=None):
request.session.set_test_cookie()
def urlpatterns(self):
urlpatterns = super(AccountMultiView, self).urlpatterns
urlpatterns += patterns('',
- url(r'^account/$', self.login_required(self.account_view), name='account'),
- url(r'^account/email/(?P<uidb36>\w+)/(?P<email>[\w.]+[+][\w.]+)/(?P<token>[^/]+)/$', self.email_change_confirm, name='email_change_confirm')
+ url(r'^account$', self.login_required(self.account_view), name='account'),
+ url(r'^account/email/(?P<uidb36>\w+)/(?P<email>[\w.]+[+][\w.]+)/(?P<token>[^/]+)$', self.email_change_confirm, name='email_change_confirm')
)
return urlpatterns
class ViewDoesNotProvideSubpaths(Exception):
- """ Raised by get_subpath when the View does not provide subpaths (the default). """
+ """ Raised by View.reverse when the View does not provide subpaths (the default). """
silent_variable_failure = True
class ViewCanNotProvideSubpath(Exception):
- """ Raised by get_subpath when the View can not provide a subpath for the supplied object. """
+ """ Raised by View.reverse when the View can not provide a subpath for the supplied arguments. """
silent_variable_failure = True
except Site.DoesNotExist:
current_site = None
+ path = request._cached_node_path
+ trailing_slash = False
+ if path[-1] == '/':
+ trailing_slash = True
+
try:
- node, subpath = Node.objects.get_with_path(request._cached_node_path, root=getattr(current_site, 'root_node', None), absolute_result=False)
+ node, subpath = Node.objects.get_with_path(path, root=getattr(current_site, 'root_node', None), absolute_result=False)
except Node.DoesNotExist:
node = None
if node:
+ if subpath is None:
+ subpath = ""
+ subpath = "/" + subpath
+
+ if trailing_slash and subpath[-1] != "/":
+ subpath += "/"
+
node.subpath = subpath
request._found_node = node
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Redirect.target_node'
+ db.add_column('philo_redirect', 'target_node', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='philo_redirect_related', null=True, to=orm['philo.Node']), keep_default=False)
+
+ # Adding field 'Redirect.url_or_subpath'
+ db.add_column('philo_redirect', 'url_or_subpath', self.gf('django.db.models.fields.CharField')(default='', max_length=200, blank=True), keep_default=False)
+
+ # Adding field 'Redirect.reversing_parameters'
+ db.add_column('philo_redirect', 'reversing_parameters', self.gf('philo.models.fields.JSONField')(default='null', blank=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Redirect.target_node'
+ db.delete_column('philo_redirect', 'target_node_id')
+
+ # Deleting field 'Redirect.url_or_subpath'
+ db.delete_column('philo_redirect', 'url_or_subpath')
+
+ # Deleting field 'Redirect.reversing_parameters'
+ db.delete_column('philo_redirect', 'reversing_parameters_json')
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.collection': {
+ 'Meta': {'object_name': 'Collection'},
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.collectionmember': {
+ 'Meta': {'object_name': 'CollectionMember'},
+ 'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.contentlet': {
+ 'Meta': {'object_name': 'Contentlet'},
+ 'content': ('philo.models.fields.TemplateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.contentreference': {
+ 'Meta': {'object_name': 'ContentReference'},
+ 'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.file': {
+ 'Meta': {'object_name': 'File'},
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.foreignkeyvalue': {
+ 'Meta': {'object_name': 'ForeignKeyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.jsonvalue': {
+ 'Meta': {'object_name': 'JSONValue'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('philo.models.fields.JSONField', [], {'default': "'null'"})
+ },
+ 'philo.manytomanyvalue': {
+ 'Meta': {'object_name': 'ManyToManyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.redirect': {
+ 'Meta': {'object_name': 'Redirect'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+ 'target': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'philo.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['philo']
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ for redirect in orm.Redirect.objects.all():
+ redirect.url_or_subpath = redirect.target
+ redirect.save()
+
+
+ def backwards(self, orm):
+ "This will cause data loss and is not advisable. Blurg!"
+ for redirect in orm.Redirect.objects.all():
+ redirect.target = redirect.url_or_subpath
+ redirect.save()
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.collection': {
+ 'Meta': {'object_name': 'Collection'},
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.collectionmember': {
+ 'Meta': {'object_name': 'CollectionMember'},
+ 'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.contentlet': {
+ 'Meta': {'object_name': 'Contentlet'},
+ 'content': ('philo.models.fields.TemplateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.contentreference': {
+ 'Meta': {'object_name': 'ContentReference'},
+ 'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.file': {
+ 'Meta': {'object_name': 'File'},
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.foreignkeyvalue': {
+ 'Meta': {'object_name': 'ForeignKeyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.jsonvalue': {
+ 'Meta': {'object_name': 'JSONValue'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('philo.models.fields.JSONField', [], {'default': "'null'"})
+ },
+ 'philo.manytomanyvalue': {
+ 'Meta': {'object_name': 'ManyToManyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.redirect': {
+ 'Meta': {'object_name': 'Redirect'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+ 'target': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'philo.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['philo']
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Deleting field 'Redirect.target'
+ db.delete_column('philo_redirect', 'target')
+
+
+ def backwards(self, orm):
+
+ # Adding field 'Redirect.target'
+ db.add_column('philo_redirect', 'target', self.gf('django.db.models.fields.CharField')(default='', max_length=200), keep_default=False)
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.collection': {
+ 'Meta': {'object_name': 'Collection'},
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.collectionmember': {
+ 'Meta': {'object_name': 'CollectionMember'},
+ 'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.contentlet': {
+ 'Meta': {'object_name': 'Contentlet'},
+ 'content': ('philo.models.fields.TemplateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.contentreference': {
+ 'Meta': {'object_name': 'ContentReference'},
+ 'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.file': {
+ 'Meta': {'object_name': 'File'},
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.foreignkeyvalue': {
+ 'Meta': {'object_name': 'ForeignKeyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.jsonvalue': {
+ 'Meta': {'object_name': 'JSONValue'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('philo.models.fields.JSONField', [], {'default': "'null'"})
+ },
+ 'philo.manytomanyvalue': {
+ 'Meta': {'object_name': 'ManyToManyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.redirect': {
+ 'Meta': {'object_name': 'Redirect'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'philo.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['philo']
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding index on 'Attribute', fields ['entity_object_id']
+ db.create_index('philo_attribute', ['entity_object_id'])
+
+ # Adding index on 'Attribute', fields ['value_object_id']
+ db.create_index('philo_attribute', ['value_object_id'])
+
+ # Adding index on 'Attribute', fields ['key']
+ db.create_index('philo_attribute', ['key'])
+
+
+ def backwards(self, orm):
+
+ # Removing index on 'Attribute', fields ['entity_object_id']
+ db.delete_index('philo_attribute', ['entity_object_id'])
+
+ # Removing index on 'Attribute', fields ['value_object_id']
+ db.delete_index('philo_attribute', ['value_object_id'])
+
+ # Removing index on 'Attribute', fields ['key']
+ db.delete_index('philo_attribute', ['key'])
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.collection': {
+ 'Meta': {'object_name': 'Collection'},
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.collectionmember': {
+ 'Meta': {'object_name': 'CollectionMember'},
+ 'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.contentlet': {
+ 'Meta': {'object_name': 'Contentlet'},
+ 'content': ('philo.models.fields.TemplateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.contentreference': {
+ 'Meta': {'object_name': 'ContentReference'},
+ 'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.file': {
+ 'Meta': {'object_name': 'File'},
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.foreignkeyvalue': {
+ 'Meta': {'object_name': 'ForeignKeyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.jsonvalue': {
+ 'Meta': {'object_name': 'JSONValue'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('philo.models.fields.JSONField', [], {'default': "'null'"})
+ },
+ 'philo.manytomanyvalue': {
+ 'Meta': {'object_name': 'ManyToManyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.redirect': {
+ 'Meta': {'object_name': 'Redirect'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'philo.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['philo']
value_content_type_limiter.register_class(model)
+register_value_model(Tag)
+
+
def unregister_value_model(model):
value_content_type_limiter.unregister_class(model)
class Attribute(models.Model):
- entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
- entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
+ entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type', db_index=True)
+ entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID', db_index=True)
entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
- value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True)
- value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True)
+ value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True, db_index=True)
+ value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
value = generic.GenericForeignKey('value_content_type', 'value_object_id')
- key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.")
+ key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.", db_index=True)
def __unicode__(self):
return u'"%s": %s' % (self.key, self.value)
# tree structure won't be that deep.
segments = path.split(pathsep)
- # Check for a trailing pathsep so we can restore it later.
- trailing_pathsep = False
- if segments[-1] == '':
- trailing_pathsep = True
-
# Clean out blank segments. Handles multiple consecutive pathseps.
while True:
try:
return kwargs
- def build_path(segments):
- path = pathsep.join(segments)
- if trailing_pathsep and segments and segments[-1] != '':
- path += pathsep
- return path
-
def find_obj(segments, depth, deepest_found=None):
if deepest_found is None:
deepest_level = 0
if deepest_level == depth:
# This should happen if nothing is found with any part of the given path.
if root is not None and deepest_found is None:
- return root, build_path(segments)
+ return root, pathsep.join(segments)
raise
return find_obj(segments, depth, deepest_found)
# Could there be a deeper one?
if obj.is_leaf_node():
- return obj, build_path(segments[deepest_level:]) or None
+ return obj, pathsep.join(segments[deepest_level:]) or None
depth += (len(segments) - depth)/2 or len(segments) - depth
depth = deepest_level + obj.get_descendant_count()
if deepest_level == depth:
- return obj, build_path(segments[deepest_level:]) or None
+ return obj, pathsep.join(segments[deepest_level:]) or None
try:
return find_obj(segments, depth, obj)
except self.model.DoesNotExist:
# Then this was the deepest.
- return obj, build_path(segments[deepest_level:])
+ return obj, pathsep.join(segments[deepest_level:])
if absolute_result:
return self.get(**make_query_kwargs(segments, root))
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
-from django.contrib.sites.models import Site
-from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect
-from django.core.exceptions import ViewDoesNotExist
+from django.contrib.sites.models import Site, RequestSite
+from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect, Http404
+from django.core.exceptions import ValidationError
from django.core.servers.basehttp import FileWrapper
from django.core.urlresolvers import resolve, clear_url_caches, reverse, NoReverseMatch
from django.template import add_to_builtins as register_templatetags
from inspect import getargspec
from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED
from philo.models.base import TreeEntity, Entity, QuerySetMapper, register_value_model
+from philo.models.fields import JSONField
from philo.utils import ContentTypeSubclassLimiter
from philo.validators import RedirectValidator
from philo.exceptions import ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths, AncestorDoesNotExist
return self.view.accepts_subpath
return False
+ def handles_subpath(self, subpath):
+ return self.view.handles_subpath(subpath)
+
def render_to_response(self, request, extra_context=None):
return self.view.render_to_response(request, extra_context)
- def get_absolute_url(self):
+ def get_absolute_url(self, request=None, with_domain=False, secure=False):
+ return self.construct_url(request=request, with_domain=with_domain, secure=secure)
+
+ def construct_url(self, subpath="/", request=None, with_domain=False, secure=False):
+ """
+ This method will construct a URL based on the Node's location.
+ If a request is passed in, that will be used as a backup in case
+ the Site lookup fails. The Site lookup takes precedence because
+ it's what's used to find the root node. This will raise:
+ - NoReverseMatch if philo-root is not reverseable
+ - Site.DoesNotExist if a domain is requested but not buildable.
+ - AncestorDoesNotExist if the root node of the site isn't an
+ ancestor of this instance.
+ """
+ # Try reversing philo-root first, since we can't do anything if that fails.
+ root_url = reverse('philo-root')
+
try:
- root = Site.objects.get_current().root_node
+ current_site = Site.objects.get_current()
except Site.DoesNotExist:
- root = None
+ if request is not None:
+ current_site = RequestSite(request)
+ elif with_domain:
+ # If they want a domain and we can't figure one out,
+ # best to reraise the error to let them know.
+ raise
+ else:
+ current_site = None
- try:
- path = self.get_path(root=root)
- if path:
- path += '/'
- root_url = reverse('philo-root')
- return '%s%s' % (root_url, path)
- except AncestorDoesNotExist, ViewDoesNotExist:
- return None
+ root = getattr(current_site, 'root_node', None)
+ path = self.get_path(root=root)
+
+ if current_site and with_domain:
+ domain = "http%s://%s" % (secure and "s" or "", current_site.domain)
+ else:
+ domain = ""
+
+ if not path or subpath == "/":
+ subpath = subpath[1:]
+
+ return '%s%s%s%s' % (domain, root_url, path, subpath)
class Meta:
app_label = 'philo'
accepts_subpath = False
- def get_subpath(self, obj):
+ def handles_subpath(self, subpath):
+ if not self.accepts_subpath and subpath != "/":
+ return False
+ return True
+
+ def reverse(self, view_name=None, args=None, kwargs=None, node=None, obj=None):
+ """Shortcut method to handle the common pattern of getting the
+ absolute url for a view's subpaths."""
if not self.accepts_subpath:
raise ViewDoesNotProvideSubpaths
- view_name, args, kwargs = self.get_reverse_params(obj)
+ if obj is not None:
+ # Perhaps just override instead of combining?
+ obj_view_name, obj_args, obj_kwargs = self.get_reverse_params(obj)
+ if view_name is None:
+ view_name = obj_view_name
+ args = list(obj_args) + list(args or [])
+ obj_kwargs.update(kwargs or {})
+ kwargs = obj_kwargs
+
try:
- return reverse(view_name, args=args, kwargs=kwargs, urlconf=self)
+ subpath = reverse(view_name, urlconf=self, args=args or [], kwargs=kwargs or {})
except NoReverseMatch:
raise ViewCanNotProvideSubpath
+
+ if node is not None:
+ return node.construct_url(subpath)
+ return subpath
def get_reverse_params(self, obj):
"""This method should return a view_name, args, kwargs tuple suitable for reversing a url for the given obj using self as the urlconf."""
return response
def actually_render_to_response(self, request, extra_context=None):
- raise NotImplementedError('View subclasses must implement render_to_response.')
+ raise NotImplementedError('View subclasses must implement actually_render_to_response.')
class Meta:
abstract = True
accepts_subpath = True
@property
- def urlpatterns(self, obj):
+ def urlpatterns(self):
raise NotImplementedError("MultiView subclasses must implement urlpatterns.")
+ def handles_subpath(self, subpath):
+ if not super(MultiView, self).handles_subpath(subpath):
+ return False
+ try:
+ resolve(subpath, urlconf=self)
+ except Http404:
+ return False
+ return True
+
def actually_render_to_response(self, request, extra_context=None):
clear_url_caches()
subpath = request.node.subpath
- if not subpath:
- subpath = ""
- subpath = "/" + subpath
view, args, kwargs = resolve(subpath, urlconf=self)
view_args = getargspec(view)
if extra_context is not None and ('extra_context' in view_args[0] or view_args[2] is not None):
kwargs['extra_context'] = extra_context
return view(request, *args, **kwargs)
- def reverse(self, view_name, args=None, kwargs=None, node=None):
- """Shortcut method to handle the common pattern of getting the absolute url for a multiview's
- subpaths."""
- subpath = reverse(view_name, urlconf=self, args=args or [], kwargs=kwargs or {})
- if node is not None:
- return '/%s/%s/' % (node.get_absolute_url().strip('/'), subpath.strip('/'))
- return subpath
-
def get_context(self):
"""Hook for providing instance-specific context - such as the value of a Field - to all views."""
return {}
- def basic_view(self, view_name):
+ def basic_view(self, field_name):
"""
- Wraps a field name and returns a simple view function that will render that view
- with a basic context. This assumes that the field name is a ForeignKey to a
- model with a render_to_response method.
+ Given the name of a field on ``self``, accesses the value of
+ that field and treats it as a ``View`` instance. Creates a
+ basic context based on self.get_context() and any extra_context
+ that was passed in, then calls the ``View`` instance's
+ render_to_response() method. This method is meant to be called
+ to return a view function appropriate for urlpatterns.
"""
- field = self._meta.get_field(view_name)
+ field = self._meta.get_field(field_name)
view = getattr(self, field.name, None)
def inner(request, extra_context=None, **kwargs):
abstract = True
-class Redirect(View):
+class TargetURLModel(models.Model):
+ target_node = models.ForeignKey(Node, blank=True, null=True, related_name="%(app_label)s_%(class)s_related")
+ url_or_subpath = models.CharField(max_length=200, validators=[RedirectValidator()], blank=True, help_text="Point to this url or, if a node is defined and accepts subpaths, this subpath of the node.")
+ reversing_parameters = JSONField(blank=True, help_text="If reversing parameters are defined, url_or_subpath will instead be interpreted as the view name to be reversed.")
+
+ def clean(self):
+ # Should this be enforced? Not enforcing it would allow creation of "headers" in the navbar.
+ if not self.target_node and not self.url_or_subpath:
+ raise ValidationError("Either a target node or a url must be defined.")
+
+ if self.reversing_parameters and not (self.url_or_subpath or self.target_node):
+ raise ValidationError("Reversing parameters require either a view name or a target node.")
+
+ try:
+ self.get_target_url()
+ except NoReverseMatch, e:
+ raise ValidationError(e.message)
+
+ super(TargetURLModel, self).clean()
+
+ def get_reverse_params(self):
+ params = self.reversing_parameters
+ args = isinstance(params, list) and params or None
+ kwargs = isinstance(params, dict) and params or None
+ return self.url_or_subpath, args, kwargs
+
+ def get_target_url(self):
+ node = self.target_node
+ if node is not None and node.accepts_subpath and self.url_or_subpath:
+ if self.reversing_parameters is not None:
+ view_name, args, kwargs = self.get_reversing_params()
+ subpath = node.view.reverse(view_name, args=args, kwargs=kwargs)
+ else:
+ subpath = self.url_or_subpath
+ if subpath[0] != '/':
+ subpath = '/' + subpath
+ return node.construct_url(subpath)
+ elif node is not None:
+ return node.get_absolute_url()
+ else:
+ if self.reversing_parameters is not None:
+ view_name, args, kwargs = self.get_reversing_params()
+ return reverse(view_name, args=args, kwargs=kwargs)
+ return self.url_or_subpath
+ target_url = property(get_target_url)
+
+ class Meta:
+ abstract = True
+
+
+class Redirect(View, TargetURLModel):
STATUS_CODES = (
(302, 'Temporary'),
(301, 'Permanent'),
)
- target = models.CharField(max_length=200, validators=[RedirectValidator()])
status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
def actually_render_to_response(self, request, extra_context=None):
- response = HttpResponseRedirect(self.target)
+ response = HttpResponseRedirect(self.target_url)
response.status_code = self.status_code
return response
app_label = 'philo'
-# Why does this exist?
class File(View):
""" For storing arbitrary files """
raise
return settings.TEMPLATE_STRING_IF_INVALID
else:
- if subpath[0] == '/':
- subpath = subpath[1:]
-
- url = node.get_absolute_url() + subpath
+ url = node.construct_url(subpath)
if self.as_var:
context[self.as_var] = url
from django.conf import settings
-from django.http import Http404
+from django.core.urlresolvers import resolve
+from django.http import Http404, HttpResponseRedirect
from django.views.decorators.vary import vary_on_headers
from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED
raise MIDDLEWARE_NOT_CONFIGURED
if not request.node:
+ if settings.APPEND_SLASH and request.path != "/":
+ path = request.path
+
+ if path[-1] == "/":
+ path = path[:-1]
+ else:
+ path += "/"
+
+ view, args, kwargs = resolve(path)
+ if view != node_view:
+ return HttpResponseRedirect(path)
raise Http404
node = request.node
subpath = request.node.subpath
- if subpath and not node.accepts_subpath:
- raise Http404
+ # Explicitly disallow trailing slashes if we are otherwise at a node's url.
+ if request.path and request.path != "/" and request.path[-1] == "/" and subpath == "/":
+ return HttpResponseRedirect(node.get_absolute_url())
+
+ if not node.handles_subpath(subpath):
+ # If the subpath isn't handled, check settings.APPEND_SLASH. If
+ # it's True, try to correct the subpath.
+ if not settings.APPEND_SLASH:
+ raise Http404
+
+ if subpath[-1] == "/":
+ subpath = subpath[:-1]
+ else:
+ subpath += "/"
+
+ redirect_url = node.construct_url(subpath)
+
+ if node.handles_subpath(subpath):
+ return HttpResponseRedirect(redirect_url)
+
+ # Perhaps there is a non-philo view at this address. Can we
+ # resolve *something* there besides node_view? If not,
+ # raise a 404.
+ view, args, kwargs = resolve(redirect_url)
+
+ if view == node_view:
+ raise Http404
+ else:
+ return HttpResponseRedirect(redirect_url)
+
return node.render_to_response(request, kwargs)
\ No newline at end of file