Merge branch 'julian' of git://github.com/lapilofu/philo
authorStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 28 Mar 2011 21:16:24 +0000 (17:16 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 28 Mar 2011 21:16:24 +0000 (17:16 -0400)
admin/base.py
contrib/shipherd/templatetags/shipherd.py
contrib/sobol/admin.py
contrib/sobol/utils.py
contrib/waldo/tokens.py
forms/entities.py
middleware.py
models/base.py
models/pages.py
urls.py
views.py

index 8151461..b616290 100644 (file)
@@ -31,54 +31,50 @@ class AttributeInline(generic.GenericTabularInline):
                template = 'admin/philo/edit_inline/tabular_attribute.html'
 
 
-def hide_proxy_fields(cls, attname, proxy_field_set):
-       val_set = set(getattr(cls, attname))
-       if proxy_field_set & val_set:
-               cls._hidden_attributes[attname] = list(val_set)
-               setattr(cls, attname, list(val_set - proxy_field_set))
+# HACK to bypass model validation for proxy fields
+class SpoofedHiddenFields(object):
+       def __init__(self, proxy_fields, value):
+               self.value = value
+               self.spoofed = list(set(value) - set(proxy_fields))
+       
+       def __get__(self, instance, owner):
+               if instance is None:
+                       return self.spoofed
+               return self.value
+
+
+class SpoofedAddedFields(SpoofedHiddenFields):
+       def __init__(self, proxy_fields, value):
+               self.value = value
+               self.spoofed = list(set(value) | set(proxy_fields))
+
+
+def hide_proxy_fields(cls, attname):
+       val = getattr(cls, attname, [])
+       proxy_fields = getattr(cls, 'proxy_fields')
+       if val:
+               setattr(cls, attname, SpoofedHiddenFields(proxy_fields, val))
+
+def add_proxy_fields(cls, attname):
+       val = getattr(cls, attname, [])
+       proxy_fields = getattr(cls, 'proxy_fields')
+       setattr(cls, attname, SpoofedAddedFields(proxy_fields, val))
 
 
 class EntityAdminMetaclass(admin.ModelAdmin.__metaclass__):
        def __new__(cls, name, bases, attrs):
-               # HACK to bypass model validation for proxy fields by masking them as readonly fields
                new_class = super(EntityAdminMetaclass, cls).__new__(cls, name, bases, attrs)
-               form = getattr(new_class, 'form', None)
-               if form:
-                       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.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.
-                               new_class._hidden_attributes = {}
-                               proxy_fields = set(proxy_fields)
-                               hide_proxy_fields(new_class, 'raw_id_fields', proxy_fields)
-               #END HACK
+               hide_proxy_fields(new_class, 'raw_id_fields')
+               add_proxy_fields(new_class, 'readonly_fields')
                return new_class
-
+# END HACK
 
 class EntityAdmin(admin.ModelAdmin):
        __metaclass__ = EntityAdminMetaclass
        form = EntityForm
        inlines = [AttributeInline]
        save_on_top = True
-       
-       def __init__(self, *args, **kwargs):
-               # HACK PART 2 restores the actual readonly fields etc. on __init__.
-               if hasattr(self, '_real_readonly_fields'):
-                       self.readonly_fields = self.__class__._real_readonly_fields
-               if hasattr(self, '_hidden_attributes'):
-                       for name, value in self._hidden_attributes.items():
-                               setattr(self, name, value)
-               # END HACK
-               super(EntityAdmin, self).__init__(*args, **kwargs)
+       proxy_fields = []
        
        def formfield_for_dbfield(self, db_field, **kwargs):
                """
@@ -137,6 +133,15 @@ class EntityAdmin(admin.ModelAdmin):
                #       kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
                
                return db_field.formfield(**kwargs)
+       
+       def get_form(self, request, obj=None, **kwargs):
+               """
+               Ensures that the form's proxy fields are included in its base fields.
+               This will not be required after http://code.djangoproject.com/ticket/14082 has been resolved
+               """
+               form = super(EntityAdmin, self).get_form(request, obj, **kwargs)
+               form.base_fields.update(form.proxy_fields)
+               return form
 
 
 class TreeAdmin(MPTTModelAdmin):
index 57fb020..1413bdf 100644 (file)
@@ -167,6 +167,4 @@ def navigation_host(node, key):
        try:
                return Navigation.objects.filter(node__in=node.get_ancestors(include_self=True), key=key).order_by('-node__level')[0].node
        except:
-               if settings.TEMPLATE_DEBUG:
-                       raise
                return node
\ No newline at end of file
index 504cde2..87dd39a 100644 (file)
@@ -6,10 +6,10 @@ from django.db.models import Count
 from django.http import HttpResponseRedirect, Http404
 from django.shortcuts import render_to_response
 from django.template import RequestContext
-from django.utils.functional import update_wrapper
 from django.utils.translation import ugettext_lazy as _
 from philo.admin import EntityAdmin
 from philo.contrib.sobol.models import Search, ResultURL, SearchView
+from functools import update_wrapper
 
 
 class ResultURLInline(admin.TabularInline):
index 723a463..3c5e537 100644 (file)
@@ -1,8 +1,8 @@
 from django.conf import settings
 from django.http import QueryDict
 from django.utils.encoding import smart_str
-from django.utils.hashcompat import sha_constructor
 from django.utils.http import urlquote_plus, urlquote
+from hashlib import sha1
 
 
 SEARCH_ARG_GET_KEY = 'q'
@@ -11,7 +11,7 @@ HASH_REDIRECT_GET_KEY = 's'
 
 
 def make_redirect_hash(search_arg, url):
-       return sha_constructor(smart_str(search_arg + url + settings.SECRET_KEY)).hexdigest()[::2]
+       return sha1(smart_str(search_arg + url + settings.SECRET_KEY)).hexdigest()[::2]
 
 
 def check_redirect_hash(hash, search_arg, url):
index 95ce0c0..80f0b11 100644 (file)
@@ -7,6 +7,7 @@ from datetime import date
 from django.conf import settings
 from django.utils.http import int_to_base36, base36_to_int
 from django.contrib.auth.tokens import PasswordResetTokenGenerator
+from hashlib import sha1
 
 
 REGISTRATION_TIMEOUT_DAYS = getattr(settings, 'WALDO_REGISTRATION_TIMEOUT_DAYS', 1)
@@ -52,8 +53,7 @@ class RegistrationTokenGenerator(PasswordResetTokenGenerator):
                # By hashing on the internal state of the user and using state that is
                # sure to change, we produce a hash that will be invalid as soon as it
                # is used.
-               from django.utils.hashcompat import sha_constructor
-               hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) + unicode(user.is_active) + user.last_login.strftime('%Y-%m-%d %H:%M:%S') + unicode(timestamp)).hexdigest()[::2]
+               hash = sha1(settings.SECRET_KEY + unicode(user.id) + unicode(user.is_active) + user.last_login.strftime('%Y-%m-%d %H:%M:%S') + unicode(timestamp)).hexdigest()[::2]
                return '%s-%s' % (ts_b36, hash)
 
 
@@ -98,8 +98,7 @@ class EmailTokenGenerator(PasswordResetTokenGenerator):
        def _make_token_with_timestamp(self, user, email, timestamp):
                ts_b36 = int_to_base36(timestamp)
                
-               from django.utils.hashcompat import sha_constructor
-               hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) + user.email + email + unicode(timestamp)).hexdigest()[::2]
+               hash = sha1(settings.SECRET_KEY + unicode(user.id) + user.email + email + unicode(timestamp)).hexdigest()[::2]
                return '%s-%s' % (ts_b36, hash)
 
 
index b6259a3..43094b9 100644 (file)
@@ -1,4 +1,4 @@
-from django.forms.models import ModelFormMetaclass, ModelForm
+from django.forms.models import ModelFormMetaclass, ModelForm, ModelFormOptions
 from django.utils.datastructures import SortedDict
 from philo.utils import fattr
 
@@ -6,7 +6,7 @@ from philo.utils import fattr
 __all__ = ('EntityForm',)
 
 
-def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
+def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=None):
        field_list = []
        ignored = []
        opts = entity_model._entity_meta
@@ -21,7 +21,14 @@ def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widge
                        kwargs = {'widget': widgets[f.name]}
                else:
                        kwargs = {}
-               formfield = formfield_callback(f, **kwargs)
+               
+               if formfield_callback is None:
+                       formfield = f.formfield(**kwargs)
+               elif not callable(formfield_callback):
+                       raise TypeError('formfield_callback must be a function or callable')
+               else:
+                       formfield = formfield_callback(f, **kwargs)
+               
                if formfield:
                        field_list.append((f.name, formfield))
                else:
@@ -35,31 +42,66 @@ def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widge
        return field_dict
 
 
-# BEGIN HACK - This will not be required after http://code.djangoproject.com/ticket/14082 has been resolved
-
-class EntityFormBase(ModelForm):
-       pass
-
-_old_metaclass_new = ModelFormMetaclass.__new__
-
-def _new_metaclass_new(cls, name, bases, attrs):
-       formfield_callback = attrs.get('formfield_callback', lambda f, **kwargs: f.formfield(**kwargs))
-       new_class = _old_metaclass_new(cls, name, bases, attrs)
-       opts = new_class._meta
-       if issubclass(new_class, EntityFormBase) and opts.model:
-               # "override" proxy fields with declared fields by excluding them if there's a name conflict.
-               exclude = (list(opts.exclude or []) + new_class.declared_fields.keys()) or None
-               proxy_fields = proxy_fields_for_entity_model(opts.model, opts.fields, exclude, opts.widgets, formfield_callback) # don't pass in formfield_callback
+class EntityFormMetaclass(ModelFormMetaclass):
+       def __new__(cls, name, bases, attrs):
+               try:
+                       parents = [b for b in bases if issubclass(b, EntityForm)]
+               except NameError:
+                       # We are defining EntityForm itself
+                       parents = None
+               sup = super(EntityFormMetaclass, cls)
+               
+               if not parents:
+                       # Then there's no business trying to use proxy fields.
+                       return sup.__new__(cls, name, bases, attrs)
+               
+               # Preemptively make a meta so we can screen out any proxy fields.
+               # After http://code.djangoproject.com/ticket/14082 has been resolved,
+               # perhaps switch to setting proxy fields as "declared"?
+               _opts = ModelFormOptions(attrs.get('Meta', None))
+               
+               # Make another copy of opts to spoof the proxy fields not being there.
+               opts = ModelFormOptions(attrs.get('Meta', None))
+               if opts.model:
+                       formfield_callback = attrs.get('formfield_callback', None)
+                       proxy_fields = proxy_fields_for_entity_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback)
+               else:
+                       proxy_fields = {}
+               
+               # Screen out all proxy fields from the meta
+               opts.fields = list(opts.fields or [])
+               opts.exclude = list(opts.exclude or [])
+               
+               for field in proxy_fields.keys():
+                       try:
+                               opts.fields.remove(field)
+                       except ValueError:
+                               pass
+                       try:
+                               opts.exclude.remove(field)
+                       except ValueError:
+                               pass
+               opts.fields = opts.fields or None
+               opts.exclude = opts.exclude or None
+               
+               # Put in the new Meta.
+               attrs['Meta'] = opts
+               
+               new_class = sup.__new__(cls, name, bases, attrs)
+               
+               intersections = set(new_class.declared_fields.keys()) & set(proxy_fields.keys())
+               for key in intersections:
+                       proxy_fields.pop(key)
+               
                new_class.proxy_fields = proxy_fields
+               new_class._meta = _opts
                new_class.base_fields.update(proxy_fields)
-       return new_class
-
-ModelFormMetaclass.__new__ = staticmethod(_new_metaclass_new)
+               return new_class
 
-# END HACK
 
-
-class EntityForm(EntityFormBase): # Would inherit from ModelForm directly if it weren't for the above HACK
+class EntityForm(ModelForm):
+       __metaclass__ = EntityFormMetaclass
+       
        def __init__(self, *args, **kwargs):
                initial = kwargs.pop('initial', None)
                instance = kwargs.get('instance', None)
index c0b1e9e..5ec3e77 100644 (file)
@@ -24,16 +24,18 @@ class LazyNode(object):
                                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:
+                       else:
                                if subpath is None:
                                        subpath = ""
                                subpath = "/" + subpath
                                
-                               if trailing_slash and subpath[-1] != "/":
-                                       subpath += "/"
-                               
-                               node.subpath = subpath
+                               if not node.handles_subpath(subpath):
+                                       node = None
+                               else:
+                                       if trailing_slash and subpath[-1] != "/":
+                                               subpath += "/"
+                                       
+                                       node.subpath = subpath
                        
                        request._found_node = node
                
@@ -46,7 +48,10 @@ class RequestNodeMiddleware(object):
                request.__class__.node = LazyNode()
        
        def process_view(self, request, view_func, view_args, view_kwargs):
-               request._cached_node_path = view_kwargs.get('path', '/')
+               try:
+                       request._cached_node_path = view_kwargs['path']
+               except KeyError:
+                       pass
        
        def process_exception(self, request, exception):
                if settings.DEBUG or not hasattr(request, 'node') or not request.node:
index faac89b..af1e880 100644 (file)
@@ -271,9 +271,9 @@ class EntityOptions(object):
 
 class EntityBase(models.base.ModelBase):
        def __new__(cls, name, bases, attrs):
+               entity_meta = attrs.pop('EntityMeta', None)
                new = super(EntityBase, cls).__new__(cls, name, bases, attrs)
-               entity_options = attrs.pop('EntityMeta', None)
-               setattr(new, '_entity_meta', EntityOptions(entity_options))
+               new.add_to_class('_entity_meta', EntityOptions(entity_meta))
                entity_class_prepared.send(sender=new)
                return new
 
index 39125ef..b7b43c5 100644 (file)
@@ -131,7 +131,7 @@ class Template(TreeModel):
                return contentlet_specs, contentreference_specs
        
        def __unicode__(self):
-               return self.get_path(pathsep=u' › ', field='name')
+               return self.name
        
        class Meta:
                app_label = 'philo'
diff --git a/urls.py b/urls.py
index 47be7da..0363224 100644 (file)
--- a/urls.py
+++ b/urls.py
@@ -3,6 +3,6 @@ from philo.views import node_view
 
 
 urlpatterns = patterns('',
-       url(r'^$', node_view, name='philo-root'),
+       url(r'^$', node_view, kwargs={'path': '/'}, name='philo-root'),
        url(r'^(?P<path>.*)$', node_view, name='philo-node-by-path')
 )
index f5a2c7f..598be36 100644 (file)
--- a/views.py
+++ b/views.py
@@ -28,7 +28,7 @@ def node_view(request, path=None, **kwargs):
        subpath = request.node.subpath
        
        # 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 == "/":
+       if request._cached_node_path != "/" and request._cached_node_path[-1] == "/" and subpath == "/":
                return HttpResponseRedirect(node.get_absolute_url())
        
        if not node.handles_subpath(subpath):