Skip to content
Browse files

Upgraded to latest aep-sample

  • Loading branch information...
1 parent 67be83e commit 0935e7e0361692a0c4c5caa8739252a8ea8b0069 @ibolmo committed May 27, 2009
Showing with 4,394 additions and 1,811 deletions.
  1. +13 −6 common/appenginepatch/aecmd.py
  2. +2 −3 common/appenginepatch/appenginepatcher/__init__.py
  3. +105 −65 common/appenginepatch/appenginepatcher/patch.py
  4. +28 −4 common/appenginepatch/appenginepatcher/tests.py
  5. +80 −0 common/appenginepatch/appenginepatcher/transactions.py
  6. +12 −2 common/appenginepatch/main.py
  7. +5 −3 common/appenginepatch/mediautils/compilemessages.py
  8. +5 −6 common/appenginepatch/mediautils/generatemedia.py
  9. +11 −6 common/appenginepatch/mediautils/management/commands/generatemedia.py
  10. +14 −0 common/appenginepatch/mediautils/middleware.py
  11. +8 −5 common/appenginepatch/mediautils/settings.py
  12. +0 −11 common/appenginepatch/mediautils/urlsauto.py
  13. +0 −1 common/appenginepatch/mediautils/views.py
  14. +3 −2 common/appenginepatch/ragendja/auth/decorators.py
  15. +8 −1 common/appenginepatch/ragendja/auth/google_models.py
  16. +1 −1 common/appenginepatch/ragendja/auth/hybrid_models.py
  17. +43 −14 common/appenginepatch/ragendja/dbutils.py
  18. +45 −4 common/appenginepatch/ragendja/forms.py
  19. +21 −0 common/appenginepatch/ragendja/json.py
  20. +12 −0 common/appenginepatch/ragendja/middleware.py
  21. +5 −1 common/appenginepatch/ragendja/settings_pre.py
  22. +27 −17 common/appenginepatch/ragendja/sites/dynamicsite.py
  23. +4 −23 common/appenginepatch/ragendja/template.py
  24. +15 −3 common/appenginepatch/ragendja/templatetags/ragendjatags.py
  25. +7 −2 common/appenginepatch/ragendja/views.py
  26. +741 −9 common/django_aep_export/admin_media/media/css/base.css
  27. +252 −47 common/django_aep_export/admin_media/media/css/changelists.css
  28. +22 −8 common/django_aep_export/admin_media/media/css/dashboard.css
  29. +306 −63 common/django_aep_export/admin_media/media/css/forms.css
  30. +0 −142 common/django_aep_export/admin_media/media/css/global.css
  31. +51 −0 common/django_aep_export/admin_media/media/css/ie.css
  32. +0 −29 common/django_aep_export/admin_media/media/css/layout.css
  33. +53 −12 common/django_aep_export/admin_media/media/css/login.css
  34. +0 −1 common/django_aep_export/admin_media/media/css/null.css
  35. +0 −8 common/django_aep_export/admin_media/media/css/patch-iewin.css
  36. +185 −46 common/django_aep_export/admin_media/media/css/rtl.css
  37. +491 −85 common/django_aep_export/admin_media/media/css/widgets.css
  38. +19 −0 common/django_aep_export/admin_media/media/js/actions.js
  39. +4 −3 common/django_aep_export/admin_media/media/js/admin/DateTimeShortcuts.js
  40. BIN common/django_aep_export/django-locale/locale/ca/LC_MESSAGES/django.mo
  41. +137 −129 common/django_aep_export/django-locale/locale/ca/LC_MESSAGES/django.po
  42. BIN common/django_aep_export/django-locale/locale/de/LC_MESSAGES/django.mo
  43. +229 −148 common/django_aep_export/django-locale/locale/de/LC_MESSAGES/django.po
  44. BIN common/django_aep_export/django-locale/locale/es/LC_MESSAGES/django.mo
  45. +137 −132 common/django_aep_export/django-locale/locale/es/LC_MESSAGES/django.po
  46. BIN common/django_aep_export/django-locale/locale/es_AR/LC_MESSAGES/django.mo
  47. +40 −40 common/django_aep_export/django-locale/locale/es_AR/LC_MESSAGES/django.po
  48. BIN common/django_aep_export/django-locale/locale/fr/LC_MESSAGES/django.mo
  49. +456 −358 common/django_aep_export/django-locale/locale/fr/LC_MESSAGES/django.po
  50. BIN common/django_aep_export/django-locale/locale/fr/LC_MESSAGES/djangojs.mo
  51. +7 −7 common/django_aep_export/django-locale/locale/fr/LC_MESSAGES/djangojs.po
  52. BIN common/django_aep_export/django-locale/locale/it/LC_MESSAGES/django.mo
  53. +39 −39 common/django_aep_export/django-locale/locale/it/LC_MESSAGES/django.po
  54. BIN common/django_aep_export/django-locale/locale/ja/LC_MESSAGES/django.mo
  55. +260 −112 common/django_aep_export/django-locale/locale/ja/LC_MESSAGES/django.po
  56. BIN common/django_aep_export/django-locale/locale/ja/LC_MESSAGES/djangojs.mo
  57. +1 −1 common/django_aep_export/django-locale/locale/ja/LC_MESSAGES/djangojs.po
  58. BIN common/django_aep_export/django-locale/locale/ka/LC_MESSAGES/django.mo
  59. +179 −73 common/django_aep_export/django-locale/locale/ka/LC_MESSAGES/django.po
  60. BIN common/django_aep_export/django-locale/locale/mk/LC_MESSAGES/django.mo
  61. +311 −139 common/django_aep_export/django-locale/locale/mk/LC_MESSAGES/django.po
  62. BIN common/django_aep_export/django-locale/locale/pl/LC_MESSAGES/django.mo
Sorry, we could not display the entire diff because it was too big.
View
19 common/appenginepatch/aecmd.py
@@ -65,13 +65,9 @@ def setup_env(manage_py_env=False):
if not manage_py_env:
return
- print 'Running on app-engine-patch 1.0'
+ print 'Running on app-engine-patch 1.1beta1'
def setup_project():
- # Remove the standard version of Django
- for k in [k for k in sys.modules if k.startswith('django')]:
- del sys.modules[k]
-
from appenginepatcher import on_production_server
if on_production_server:
# This fixes a pwd import bug for os.path.expanduser()
@@ -101,4 +97,15 @@ def setup_project():
for zip_package in os.listdir(packages_dir):
EXTRA_PATHS.append(os.path.join(packages_dir, zip_package))
- sys.path = EXTRA_PATHS + sys.path
+ # App Engine causes main.py to be reloaded if an exception gets raised
+ # on the first request of a main.py instance, so don't call setup_project()
+ # multiple times. We ensure this indirectly by checking if we've already
+ # modified sys.path.
+ if len(sys.path) < len(EXTRA_PATHS) or \
+ sys.path[:len(EXTRA_PATHS)] != EXTRA_PATHS:
+
+ # Remove the standard version of Django
+ for k in [k for k in sys.modules if k.startswith('django')]:
+ del sys.modules[k]
+
+ sys.path = EXTRA_PATHS + sys.path
View
5 common/appenginepatch/appenginepatcher/__init__.py
@@ -14,6 +14,5 @@
except ImportError:
appid = None
-on_production_server = not (
- os.environ.get('SERVER_SOFTWARE', '').lower().startswith('devel') or
- sys.argv[0].endswith('manage.py'))
+on_production_server = have_appserver and \
+ not os.environ.get('SERVER_SOFTWARE', '').lower().startswith('devel')
View
170 common/appenginepatch/appenginepatcher/patch.py
@@ -15,16 +15,31 @@
DEFAULT_NAMES = ('verbose_name', 'ordering', 'permissions', 'app_label',
'abstract', 'db_table', 'db_tablespace')
+# Add checkpoints to patching procedure, so we don't apply certain patches
+# multiple times. This can happen if an exeception gets raised on the first
+# request of an instance. In that case, main.py gets reloaded and patch_all()
+# gets executed yet another time.
+done_patch_all = False
+
def patch_all():
+ global done_patch_all
+ if done_patch_all:
+ return
patch_python()
patch_app_engine()
- patch_django()
+
+ # Add signals: post_save_committed, post_delete_committed
+ from appenginepatcher import transactions
+
setup_logging()
+ done_patch_all = True
def patch_python():
- # Remove modules that we want to override
+ # Remove modules that we want to override. Don't remove modules that we've
+ # already overridden.
for module in ('memcache',):
- if module in sys.modules:
+ if module in sys.modules and \
+ not sys.modules[module].__file__.startswith(base_path):
del sys.modules[module]
# For some reason the imp module can't be replaced via sys.path
@@ -54,6 +69,7 @@ def __get__(self, query, unused):
except:
return query._model_class
db.Query.model = ModelProperty()
+ db.GqlQuery.model = ModelProperty()
# Add a few Model methods that are needed for serialization and ModelForm
def _get_pk_val(self):
@@ -73,11 +89,32 @@ def __ne__(self, other):
def pk(self):
return self._get_pk_val()
db.Model.id = db.Model.pk = property(pk)
+ def serializable_value(self, field_name):
+ """
+ Returns the value of the field name for this instance. If the field is
+ a foreign key, returns the id value, instead of the object. If there's
+ no Field object with this name on the model, the model attribute's
+ value is returned directly.
+
+ Used to serialize a field's value (in the serializer, or form output,
+ for example). Normally, you would just access the attribute directly
+ and not use this method.
+ """
+ from django.db.models.fields import FieldDoesNotExist
+ try:
+ field = self._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ return getattr(self, field_name)
+ return getattr(self, field.attname)
+ db.Model.serializable_value = serializable_value
# Make Property more Django-like (needed for serialization and ModelForm)
db.Property.serialize = True
db.Property.editable = True
db.Property.help_text = ''
+ def blank(self):
+ return not self.required
+ db.Property.blank = property(blank)
def _get_verbose_name(self):
if not getattr(self, '_verbose_name', None):
self._verbose_name = self.name.replace('_', ' ')
@@ -98,6 +135,7 @@ def __init__(self, property):
self.multiple = True
self.parent_link = False
self.related_name = getattr(property, 'collection_name', None)
+ self.through = None
class RelProperty(object):
def __get__(self, property, cls):
@@ -113,7 +151,7 @@ def __get__(self, property, cls):
def formfield(self, **kwargs):
return self.get_form_field(**kwargs)
db.Property.formfield = formfield
-
+
# Add repr to make debugging a little bit easier
from django.utils.datastructures import SortedDict
def __repr__(self):
@@ -257,7 +295,9 @@ def local_fields(self):
def local_many_to_many(self):
return tuple(sorted([p for p in self.model.properties().values()
if isinstance(p, db.ListProperty) and
- not p.name == '_class'],
+ not (issubclass(self.model,
+ polymodel.PolyModel)
+ and p.name == 'class')],
key=lambda prop: prop.creation_counter))
@property
@@ -268,10 +308,10 @@ def get_field(self, name, many_to_many=True):
"""
Returns the requested field by name. Raises FieldDoesNotExist on error.
"""
- from django.db.models.fields import FieldDoesNotExist
for f in self.fields:
if f.name == name:
return f
+ from django.db.models.fields import FieldDoesNotExist
raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
def get_all_related_objects(self, local_only=False):
@@ -389,59 +429,70 @@ def _initialize_model(cls, bases):
# Register models with Django
from django.db.models import signals
- old_propertied_class_init = db.PropertiedClass.__init__
- def __init__(cls, name, bases, attrs, map_kind=True):
- """Creates a combined appengine and Django model.
+ if not hasattr(db.PropertiedClass.__init__, 'patched'):
+ old_propertied_class_init = db.PropertiedClass.__init__
+ def __init__(cls, name, bases, attrs, map_kind=True):
+ """Creates a combined appengine and Django model.
- The resulting model will be known to both the appengine libraries and
- Django.
- """
- _initialize_model(cls, bases)
- old_propertied_class_init(cls, name, bases, attrs,
- not cls._meta.abstract)
- signals.class_prepared.send(sender=cls)
- db.PropertiedClass.__init__ = __init__
-
- old_poly_init = polymodel.PolymorphicClass.__init__
- def __init__(cls, name, bases, attrs):
- if polymodel.PolyModel not in bases:
+ The resulting model will be known to both the appengine libraries
+ and Django.
+ """
_initialize_model(cls, bases)
- old_poly_init(cls, name, bases, attrs)
- if polymodel.PolyModel not in bases:
+ old_propertied_class_init(cls, name, bases, attrs,
+ not cls._meta.abstract)
signals.class_prepared.send(sender=cls)
- polymodel.PolymorphicClass.__init__ = __init__
+ __init__.patched = True
+ db.PropertiedClass.__init__ = __init__
+
+ if not hasattr(polymodel.PolymorphicClass.__init__, 'patched'):
+ old_poly_init = polymodel.PolymorphicClass.__init__
+ def __init__(cls, name, bases, attrs):
+ if polymodel.PolyModel not in bases:
+ _initialize_model(cls, bases)
+ old_poly_init(cls, name, bases, attrs)
+ if polymodel.PolyModel not in bases:
+ signals.class_prepared.send(sender=cls)
+ __init__.patched = True
+ polymodel.PolymorphicClass.__init__ = __init__
@classmethod
def kind(cls):
return cls._meta.db_table
db.Model.kind = kind
# Add model signals
- old_model_init = db.Model.__init__
- def __init__(self, *args, **kwargs):
- signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
- old_model_init(self, *args, **kwargs)
- signals.post_init.send(sender=self.__class__, instance=self)
- db.Model.__init__ = __init__
-
- old_put = db.Model.put
- def put(self, *args, **kwargs):
- raw = False
- signals.pre_save.send(sender=self.__class__, instance=self, raw=raw)
- created = not self.is_saved()
- result = old_put(self, *args, **kwargs)
- signals.post_save.send(sender=self.__class__, instance=self,
- created=created, raw=raw)
- return result
- db.Model.put = put
-
- old_delete = db.Model.delete
- def delete(self, *args, **kwargs):
- signals.pre_delete.send(sender=self.__class__, instance=self)
- result = old_delete(self, *args, **kwargs)
- signals.post_delete.send(sender=self.__class__, instance=self)
- return result
- db.Model.delete = delete
+ if not hasattr(db.Model.__init__, 'patched'):
+ old_model_init = db.Model.__init__
+ def __init__(self, *args, **kwargs):
+ signals.pre_init.send(sender=self.__class__, args=args,
+ kwargs=kwargs)
+ old_model_init(self, *args, **kwargs)
+ signals.post_init.send(sender=self.__class__, instance=self)
+ __init__.patched = True
+ db.Model.__init__ = __init__
+
+ if not hasattr(db.Model.put, 'patched'):
+ old_put = db.Model.put
+ def put(self, *args, **kwargs):
+ raw = False
+ signals.pre_save.send(sender=self.__class__, instance=self, raw=raw)
+ created = not self.is_saved()
+ result = old_put(self, *args, **kwargs)
+ signals.post_save.send(sender=self.__class__, instance=self,
+ created=created, raw=raw)
+ return result
+ put.patched = True
+ db.Model.put = put
+
+ if not hasattr(db.Model.delete, 'patched'):
+ old_delete = db.Model.delete
+ def delete(self, *args, **kwargs):
+ signals.pre_delete.send(sender=self.__class__, instance=self)
+ result = old_delete(self, *args, **kwargs)
+ signals.post_delete.send(sender=self.__class__, instance=self)
+ return result
+ delete.patched = True
+ db.Model.delete = delete
# This has to come last because we load Django here
from django.db.models.fields import BLANK_CHOICE_DASH
@@ -510,15 +561,11 @@ def get_form_field(self, **kwargs):
return super(db.TimeProperty, self).get_form_field(**defaults)
db.TimeProperty.get_form_field = get_form_field
- # Fix default value of UserProperty (Google resolves the user too early)
- # http://code.google.com/p/googleappengine/issues/detail?id=879
- from django.utils.functional import lazy
- from google.appengine.api import users
- def get_form_field(self, **kwargs):
- defaults = {'initial': lazy(users.GetCurrentUser, users.User)()}
- defaults.update(kwargs)
- return super(db.UserProperty, self).get_form_field(**defaults)
- db.UserProperty.get_form_field = get_form_field
+ # Improve handing of StringListProperty
+ def get_form_field(self, **defaults):
+ defaults['required'] = False
+ return super(db.StringListProperty, self).get_form_field(**defaults)
+ db.StringListProperty.get_form_field = get_form_field
# Fix file uploads via BlobProperty
def get_form_field(self, **kwargs):
@@ -549,13 +596,6 @@ def get_form_field(self, **kwargs):
return super(db.ReferenceProperty, self).get_form_field(**defaults)
db.ReferenceProperty.get_form_field = get_form_field
-def patch_django():
- # Most patches are part of the django-app-engine project:
- # http://www.bitbucket.org/wkornewald/django-app-engine/
-
- # Activate ragendja's GLOBALTAGS support (automatically done on import)
- from ragendja import template
-
def setup_logging():
from django.conf import settings
if settings.DEBUG:
View
32 common/appenginepatch/appenginepatcher/tests.py
@@ -5,6 +5,7 @@
from ragendja.testutils import ModelTestCase
from google.appengine.ext import db
from google.appengine.ext.db.polymodel import PolyModel
+from datetime import datetime
# Test class Meta
@@ -64,23 +65,25 @@ def handle_pre_delete(**kwargs):
class SerializeModel(db.Model):
name = db.StringProperty()
count = db.IntegerProperty()
+ created = db.DateTimeProperty()
class SerializerTest(ModelTestCase):
model = SerializeModel
def test_serializer(self, format='json'):
from django.core import serializers
+ created = datetime.now()
x = SerializeModel(key_name='blue_key', name='blue', count=4)
x.put()
- SerializeModel(name='green', count=1).put()
+ SerializeModel(name='green', count=1, created=created).put()
data = serializers.serialize(format, SerializeModel.all())
db.delete(SerializeModel.all().fetch(100))
for obj in serializers.deserialize(format, data):
obj.save()
self.validate_state(
- ('key.name', 'name', 'count'),
- (None, 'green', 1),
- ('blue_key', 'blue', 4),
+ ('key.name', 'name', 'count', 'created'),
+ (None, 'green', 1, created),
+ ('blue_key', 'blue', 4, None),
)
def test_xml_serializer(self):
@@ -116,3 +119,24 @@ def test_cleanup(self):
self.assertEqual(SigChild.all().count(), 0)
self.assertEqual(TestC.all().count(), 0)
self.assertEqual(TestModelRel.all().count(), 0)
+
+from ragendja.dbutils import FakeModel, FakeModelProperty, \
+ FakeModelListProperty
+
+class FM(db.Model):
+ data = FakeModelProperty(FakeModel, indexed=False)
+
+class FML(db.Model):
+ data = FakeModelListProperty(FakeModel, indexed=False)
+
+# Test FakeModel, FakeModelProperty, FakeModelListProperty
+class RelationsCleanupTest(TestCase):
+ def test_fake_model_property(self):
+ value = {'bla': [1, 2, {'blub': 'bla'*1000}]}
+ FM(data=FakeModel(value=value)).put()
+ self.assertEqual(FM.all()[0].data.value, value)
+
+ def test_fake_model_list_property(self):
+ value = {'bla': [1, 2, {'blub': 'bla'*1000}]}
+ FML(data=[FakeModel(value=value)]).put()
+ self.assertEqual(FML.all()[0].data[0].value, value)
View
80 common/appenginepatch/appenginepatcher/transactions.py
@@ -0,0 +1,80 @@
+from google.appengine.api import apiproxy_stub_map
+from google.appengine.ext import db
+from django.dispatch import Signal
+from django.db.models import signals
+from django.utils._threading_local import local
+from functools import wraps
+
+# Add signals which can be run after a transaction has been committed
+signals.post_save_committed = Signal()
+signals.post_delete_committed = Signal()
+
+local = local()
+
+# Patch transaction handlers, so we can support post_xxx_committed signals
+run_in_transaction = db.run_in_transaction
+if not hasattr(run_in_transaction, 'patched'):
+ @wraps(run_in_transaction)
+ def handle_signals(*args, **kwargs):
+ try:
+ if not getattr(local, 'in_transaction', False):
+ local.in_transaction = True
+ local.notify = []
+ result = run_in_transaction(*args, **kwargs)
+ except:
+ local.in_transaction = False
+ local.notify = []
+ raise
+ else:
+ commit()
+ return result
+ handle_signals.patched = True
+ db.run_in_transaction = handle_signals
+
+run_in_transaction_custom_retries = db.run_in_transaction_custom_retries
+if not hasattr(run_in_transaction_custom_retries, 'patched'):
+ @wraps(run_in_transaction_custom_retries)
+ def handle_signals(*args, **kwargs):
+ try:
+ result = run_in_transaction_custom_retries(*args, **kwargs)
+ except:
+ local.in_transaction = False
+ local.notify = []
+ raise
+ else:
+ commit()
+ return result
+ handle_signals.patched = True
+ db.run_in_transaction_custom_retries = handle_signals
+
+def hook(service, call, request, response):
+ if call == 'Rollback':
+ # This stores a list of tuples (action, sender, kwargs)
+ # Possible actions: 'delete', 'save'
+ local.in_transaction = True
+ local.notify = []
+apiproxy_stub_map.apiproxy.GetPostCallHooks().Append('tx_signals', hook)
+
+def commit():
+ local.in_transaction = False
+ for action, sender, kwds in local.notify:
+ signal = getattr(signals, 'post_%s_committed' % action)
+ signal.send(sender=sender, **kwds)
+
+def entity_saved(sender, **kwargs):
+ if 'signal' in kwargs:
+ del kwargs['signal']
+ if getattr(local, 'in_transaction', False):
+ local.notify.append(('save', sender, kwargs))
+ else:
+ signals.post_save_committed.send(sender=sender, **kwargs)
+signals.post_save.connect(entity_saved)
+
+def entity_deleted(sender, **kwargs):
+ if 'signal' in kwargs:
+ del kwargs['signal']
+ if getattr(local, 'in_transaction', False):
+ local.notify.append(('delete', sender, kwargs))
+ else:
+ signals.post_delete_committed.send(sender=sender, **kwargs)
+signals.post_delete.connect(entity_deleted)
View
14 common/appenginepatch/main.py
@@ -1,9 +1,13 @@
# -*- coding: utf-8 -*-
import os, sys
-# Add current folder to sys.path, so we can import aecmd
+# Add current folder to sys.path, so we can import aecmd.
+# App Engine causes main.py to be reloaded if an exception gets raised
+# on the first request of a main.py instance, so don't add current_dir multiple
+# times.
current_dir = os.path.abspath(os.path.dirname(__file__))
-sys.path = [current_dir] + sys.path
+if current_dir not in sys.path:
+ sys.path = [current_dir] + sys.path
import aecmd
aecmd.setup_project()
@@ -16,6 +20,12 @@
from django.conf import settings
def real_main():
+ # Reset path and environment variables
+ global path_backup
+ try:
+ sys.path = path_backup[:]
+ except:
+ path_backup = sys.path[:]
os.environ.update(aecmd.env_ext)
setup_logging()
View
8 common/appenginepatch/mediautils/compilemessages.py
@@ -64,6 +64,7 @@ def update_js_translations(locale):
file.close()
def load_translations(path):
+ """Loads translations grouped into logical sections."""
file = codecs.open(path, 'r', 'utf-8')
lines = file.readlines()
file.close()
@@ -72,20 +73,21 @@ def load_translations(path):
start = -1
resultlines = []
for index, line in enumerate(lines):
+ # Group comments
if line.startswith('#'):
if resultlines and resultlines[-1].startswith('#'):
resultlines[-1] = resultlines[-1] + lines[index]
else:
resultlines.append(lines[index])
continue
- if msgid is not None and (not line.strip() or line.startswith('msgid')):
+ if msgid is not None and (not line.strip() or line.startswith('msgid ')):
mapping[msgid] = len(resultlines)
resultlines.append(''.join(lines[start:index]))
msgid = None
start = -1
- if line.startswith('msgid'):
+ if line.startswith('msgid '):
line = line[len('msgid'):].strip()
start = -1
msgid = ''
@@ -94,7 +96,7 @@ def load_translations(path):
resultlines.append(lines[index])
continue
- if line.startswith('msgstr'):
+ if line.startswith('msgstr') and start < 0:
start = index
if start < 0:
View
11 common/appenginepatch/mediautils/generatemedia.py
@@ -2,7 +2,7 @@
from django.conf import settings
from django.utils.simplejson import dumps
from os.path import getmtime
-import os, codecs, shutil
+import os, codecs, shutil, logging
MEDIA_VERSION = unicode(settings.MEDIA_VERSION)
COMPRESSOR = os.path.join(os.path.dirname(__file__), '.yuicompressor.jar')
@@ -75,7 +75,8 @@ def compress_file(path):
from subprocess import Popen
print ' Running yuicompressor...',
try:
- cmd = Popen(['java', '-jar', COMPRESSOR, path, '-o', path])
+ cmd = Popen(['java', '-jar', COMPRESSOR, '--charset', 'UTF-8',
+ path, '-o', path])
if cmd.wait() == 0:
print '%d bytes' % os.path.getsize(path)
else:
@@ -105,12 +106,12 @@ def get_file_content(handler, cache, **kwargs):
path = get_file_path(handler, **kwargs)
if path not in cache:
if isinstance(handler, basestring):
- file = codecs.open(path, 'r', 'utf-8')
try:
+ file = codecs.open(path, 'r', 'utf-8')
cache[path] = file.read().lstrip(codecs.BOM_UTF8.decode('utf-8')
).replace('\r\n', '\n').replace('\r', '\n')
except:
- print 'Error in %s', path
+ logging.error('Error in %s' % path)
raise
file.close()
elif callable(handler):
@@ -146,8 +147,6 @@ def get_targets(combine_media=settings.COMBINE_MEDIA, **kwargs):
"""Returns all files that must be combined."""
targets = []
for target in sorted(combine_media.keys()):
- import logging
- logging.info('Target: %s' % target)
group = combine_media[target]
if '.site_data.js' in group:
index = list(group).index('.site_data.js')
View
17 common/appenginepatch/mediautils/management/commands/generatemedia.py
@@ -26,20 +26,25 @@
"""
from django.core.management.base import NoArgsCommand
from optparse import make_option
-from mediautils.generatemedia import generatemedia, MEDIA_ROOT
+from mediautils.generatemedia import generatemedia, updatemedia, MEDIA_ROOT
import os, shutil
class Command(NoArgsCommand):
help = 'Combines and compresses your media files and saves them in _generated_media.'
option_list = NoArgsCommand.option_list + (
- make_option('--compressed', action='store_true', dest='compressed',
- help='Run yuicompressor on generated media.'),
+ make_option('--uncompressed', action='store_true', dest='uncompressed',
+ help='Do not run yuicompressor on generated media.'),
+ make_option('--update', action='store_true', dest='update',
+ help='Only update changed files instead of regenerating everything.'),
)
requires_model_validation = False
def handle_noargs(self, **options):
compressed = None
- if options.get('compressed'):
- compressed = True
- generatemedia(compressed)
+ if options.get('uncompressed'):
+ compressed = False
+ if options.get('update'):
+ updatemedia(compressed)
+ else:
+ generatemedia(compressed)
View
14 common/appenginepatch/mediautils/middleware.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+from mediautils.views import get_file
+
+class MediaMiddleware(object):
+ """Returns media files.
+
+ This is a middleware, so it can handle the request as early as possible
+ and thus with minimum overhead."""
+ def process_request(self, request):
+ if request.path.startswith(settings.MEDIA_URL):
+ path = request.path[len(settings.MEDIA_URL):]
+ return get_file(request, path)
+ return None
View
13 common/appenginepatch/mediautils/settings.py
@@ -1,5 +1,8 @@
-from ragendja.settings_post import *
-if not on_production_server and MEDIA_URL.startswith('/'):
- if ADMIN_MEDIA_PREFIX.startswith(MEDIA_URL):
- ADMIN_MEDIA_PREFIX = '/generated_media' + ADMIN_MEDIA_PREFIX
- MEDIA_URL = '/generated_media' + MEDIA_URL
+from ragendja.settings_post import *
+if have_appserver and not on_production_server and MEDIA_URL.startswith('/'):
+ if ADMIN_MEDIA_PREFIX.startswith(MEDIA_URL):
+ ADMIN_MEDIA_PREFIX = '/generated_media' + ADMIN_MEDIA_PREFIX
+ MEDIA_URL = '/generated_media' + MEDIA_URL
+ MIDDLEWARE_CLASSES = (
+ 'mediautils.middleware.MediaMiddleware',
+ ) + MIDDLEWARE_CLASSES
View
11 common/appenginepatch/mediautils/urlsauto.py
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.conf.urls.defaults import *
-from django.conf import settings
-from appenginepatcher import on_production_server
-
-urlpatterns = patterns('')
-if not on_production_server:
- urlpatterns = patterns('',
- (r'^%s(?P<path>.+)$' % settings.MEDIA_URL.lstrip('/'),
- 'mediautils.views.get_file'),
- )
View
1 common/appenginepatch/mediautils/views.py
@@ -8,7 +8,6 @@
@cache_control(public=True, max_age=3600*24*60*60)
def get_file(request, path):
- request.user = 'hey'
media_dirs = get_media_dirs()
data = {'media_dirs': media_dirs}
targets = get_targets(**data)
View
5 common/appenginepatch/ragendja/auth/decorators.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.decorators import login_required
+from functools import wraps
from ragendja.auth.views import google_redirect_to_login
from ragendja.template import render_to_response
@@ -12,11 +13,11 @@ def wrapped(request, *args, **kwargs):
if request.user.is_active and request.user.is_staff:
return view(request, *args, **kwargs)
return render_to_response(request, 'no_access.html')
- return wrapped
+ return wraps(view)(wrapped)
def google_login_required(function):
def login_required_wrapper(request, *args, **kw):
if request.user.is_authenticated():
return function(request, *args, **kw)
return google_redirect_to_login(request.get_full_path())
- return login_required_wrapper
+ return wraps(function)(login_required_wrapper)
View
9 common/appenginepatch/ragendja/auth/google_models.py
@@ -8,7 +8,14 @@ class GoogleUserTraits(EmailUserTraits):
@classmethod
def get_djangouser_for_user(cls, user):
django_user = cls.all().filter('user =', user).get()
- if not django_user:
+ if django_user:
+ if getattr(settings, 'AUTH_ADMIN_USER_AS_SUPERUSER', True):
+ is_admin = users.is_current_user_admin()
+ if django_user.is_staff != is_admin or \
+ django_user.is_superuser != is_admin:
+ django_user.is_superuser = django_user.is_staff = is_admin
+ django_user.put()
+ else:
django_user = cls.create_djangouser_for_user(user)
django_user.is_active = True
if getattr(settings, 'AUTH_ADMIN_USER_AS_SUPERUSER', True) and \
View
2 common/appenginepatch/ragendja/auth/hybrid_models.py
@@ -16,4 +16,4 @@ class Meta:
@classmethod
def create_djangouser_for_user(cls, user):
- return cls(user=user, email=user.email(), username=user.nickname())
+ return cls(user=user, email=user.email(), username=user.email())
View
57 common/appenginepatch/ragendja/dbutils.py
@@ -102,7 +102,7 @@ def db_create(model, parent=None, key_name_format=u'%s',
if result:
return result
-def prefetch_references(object_list, references):
+def prefetch_references(object_list, references, cache=None):
"""
Dereferences the given (Key)ReferenceProperty fields of a list of objects
in as few get() calls as possible.
@@ -136,6 +136,16 @@ def prefetch_references(object_list, references):
continue
key = property.get_value_for_datastore(item)
if key:
+ # Check if we already have a matching item in cache
+ if cache:
+ found_cached = None
+ for cached_item in cache:
+ if cached_item.key() == key:
+ found_cached = cached_item
+ if found_cached:
+ setattr(item, name, found_cached)
+ continue
+ # No item found in cache. Retrieve it.
key = str(key)
prefetch[key] = prefetch.get(key, ()) + ((item, name),)
for target_model, prefetch in targets.values():
@@ -446,21 +456,21 @@ class FakeModel(object):
"""
# Important: If you want to change your fields at a later point you have
# to write a converter which upgrades your datastore schema.
- fields = ()
+ fields = ('value',)
def __init__(self, **kwargs):
if sorted(kwargs.keys()) != sorted(self.fields):
raise ValueError('You have to pass the following values to '
'the constructor: %s' % ', '.join(self.fields))
- for key, value in kwargs:
+ for key, value in kwargs.items():
setattr(self, key, value)
class _meta(object):
installed = True
def get_value_for_datastore(self):
- return simplejson.dumps([getattr(self, field) for field in fields])
+ return simplejson.dumps([getattr(self, field) for field in self.fields])
@property
def pk(self):
@@ -480,13 +490,14 @@ def make_value_from_datastore(cls, value):
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__,
- ' | '.join([getattr(self, field)
+ ' | '.join([unicode(getattr(self, field))
for field in self.fields]))
class FakeModelProperty(db.Property):
data_type = basestring
- def __init__(self, model, *args, **kwargs):
+ def __init__(self, model, raw=False, *args, **kwargs):
+ self.raw = raw
self.model = model
super(FakeModelProperty, self).__init__(*args, **kwargs)
@@ -502,10 +513,19 @@ def validate(self, value):
def get_value_for_datastore(self, model_instance):
fake_model = getattr(model_instance, self.name)
+ if not fake_model:
+ return None
+ if not self.indexed:
+ return db.Text(fake_model.get_value_for_datastore())
return fake_model.get_value_for_datastore()
def make_value_from_datastore(self, value):
- return self.model.make_value_from_datastore(value)
+ if not value:
+ return None
+ return self.model.make_value_from_datastore(unicode(value))
+
+ def get_value_for_form(self, instance):
+ return self.get_value_for_datastore(instance)
def __set__(self, model_instance, value):
if isinstance(value, basestring):
@@ -514,23 +534,29 @@ def __set__(self, model_instance, value):
@classmethod
def get_fake_defaults(self, fake_model, multiple=False, **kwargs):
- from django import forms
- choices = tuple([(item.get_value_for_datastore(), unicode(item))
- for item in fake_model.all()])
- form = multiple and forms.MultipleChoiceField or forms.ChoiceField
- defaults = {'form_class': form, 'choices': choices}
+ from ragendja import forms
+ form = multiple and forms.FakeModelMultipleChoiceField or \
+ forms.FakeModelChoiceField
+ defaults = {'form_class': form, 'fake_model': fake_model}
defaults.update(kwargs)
return defaults
def get_form_field(self, **kwargs):
- defaults = FakeModelProperty.get_fake_defaults(self.model, **kwargs)
+ if self.raw:
+ from django import forms
+ defaults = kwargs
+ defaults['widget'] = forms.TextInput(attrs={'size': 80})
+ else:
+ defaults = FakeModelProperty.get_fake_defaults(self.model, **kwargs)
return super(FakeModelProperty, self).get_form_field(**defaults)
class FakeModelListProperty(db.ListProperty):
fake_item_type = basestring
def __init__(self, model, *args, **kwargs):
self.model = model
+ if not kwargs.get('indexed', True):
+ self.fake_item_type = db.Text
super(FakeModelListProperty, self).__init__(
self.__class__.fake_item_type, *args, **kwargs)
@@ -549,11 +575,14 @@ def validate(self, value):
def get_value_for_datastore(self, model_instance):
fake_models = getattr(model_instance, self.name)
+ if not self.indexed:
+ return [db.Text(fake_model.get_value_for_datastore())
+ for fake_model in fake_models]
return [fake_model.get_value_for_datastore()
for fake_model in fake_models]
def make_value_from_datastore(self, value):
- return [self.model.make_value_from_datastore(item)
+ return [self.model.make_value_from_datastore(unicode(item))
for item in value]
def get_value_for_form(self, instance):
View
49 common/appenginepatch/ragendja/forms.py
@@ -1,6 +1,7 @@
from copy import deepcopy
import re
+from django import forms
from django.utils.datastructures import SortedDict, MultiValueDict
from django.utils.html import conditional_escape
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
@@ -9,6 +10,46 @@
from google.appengine.ext import db
from ragendja.dbutils import transaction
+class FakeModelIterator(object):
+ def __init__(self, fake_model):
+ self.fake_model = fake_model
+
+ def __iter__(self):
+ for item in self.fake_model.all():
+ yield (item.get_value_for_datastore(), unicode(item))
+
+class FakeModelChoiceField(forms.ChoiceField):
+ def __init__(self, fake_model, *args, **kwargs):
+ self.fake_model = fake_model
+ kwargs['choices'] = ()
+ super(FakeModelChoiceField, self).__init__(*args, **kwargs)
+
+ def _get_choices(self):
+ return self._choices
+ def _set_choices(self, choices):
+ self._choices = self.widget.choices = FakeModelIterator(self.fake_model)
+ choices = property(_get_choices, _set_choices)
+
+ def clean(self, value):
+ value = super(FakeModelChoiceField, self).clean(value)
+ return self.fake_model.make_value_from_datastore(value)
+
+class FakeModelMultipleChoiceField(forms.MultipleChoiceField):
+ def __init__(self, fake_model, *args, **kwargs):
+ self.fake_model = fake_model
+ kwargs['choices'] = ()
+ super(FakeModelMultipleChoiceField, self).__init__(*args, **kwargs)
+
+ def _get_choices(self):
+ return self._choices
+ def _set_choices(self, choices):
+ self._choices = self.widget.choices = FakeModelIterator(self.fake_model)
+ choices = property(_get_choices, _set_choices)
+
+ def clean(self, value):
+ value = super(FakeModelChoiceField, self).clean(value)
+ return self.fake_model.make_value_from_datastore(value)
+
class FormWithSets(object):
def __init__(self, form, formsets=()):
self.form = form
@@ -28,6 +69,8 @@ def __init__(self, form, formsets=()):
def __call__(self, *args, **kwargs):
prefix = kwargs['prefix'] + '-' if 'prefix' in kwargs else ''
form = self.form(*args, **kwargs)
+ if 'initial' in kwargs:
+ del kwargs['initial']
formsets = []
for name, formset in self.formsets:
kwargs['prefix'] = prefix + name
@@ -115,13 +158,11 @@ def render(self, name, value, attrs=None):
current_row = []
if len(current_row) != 0:
raise Exception('Unbalanced render')
- def last_first(tuple):
- return tuple[-1:] + tuple[:-1]
return mark_safe(u'%s<table%s><tr>%s</tr><tr>%s</tr></table>%s'%(
table_sections[0],
flatatt(attrs),
- u''.join(last_first(heads)),
- u'</tr><tr>'.join((u''.join(last_first(x)) for x in output)),
+ u''.join(heads),
+ u'</tr><tr>'.join((u''.join(x) for x in output)),
table_sections[2]))
class CachedQuerySet(object):
View
21 common/appenginepatch/ragendja/json.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+from django.core.serializers.json import DjangoJSONEncoder
+from django.http import HttpResponse
+from django.utils import simplejson
+from django.utils.encoding import force_unicode
+from django.utils.functional import Promise
+
+class LazyEncoder(DjangoJSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, Promise):
+ return force_unicode(obj)
+ return super(LazyEncoder, self).default(obj)
+
+class JSONResponse(HttpResponse):
+ def __init__(self, pyobj, **kwargs):
+ super(JSONResponse, self).__init__(
+ simplejson.dumps(pyobj, cls=LazyEncoder),
+ content_type='application/json; charset=%s' %
+ settings.DEFAULT_CHARSET,
+ **kwargs)
View
12 common/appenginepatch/ragendja/middleware.py
@@ -2,6 +2,10 @@
from django.conf import settings
from django.contrib.auth.views import redirect_to_login
from django.utils.cache import patch_cache_control
+from google.appengine.runtime.apiproxy_errors import CapabilityDisabledError
+from google.appengine.ext import db
+from ragendja.template import render_to_response
+from ragendja.views import server_error, maintenance
LOGIN_REQUIRED_PREFIXES = getattr(settings, 'LOGIN_REQUIRED_PREFIXES', ())
NO_LOGIN_REQUIRED_PREFIXES = getattr(settings, 'NO_LOGIN_REQUIRED_PREFIXES', ())
@@ -34,3 +38,11 @@ def process_response(self, request, response):
patch_cache_control(response,
no_store=True, no_cache=True, must_revalidate=True, max_age=0)
return response
+
+class ErrorMiddleware(object):
+ """Displays a default template on CapabilityDisabledError."""
+ def process_exception(self, request, exception):
+ if isinstance(exception, CapabilityDisabledError):
+ return maintenance(request)
+ elif isinstance(exception, db.Timeout):
+ return server_error(request)
View
6 common/appenginepatch/ragendja/settings_pre.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from appenginepatcher import on_production_server
+from appenginepatcher import on_production_server, have_appserver
import os
DEBUG = not on_production_server
@@ -11,6 +11,7 @@
ADMINS = ()
DATABASE_ENGINE = 'appengine'
+DATABASE_SUPPORTS_TRANSACTIONS = False
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
@@ -54,4 +55,7 @@
COMBINE_MEDIA = {}
+if not on_production_server:
+ INTERNAL_IPS = ('127.0.0.1',)
+
IGNORE_APP_SETTINGS = ()
View
44 common/appenginepatch/ragendja/sites/dynamicsite.py
@@ -1,4 +1,5 @@
from django.conf import settings
+from django.core.cache import cache
from django.contrib.sites.models import Site
from ragendja.dbutils import db_create
from ragendja.pyutils import make_tls_property
@@ -17,22 +18,31 @@ def process_request(self, request):
else:
domain = request.get_host().split(':')[0]
- # Try exact domain and fall back to with/without 'www.'
- site = Site.all().filter('domain =', domain).get()
- if not site:
- if domain.startswith('www.'):
- fallback_domain = domain[4:]
- else:
- fallback_domain = 'www.' + domain
- site = Site.all().filter('domain =', fallback_domain).get()
-
- # Add site if it doesn't exist
- if not site and getattr(settings, 'CREATE_SITES_AUTOMATICALLY', True):
- site = db_create(Site, domain=domain, name=domain)
- site.put()
-
- # Set SITE_ID for this thread/request
+ # We cache the SITE_ID
+ cache_key = 'Site:domain:%s' % domain
+ site = cache.get(cache_key)
if site:
- SITE_ID.value = str(site.key())
+ SITE_ID.value = site
else:
- SITE_ID.value = _default_site_id
+ site = Site.all().filter('domain =', domain).get()
+ if not site:
+ # Fall back to with/without 'www.'
+ if domain.startswith('www.'):
+ fallback_domain = domain[4:]
+ else:
+ fallback_domain = 'www.' + domain
+ site = Site.all().filter('domain =', fallback_domain).get()
+
+ # Add site if it doesn't exist
+ if not site and getattr(settings, 'CREATE_SITES_AUTOMATICALLY',
+ True):
+ site = db_create(Site, domain=domain, name=domain)
+ site.put()
+
+ # Set SITE_ID for this thread/request
+ if site:
+ SITE_ID.value = str(site.key())
+ else:
+ SITE_ID.value = _default_site_id
+
+ cache.set(cache_key, SITE_ID.value, 5*60)
View
27 common/appenginepatch/ragendja/template.py
@@ -7,22 +7,13 @@
The app_prefixed_loader is a template loader that loads directly from the app's
'templates' folder when you specify an app prefix ('app/template.html').
-It's possible to register global template libraries by adding this to your
-settings:
-GLOBALTAGS = (
- 'myapp.templatetags.cooltags',
-)
-
The JSONResponse() function automatically converts a given Python object into
JSON and returns it as an HttpResponse.
"""
from django.conf import settings
from django.http import HttpResponse
from django.template import RequestContext, add_to_builtins, loader, \
TemplateDoesNotExist
-from django.utils.functional import Promise
-from django.utils.encoding import force_unicode
-from django.utils import simplejson
from ragendja.apputils import get_app_dirs
import os
@@ -60,25 +51,15 @@ def render_to_response(request, template_name, data=None, mimetype=None):
return HttpResponse(render_to_string(request, template_name, data),
content_type='%s; charset=%s' % (mimetype, settings.DEFAULT_CHARSET))
-class LazyEncoder(simplejson.JSONEncoder):
- def default(self, obj):
- if isinstance(obj, Promise):
- return force_unicode(obj)
- return obj
-
def JSONResponse(pyobj):
- return HttpResponse(simplejson.dumps(pyobj, cls=LazyEncoder),
- content_type='application/json; charset=%s' % settings.DEFAULT_CHARSET)
+ from ragendja.json import JSONResponse as real_class
+ global JSONResponse
+ JSONResponse = real_class
+ return JSONResponse(pyobj)
def TextResponse(string=''):
return HttpResponse(string,
content_type='text/plain; charset=%s' % settings.DEFAULT_CHARSET)
-# Load app modules after all definitions, so imports won't break.
-
-# Register global template libraries.
-for lib in getattr(settings, 'GLOBALTAGS', ()):
- add_to_builtins(lib)
-
# This is needed by app_prefixed_loader.
app_template_dirs = get_app_dirs('templates')
View
18 common/appenginepatch/ragendja/templatetags/ragendjatags.py
@@ -2,20 +2,32 @@
from copy import deepcopy
from django.forms.forms import NON_FIELD_ERRORS
from django.template import Library
-from django.utils import simplejson
from django.utils.datastructures import SortedDict
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from ragendja.dbutils import prefetch_references
-from ragendja.template import LazyEncoder
register = Library()
register.filter('prefetch_references', prefetch_references)
+_js_escapes = {
+ '>': r'\x3E',
+ '<': r'\x3C',
+ '&': r'\x26',
+ '=': r'\x3D',
+ '-': r'\x2D',
+ ';': r'\x3B',
+}
+
@register.filter
def encodejs(value):
- return mark_safe(simplejson.dumps(value, cls=LazyEncoder))
+ from django.utils import simplejson
+ from ragendja.json import LazyEncoder
+ value = simplejson.dumps(value, cls=LazyEncoder)
+ for bad, good in _js_escapes.items():
+ value = value.replace(bad, good)
+ return mark_safe(value)
@register.filter
def urlquerybase(url):
View
9 common/appenginepatch/ragendja/views.py
@@ -1,4 +1,9 @@
-from ragendja.template import render_to_response
+from django.http import HttpResponseServerError
+from ragendja.template import render_to_string
def server_error(request, *args, **kwargs):
- return render_to_response(request, '500.html')
+ return HttpResponseServerError(render_to_string(request, '500.html'))
+
+def maintenance(request, *args, **kwargs):
+ return HttpResponseServerError(render_to_string(request,
+ 'maintenance.html'))
View
750 common/django_aep_export/admin_media/media/css/base.css
@@ -1,14 +1,746 @@
/*
- DJANGO Admin
- by Wilson Miner wilson@lawrence.com
+ DJANGO Admin styles
*/
-/* Block IE 5 */
-@import "null.css?\"\{";
+body {
+ margin: 0;
+ padding: 0;
+ font-size: 12px;
+ font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
+ color: #333;
+ background: #fff;
+}
-/* Import other styles */
-@import url('global.css');
-@import url('layout.css');
+/* LINKS */
+
+a:link, a:visited {
+ color: #5b80b2;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #036;
+}
+
+a img {
+ border: none;
+}
+
+a.section:link, a.section:visited {
+ color: white;
+ text-decoration: none;
+}
+
+/* GLOBAL DEFAULTS */
+
+p, ol, ul, dl {
+ margin: .2em 0 .8em 0;
+}
+
+p {
+ padding: 0;
+ line-height: 140%;
+}
+
+h1,h2,h3,h4,h5 {
+ font-weight: bold;
+}
+
+h1 {
+ font-size: 18px;
+ color: #666;
+ padding: 0 6px 0 0;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ font-size: 16px;
+ margin: 1em 0 .5em 0;
+}
+
+h2.subhead {
+ font-weight: normal;
+ margin-top: 0;
+}
+
+h3 {
+ font-size: 14px;
+ margin: .8em 0 .3em 0;
+ color: #666;
+ font-weight: bold;
+}
+
+h4 {
+ font-size: 12px;
+ margin: 1em 0 .8em 0;
+ padding-bottom: 3px;
+}
+
+h5 {
+ font-size: 10px;
+ margin: 1.5em 0 .5em 0;
+ color: #666;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+ul li {
+ list-style-type: square;
+ padding: 1px 0;
+}
+
+ul.plainlist {
+ margin-left: 0 !important;
+}
+
+ul.plainlist li {
+ list-style-type: none;
+}
+
+li ul {
+ margin-bottom: 0;
+}
+
+li, dt, dd {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+dt {
+ font-weight: bold;
+ margin-top: 4px;
+}
+
+dd {
+ margin-left: 0;
+}
+
+form {
+ margin: 0;
+ padding: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+blockquote {
+ font-size: 11px;
+ color: #777;
+ margin-left: 2px;
+ padding-left: 10px;
+ border-left: 5px solid #ddd;
+}
+
+code, pre {
+ font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+ background: inherit;
+ color: #666;
+ font-size: 11px;
+}
+
+pre.literal-block {
+ margin: 10px;
+ background: #eee;
+ padding: 6px 8px;
+}
+
+code strong {
+ color: #930;
+}
+
+hr {
+ clear: both;
+ color: #eee;
+ background-color: #eee;
+ height: 1px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ font-size: 1px;
+ line-height: 1px;
+}
+
+/* TEXT STYLES & MODIFIERS */
+
+.small {
+ font-size: 11px;
+}
+
+.tiny {
+ font-size: 10px;
+}
+
+p.tiny {
+ margin-top: -2px;
+}
+
+.mini {
+ font-size: 9px;
+}
+
+p.mini {
+ margin-top: -3px;
+}
+
+.help, p.help {
+ font-size: 10px !important;
+ color: #999;
+}
+
+p img, h1 img, h2 img, h3 img, h4 img, td img {
+ vertical-align: middle;
+}
+
+.quiet, a.quiet:link, a.quiet:visited {
+ color: #999 !important;
+ font-weight: normal !important;
+}
+
+.quiet strong {
+ font-weight: bold !important;
+}
+
+.float-right {
+ float: right;
+}
+
+.float-left {
+ float: left;
+}
+
+.clear {
+ clear: both;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-right {
+ text-align: right;
+}
+
+.example {
+ margin: 10px 0;
+ padding: 5px 10px;
+ background: #efefef;
+}
+
+.nowrap {
+ white-space: nowrap;
+}
+
+/* TABLES */
+
+table {
+ border-collapse: collapse;
+ border-color: #ccc;
+}
+
+td, th {
+ font-size: 11px;
+ line-height: 13px;
+ border-bottom: 1px solid #eee;
+ vertical-align: top;
+ padding: 5px;
+ font-family: "Lucida Grande", Verdana, Arial, sans-serif;
+}
+
+th {
+ text-align: left;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+thead th,
+tfoot td {
+ color: #666;
+ padding: 2px 5px;
+ font-size: 11px;
+ background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x;
+ border-left: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+
+tfoot td {
+ border-bottom: none;
+ border-top: 1px solid #ddd;
+}
+
+thead th:first-child,
+tfoot td:first-child {
+ border-left: none !important;
+}
+
+thead th.optional {
+ font-weight: normal !important;
+}
+
+fieldset table {
+ border-right: 1px solid #eee;
+}
+
+tr.row-label td {
+ font-size: 9px;
+ padding-top: 2px;
+ padding-bottom: 0;
+ border-bottom: none;
+ color: #666;
+ margin-top: -1px;
+}
+
+tr.alt {
+ background: #f6f6f6;
+}
+
+.row1 {
+ background: #EDF3FE;
+}
+
+.row2 {
+ background: white;
+}
+
+/* SORTABLE TABLES */
+
+thead th a:link, thead th a:visited {
+ color: #666;
+ display: block;
+}
+
+table thead th.sorted {
+ background-position: bottom left !important;
+}
+
+table thead th.sorted a {
+ padding-right: 13px;
+}
+
+table thead th.ascending a {
+ background: url(../img/admin/arrow-down.gif) right .4em no-repeat;
+}
+
+table thead th.descending a {
+ background: url(../img/admin/arrow-up.gif) right .4em no-repeat;
+}
+
+/* ORDERABLE TABLES */
+
+table.orderable tbody tr td:hover {
+ cursor: move;
+}
+
+table.orderable tbody tr td:first-child {
+ padding-left: 14px;
+ background-image: url(../img/admin/nav-bg-grabber.gif);
+ background-repeat: repeat-y;
+}
+
+table.orderable-initalized .order-cell, body>tr>td.order-cell {
+ display: none;
+}
+
+/* FORM DEFAULTS */
+
+input, textarea, select {
+ margin: 2px 0;
+ padding: 2px 3px;
+ vertical-align: middle;
+ font-family: "Lucida Grande", Verdana, Arial, sans-serif;
+ font-weight: normal;
+ font-size: 11px;
+}
+
+textarea {
+ vertical-align: top !important;
+}
+
+input[type=text], input[type=password], textarea, select, .vTextField {
+ border: 1px solid #ccc;
+}
+
+/* FORM BUTTONS */
+
+.button, input[type=submit], input[type=button], .submit-row input {
+ background: white url(../img/admin/nav-bg.gif) bottom repeat-x;
+ padding: 3px;
+ color: black;
+ border: 1px solid #bbb;
+ border-color: #ddd #aaa #aaa #ddd;
+}
+
+.button:active, input[type=submit]:active, input[type=button]:active {
+ background-image: url(../img/admin/nav-bg-reverse.gif);
+ background-position: top;
+}
+
+.button.default, input[type=submit].default, .submit-row input.default {
+ border: 2px solid #5b80b2;
+ background: #7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x;
+ font-weight: bold;
+ color: white;
+ float: right;
+}
+
+.button.default:active, input[type=submit].default:active {
+ background-image: url(../img/admin/default-bg-reverse.gif);
+ background-position: top;
+}
+
+/* MODULES */
+
+.module {
+ border: 1px solid #ccc;
+ margin-bottom: 5px;
+ background: white;
+}
+
+.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.module blockquote {
+ margin-left: 12px;
+}
+
+.module ul, .module ol {
+ margin-left: 1.5em;
+}
+
+.module h3 {
+ margin-top: .6em;
+}
+
+.module h2, .module caption, .inline-group h2 {
+ margin: 0;
+ padding: 2px 5px 3px 5px;
+ font-size: 11px;
+ text-align: left;
+ font-weight: bold;
+ background: #7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x;
+ color: white;
+}
+
+.module table {
+ border-collapse: collapse;
+}
+
+/* MESSAGES & ERRORS */
+
+ul.messagelist {
+ padding: 0 0 5px 0;
+ margin: 0;
+}
+
+ul.messagelist li {
+ font-size: 12px;
+ display: block;
+ padding: 4px 5px 4px 25px;
+ margin: 0 0 3px 0;
+ border-bottom: 1px solid #ddd;
+ color: #666;
+ background: #ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat;
+}
+
+.errornote {
+ font-size: 12px !important;
+ display: block;
+ padding: 4px 5px 4px 25px;
+ margin: 0 0 3px 0;
+ border: 1px solid red;
+ color: red;
+ background: #ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat;
+}
+
+ul.errorlist {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.errorlist li {
+ font-size: 12px !important;
+ display: block;
+ padding: 4px 5px 4px 25px;
+ margin: 0 0 3px 0;
+ border: 1px solid red;
+ color: white;
+ background: red url(../img/admin/icon_alert.gif) 5px .3em no-repeat;
+}
+
+td ul.errorlist {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+td ul.errorlist li {
+ margin: 0 !important;
+}
+
+.errors {
+ background: #ffc;
+}
+
+.errors input, .errors select {
+ border: 1px solid red;
+}
+
+div.system-message {
+ background: #ffc;
+ margin: 10px;
+ padding: 6px 8px;
+ font-size: .8em;
+}
+
+div.system-message p.system-message-title {
+ padding: 4px 5px 4px 25px;
+ margin: 0;
+ color: red;
+ background: #ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat;
+}
+
+.description {
+ font-size: 12px;
+ padding: 5px 0 0 12px;
+}
+
+/* BREADCRUMBS */
+
+div.breadcrumbs {
+ background: white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x;
+ padding: 2px 8px 3px 8px;
+ font-size: 11px;
+ color: #999;
+ border-top: 1px solid white;
+ border-bottom: 1px solid #ccc;
+ text-align: left;
+}
+
+/* ACTION ICONS */
+
+.addlink {
+ padding-left: 12px;
+ background: url(../img/admin/icon_addlink.gif) 0 .2em no-repeat;
+}
+
+.changelink {
+ padding-left: 12px;
+ background: url(../img/admin/icon_changelink.gif) 0 .2em no-repeat;
+}
+
+.deletelink {
+ padding-left: 12px;
+ background: url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat;
+}
+
+a.deletelink:link, a.deletelink:visited {
+ color: #CC3434;
+}
+
+a.deletelink:hover {
+ color: #993333;
+}
+
+/* OBJECT TOOLS */
+
+.object-tools {
+ font-size: 10px;
+ font-weight: bold;
+ font-family: Arial,Helvetica,sans-serif;
+ padding-left: 0;
+ float: right;
+ position: relative;
+ margin-top: -2.4em;
+ margin-bottom: -2em;
+}
+
+.form-row .object-tools {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ float: none;
+ height: 2em;
+ padding-left: 3.5em;
+}
+
+.object-tools li {
+ display: block;
+ float: left;
+ background: url(../img/admin/tool-left.gif) 0 0 no-repeat;
+ padding: 0 0 0 8px;
+ margin-left: 2px;
+ height: 16px;
+}
+
+.object-tools li:hover {
+ background: url(../img/admin/tool-left_over.gif) 0 0 no-repeat;
+}
+
+.object-tools a:link, .object-tools a:visited {
+ display: block;
+ float: left;
+ color: white;
+ padding: .1em 14px .1em 8px;
+ height: 14px;
+ background: #999 url(../img/admin/tool-right.gif) 100% 0 no-repeat;
+}
+
+.object-tools a:hover, .object-tools li:hover a {
+ background: #5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat;
+}
+
+.object-tools a.viewsitelink, .object-tools a.golink {
+ background: #999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat;
+ padding-right: 28px;
+}
+
+.object-tools a.viewsitelink:hover, .object-tools a.golink:hover {
+ background: #5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat;
+}
+
+.object-tools a.addlink {
+ background: #999 url(../img/admin/tooltag-add.gif) top right no-repeat;
+ padding-right: 28px;
+}
+
+.object-tools a.addlink:hover {
+ background: #5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat;
+}
+
+/* OBJECT HISTORY */
+
+table#change-history {
+ width: 100%;
+}
+
+table#change-history tbody th {
+ width: 16em;
+}
+
+/* PAGE STRUCTURE */
+
+#container {
+ position: relative;
+ width: 100%;
+ min-width: 760px;
+ padding: 0;
+}
+
+#content {
+ margin: 10px 15px;
+}
+
+#header {
+ width: 100%;
+}
+
+#content-main {
+ float: left;
+ width: 100%;
+}
+
+#content-related {
+ float: right;
+ width: 18em;
+ position: relative;
+ margin-right: -19em;
+}
+
+#footer {
+ clear: both;
+ padding: 10px;
+}
+
+/* COLUMN TYPES */
+
+.colMS {
+ margin-right: 20em !important;
+}
+
+.colSM {
+ margin-left: 20em !important;
+}
+
+.colSM #content-related {
+ float: left;
+ margin-right: 0;
+ margin-left: -19em;
+}
+
+.colSM #content-main {
+ float: right;
+}
+
+.popup .colM {
+ width: 95%;
+}
+
+.subcol {
+ float: left;
+ width: 46%;
+ margin-right: 15px;
+}
+
+.dashboard #content {
+ width: 500px;
+}
+
+/* HEADER */
+
+#header {
+ background: #417690;
+ color: #ffc;
+ overflow: hidden;
+}
+
+#header a:link, #header a:visited {
+ color: white;
+}
+
+#header a:hover {
+ text-decoration: underline;
+}
+
+#branding h1 {
+ padding: 0 10px;
+ font-size: 18px;
+ margin: 8px 0;
+ font-weight: normal;
+ color: #f4f379;
+}
+
+#branding h2 {
+ padding: 0 10px;
+ font-size: 14px;
+ margin: -8px 0 8px 0;
+ font-weight: normal;
+ color: #ffc;
+}
+
+#user-tools {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 1.2em 10px;
+ font-size: 11px;
+ text-align: right;
+}
+
+/* SIDEBAR */
+
+#content-related h3 {
+ font-size: 12px;
+ color: #666;
+ margin-bottom: 3px;
+}
+
+#content-related h4 {
+ font-size: 11px;
+}
+
+#content-related .module h2 {
+ background: #eee url(../img/admin/nav-bg.gif) bottom left repeat-x;
+ color: #666;
+}
-/* Import patch for IE 6 Windows */
-/*\*/ @import "patch-iewin.css"; /**/
View
299 common/django_aep_export/admin_media/media/css/changelists.css
@@ -1,50 +1,255 @@
-@import url('base.css');
-
/* CHANGELISTS */
-#changelist { position:relative; width:100%; }
-#changelist table { width:100%; }
-.change-list .filtered table { border-right:1px solid #ddd; }
-.change-list .filtered { min-height:400px; }
-.change-list .filtered { background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important; }
-.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; }
-.change-list .filtered table tbody th { padding-right:1em; }
-#changelist .toplinks { border-bottom:1px solid #ccc !important; }
-#changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/admin/nav-bg.gif) 0 180% repeat-x; overflow:hidden; }
-.change-list .filtered .paginator { border-right:1px solid #ddd; }
-
-/* CHANGELIST TABLES */
-#changelist table thead th { white-space:nowrap; }
-#changelist table tbody td { border-left: 1px solid #ddd; }
-#changelist table tfoot { color: #666; }
-
-/* TOOLBAR */
-#changelist #toolbar { padding:3px; border-bottom:1px solid #ddd; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; color:#666; }
-#changelist #toolbar form input { font-size:11px; padding:1px 2px; }
-#changelist #toolbar form #searchbar { padding:2px; }
-#changelist #changelist-search img { vertical-align:middle; }
-
-/* FILTER COLUMN */
-#changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; }
-#changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; }
-#changelist-filter h3 { font-size:12px; margin-bottom:0; }
-#changelist-filter ul { padding-left:0;margin-left:10px; }
-#changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; }
-#changelist-filter a { color:#999; }
-#changelist-filter a:hover { color:#036; }
-#changelist-filter li.selected { border-left:5px solid #ccc; padding-left:5px;margin-left:-10px; }
-#changelist-filter li.selected a { color:#5b80b2 !important; }
-
-/* DATE DRILLDOWN */
-.change-list ul.toplinks { display:block; background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left; padding:0 !important; margin:0 !important; width:100%; }
-.change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; }
-.change-list ul.toplinks .date-back a { color:#999; }
-.change-list ul.toplinks .date-back a:hover { color:#036; }
+
+#changelist {
+ position: relative;
+ width: 100%;
+}
+
+#changelist table {
+ width: 100%;
+}
+
+.change-list .filtered table {
+ border-right: 1px solid #ddd;
+}
+
+.change-list .filtered {
+ min-height: 400px;
+}
+
+.change-list .filtered {
+ background: white url(../img/admin/changelist-bg.gif) top right repeat-y !important;
+}
+
+.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
+ margin-right: 160px !important;
+ width: auto !important;
+}