Merged cowell back into core.
authorStephen Burrows <stephen.r.burrows@gmail.com>
Thu, 27 Jan 2011 21:27:48 +0000 (16:27 -0500)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Thu, 27 Jan 2011 21:27:48 +0000 (16:27 -0500)
admin/base.py
contrib/cowell/__init__.py [deleted file]
contrib/cowell/admin.py [deleted file]
contrib/cowell/widgets.py [deleted file]
forms/__init__.py
forms/entities.py [moved from contrib/cowell/forms.py with 88% similarity]
models/__init__.py
models/fields/__init__.py [moved from models/fields.py with 97% similarity]
models/fields/entities.py [moved from contrib/cowell/fields.py with 94% similarity]

index acba9c3..81ff8f8 100644 (file)
@@ -5,8 +5,10 @@ from django.http import HttpResponse
 from django.utils import simplejson as json
 from django.utils.html import escape
 from philo.models import Tag, Attribute
+from philo.models.fields.entities import ForeignKeyAttribute, ManyToManyAttribute
 from philo.admin.forms.attributes import AttributeForm, AttributeInlineFormSet
 from philo.admin.widgets import TagFilteredSelectMultiple
+from philo.forms.entities import EntityForm, proxy_fields_for_entity_model
 from mptt.admin import MPTTModelAdmin
 
 
@@ -29,16 +31,115 @@ 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))
+
+
+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()
+                               readonly_fields = new_class.readonly_fields
+                               new_class._real_readonly_fields = readonly_fields
+                               new_class.readonly_fields = list(readonly_fields) + 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
+               return new_class
+
+
 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)
+       
+       def formfield_for_dbfield(self, db_field, **kwargs):
+               """
+               Override the default behavior to provide special formfields for EntityEntitys.
+               Essentially clones the ForeignKey/ManyToManyField special behavior for the Attribute versions.
+               """
+               if not db_field.choices and isinstance(db_field, (ForeignKeyAttribute, ManyToManyAttribute)):
+                       request = kwargs.pop("request", None)
+                       # Combine the field kwargs with any options for formfield_overrides.
+                       # Make sure the passed in **kwargs override anything in
+                       # formfield_overrides because **kwargs is more specific, and should
+                       # always win.
+                       if db_field.__class__ in self.formfield_overrides:
+                               kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
+                       
+                       # Get the correct formfield.
+                       if isinstance(db_field, ManyToManyAttribute):
+                               formfield = self.formfield_for_manytomanyattribute(db_field, request, **kwargs)
+                       elif isinstance(db_field, ForeignKeyAttribute):
+                               formfield = self.formfield_for_foreignkeyattribute(db_field, request, **kwargs)
+                       
+                       # For non-raw_id fields, wrap the widget with a wrapper that adds
+                       # extra HTML -- the "add other" interface -- to the end of the
+                       # rendered output. formfield can be None if it came from a
+                       # OneToOneField with parent_link=True or a M2M intermediary.
+                       # TODO: Implement this.
+                       #if formfield and db_field.name not in self.raw_id_fields:
+                       #       formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field, self.admin_site)
+                       
+                       return formfield
+               return super(EntityAdmin, self).formfield_for_dbfield(db_field, **kwargs)
+       
+       def formfield_for_foreignkeyattribute(self, db_field, request=None, **kwargs):
+               """Get a form field for a ForeignKeyAttribute field."""
+               db = kwargs.get('using')
+               if db_field.name in self.raw_id_fields:
+                       kwargs['widget'] = admin.widgets.ForeignKeyRawIdWidget(db_field, db)
+               #TODO: Add support for radio fields
+               #elif db_field.name in self.radio_fields:
+               #       kwargs['widget'] = widgets.AdminRadioSelect(attrs={
+               #               'class': get_ul_class(self.radio_fields[db_field.name]),
+               #       })
+               #       kwargs['empty_label'] = db_field.blank and _('None') or None
+               
+               return db_field.formfield(**kwargs)
+       
+       def formfield_for_manytomanyattribute(self, db_field, request=None, **kwargs):
+               """Get a form field for a ManyToManyAttribute field."""
+               db = kwargs.get('using')
+               
+               if db_field.name in self.raw_id_fields:
+                       kwargs['widget'] = admin.widgets.ManyToManyRawIdWidget(db_field, using=db)
+                       kwargs['help_text'] = ''
+               #TODO: Add support for filtered fields.
+               #elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
+               #       kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
+               
+               return db_field.formfield(**kwargs)
 
 
 class TreeAdmin(MPTTModelAdmin):
        pass
 
 
-class TreeEntityAdmin(TreeAdmin, EntityAdmin):
+class TreeEntityAdmin(EntityAdmin, TreeAdmin):
        pass
 
 
diff --git a/contrib/cowell/__init__.py b/contrib/cowell/__init__.py
deleted file mode 100644 (file)
index 710d164..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-"""
-Cowell handles the code necessary for creating AttributeFields on Entities. This can be used
-to give the appearance of fields added to models without the needing to change the schema or
-to define multiple models due to minor differences in requirements.
-"""
\ No newline at end of file
diff --git a/contrib/cowell/admin.py b/contrib/cowell/admin.py
deleted file mode 100644 (file)
index 94df7cb..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-from django import forms
-from django.contrib import admin
-from philo.contrib.cowell.fields import ForeignKeyAttribute, ManyToManyAttribute
-from philo.contrib.cowell.forms import ProxyFieldForm, proxy_fields_for_entity_model
-from philo.contrib.cowell.widgets import ForeignKeyAttributeRawIdWidget, ManyToManyAttributeRawIdWidget
-from philo.admin import EntityAdmin
-
-
-def hide_proxy_fields(hidden, attrs, attname, attvalue, proxy_fields):
-       attvalue = set(attvalue)
-       proxy_fields = set(proxy_fields)
-       if proxy_fields & attvalue:
-               hidden[attname] = list(attvalue)
-               attrs[attname] = list(attvalue - proxy_fields)
-
-
-class ProxyFieldAdminMetaclass(EntityAdmin.__metaclass__):
-       def __new__(cls, name, bases, attrs):
-               # HACK to bypass model validation for proxy fields by masking them as readonly fields
-               form = attrs.get('form')
-               if form:
-                       opts = form._meta
-                       if issubclass(form, ProxyFieldForm) and opts.model:
-                               proxy_fields = proxy_fields_for_entity_model(opts.model).keys()
-                               readonly_fields = attrs.pop('readonly_fields', ())
-                               cls._real_readonly_fields = readonly_fields
-                               attrs['readonly_fields'] = list(readonly_fields) + proxy_fields
-                               
-                               # Additional HACKS to handle raw_id_fields and other attributes that the admin
-                               # uses model._meta.get_field to validate.
-                               hidden_attributes = {}
-                               hide_proxy_fields(hidden_attributes, attrs, 'raw_id_fields', attrs.pop('raw_id_fields', ()), proxy_fields)
-                               attrs['_hidden_attributes'] = hidden_attributes
-               #END HACK
-               return EntityAdmin.__metaclass__.__new__(cls, name, bases, attrs)
-
-
-class ProxyFieldAdmin(EntityAdmin):
-       __metaclass__ = ProxyFieldAdminMetaclass
-       #form = ProxyFieldForm
-       
-       def __init__(self, *args, **kwargs):
-               # HACK PART 2 restores the actual readonly fields etc. on __init__.
-               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(ProxyFieldAdmin, self).__init__(*args, **kwargs)
-       
-       def formfield_for_dbfield(self, db_field, **kwargs):
-               """
-               Override the default behavior to provide special formfields for EntityProxyFields.
-               Essentially clones the ForeignKey/ManyToManyField special behavior for the Attribute versions.
-               """
-               if not db_field.choices and isinstance(db_field, (ForeignKeyAttribute, ManyToManyAttribute)):
-                       request = kwargs.pop("request", None)
-                       # Combine the field kwargs with any options for formfield_overrides.
-                       # Make sure the passed in **kwargs override anything in
-                       # formfield_overrides because **kwargs is more specific, and should
-                       # always win.
-                       if db_field.__class__ in self.formfield_overrides:
-                               kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
-                       
-                       # Get the correct formfield.
-                       if isinstance(db_field, ManyToManyAttribute):
-                               formfield = self.formfield_for_manytomanyattribute(db_field, request, **kwargs)
-                       elif isinstance(db_field, ForeignKeyAttribute):
-                               formfield = self.formfield_for_foreignkeyattribute(db_field, request, **kwargs)
-                       
-                       # For non-raw_id fields, wrap the widget with a wrapper that adds
-                       # extra HTML -- the "add other" interface -- to the end of the
-                       # rendered output. formfield can be None if it came from a
-                       # OneToOneField with parent_link=True or a M2M intermediary.
-                       # TODO: Implement this.
-                       #if formfield and db_field.name not in self.raw_id_fields:
-                       #       formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field, self.admin_site)
-                       
-                       return formfield
-               return super(ProxyFieldAdmin, self).formfield_for_dbfield(db_field, **kwargs)
-       
-       def formfield_for_foreignkeyattribute(self, db_field, request=None, **kwargs):
-               """Get a form field for a ForeignKeyAttribute field."""
-               db = kwargs.get('using')
-               if db_field.name in self.raw_id_fields:
-                       kwargs['widget'] = ForeignKeyAttributeRawIdWidget(db_field, db)
-               #TODO: Add support for radio fields
-               #elif db_field.name in self.radio_fields:
-               #       kwargs['widget'] = widgets.AdminRadioSelect(attrs={
-               #               'class': get_ul_class(self.radio_fields[db_field.name]),
-               #       })
-               #       kwargs['empty_label'] = db_field.blank and _('None') or None
-               
-               return db_field.formfield(**kwargs)
-       
-       def formfield_for_manytomanyattribute(self, db_field, request=None, **kwargs):
-               """Get a form field for a ManyToManyAttribute field."""
-               db = kwargs.get('using')
-               
-               if db_field.name in self.raw_id_fields:
-                       kwargs['widget'] = ManyToManyAttributeRawIdWidget(db_field, using=db)
-                       kwargs['help_text'] = ''
-               #TODO: Add support for filtered fields.
-               #elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
-               #       kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
-               
-               return db_field.formfield(**kwargs)
\ No newline at end of file
diff --git a/contrib/cowell/widgets.py b/contrib/cowell/widgets.py
deleted file mode 100644 (file)
index cff09f4..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-from django.contrib.admin.widgets import ForeignKeyRawIdWidget, ManyToManyRawIdWidget
-
-
-class ForeignKeyAttributeRawIdWidget(ForeignKeyRawIdWidget):
-       pass
-
-
-class ManyToManyAttributeRawIdWidget(ManyToManyRawIdWidget):
-       pass
\ No newline at end of file
index 9e60966..7e0b0d9 100644 (file)
@@ -1 +1,2 @@
-from philo.forms.fields import *
\ No newline at end of file
+from philo.forms.fields import *
+from philo.forms.entities import *
\ No newline at end of file
similarity index 88%
rename from contrib/cowell/forms.py
rename to forms/entities.py
index aa8b6ca..b6259a3 100644 (file)
@@ -3,7 +3,7 @@ from django.utils.datastructures import SortedDict
 from philo.utils import fattr
 
 
-__all__ = ('ProxyFieldForm',)
+__all__ = ('EntityForm',)
 
 
 def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
@@ -37,7 +37,7 @@ def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widge
 
 # BEGIN HACK - This will not be required after http://code.djangoproject.com/ticket/14082 has been resolved
 
-class ProxyFieldFormBase(ModelForm):
+class EntityFormBase(ModelForm):
        pass
 
 _old_metaclass_new = ModelFormMetaclass.__new__
@@ -46,7 +46,7 @@ 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, ProxyFieldFormBase) and opts.model:
+       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
@@ -59,7 +59,7 @@ ModelFormMetaclass.__new__ = staticmethod(_new_metaclass_new)
 # END HACK
 
 
-class ProxyFieldForm(ProxyFieldFormBase): # Would inherit from ModelForm directly if it weren't for the above HACK
+class EntityForm(EntityFormBase): # Would inherit from ModelForm directly if it weren't for the above HACK
        def __init__(self, *args, **kwargs):
                initial = kwargs.pop('initial', None)
                instance = kwargs.get('instance', None)
@@ -76,12 +76,12 @@ class ProxyFieldForm(ProxyFieldFormBase): # Would inherit from ModelForm directl
                if initial is not None:
                        new_initial.update(initial)
                kwargs['initial'] = new_initial
-               super(ProxyFieldForm, self).__init__(*args, **kwargs)
+               super(EntityForm, self).__init__(*args, **kwargs)
        
        @fattr(alters_data=True)
        def save(self, commit=True):
                cleaned_data = self.cleaned_data
-               instance = super(ProxyFieldForm, self).save(commit=False)
+               instance = super(EntityForm, self).save(commit=False)
                
                for f in instance._entity_meta.proxy_fields:
                        if not f.editable or not f.name in cleaned_data:
index 76d7812..523f789 100644 (file)
@@ -2,7 +2,6 @@ from philo.models.base import *
 from philo.models.collections import *
 from philo.models.nodes import *
 from philo.models.pages import *
-from philo.models.fields import *
 from django.contrib.auth.models import User, Group
 from django.contrib.sites.models import Site
 
similarity index 97%
rename from models/fields.py
rename to models/fields/__init__.py
index 0289e57..d8ed839 100644 (file)
@@ -3,6 +3,7 @@ from django.db import models
 from django.utils import simplejson as json
 from philo.forms.fields import JSONFormField
 from philo.validators import TemplateValidator, json_validator
+#from philo.models.fields.entities import *
 
 
 class TemplateField(models.TextField):
similarity index 94%
rename from contrib/cowell/fields.py
rename to models/fields/entities.py
index 176554e..6c407d0 100644 (file)
@@ -1,11 +1,11 @@
 """
-The Attributes defined in this file can be assigned as fields on a
-subclass of philo.models.Entity. They act like any other model fields,
-but instead of saving their data to the database, they save it to
-attributes related to a model instance. Additionally, a new attribute will
-be created for an instance if and only if the field's value has been set.
-This is relevant i.e. for passthroughs, where the value of the field may
-be defined by some other instance's attributes.
+The EntityProxyFields defined in this file can be assigned as fields on
+a subclass of philo.models.Entity. They act like any other model
+fields, but instead of saving their data to the database, they save it
+to attributes related to a model instance. Additionally, a new
+attribute will be created for an instance if and only if the field's
+value has been set. This is relevant i.e. for passthroughs, where the
+value of the field may be defined by some other instance's attributes.
 
 Example::