X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/dbb5ab771be406d90ac522e0ecd644e5ddb21e6f..bf7348280872f3e17f6cb766f27d57c41d9e2ae0:/models/nodes.py diff --git a/models/nodes.py b/models/nodes.py index ab8adf7..99be196 100644 --- a/models/nodes.py +++ b/models/nodes.py @@ -3,12 +3,15 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic 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 django.utils.encoding import smart_str 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 @@ -38,7 +41,7 @@ class Node(TreeEntity): 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=None, request=None, with_domain=False, secure=False): + 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 @@ -72,10 +75,10 @@ class Node(TreeEntity): else: domain = "" - if not path: + if not path or subpath == "/": subpath = subpath[1:] - return '%s%s%s%s' % (domain, root_url, path, subpath or "") + return '%s%s%s%s' % (domain, root_url, path, subpath) class Meta: app_label = 'philo' @@ -112,8 +115,8 @@ class View(Entity): try: subpath = reverse(view_name, urlconf=self, args=args or [], kwargs=kwargs or {}) - except NoReverseMatch: - raise ViewCanNotProvideSubpath + except NoReverseMatch, e: + raise ViewCanNotProvideSubpath(e.message) if node is not None: return node.construct_url(subpath) @@ -202,16 +205,69 @@ class MultiView(View): 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): + 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, ViewCanNotProvideSubpath), e: + raise ValidationError(e.message) + + super(TargetURLModel, self).clean() + + def get_reverse_params(self): + params = self.reversing_parameters + args = kwargs = None + if isinstance(params, list): + args = params + elif isinstance(params, dict): + # Convert unicode keys to strings for Python < 2.6.5. Compare + # http://stackoverflow.com/questions/4598604/how-to-pass-unicode-keywords-to-kwargs + kwargs = dict([(smart_str(k, 'ascii'), v) for k, v in params.items()]) + 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_reverse_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_reverse_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(TargetURLModel, View): 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