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 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.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
 
 
 from mptt.admin import MPTTModelAdmin
 
 
@@ -29,16 +31,115 @@ class AttributeInline(generic.GenericTabularInline):
                template = 'admin/philo/edit_inline/tabular_attribute.html'
 
 
                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):
 class EntityAdmin(admin.ModelAdmin):
+       __metaclass__ = EntityAdminMetaclass
+       form = EntityForm
        inlines = [AttributeInline]
        save_on_top = True
        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 TreeAdmin(MPTTModelAdmin):
        pass
 
 
-class TreeEntityAdmin(TreeAdmin, EntityAdmin):
+class TreeEntityAdmin(EntityAdmin, TreeAdmin):
        pass
 
 
        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
 
 
 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)):
 
 
 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
 
 
 # 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__
        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
        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
                # "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
 
 
 # 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)
        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
                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
        
        @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:
                
                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.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
 
 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 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):
 
 
 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::
 
 
 Example::