Browse Source

MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Adrian Holovaty 19 years ago
parent
commit
f69cf70ed8
100 changed files with 2484 additions and 1700 deletions
  1. 4 0
      AUTHORS
  2. 1 1
      django/__init__.py
  3. 5 5
      django/bin/daily_cleanup.py
  4. 73 0
      django/conf/__init__.py
  5. 3 0
      django/conf/app_template/models.py
  6. 0 1
      django/conf/app_template/models/__init__.py
  7. 0 3
      django/conf/app_template/models/app_name.py
  8. 11 9
      django/conf/global_settings.py
  9. 10 5
      django/conf/project_template/settings.py
  10. 1 1
      django/conf/project_template/urls.py
  11. 0 77
      django/conf/settings.py
  12. 7 7
      django/conf/urls/registration.py
  13. 10 10
      django/contrib/admin/filterspecs.py
  14. 13 2
      django/contrib/admin/media/css/base.css
  15. 12 6
      django/contrib/admin/media/css/changelists.css
  16. 10 0
      django/contrib/admin/media/css/dashboard.css
  17. 60 0
      django/contrib/admin/media/css/forms.css
  18. 60 244
      django/contrib/admin/media/css/global.css
  19. 29 0
      django/contrib/admin/media/css/layout.css
  20. 13 0
      django/contrib/admin/media/css/login.css
  21. 2 3
      django/contrib/admin/media/css/patch-iewin.css
  22. 101 0
      django/contrib/admin/media/css/widgets.css
  23. BIN
      django/contrib/admin/media/img/admin/deleted-overlay.gif
  24. BIN
      django/contrib/admin/media/img/admin/inline-delete-8bit.png
  25. BIN
      django/contrib/admin/media/img/admin/inline-delete.png
  26. BIN
      django/contrib/admin/media/img/admin/inline-restore-8bit.png
  27. BIN
      django/contrib/admin/media/img/admin/inline-restore.png
  28. BIN
      django/contrib/admin/media/img/admin/inline-splitter-bg.gif
  29. 1 1
      django/contrib/admin/media/js/admin/RelatedObjectLookups.js
  30. 51 0
      django/contrib/admin/models.py
  31. 0 1
      django/contrib/admin/models/__init__.py
  32. 0 50
      django/contrib/admin/models/admin.py
  33. 1 1
      django/contrib/admin/templates/admin/404.html
  34. 1 1
      django/contrib/admin/templates/admin/500.html
  35. 1 1
      django/contrib/admin/templates/admin/base_site.html
  36. 20 27
      django/contrib/admin/templates/admin/change_form.html
  37. 3 2
      django/contrib/admin/templates/admin/change_list.html
  38. 11 1
      django/contrib/admin/templates/admin/delete_confirmation.html
  39. 1 1
      django/contrib/admin/templates/admin/edit_inline_stacked.html
  40. 0 8
      django/contrib/admin/templates/admin/field_line.html
  41. 7 6
      django/contrib/admin/templates/admin/index.html
  42. 16 15
      django/contrib/admin/templates/admin/login.html
  43. 6 6
      django/contrib/admin/templates/admin/object_history.html
  44. 1 1
      django/contrib/admin/templates/admin/search_form.html
  45. 1 1
      django/contrib/admin/templates/admin/template_validator.html
  46. 1 1
      django/contrib/admin/templates/admin_doc/bookmarklets.html
  47. 1 1
      django/contrib/admin/templates/admin_doc/index.html
  48. 1 1
      django/contrib/admin/templates/admin_doc/missing_docutils.html
  49. 2 2
      django/contrib/admin/templates/admin_doc/model_detail.html
  50. 9 8
      django/contrib/admin/templates/admin_doc/model_index.html
  51. 1 1
      django/contrib/admin/templates/admin_doc/template_detail.html
  52. 1 1
      django/contrib/admin/templates/admin_doc/template_filter_index.html
  53. 1 1
      django/contrib/admin/templates/admin_doc/template_tag_index.html
  54. 1 1
      django/contrib/admin/templates/admin_doc/view_detail.html
  55. 1 1
      django/contrib/admin/templates/admin_doc/view_index.html
  56. 1 1
      django/contrib/admin/templates/registration/logged_out.html
  57. 1 1
      django/contrib/admin/templates/registration/password_change_done.html
  58. 1 1
      django/contrib/admin/templates/registration/password_change_form.html
  59. 1 1
      django/contrib/admin/templates/registration/password_reset_done.html
  60. 1 1
      django/contrib/admin/templates/registration/password_reset_form.html
  61. 14 6
      django/contrib/admin/templates/widget/foreign.html
  62. 1 1
      django/contrib/admin/templates/widget/many_to_many.html
  63. 2 1
      django/contrib/admin/templates/widget/one_to_one.html
  64. 52 60
      django/contrib/admin/templatetags/admin_list.py
  65. 55 68
      django/contrib/admin/templatetags/admin_modify.py
  66. 12 7
      django/contrib/admin/templatetags/adminapplist.py
  67. 4 3
      django/contrib/admin/templatetags/adminmedia.py
  68. 3 3
      django/contrib/admin/templatetags/log.py
  69. 31 0
      django/contrib/admin/urls.py
  70. 0 58
      django/contrib/admin/urls/admin.py
  71. 3 3
      django/contrib/admin/utils.py
  72. 15 15
      django/contrib/admin/views/decorators.py
  73. 91 54
      django/contrib/admin/views/doc.py
  74. 462 391
      django/contrib/admin/views/main.py
  75. 12 12
      django/contrib/admin/views/template.py
  76. 2 0
      django/contrib/auth/__init__.py
  77. 84 0
      django/contrib/auth/create_superuser.py
  78. 5 3
      django/contrib/auth/decorators.py
  79. 108 0
      django/contrib/auth/forms.py
  80. 6 6
      django/contrib/auth/handlers/modpython.py
  81. 53 0
      django/contrib/auth/management.py
  82. 19 0
      django/contrib/auth/middleware.py
  83. 264 0
      django/contrib/auth/models.py
  84. 84 0
      django/contrib/auth/views.py
  85. 18 18
      django/contrib/comments/feeds.py
  86. 285 0
      django/contrib/comments/models.py
  87. 0 1
      django/contrib/comments/models/__init__.py
  88. 0 287
      django/contrib/comments/models/comments.py
  89. 39 41
      django/contrib/comments/templatetags/comments.py
  90. 57 54
      django/contrib/comments/views/comments.py
  91. 10 9
      django/contrib/comments/views/karma.py
  92. 18 29
      django/contrib/comments/views/userflags.py
  93. 0 0
      django/contrib/contenttypes/__init__.py
  94. 49 0
      django/contrib/contenttypes/models.py
  95. 3 3
      django/contrib/flatpages/middleware.py
  96. 33 0
      django/contrib/flatpages/models.py
  97. 0 1
      django/contrib/flatpages/models/__init__.py
  98. 0 33
      django/contrib/flatpages/models/flatpages.py
  99. 12 12
      django/contrib/flatpages/views.py
  100. 8 1
      django/contrib/markup/templatetags/markup.py

+ 4 - 0
AUTHORS

@@ -71,6 +71,7 @@ answer newbie questions, and generally made Django that much better:
     lakin.wecker@gmail.com
     Stuart Langridge <http://www.kryogenix.org/>
     Eugene Lazutkin <http://lazutkin.com/blog/>
+    Christopher Lenz <http://www.cmlenz.net/>
     limodou
     Martin Maney <http://www.chipy.org/Martin_Maney>
     Manuzhai
@@ -79,6 +80,7 @@ answer newbie questions, and generally made Django that much better:
     mattycakes@gmail.com
     Jason McBrayer <http://www.carcosa.net/jason/>
     michael.mcewan@gmail.com
+    mir@noris.de
     mmarshall
     Eric Moritz <http://eric.themoritzfamily.com/>
     Robin Munn <http://www.geekforgod.com/>
@@ -102,7 +104,9 @@ answer newbie questions, and generally made Django that much better:
     Aaron Swartz <http://www.aaronsw.com/>
     Tom Tobin
     Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
+    Malcolm Tredinnick
     Amit Upadhyay
+    Geert Vanderkelen
     Milton Waddams
     Rachel Willmer <http://www.willmer.com/kb/>
     wojtek

+ 1 - 1
django/__init__.py

@@ -1 +1 @@
-VERSION = (0, 9, 1, 'SVN')
+VERSION = (0, 95, 'post-magic-removal')

+ 5 - 5
django/bin/daily_cleanup.py

@@ -1,17 +1,17 @@
 "Daily cleanup file"
 
-from django.core.db import db
+from django.db import backend, connection, transaction
 
 DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
 
 def clean_up():
     # Clean up old database records
-    cursor = db.cursor()
+    cursor = connection.cursor()
     cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \
-        (db.quote_name('core_sessions'), db.quote_name('expire_date')))
+        (backend.quote_name('core_sessions'), backend.quote_name('expire_date')))
     cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \
-        (db.quote_name('registration_challenges'), db.quote_name('request_date')))
-    db.commit()
+        (backend.quote_name('registration_challenges'), backend.quote_name('request_date')))
+    transaction.commit_unless_managed()
 
 if __name__ == "__main__":
     clean_up()

+ 73 - 0
django/conf/__init__.py

@@ -0,0 +1,73 @@
+"""
+Settings and configuration for Django.
+
+Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment
+variable, and then from django.conf.global_settings; see the global settings file for
+a list of all possible variables.
+"""
+
+import os
+import sys
+from django.conf import global_settings
+
+ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
+
+class Settings:
+
+    def __init__(self, settings_module):
+
+        # update this dict from global settings (but only for ALL_CAPS settings)
+        for setting in dir(global_settings):
+            if setting == setting.upper():
+                setattr(self, setting, getattr(global_settings, setting))
+
+        # store the settings module in case someone later cares
+        self.SETTINGS_MODULE = settings_module
+
+        try:
+            mod = __import__(self.SETTINGS_MODULE, '', '', [''])
+        except ImportError, e:
+            raise EnvironmentError, "Could not import settings '%s' (is it on sys.path?): %s" % (self.SETTINGS_MODULE, e)
+
+        # Settings that should be converted into tuples if they're mistakenly entered
+        # as strings.
+        tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
+
+        for setting in dir(mod):
+            if setting == setting.upper():
+                setting_value = getattr(mod, setting)
+                if setting in tuple_settings and type(setting_value) == str:
+                    setting_value = (setting_value,) # In case the user forgot the comma.
+                setattr(self, setting, setting_value)
+
+        # Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
+        # of all those apps.
+        new_installed_apps = []
+        for app in self.INSTALLED_APPS:
+            if app.endswith('.*'):
+                appdir = os.path.dirname(__import__(app[:-2], '', '', ['']).__file__)
+                for d in os.listdir(appdir):
+                    if d.isalpha() and os.path.isdir(os.path.join(appdir, d)):
+                        new_installed_apps.append('%s.%s' % (app[:-2], d))
+            else:
+                new_installed_apps.append(app)
+        self.INSTALLED_APPS = new_installed_apps
+
+        # move the time zone info into os.environ
+        os.environ['TZ'] = self.TIME_ZONE
+
+# try to load DJANGO_SETTINGS_MODULE
+try:
+    settings_module = os.environ[ENVIRONMENT_VARIABLE]
+    if not settings_module: # If it's set but is an empty string.
+        raise KeyError
+except KeyError:
+    raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
+
+# instantiate the configuration object
+settings = Settings(settings_module)
+
+# install the translation machinery so that it is available
+from django.utils import translation
+translation.install()
+

+ 3 - 0
django/conf/app_template/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 0 - 1
django/conf/app_template/models/__init__.py

@@ -1 +0,0 @@
-__all__ = ['{{ app_name }}']

+ 0 - 3
django/conf/app_template/models/app_name.py

@@ -1,3 +0,0 @@
-from django.core import meta
-
-# Create your models here.

+ 11 - 9
django/conf/global_settings.py

@@ -79,7 +79,7 @@ SERVER_EMAIL = 'root@localhost'
 SEND_BROKEN_LINK_EMAILS = False
 
 # Database connection info.
-DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_ENGINE = ''           # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
 DATABASE_NAME = ''             # Or path to database file if using sqlite3.
 DATABASE_USER = ''             # Not used with sqlite3.
 DATABASE_PASSWORD = ''         # Not used with sqlite3.
@@ -102,19 +102,16 @@ INSTALLED_APPS = ()
 # List of locations of the template source files, in search order.
 TEMPLATE_DIRS = ()
 
-# Extension on all templates.
-TEMPLATE_FILE_EXTENSION = '.html'
-
 # List of callables that know how to import templates from various sources.
 # See the comments in django/core/template/loader.py for interface
 # documentation.
 TEMPLATE_LOADERS = (
-    'django.core.template.loaders.filesystem.load_template_source',
-    'django.core.template.loaders.app_directories.load_template_source',
-#     'django.core.template.loaders.eggs.load_template_source',
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
 )
 
-# List of processors used by DjangoContext to populate the context.
+# List of processors used by RequestContext to populate the context.
 # Each one should be a callable that takes the request object as its
 # only parameter and returns a dictionary to add to the context.
 TEMPLATE_CONTEXT_PROCESSORS = (
@@ -205,6 +202,10 @@ TIME_FORMAT = 'P'
 # http://psyco.sourceforge.net/
 ENABLE_PSYCO = False
 
+# Do you want to manage transactions manually?
+# Hint: you really don't!
+TRANSACTIONS_MANAGED = False
+
 ##############
 # MIDDLEWARE #
 ##############
@@ -213,7 +214,8 @@ ENABLE_PSYCO = False
 # this middleware classes will be applied in the order given, and in the
 # response phase the middleware will be applied in reverse order.
 MIDDLEWARE_CLASSES = (
-    "django.middleware.sessions.SessionMiddleware",
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
 #     "django.middleware.http.ConditionalGetMiddleware",
 #     "django.middleware.gzip.GZipMiddleware",
     "django.middleware.common.CommonMiddleware",

+ 10 - 5
django/conf/project_template/settings.py

@@ -9,7 +9,7 @@ ADMINS = (
 
 MANAGERS = ADMINS
 
-DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_ENGINE = ''           # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
 DATABASE_NAME = ''             # Or path to database file if using sqlite3.
 DATABASE_USER = ''             # Not used with sqlite3.
 DATABASE_PASSWORD = ''         # Not used with sqlite3.
@@ -45,14 +45,15 @@ SECRET_KEY = ''
 
 # List of callables that know how to import templates from various sources.
 TEMPLATE_LOADERS = (
-    'django.core.template.loaders.filesystem.load_template_source',
-    'django.core.template.loaders.app_directories.load_template_source',
-#     'django.core.template.loaders.eggs.load_template_source',
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
 )
 
 MIDDLEWARE_CLASSES = (
     "django.middleware.common.CommonMiddleware",
-    "django.middleware.sessions.SessionMiddleware",
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.middleware.doc.XViewMiddleware",
 )
 
@@ -64,4 +65,8 @@ TEMPLATE_DIRS = (
 )
 
 INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
 )

+ 1 - 1
django/conf/project_template/urls.py

@@ -5,5 +5,5 @@ urlpatterns = patterns('',
     # (r'^{{ project_name }}/', include('{{ project_name }}.apps.foo.urls.foo')),
 
     # Uncomment this for admin:
-#     (r'^admin/', include('django.contrib.admin.urls.admin')),
+#     (r'^admin/', include('django.contrib.admin.urls')),
 )

+ 0 - 77
django/conf/settings.py

@@ -1,77 +0,0 @@
-"""
-Settings and configuration for Django.
-
-Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment
-variable, and then from django.conf.global_settings; see the global settings file for
-a list of all possible variables.
-"""
-
-import os
-import sys
-from django.conf import global_settings
-
-ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
-
-# get a reference to this module (why isn't there a __module__ magic var?)
-me = sys.modules[__name__]
-
-# update this dict from global settings (but only for ALL_CAPS settings)
-for setting in dir(global_settings):
-    if setting == setting.upper():
-        setattr(me, setting, getattr(global_settings, setting))
-
-# try to load DJANGO_SETTINGS_MODULE
-try:
-    me.SETTINGS_MODULE = os.environ[ENVIRONMENT_VARIABLE]
-    if not me.SETTINGS_MODULE: # If it's set but is an empty string.
-        raise KeyError
-except KeyError:
-    raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
-
-try:
-    mod = __import__(me.SETTINGS_MODULE, '', '', [''])
-except ImportError, e:
-    raise EnvironmentError, "Could not import %s '%s' (is it on sys.path?): %s" % (ENVIRONMENT_VARIABLE, me.SETTINGS_MODULE, e)
-
-# Settings that should be converted into tuples if they're mistakenly entered
-# as strings.
-tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
-
-for setting in dir(mod):
-    if setting == setting.upper():
-        setting_value = getattr(mod, setting)
-        if setting in tuple_settings and type(setting_value) == str:
-            setting_value = (setting_value,) # In case the user forgot the comma.
-        setattr(me, setting, setting_value)
-
-# Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
-# of all those apps.
-new_installed_apps = []
-for app in me.INSTALLED_APPS:
-    if app.endswith('.*'):
-        appdir = os.path.dirname(__import__(app[:-2], '', '', ['']).__file__)
-        for d in os.listdir(appdir):
-            if d.isalpha() and os.path.isdir(os.path.join(appdir, d)):
-                new_installed_apps.append('%s.%s' % (app[:-2], d))
-    else:
-        new_installed_apps.append(app)
-me.INSTALLED_APPS = new_installed_apps
-
-# save DJANGO_SETTINGS_MODULE in case anyone in the future cares
-me.SETTINGS_MODULE = os.environ.get(ENVIRONMENT_VARIABLE, '')
-
-# move the time zone info into os.environ
-os.environ['TZ'] = me.TIME_ZONE
-
-# finally, clean up my namespace
-for k in dir(me):
-    if not k.startswith('_') and k != 'me' and k != k.upper():
-        delattr(me, k)
-del me, k
-
-# as the last step, install the translation machinery and
-# remove the module again to not clutter the namespace.
-from django.utils import translation
-translation.install()
-del translation
-

+ 7 - 7
django/conf/urls/registration.py

@@ -1,9 +1,9 @@
 from django.conf.urls.defaults import *
 
 urlpatterns = patterns('',
-    (r'^login/$', 'django.views.auth.login.login'),
-    (r'^logout/$', 'django.views.auth.login.logout'),
-    (r'^login_another/$', 'django.views.auth.login.logout_then_login'),
+    (r'^login/$', 'django.contrib.auth.view.login'),
+    (r'^logout/$', 'django.contrib.auth.views.logout'),
+    (r'^login_another/$', 'django.contrib.auth.views.logout_then_login'),
 
     (r'^register/$', 'ellington.registration.views.registration.signup'),
     (r'^register/(?P<challenge_string>\w{32})/$', 'ellington.registration.views.registration.register_form'),
@@ -12,8 +12,8 @@ urlpatterns = patterns('',
     (r'^profile/welcome/$', 'ellington.registration.views.profile.profile_welcome'),
     (r'^profile/edit/$', 'ellington.registration.views.profile.edit_profile'),
 
-    (r'^password_reset/$', 'django.views.registration.passwords.password_reset'),
-    (r'^password_reset/done/$', 'django.views.registration.passwords.password_reset_done'),
-    (r'^password_change/$', 'django.views.registration.passwords.password_change'),
-    (r'^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
+    (r'^password_reset/$', 'django.contrib.auth.views.password_reset'),
+    (r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'),
+    (r'^password_change/$', 'django.contrib.auth.views.password_change'),
+    (r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
 )

+ 10 - 10
django/contrib/admin/filterspecs.py

@@ -6,7 +6,7 @@ Each filter subclass knows how to display a filter for a field that passes a
 certain test -- e.g. being a DateField or ForeignKey.
 """
 
-from django.core import meta
+from django.db import models
 import datetime
 
 class FilterSpec(object):
@@ -50,13 +50,13 @@ class FilterSpec(object):
 class RelatedFilterSpec(FilterSpec):
     def __init__(self, f, request, params):
         super(RelatedFilterSpec, self).__init__(f, request, params)
-        if isinstance(f, meta.ManyToManyField):
-            self.lookup_title = f.rel.to.verbose_name
+        if isinstance(f, models.ManyToManyField):
+            self.lookup_title = f.rel.to._meta.verbose_name
         else:
             self.lookup_title = f.verbose_name
-        self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to.pk.name)
+        self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to._meta.pk.name)
         self.lookup_val = request.GET.get(self.lookup_kwarg, None)
-        self.lookup_choices = f.rel.to.get_model_module().get_list()
+        self.lookup_choices = f.rel.to._default_manager.all()
 
     def has_output(self):
         return len(self.lookup_choices) > 1
@@ -69,7 +69,7 @@ class RelatedFilterSpec(FilterSpec):
                'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
                'display': _('All')}
         for val in self.lookup_choices:
-            pk_val = getattr(val, self.field.rel.to.pk.attname)
+            pk_val = getattr(val, self.field.rel.to._meta.pk.attname)
             yield {'selected': self.lookup_val == str(pk_val),
                    'query_string': cl.get_query_string( {self.lookup_kwarg: pk_val}),
                    'display': val}
@@ -103,7 +103,7 @@ class DateFieldFilterSpec(FilterSpec):
 
         today = datetime.date.today()
         one_week_ago = today - datetime.timedelta(days=7)
-        today_str = isinstance(self.field, meta.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
+        today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
 
         self.links = (
             (_('Any date'), {}),
@@ -126,7 +126,7 @@ class DateFieldFilterSpec(FilterSpec):
                    'query_string': cl.get_query_string( param_dict, self.field_generic),
                    'display': title}
 
-FilterSpec.register(lambda f: isinstance(f, meta.DateField), DateFieldFilterSpec)
+FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
 
 class BooleanFieldFilterSpec(FilterSpec):
     def __init__(self, f, request, params):
@@ -144,9 +144,9 @@ class BooleanFieldFilterSpec(FilterSpec):
             yield {'selected': self.lookup_val == v and not self.lookup_val2,
                    'query_string': cl.get_query_string( {self.lookup_kwarg: v}, [self.lookup_kwarg2]),
                    'display': k}
-        if isinstance(self.field, meta.NullBooleanField):
+        if isinstance(self.field, models.NullBooleanField):
             yield {'selected': self.lookup_val2 == 'True',
                    'query_string': cl.get_query_string( {self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
                    'display': _('Unknown')}
 
-FilterSpec.register(lambda f: isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField), BooleanFieldFilterSpec)
+FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)

+ 13 - 2
django/contrib/admin/media/css/base.css

@@ -1,3 +1,14 @@
-@import url(global.css);
-@import url(changelists.css);
+/*
+    DJANGO Admin
+    by Wilson Miner wilson@lawrence.com
+*/
+
+/* Block IE 5 */
+@import "null?\"\{";
+
+/* Import other styles */
+@import url('global.css');
+@import url('layout.css');
+
+/* Import patch for IE 6 Windows */
 /*\*/ @import "patch-iewin.css"; /**/

+ 12 - 6
django/contrib/admin/media/css/changelists.css

@@ -1,16 +1,13 @@
-/*
-    DJANGO Admin Changelist Styles
-    by Wilson Miner wilson@lawrence.com
-    Copyright (c) 2005 Lawrence Journal-World
-*/
+@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; _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:10px; }
+.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; }
@@ -42,3 +39,12 @@
 .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; }
+
+/* PAGINATOR */
+.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
+.paginator a:link, .paginator a:visited	{ padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; }
+.paginator a.showall { padding:0 !important; border:none !important; }
+.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
+.paginator .end	{ border-width:2px !important; margin-right:6px; }
+.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
+.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }

+ 10 - 0
django/contrib/admin/media/css/dashboard.css

@@ -0,0 +1,10 @@
+@import url('base.css');
+
+/* DASHBOARD */
+.dashboard .module table th { width:100%; }
+.dashboard .module table td { white-space:nowrap; }
+.dashboard .module table td a { display:block; padding-right:.6em; }
+
+/*  RECENT ACTIONS MODULE  */
+.module ul.actionlist { margin-left:0; }
+ul.actionlist li { list-style-type:none; }

+ 60 - 0
django/contrib/admin/media/css/forms.css

@@ -0,0 +1,60 @@
+@import url('base.css');
+@import url('widgets.css');
+
+/* FORM ROWS */
+.form-row { overflow:hidden; padding:8px 12px; font-size:11px; border-bottom:1px solid #eee; }
+.form-row img, .form-row input { vertical-align:middle; }
+form .form-row p { padding-left:0; font-size:11px; }
+
+/* FORM LABELS */
+form h4	{ margin:0 !important; padding:0 !important; border:none !important; }
+label { font-weight:normal !important; color:#666; font-size:12px; }
+label.inline { margin-left:20px; }
+.required label, label.required	{ font-weight:bold !important; color:#333 !important; }
+
+/* RADIO BUTTONS */
+form ul.radiolist li { list-style-type:none; }
+form ul.radiolist label { float:none; display:inline; }
+form ul.inline { margin-left:0; padding:0; }
+form ul.inline li { float:left; padding-right:7px; }
+
+/* ALIGNED FIELDSETS */
+.aligned label { display:block; padding:0 1em 3px 0; float:left; width:8em; }
+.aligned label.inline { display:inline; float:none; }
+.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
+form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
+form .aligned table p { margin-left:0; padding-left:0; }
+form .aligned p.help { padding-left:38px; }
+.aligned .vCheckboxLabel { float:none !important; display:inline; padding-left:4px; }
+.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; }
+.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
+
+/* WIDE FIELDSETS */
+.wide label { width:15em !important; }
+form .wide p { margin-left:15em; }
+form .wide p.help { padding-left:38px; }
+.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
+
+/* COLLAPSED FIELDSETS */
+fieldset.collapsed * { display:none; }
+fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
+fieldset.collapsed h2 { background-image:url(../img/admin/nav-bg.gif); background-position:bottom left; color:#999; }
+fieldset.collapsed .collapse-toggle { padding:3px 5px !important; background:transparent; display:inline !important;}
+
+/* MONOSPACE TEXTAREAS */
+fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
+
+/* SUBMIT ROW */
+.submit-row { padding:5px 7px; text-align:right; background:white url(../img/admin/nav-bg.gif) 0 100% repeat-x; border:1px solid #ccc; margin:5px 0; }
+.submit-row input { margin:0 0 0 5px; }
+.submit-row p { margin-top:0.3em; }
+.submit-row .deletelink { background:url(../img/admin/icon_deletelink.gif) 0 50% no-repeat; padding-left:14px; }
+
+/* CUSTOM FORM FIELDS */
+.vSelectMultipleField { vertical-align:top !important; }
+.vCheckboxField { border:none; }
+.vDateField, .vTimeField { margin-right:2px; }
+.vURLField { width:30em; }
+.vLargeTextField, .vXMLLargeTextField { width:48em; }
+.flatpages-flatpage #id_content { height:40.2em; }
+.module table .vPositiveSmallIntegerField { width:2.2em; }

+ 60 - 244
django/contrib/admin/media/css/global.css

@@ -1,19 +1,14 @@
-/*
-    DJANGO Admin Global Styles
-    by Wilson Miner wilson@lawrence.com
-    Copyright (c) 2005 Lawrence Journal-World
-*/
-
-body { margin:0; padding:0; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
+body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
 
 /* LINKS */
 a:link, a:visited { color: #5b80b2; text-decoration:none; }
 a:hover { color: #036; }
 a img { border:none; }
 
-/*  GLOBAL DEFAULTS */
-p, ol, ul, dl { margin:.2em 0 .8em 0; font-size:12px; }
+/* 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; }
@@ -21,6 +16,7 @@ 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; }
@@ -28,150 +24,83 @@ 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; }
 
-/*  PAGE STRUCTURE  */
-#container { position:relative; width:100%; min-width:760px; }
-#content { margin:10px 15px; }
-#header { width:100%; }
-#content-main { float:left; width:100%; }
-#content-related { float:right; width:220px; position:relative; margin-right:-230px; }
-#footer	{ clear:both; padding:10px; }
-
-/*  COLUMN TYPES  */
-.colMS { margin-right:245px !important; }
-.colSM { margin-left:245px !important; }
-.colSM #content-related { float:left; margin-right:0; margin-left:-230px; } 
-.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; min-height:2.4em; overflow:hidden; }
-#header a:link, #header a:visited { color:white; }
-#header a:hover { text-decoration:underline; }
-#branding h1 { padding:0.5em 10px 0 10px; font-size:18px; margin:0; font-weight:normal; color:#f4f379; }
-#branding h2 { padding:0 10px 0.8em 10px; font-size:14px; margin: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; }
+/* 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  */
+/* 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; }
-thead th	{ font-weight:bold; 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; }
-thead th:first-child { border-left:none !important; }
-.superwide table th, .superwide table td, .superwide table input, .superwide table select { font-size:10px; }
-.module table { border-collapse: collapse; }
+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; }
-#home-page table.module tr:hover { background:#EDF3FE; }
 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; }
-table#change-history { width:100%; }
-table#change-history tbody th { width:16em; }
 
-/*  TABLE SORTING  */
+/* 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; }
 
-/*  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 h2,  .module caption { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; background:#7CA0C7 url(../img/admin/default-bg.gif) left top repeat-x; color:white; font-weight:bold; }
-.module caption	{ border:1px solid #ccc; border-bottom:none; }
-.module h3 { margin-top:.6em; }
-#content-related .module h2	{ background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
-#content-main .verbose .actionlist { float:right; font-size:10px; width:17em; position:relative; top:-1.6em; margin:0 8px; }
-
-/* DASHBOARD */
-.dashboard .module table th { width:100%; }
-.dashboard .module table td { white-space:nowrap; }
-.dashboard .module table td a { display:block; padding-right:.6em; }
+/* 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; }
 
-/*  RECENT ACTIONS MODULE  */
-.module ul.actionlist { margin-left:0; }
-ul.actionlist li { list-style-type:none; }
-
-/*  FORM DEFAULTS  */
-input, textarea, select	{ margin:2px 0; padding:2px 3px; vertical-align:middle; border:1px solid #ccc; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
+/* 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=checkbox], input[type=radio] { border:none; }
+input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; }
 
 /*  FORM BUTTONS  */
 input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; }
 input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; }
 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; }
 input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; }
-.submit-row { padding:5px 7px; text-align:right; background:#ffc; border:1px solid #ccc; margin:5px 0; }
-.submit-row input { margin:0 0 0 5px; }
-.submit-row .float-left { padding-top:.1em; }
-
-/*  FORM ROWS  */
-.form-row { clear:both; padding:8px 12px; font-size:11px; }
-html>body .form-row { border-bottom:1px solid #eee; }
-.form-row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
-.form-row img, .form-row input { vertical-align:middle; }
-form .form-row p { padding-left:0; font-size:11px; }
-
-/*  FORM LABELS  */
-form h4	{ margin:0 !important; padding:0 !important; border:none !important; }
-label { font-weight:normal !important; color:#666; font-size:12px; }
-label.inline { margin-left:20px; }
-.required label, label.required	{ font-weight:bold !important; color:#333 !important; }
-
-/*  RADIO BUTTONS */
-form ul.radiolist li { list-style-type:none; }
-form ul.radiolist label { float:none; display:inline; }
-form ul.inline { margin-left:0; padding:0; }
-form ul.inline li { float:left; padding-right:7px; }
-
-/*  ALIGNED FIELDSETS  */
-.aligned label { display:block; padding:0 1em 3px 0; float:left; text-align:left; width:8em; }
-.aligned label.inline { display:inline; float:none; }
-.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
-form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
-form .aligned table p { margin-left:0; padding-left:0; }
-form .aligned p.help { padding-left:38px; }
-.aligned .vCheckboxLabel { float:none !important; display:inline; }
-.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; }
-.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
-
-/*  WIDE FIELDSETS  */
-.wide label { width:15em !important; }
-form .wide p { margin-left:15em; }
-form .wide p.help { padding-left:38px; }
-.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
 
-/*  COLLAPSED FIELDSETS  */
-fieldset.collapsed * { display:none; }
-fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
-fieldset.collapsed .collapse-toggle { display: inline !important; }
-fieldset.collapse h2 a.collapse-toggle { color:#ffc; }
-fieldset.collapse h2 a.collapse-toggle:hover { text-decoration:underline; }
-.hidden { display:none; }
-
-/* MONOSPACE TEXTAREAS */
-fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
+/* 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 { 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  */
+/* 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; }
@@ -183,17 +112,21 @@ td ul.errorlist li { margin:0 !important; }
 .error input, .error 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; }
 
-/*  ACTION ICONS  */
+/* 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 .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; margin-bottom:5px; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
-.form-row .object-tools { margin-top:0; margin-bottom:0; }
+/* 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; }
@@ -203,123 +136,6 @@ a.deletelink:hover { color:#993333; }
 .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; }
 
-/*  INLINE CONTROLS  */
-#inline-controls { font-weight:bold; font-size:12px; }
-#inline-specific-controls { margin-left:6px; padding:0 8px; border-left:6px solid #ccc;  }
-
-/*  BREADCRUMBS  */
-p.breadcrumbs { font-size:11px; color:#ccc;text-align:left; } /* old breadcrumbs style */
-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; }
-
-/*  SELECTOR (FILTER INTERFACE)  */
-.selector { width:580px; float:left; }
-.selector select { width:270px; height:170px; }
-.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; }
-.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; }
-.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
-.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; }
-.selector .selector-chosen .selector-filter { padding:4px 5px; }
-.selector .selector-available input { width:230px; }
-.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:13% 3px 0 3px; padding:0; }
-.selector-chooser li { margin:0; padding:3px; list-style-type:none; }
-.selector select { margin-bottom:5px; margin-top:0; }
-.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; }
-.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; }
-.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; }
-a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666;  padding:3px 0 3px 18px; }
-a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; }
-a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; }
-a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; }
-
-/*  Stacked selectors for long items  */
-.stacked { float:left; width:500px; }
-.stacked select { width:480px; height:100px; }
-.stacked .selector-available, .stacked .selector-chosen { width:480px; }
-.stacked .selector-available { margin-bottom:0; }
-.stacked .selector-available input { width:442px; }
-.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; }
-.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; }
-.stacked .selector-chooseall, .stacked .selector-clearall { display:none; }
-.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); }
-.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); }
-
-/*  DATE AND TIME  */
-p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
-.datetime span { font-size:11px; font-weight:normal; color:#ccc; white-space:nowrap; }
-.vDateField { margin-left:4px; }
-table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
-
-/*  FILE UPLOADS  */
-p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
-.file-upload a { font-weight:normal; }
-.file-upload .deletelink { margin-left:5px; }
-
-/*  CALENDARS & CLOCKS  */
-.calendarbox, .clockbox { margin:5px auto; font-size:11px; width: 16em; text-align: center; background:white; position:relative; }
-.clockbox { width:9em; }
-.calendar { margin:0; padding: 0; }
-.calendar table { margin: 0; padding: 0; border-collapse:collapse; background:white; width:99%; }
-.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
-.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
-.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
-.calendar td.selected a { background: #C9DBED; }
-.calendar td.nonday { background:#efefef; }
-.calendar td.today a { background:#ffc; }
-.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
-.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
-.calendar td a:active, .timelist a:active { background: #036; color:white; }
-.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; }
-.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
-.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; }
-.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
-.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
-.calendarnav-previous { top:0; left:0; }
-.calendarnav-next { top:0; right:0; }
-.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x;  border-top:1px solid #ddd; }
-.calendar-cancel a { padding:2px; color:#999; }
-ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
-.timelist a { padding:2px; }
-
-/*  ORDERING WIDGET  */
-ul#orderthese { position:absolute; top:8em; right:0; width:240px; padding:0; margin:0; list-style-type:none; }
-ul#orderthese li { list-style-type:none; display:block; padding:0; margin:6px 0; width:214px; background:#f6f6f6; white-space:nowrap; overflow:hidden; }
-ul#orderthese li span { display:block; border:1px solid #e7e7e7; background:transparent url(../img/admin/nav-bg-grabber.gif) top left repeat-y; font-size:10px !important; padding:4px 6px 4px 12px; }
-ul#orderthese span:hover { background-color:#efefef; }
-
-/*  PAGINATOR  */
-.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
-.paginator a:link, .paginator a:visited	{ padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; }
-.paginator a.showall { padding:0 !important; border:none !important; }
-.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
-.paginator .end	{ border-width:2px !important; margin-right:6px; }
-.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
-.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }
-
-/*  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; }
-
-/*  CUSTOM FORM FIELDS  */
-.vSelectMultipleField { vertical-align:top !important; }
-.vCheckboxField { border:none; }
-.vDateField, .vTimeField { margin-right:2px; }
-.vFileUploadField { border:none; }
-.vURLField { width:380px; }
-.vLargeTextField, .vXMLLargeTextField { width:480px; }
-.colM .vLargeTextField, .colM .vXMLLargeTextField { width:720px; }
-body.core-flatfile #id_content { height: 400px; }
-.module table .vPositiveSmallIntegerField { width: 22px; }
+/* OBJECT HISTORY */
+table#change-history { width:100%; }
+table#change-history tbody th { width:16em; }

+ 29 - 0
django/contrib/admin/media/css/layout.css

@@ -0,0 +1,29 @@
+/* PAGE STRUCTURE */
+#container { position:relative; width:100%; min-width:760px; }
+#content { margin:10px 15px; }
+#header { width:100%; }
+#content-main { float:left; width:100%; }
+#content-related { float:right; width:220px; position:relative; margin-right:-230px; }
+#footer	{ clear:both; padding:10px; }
+
+/*  COLUMN TYPES  */
+.colMS { margin-right:245px !important; }
+.colSM { margin-left:245px !important; }
+.colSM #content-related { float:left; margin-right:0; margin-left:-230px; }
+.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; }

+ 13 - 0
django/contrib/admin/media/css/login.css

@@ -0,0 +1,13 @@
+@import url('base.css');
+@import url('layout.css');
+
+/* LOGIN FORM */
+body.login { background:#eee; }
+.login #container { background:white; border:1px solid #ccc; width:28em; min-width:300px; margin-left:auto; margin-right:auto; margin-top:100px; }
+.login #content-main { width:100%; }
+.login form { margin-top:1em; }
+.login .form-row { padding:4px 0; float:left; width:100%; }
+.login .form-row label { float:left; width:7em; padding-right:0.5em; line-height:2em; text-align:right; font-size:1em; color:#333; }
+.login .form-row #id_username, .login .form-row #id_password { width:16em; }
+.login span.help { font-size:10px; display:block; }
+.login .submit-row { clear:both; padding:1em 0 0 7.4em; }

+ 2 - 3
django/contrib/admin/media/css/patch-iewin.css

@@ -1,7 +1,6 @@
 * html #container { position:static; } /* keep header from flowing off the page */
 * html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */
 * html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */
+* html .form-row { height:1%; }
 * html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */
-* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */
-* html #content { width /**/: 768px; } /* fixed width for IE5 */
-* html #content-main { width /**/: 535px; } /* fixed width for IE5 */
+* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */

+ 101 - 0
django/contrib/admin/media/css/widgets.css

@@ -0,0 +1,101 @@
+/* SELECTOR (FILTER INTERFACE) */
+.selector { width:580px; float:left; }
+.selector select { width:270px; height:17.2em; }
+.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; }
+.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; }
+.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
+.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; }
+.selector .selector-chosen .selector-filter { padding:4px 5px; }
+.selector .selector-available input { width:230px; }
+.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:8em 3px 0 3px; padding:0; }
+.selector-chooser li { margin:0; padding:3px; list-style-type:none; }
+.selector select { margin-bottom:5px; margin-top:0; }
+.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; }
+.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; }
+.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; }
+a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666;  padding:3px 0 3px 18px; }
+a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; }
+a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; }
+a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; }
+
+/* STACKED SELECTORS */
+.stacked { float:left; width:500px; }
+.stacked select { width:480px; height:10.1em; }
+.stacked .selector-available, .stacked .selector-chosen { width:480px; }
+.stacked .selector-available { margin-bottom:0; }
+.stacked .selector-available input { width:442px; }
+.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; }
+.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; }
+.stacked .selector-chooseall, .stacked .selector-clearall { display:none; }
+.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); }
+.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); }
+
+/* DATE AND TIME */
+p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.datetime span { font-size:11px; color:#ccc; font-weight:normal; white-space:nowrap; }
+.vDateField { margin-left:4px; }
+table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
+
+/* FILE UPLOADS */
+p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.file-upload a { font-weight:normal; }
+.file-upload .deletelink { margin-left:5px; }
+
+/* CALENDARS & CLOCKS */
+.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; }
+.clockbox { width:9em; }
+.calendar { margin:0; padding: 0; }
+.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; }
+.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
+.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
+.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
+.calendar td.selected a { background: #C9DBED; }
+.calendar td.nonday { background:#efefef; }
+.calendar td.today a { background:#ffc; }
+.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
+.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
+.calendar td a:active, .timelist a:active { background: #036; color:white; }
+.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; }
+.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
+.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; }
+.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
+.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
+.calendarnav-previous { top:0; left:0; }
+.calendarnav-next { top:0; right:0; }
+.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x;  border-top:1px solid #ddd; }
+.calendar-cancel a { padding:2px; color:#999; }
+ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
+.timelist a { padding:2px; }
+
+/* INLINE ORDERER */
+ul.orderer {  position:relative; padding:0 !important; margin:0 !important; list-style-type:none; }
+ul.orderer li { list-style-type:none; display:block; padding:0; margin:0; border:1px solid #bbb; border-width:0 1px 1px 0; white-space:nowrap; overflow:hidden; background:#e2e2e2 url(../img/admin/nav-bg-grabber.gif) repeat-y; }
+ul.orderer li:hover { cursor:move; background-color:#ddd; }
+ul.orderer li a.selector { margin-left:12px; overflow:hidden; width:83%; font-size:10px !important; padding:0.6em 0; }
+ul.orderer li a:link, ul.orderer li a:visited { color:#333; }
+ul.orderer li .inline-deletelink { position:absolute; right:4px; margin-top:0.6em; }
+ul.orderer li.selected { background-color:#f8f8f8; border-right-color:#f8f8f8; }
+ul.orderer li.deleted { background:#bbb url(../img/admin/deleted-overlay.gif); }
+ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited { color:#888; }
+ul.orderer li.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png);  }
+ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover { cursor:default; }
+
+/* EDIT INLINE */
+.inline-deletelink { display:block; text-indent:-9999px; background:transparent url(../img/admin/inline-delete.png) no-repeat; width:15px; height:15px; margin:0.4em 0; border: 0px none; }
+.inline-deletelink:hover { background-position:-15px 0; cursor:pointer; }
+.editinline button.addlink { border: 0px none; color: #5b80b2; font-size: 100%; cursor: pointer; }
+.editinline button.addlink:hover { color: #036; cursor: pointer; }
+.editinline table .help { text-align:right; float:right; padding-left:2em; }
+.editinline tfoot .addlink { white-space:nowrap; }
+.editinline table thead th:last-child { border-left:none; }
+.editinline tr.deleted { background:#ddd url(../img/admin/deleted-overlay.gif); }
+.editinline tr.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); }
+.editinline tr.deleted td:hover { cursor:default; }
+.editinline tr.deleted td:first-child { background-image:none !important; }
+
+/* EDIT INLINE - STACKED */
+.editinline-stacked { min-width:758px; }
+.editinline-stacked .inline-object { margin-left:210px; background:white; }
+.editinline-stacked .inline-source { float:left; width:200px; background:#f8f8f8;  }
+.editinline-stacked .inline-splitter { float:left; width:9px; background:#f8f8f8 url(../img/admin/inline-splitter-bg.gif) 50% 50% no-repeat; border-right:1px solid #ccc; }
+.editinline-stacked .controls { clear:both; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; padding:3px 4px; font-size:11px; border-top:1px solid #ddd; }

BIN
django/contrib/admin/media/img/admin/deleted-overlay.gif


BIN
django/contrib/admin/media/img/admin/inline-delete-8bit.png


BIN
django/contrib/admin/media/img/admin/inline-delete.png


BIN
django/contrib/admin/media/img/admin/inline-restore-8bit.png


BIN
django/contrib/admin/media/img/admin/inline-restore.png


BIN
django/contrib/admin/media/img/admin/inline-splitter-bg.gif


+ 1 - 1
django/contrib/admin/media/js/admin/RelatedObjectLookups.js

@@ -39,7 +39,7 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
         if (elem.nodeName == 'SELECT') {
             var o = new Option(newRepr, newId);
             elem.options[elem.options.length] = o;
-            elem.selectedIndex = elem.length - 1;
+            o.selected = true;
         } else if (elem.nodeName == 'INPUT') {
             elem.value = newId;
         }

+ 51 - 0
django/contrib/admin/models.py

@@ -0,0 +1,51 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.models import User
+from django.utils.translation import gettext_lazy as _
+
+ADDITION = 1
+CHANGE = 2
+DELETION = 3
+
+class LogEntryManager(models.Manager):
+    def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
+        e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
+        e.save()
+
+class LogEntry(models.Model):
+    action_time = models.DateTimeField(_('action time'), auto_now=True)
+    user = models.ForeignKey(User)
+    content_type = models.ForeignKey(ContentType, blank=True, null=True)
+    object_id = models.TextField(_('object id'), blank=True, null=True)
+    object_repr = models.CharField(_('object repr'), maxlength=200)
+    action_flag = models.PositiveSmallIntegerField(_('action flag'))
+    change_message = models.TextField(_('change message'), blank=True)
+    objects = LogEntryManager()
+    class Meta:
+        verbose_name = _('log entry')
+        verbose_name_plural = _('log entries')
+        db_table = 'django_admin_log'
+        ordering = ('-action_time',)
+
+    def __repr__(self):
+        return str(self.action_time)
+
+    def is_addition(self):
+        return self.action_flag == ADDITION
+
+    def is_change(self):
+        return self.action_flag == CHANGE
+
+    def is_deletion(self):
+        return self.action_flag == DELETION
+
+    def get_edited_object(self):
+        "Returns the edited object represented by this log entry"
+        return self.content_type.get_object_for_this_type(pk=self.object_id)
+
+    def get_admin_url(self):
+        """
+        Returns the admin URL to edit the object represented by this log entry.
+        This is relative to the Django admin index page.
+        """
+        return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)

+ 0 - 1
django/contrib/admin/models/__init__.py

@@ -1 +0,0 @@
-__all__ = ['admin']

+ 0 - 50
django/contrib/admin/models/admin.py

@@ -1,50 +0,0 @@
-from django.core import meta
-from django.models import auth, core
-from django.utils.translation import gettext_lazy as _
-
-class LogEntry(meta.Model):
-    action_time = meta.DateTimeField(_('action time'), auto_now=True)
-    user = meta.ForeignKey(auth.User)
-    content_type = meta.ForeignKey(core.ContentType, blank=True, null=True)
-    object_id = meta.TextField(_('object id'), blank=True, null=True)
-    object_repr = meta.CharField(_('object repr'), maxlength=200)
-    action_flag = meta.PositiveSmallIntegerField(_('action flag'))
-    change_message = meta.TextField(_('change message'), blank=True)
-    class META:
-        module_name = 'log'
-        verbose_name = _('log entry')
-        verbose_name_plural = _('log entries')
-        db_table = 'django_admin_log'
-        ordering = ('-action_time',)
-        module_constants = {
-            'ADDITION': 1,
-            'CHANGE': 2,
-            'DELETION': 3,
-        }
-
-    def __repr__(self):
-        return str(self.action_time)
-
-    def is_addition(self):
-        return self.action_flag == ADDITION
-
-    def is_change(self):
-        return self.action_flag == CHANGE
-
-    def is_deletion(self):
-        return self.action_flag == DELETION
-
-    def get_edited_object(self):
-        "Returns the edited object represented by this log entry"
-        return self.get_content_type().get_object_for_this_type(pk=self.object_id)
-
-    def get_admin_url(self):
-        """
-        Returns the admin URL to edit the object represented by this log entry.
-        This is relative to the Django admin index page.
-        """
-        return "%s/%s/%s/" % (self.get_content_type().get_package(), self.get_content_type().python_module_name, self.object_id)
-
-    def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
-        e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
-        e.save()

+ 1 - 1
django/contrib/admin/templates/admin/404.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
 {% block title %}{% trans 'Page not found' %}{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin/500.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> &rsaquo; {% trans "Server error" %}</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin/base_site.html

@@ -1,4 +1,4 @@
-{% extends "admin/base" %}
+{% extends "admin/base.html" %}
 {% load i18n %}
 
 {% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}

+ 20 - 27
django/contrib/admin/templates/admin/change_form.html

@@ -1,36 +1,39 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n admin_modify adminmedia %}
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../jsi18n/"></script>
-{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %}
+{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
 {% endblock %}
-{% block coltype %}{{ bound_manipulator.coltype }}{% endblock %}
-{% block bodyclass %}{{ app_label }}-{{ bound_manipulator.object_name.lower }} change-form{% endblock %}
+{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
+{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
+{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
 {% block userlinks %}<a href="../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
 {% block breadcrumbs %}{% if not is_popup %}
 <div class="breadcrumbs">
      <a href="../../../">{% trans "Home" %}</a> &rsaquo;
-     <a href="../">{{ bound_manipulator.verbose_name_plural|capfirst }}</a> &rsaquo;
-     {% if add %}{% trans "Add" %} {{ bound_manipulator.verbose_name }}{% else %}{{ bound_manipulator.original|striptags|truncatewords:"18" }}{% endif %}
+     <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
+     {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|striptags|truncatewords:"18" }}{% endif %}
 </div>
 {% endif %}{% endblock %}
 {% block content %}<div id="content-main">
 {% if change %}{% if not is_popup %}
   <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
-  {% if bound_manipulator.has_absolute_url %}<li><a href="/r/{{ bound_manipulator.content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+  {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
   </ul>
 {% endif %}{% endif %}
-<form {{ bound_manipulator.form_enc_attrib }} action="{{ form_url }}" method="post">{% block form_top %}{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post">{% block form_top %}{% endblock %}
+<div>
 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
-{% if bound_manipulator.save_on_top %}{% submit_row bound_manipulator %}{% endif %}
+{% if opts.admin.save_on_top %}{% submit_row %}{% endif %}
 {% if form.error_dict %}
     <p class="errornote">
     {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
     </p>
 {% endif %}
-{% for bound_field_set in bound_manipulator.bound_field_sets %}
+{% for bound_field_set in bound_field_sets %}
    <fieldset class="module aligned {{ bound_field_set.classes }}">
     {% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %}
+    {% if bound_field_set.description %}<div class="description">{{ bound_field_set.description }}</div>{% endif %}
     {% for bound_field_line in bound_field_set %}
         {% admin_field_line bound_field_line %}
         {% for bound_field in bound_field_line %}
@@ -41,7 +44,7 @@
 {% endfor %}
 {% block after_field_sets %}{% endblock %}
 {% if change %}
-   {% if bound_manipulator.ordered_objects %}
+   {% if ordered_objects %}
    <fieldset class="module"><h2>{% trans "Ordering" %}</h2>
    <div class="form-row{% if form.order_.errors %} error{% endif %} ">
    {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
@@ -49,27 +52,17 @@
    </div></fieldset>
    {% endif %}
 {% endif %}
-{% for related_object in bound_manipulator.inline_related_objects %}{% edit_inline related_object %}{% endfor %}
+{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
 {% block after_related_objects %}{% endblock %}
-{% submit_row bound_manipulator %}
+{% submit_row %}
 {% if add %}
-   <script type="text/javascript">document.getElementById("{{ bound_manipulator.first_form_field_id }}").focus();</script>
+   <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
 {% endif %}
-{% if bound_manipulator.auto_populated_fields %}
+{% if auto_populated_fields %}
    <script type="text/javascript">
-   {% auto_populated_field_script bound_manipulator.auto_populated_fields change %}
+   {% auto_populated_field_script auto_populated_fields change %}
    </script>
 {% endif %}
-{% if change %}
-   {% if bound_manipulator.ordered_objects %}
-      {% if form.order_objects %}<ul id="orderthese">
-          {% for object in form.order_objects %}
-             <li id="p{% object_pk bound_manipulator object %}">
-             <span id="handlep{% object_pk bound_manipulator object %}">{{ object|truncatewords:"5" }}</span>
-             </li>
-             {% endfor %}
-      </ul>{% endif %}
-   {% endif %}
-{% endif %}
+</div>
 </form></div>
 {% endblock %}

+ 3 - 2
django/contrib/admin/templates/admin/change_list.html

@@ -1,8 +1,9 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load adminmedia admin_list i18n %}
+{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
 {% block bodyclass %}change-list{% endblock %}
 {% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / <a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
-{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }} </div>{% endblock %}{% endif %}
+{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %}
 {% block coltype %}flex{% endblock %}
 {% block content %}
 <div id="content-main">

+ 11 - 1
django/contrib/admin/templates/admin/delete_confirmation.html

@@ -1,6 +1,14 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+     <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
+     <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
+     <a href="../">{{ object|striptags|truncatewords:"18" }}</a> &rsaquo;
+     {% trans 'Delete' %}
+</div>
+{% endblock %}
 {% block content %}
 {% if perms_lacking %}
     <p>{% blocktrans %}Deleting the {{ object_name }} '{{ object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
@@ -13,8 +21,10 @@
     <p>{% blocktrans %}Are you sure you want to delete the {{ object_name }} "{{ object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
     <ul>{{ deleted_objects|unordered_list }}</ul>
     <form action="" method="post">
+    <div>
     <input type="hidden" name="post" value="yes" />
     <input type="submit" value="{% trans "Yes, I'm sure" %}" />
+    </div>
     </form>
 {% endif %}
 {% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin/edit_inline_stacked.html

@@ -13,4 +13,4 @@
          {% endif %}
       {% endfor %}
     {% endfor %}
-</fieldset>
+</fieldset>

+ 0 - 8
django/contrib/admin/templates/admin/field_line.html

@@ -9,14 +9,6 @@
   {% if not bound_field.has_label_first %}
     {% field_label bound_field %}
   {% endif %}
-  {% if change %}
-    {% if bound_field.field.primary_key %}
-      {{ bound_field.original_value }}
-    {% endif %}
-    {% if bound_field.raw_id_admin %}
-      {% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
-    {% endif %}
-  {% endif %}
   {% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text }}</p>{% endif %}
 {% endfor %}
 </div>

+ 7 - 6
django/contrib/admin/templates/admin/index.html

@@ -1,6 +1,7 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %}
 {% block coltype %}colMS{% endblock %}
 {% block bodyclass %}dashboard{% endblock %}
 {% block breadcrumbs %}{% endblock %}
@@ -13,14 +14,14 @@
 {% if app_list %}
     {% for app in app_list %}
         <div class="module">
-        <h2>{{ app.name }}</h2>
-        <table>
+        <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
+        <caption>{{ app.name }}</caption>
         {% for model in app.models %}
             <tr>
             {% if model.perms.change %}
-                <th><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
+                <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
             {% else %}
-                <th>{{ model.name }}</th>
+                <th scope="row">{{ model.name }}</th>
             {% endif %}
 
             {% if model.perms.add %}
@@ -57,7 +58,7 @@
             {% else %}
             <ul class="actionlist">
             {% for entry in admin_log %}
-                <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.get_content_type.name|capfirst }}</span></li>
+                <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst }}</span></li>
             {% endfor %}
             </ul>
             {% endif %}

+ 16 - 15
django/contrib/admin/templates/admin/login.html

@@ -1,6 +1,9 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/login.css{% endblock %}
+{% block bodyclass %}login{% endblock %}
+{% block content_title %}{% endblock %}
 {% block breadcrumbs %}{% endblock %}
 
 {% block content %}
@@ -9,20 +12,18 @@
 <p class="errornote">{{ error_message }}</p>
 {% endif %}
 <div id="content-main">
-<form action="{{ app_path }}" method="post">
-
-<p class="aligned">
-<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
-</p>
-<p class="aligned">
-<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
-<input type="hidden" name="this_is_the_login_form" value="1" />
-<input type="hidden" name="post_data" value="{{ post_data }}" />{% comment %} <span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
-</p>
-
-<div class="aligned ">
-<label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
-</div>
+<form action="{{ app_path }}" method="post" id="login-form">
+	<div class="form-row">
+		<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
+	</div>
+	<div class="form-row">
+		<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
+		<input type="hidden" name="this_is_the_login_form" value="1" />
+		<input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
+	</div>
+	<div class="submit-row">
+		<label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
+	</div>
 </form>
 
 <script type="text/javascript">

+ 6 - 6
django/contrib/admin/templates/admin/object_history.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
 {% block breadcrumbs %}
@@ -15,16 +15,16 @@
     <table id="change-history">
         <thead>
         <tr>
-            <th>{% trans 'Date/time' %}</th>
-            <th>{% trans 'User' %}</th>
-            <th>{% trans 'Action' %}</th>
+            <th scope="col">{% trans 'Date/time' %}</th>
+            <th scope="col">{% trans 'User' %}</th>
+            <th scope="col">{% trans 'Action' %}</th>
         </tr>
         </thead>
         <tbody>
         {% for action in action_list %}
         <tr>
-            <th>{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
-            <td>{{ action.get_user.username }}{% if action.get_user.first_name %} ({{ action.get_user.first_name }} {{ action.get_user.last_name }}){% endif %}</td>
+            <th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
+            <td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}</td>
             <td>{{ action.change_message}}</td>
         </tr>
         {% endfor %}

+ 1 - 1
django/contrib/admin/templates/admin/search_form.html

@@ -3,7 +3,7 @@
 {% if cl.lookup_opts.admin.search_fields %}
 <div id="toolbar"><form id="changelist-search" action="" method="get">
 <div><!-- DIV needed for valid HTML -->
-<label><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
+<label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
 <input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
 <input type="submit" value="{% trans 'Go' %}" />
 {% if show_result_count %}

+ 1 - 1
django/contrib/admin/templates/admin/template_validator.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 
 {% block content %}
 

+ 1 - 1
django/contrib/admin/templates/admin_doc/bookmarklets.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 
 {% block breadcrumbs %}{% load i18n %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %}
 {% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin_doc/index.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
 {% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin_doc/missing_docutils.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
 {% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}

+ 2 - 2
django/contrib/admin/templates/admin_doc/model_detail.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
 {% block extrahead %}
@@ -41,6 +41,6 @@
 </table>
 </div>
 
-<p class="small"><a href="../">&lsaquo; Back to Models Documentation</p>
+<p class="small"><a href="../">&lsaquo; Back to Models Documentation</a></p>
 </div>
 {% endblock %}

+ 9 - 8
django/contrib/admin/templates/admin_doc/model_index.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block coltype %}colSM{% endblock %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Models</div>{% endblock %}
@@ -8,18 +8,19 @@
 
 {% block content %}
 
-<h1>Models Documentation</h1>
+<h1>Model documentation</h1>
+
+{% regroup models by app_label as grouped_models %}
 
 <div id="content-main">
-{% regroup models|dictsort:"module" by module as grouped_models %}
 {% for group in grouped_models %}
 <div class="module">
-<h2 id='{{ group.grouper }}'>{{ group.grouper }}</h2>
+<h2 id="{{ group.grouper }}">{{ group.grouper|capfirst }}</h2>
 
 <table class="xfull">
 {% for model in group.list %}
 <tr>
-<th><a href="{{ model.name }}/">{{ model.class }}</a></th>
+<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th>
 </tr>
 {% endfor %}
 </table>
@@ -32,11 +33,11 @@
 {% block sidebar %}
 <div id="content-related" class="sidebar">
 <div class="module">
-<h2>Model Groups Quick List</h2>
+<h2>Model groups</h2>
 <ul>
-{% regroup models|dictsort:"module" by module as grouped_models %}
+{% regroup models by app_label as grouped_models %}
 {% for group in grouped_models %}
-    <li><a href="#{{ group.grouper }}">{{ group.grouper }}</a></li>
+    <li><a href="#{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li>
 {% endfor %}
 </ul>
 </div>

+ 1 - 1
django/contrib/admin/templates/admin_doc/template_detail.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; Templates &rsaquo; {{ name }}</div>{% endblock %}
 {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin_doc/template_filter_index.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block coltype %}colSM{% endblock %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; filters</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin_doc/template_tag_index.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block coltype %}colSM{% endblock %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Tags</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin_doc/view_detail.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Views</a> &rsaquo; {{ name }}</div>{% endblock %}
 {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/admin_doc/view_index.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block coltype %}colSM{% endblock %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/registration/logged_out.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/registration/password_change_done.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/registration/password_change_form.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 {% block userlinks %}<a href="../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/registration/password_reset_done.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}

+ 1 - 1
django/contrib/admin/templates/registration/password_reset_form.html

@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
 {% load i18n %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}

+ 14 - 6
django/contrib/admin/templates/widget/foreign.html

@@ -1,12 +1,20 @@
 {% load admin_modify adminmedia %}
 {% output_all bound_field.form_fields %}
 {% if bound_field.raw_id_admin %}
-{% if bound_field.field.rel.limit_choices_to %}
-    <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}{{"&"|escape}}{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
-{% else %}
-    <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
-{% endif %}
+    {% if bound_field.field.rel.limit_choices_to %}
+        <a href="{{ bound_field.related_url }}?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}&amp;{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
+    {% else %}
+        <a href="{{ bound_field.related_url }}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
+    {% endif %}
 {% else %}
 {% if bound_field.needs_add_label %}
-    <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
+    <a href="{{ bound_field.related_url }}add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
 {% endif %}{% endif %}
+{% if change %}
+    {% if bound_field.field.primary_key %}
+        {{ bound_field.original_value }}
+    {% endif %}
+    {% if bound_field.raw_id_admin %}
+        {% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
+    {% endif %}
+{% endif %}

+ 1 - 1
django/contrib/admin/templates/widget/many_to_many.html

@@ -1 +1 @@
-{% include "widget/foreign" %}
+{% include "widget/foreign.html" %}

+ 2 - 1
django/contrib/admin/templates/widget/one_to_one.html

@@ -1 +1,2 @@
-{% include "widget/foreign" %}
+{% if add %}{% include "widget/foreign.html" %}{% endif %}
+{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}

+ 52 - 60
django/contrib/admin/templatetags/admin_list.py

@@ -1,14 +1,15 @@
-from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR
+from django import template
+from django.conf import settings
+from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, ALL_VAR
 from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
 from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS
-from django.core import meta, template
 from django.core.exceptions import ObjectDoesNotExist
+from django.db import models
 from django.utils import dateformat
 from django.utils.html import escape
 from django.utils.text import capfirst
 from django.utils.translation import get_date_formats
-from django.conf.settings import ADMIN_MEDIA_PREFIX
-from django.core.template import Library
+from django.template import Library
 
 register = Library()
 
@@ -16,11 +17,11 @@ DOT = '.'
 
 def paginator_number(cl,i):
     if i == DOT:
-       return '... '
+        return '... '
     elif i == cl.page_num:
-       return '<span class="this-page">%d</span> ' % (i+1)
+        return '<span class="this-page">%d</span> ' % (i+1)
     else:
-       return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
+        return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
 paginator_number = register.simple_tag(paginator_number)
 
 def pagination(cl):
@@ -64,7 +65,7 @@ def pagination(cl):
         'ALL_VAR': ALL_VAR,
         '1': 1,
     }
-pagination = register.inclusion_tag('admin/pagination')(pagination)
+pagination = register.inclusion_tag('admin/pagination.html')(pagination)
 
 def result_headers(cl):
     lookup_opts = cl.lookup_opts
@@ -72,22 +73,22 @@ def result_headers(cl):
     for i, field_name in enumerate(lookup_opts.admin.list_display):
         try:
             f = lookup_opts.get_field(field_name)
-        except meta.FieldDoesNotExist:
+        except models.FieldDoesNotExist:
             # For non-field list_display values, check for the function
             # attribute "short_description". If that doesn't exist, fall
-            # back to the method name. And __repr__ is a special-case.
-            if field_name == '__repr__':
+            # back to the method name. And __str__ is a special-case.
+            if field_name == '__str__':
                 header = lookup_opts.verbose_name
             else:
-                func = getattr(cl.mod.Klass, field_name) # Let AttributeErrors propagate.
+                attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
                 try:
-                    header = func.short_description
+                    header = attr.short_description
                 except AttributeError:
-                    header = func.__name__.replace('_', ' ')
+                    header = field_name.replace('_', ' ')
             # Non-field list_display values don't get ordering capability.
             yield {"text": header}
         else:
-            if isinstance(f.rel, meta.ManyToOneRel) and f.null:
+            if isinstance(f.rel, models.ManyToOneRel) and f.null:
                 yield {"text": f.verbose_name}
             else:
                 th_classes = []
@@ -108,34 +109,37 @@ def items_for_result(cl, result):
         row_class = ''
         try:
             f = cl.lookup_opts.get_field(field_name)
-        except meta.FieldDoesNotExist:
-            # For non-field list_display values, the value is a method
-            # name. Execute the method.
+        except models.FieldDoesNotExist:
+            # For non-field list_display values, the value is either a method
+            # or a property.
             try:
-                func = getattr(result, field_name)
-                result_repr = str(func())
+                attr = getattr(result, field_name)
+                allow_tags = getattr(attr, 'allow_tags', False)
+                if callable(attr):
+                    attr = attr()
+                result_repr = str(attr)
             except AttributeError, ObjectDoesNotExist:
                 result_repr = EMPTY_CHANGELIST_VALUE
             else:
                 # Strip HTML tags in the resulting text, except if the
                 # function has an "allow_tags" attribute set to True.
-                if not getattr(func, 'allow_tags', False):
+                if not allow_tags:
                     result_repr = escape(result_repr)
         else:
             field_val = getattr(result, f.attname)
 
-            if isinstance(f.rel, meta.ManyToOneRel):
+            if isinstance(f.rel, models.ManyToOneRel):
                 if field_val is not None:
-                    result_repr = getattr(result, 'get_%s' % f.name)()
+                    result_repr = getattr(result, f.name)
                 else:
                     result_repr = EMPTY_CHANGELIST_VALUE
             # Dates and times are special: They're formatted in a certain way.
-            elif isinstance(f, meta.DateField) or isinstance(f, meta.TimeField):
+            elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
                 if field_val:
                     (date_format, datetime_format, time_format) = get_date_formats()
-                    if isinstance(f, meta.DateTimeField):
+                    if isinstance(f, models.DateTimeField):
                         result_repr = capfirst(dateformat.format(field_val, datetime_format))
-                    elif isinstance(f, meta.TimeField):
+                    elif isinstance(f, models.TimeField):
                         result_repr = capfirst(dateformat.time_format(field_val, time_format))
                     else:
                         result_repr = capfirst(dateformat.format(field_val, date_format))
@@ -143,15 +147,15 @@ def items_for_result(cl, result):
                     result_repr = EMPTY_CHANGELIST_VALUE
                 row_class = ' class="nowrap"'
             # Booleans are special: We use images.
-            elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
+            elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
                 BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
-                result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
+                result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
             # ImageFields are special: Use a thumbnail.
-            elif isinstance(f, meta.ImageField):
+            elif isinstance(f, models.ImageField):
                 from django.parts.media.photos import get_thumbnail_url
                 result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
             # FloatFields are special: Zero-pad the decimals.
-            elif isinstance(f, meta.FloatField):
+            elif isinstance(f, models.FloatField):
                 if field_val is not None:
                     result_repr = ('%%.%sf' % f.decimal_places) % field_val
                 else:
@@ -163,7 +167,7 @@ def items_for_result(cl, result):
             else:
                 result_repr = escape(str(field_val))
         if result_repr == '':
-                result_repr = '&nbsp;'
+            result_repr = '&nbsp;'
         if first: # First column is a special case
             first = False
             url = cl.url_for_result(result)
@@ -181,28 +185,20 @@ def result_list(cl):
     return {'cl': cl,
             'result_headers': list(result_headers(cl)),
             'results': list(results(cl))}
-result_list = register.inclusion_tag("admin/change_list_results")(result_list)
+result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
 
 def date_hierarchy(cl):
-    lookup_opts, params, lookup_params, lookup_mod = \
-      cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod
-
-    if lookup_opts.admin.date_hierarchy:
-        field_name = lookup_opts.admin.date_hierarchy
-
+    if cl.lookup_opts.admin.date_hierarchy:
+        field_name = cl.lookup_opts.admin.date_hierarchy
         year_field = '%s__year' % field_name
         month_field = '%s__month' % field_name
         day_field = '%s__day' % field_name
         field_generic = '%s__' % field_name
-        year_lookup = params.get(year_field)
-        month_lookup = params.get(month_field)
-        day_lookup = params.get(day_field)
-
-        def link(d):
-            return cl.get_query_string(d, [field_generic])
+        year_lookup = cl.params.get(year_field)
+        month_lookup = cl.params.get(month_field)
+        day_lookup = cl.params.get(day_field)
 
-        def get_dates(unit, params):
-            return getattr(lookup_mod, 'get_%s_list' % field_name)(unit, **params)
+        link = lambda d: cl.get_query_string(d, [field_generic])
 
         if year_lookup and month_lookup and day_lookup:
             month_name = MONTHS[int(month_lookup)]
@@ -215,9 +211,7 @@ def date_hierarchy(cl):
                 'choices': [{'title': "%s %s" % (month_name, day_lookup)}]
             }
         elif year_lookup and month_lookup:
-            date_lookup_params = lookup_params.copy()
-            date_lookup_params.update({year_field: year_lookup, month_field: month_lookup})
-            days = get_dates('day', date_lookup_params)
+            days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
             return {
                 'show': True,
                 'back': {
@@ -230,9 +224,7 @@ def date_hierarchy(cl):
                 } for day in days]
             }
         elif year_lookup:
-            date_lookup_params = lookup_params.copy()
-            date_lookup_params.update({year_field: year_lookup})
-            months = get_dates('month', date_lookup_params)
+            months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month')
             return {
                 'show' : True,
                 'back': {
@@ -240,20 +232,20 @@ def date_hierarchy(cl):
                     'title': _('All dates')
                 },
                 'choices': [{
-                    'link': link( {year_field: year_lookup, month_field: month.month}),
-                    'title': "%s %s" % (month.strftime('%B') ,  month.year)
+                    'link': link({year_field: year_lookup, month_field: month.month}),
+                    'title': "%s %s" % (month.strftime('%B'), month.year)
                 } for month in months]
             }
         else:
-            years = get_dates('year', lookup_params)
+            years = cl.query_set.dates(field_name, 'year')
             return {
                 'show': True,
                 'choices': [{
                     'link': link({year_field: year.year}),
                     'title': year.year
-                } for year in years ]
+                } for year in years]
             }
-date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy)
+date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy)
 
 def search_form(cl):
     return {
@@ -261,12 +253,12 @@ def search_form(cl):
         'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
         'search_var': SEARCH_VAR
     }
-search_form = register.inclusion_tag('admin/search_form')(search_form)
+search_form = register.inclusion_tag('admin/search_form.html')(search_form)
 
 def filter(cl, spec):
     return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
-filter = register.inclusion_tag('admin/filter')(filter)
+filter = register.inclusion_tag('admin/filter.html')(filter)
 
 def filters(cl):
     return {'cl': cl}
-filters = register.inclusion_tag('admin/filters')(filters)
+filters = register.inclusion_tag('admin/filters.html')(filters)

+ 55 - 68
django/contrib/admin/templatetags/admin_modify.py

@@ -1,11 +1,13 @@
-from django.core import template, template_loader, meta
+from django import template
+from django.contrib.admin.views.main import AdminBoundField
+from django.template import loader
 from django.utils.html import escape
 from django.utils.text import capfirst
 from django.utils.functional import curry
-from django.contrib.admin.views.main import AdminBoundField
-from django.core.meta.fields import BoundField, Field
-from django.core.meta import BoundRelatedObject, TABULAR, STACKED
-from django.conf.settings import ADMIN_MEDIA_PREFIX
+from django.db import models
+from django.db.models.fields import Field
+from django.db.models.related import BoundRelatedObject
+from django.conf import settings
 import re
 
 register = template.Library()
@@ -16,30 +18,28 @@ def class_name_to_underscored(name):
     return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
 
 def include_admin_script(script_path):
-    return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path)
+    return '<script type="text/javascript" src="%s%s"></script>' % (settings.ADMIN_MEDIA_PREFIX, script_path)
 include_admin_script = register.simple_tag(include_admin_script)
 
-def submit_row(context, bound_manipulator):
+def submit_row(context):
+    opts = context['opts']
     change = context['change']
-    add = context['add']
-    show_delete = context['show_delete']
-    has_delete_permission = context['has_delete_permission']
     is_popup = context['is_popup']
     return {
-        'onclick_attrib': (bound_manipulator.ordered_objects and change
+        'onclick_attrib': (opts.get_ordered_objects() and change
                             and 'onclick="submitOrderForm();"' or ''),
-        'show_delete_link': (not is_popup and has_delete_permission
-                              and (change or show_delete)),
-        'show_save_as_new': not is_popup and change and bound_manipulator.save_as,
-        'show_save_and_add_another': not is_popup and (not bound_manipulator.save_as or add),
-        'show_save_and_continue': not is_popup,
+        'show_delete_link': (not is_popup and context['has_delete_permission']
+                              and (change or context['show_delete'])),
+        'show_save_as_new': not is_popup and change and opts.admin.save_as,
+        'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']),
+        'show_save_and_continue': not is_popup and context['has_change_permission'],
         'show_save': True
     }
-submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(submit_row)
+submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row)
 
 def field_label(bound_field):
     class_names = []
-    if isinstance(bound_field.field, meta.BooleanField):
+    if isinstance(bound_field.field, models.BooleanField):
         class_names.append("vCheckboxLabel")
         colon = ""
     else:
@@ -64,16 +64,15 @@ class FieldWidgetNode(template.Node):
         if not cls.nodelists.has_key(klass):
             try:
                 field_class_name = klass.__name__
-                template_name = "widget/%s" % \
-                    class_name_to_underscored(field_class_name)
-                nodelist = template_loader.get_template(template_name).nodelist
+                template_name = "widget/%s.html" % class_name_to_underscored(field_class_name)
+                nodelist = loader.get_template(template_name).nodelist
             except template.TemplateDoesNotExist:
                 super_klass = bool(klass.__bases__) and klass.__bases__[0] or None
                 if super_klass and super_klass != Field:
                     nodelist = cls.get_nodelist(super_klass)
                 else:
                     if not cls.default:
-                        cls.default = template_loader.get_template("widget/default").nodelist
+                        cls.default = loader.get_template("widget/default.html").nodelist
                     nodelist = cls.default
 
             cls.nodelists[klass] = nodelist
@@ -97,21 +96,22 @@ class FieldWrapper(object):
         self.field = field
 
     def needs_header(self):
-        return not isinstance(self.field, meta.AutoField)
+        return not isinstance(self.field, models.AutoField)
 
     def header_class_attribute(self):
         return self.field.blank and ' class="optional"' or ''
 
     def use_raw_id_admin(self):
-         return isinstance(self.field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) \
+        return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \
             and self.field.rel.raw_id_admin
 
 class FormFieldCollectionWrapper(object):
-    def __init__(self, field_mapping, fields):
+    def __init__(self, field_mapping, fields, index):
         self.field_mapping = field_mapping
         self.fields = fields
         self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original'])
                              for field in self.fields]
+        self.index = index
 
 class TabularBoundRelatedObject(BoundRelatedObject):
     def __init__(self, related_object, field_mapping, original):
@@ -120,29 +120,25 @@ class TabularBoundRelatedObject(BoundRelatedObject):
 
         fields = self.relation.editable_fields()
 
-        self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields)
-                                               for field_mapping in self.field_mappings]
+        self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i)
+                                               for (i,field_mapping) in self.field_mappings.items() ]
         self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list])
         self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
 
     def template_name(self):
-        return "admin/edit_inline_tabular"
+        return "admin/edit_inline_tabular.html"
 
 class StackedBoundRelatedObject(BoundRelatedObject):
     def __init__(self, related_object, field_mapping, original):
         super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original)
         fields = self.relation.editable_fields()
-        self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields)
-                                               for field_mapping in self.field_mappings]
+        self.field_mappings.fill()
+        self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i)
+                                               for (i,field_mapping) in self.field_mappings.items()]
         self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
 
     def template_name(self):
-        return "admin/edit_inline_stacked"
-
-bound_related_object_overrides = {
-    TABULAR: TabularBoundRelatedObject,
-    STACKED: StackedBoundRelatedObject,
-}
+        return "admin/edit_inline_stacked.html"
 
 class EditInlineNode(template.Node):
     def __init__(self, rel_var):
@@ -150,21 +146,16 @@ class EditInlineNode(template.Node):
 
     def render(self, context):
         relation = template.resolve_variable(self.rel_var, context)
-
         context.push()
-
-        klass = relation.field.rel.edit_inline
-        bound_related_object_class = bound_related_object_overrides.get(klass, klass)
-
+        if relation.field.rel.edit_inline == models.TABULAR:
+            bound_related_object_class = TabularBoundRelatedObject
+        else:
+            bound_related_object_class = StackedBoundRelatedObject
         original = context.get('original', None)
-
         bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
         context['bound_related_object'] = bound_related_object
-
-        t = template_loader.get_template(bound_related_object.template_name())
-
+        t = loader.get_template(bound_related_object.template_name())
         output = t.render(context)
-
         context.pop()
         return output
 
@@ -191,30 +182,30 @@ auto_populated_field_script = register.simple_tag(auto_populated_field_script)
 
 def filter_interface_script_maybe(bound_field):
     f = bound_field.field
-    if f.rel and isinstance(f.rel, meta.ManyToManyRel) and f.rel.filter_interface:
-       return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
+    if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
+        return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
               ' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
-              f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX)
+              f.name, f.verbose_name, f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
     else:
         return ''
 filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
 
-def do_one_arg_tag(node_factory, parser,token):
-    tokens = token.contents.split()
-    if len(tokens) != 2:
-        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
-    return node_factory(tokens[1])
-
-def register_one_arg_tag(node):
-    tag_name = class_name_to_underscored(node.__name__)
-    parse_func = curry(do_one_arg_tag, node)
-    register.tag(tag_name, parse_func)
+def field_widget(parser, token):
+    bits = token.contents.split()
+    if len(bits) != 2:
+        raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
+    return FieldWidgetNode(bits[1])
+field_widget = register.tag(field_widget)
 
-register_one_arg_tag(FieldWidgetNode)
-register_one_arg_tag(EditInlineNode)
+def edit_inline(parser, token):
+    bits = token.contents.split()
+    if len(bits) != 2:
+        raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
+    return EditInlineNode(bits[1])
+edit_inline = register.tag(edit_inline)
 
 def admin_field_line(context, argument_val):
-    if (isinstance(argument_val, BoundField)):
+    if isinstance(argument_val, AdminBoundField):
         bound_fields = [argument_val]
     else:
         bound_fields = [bf for bf in argument_val]
@@ -229,7 +220,7 @@ def admin_field_line(context, argument_val):
                 break
 
     # Assumes BooleanFields won't be stacked next to each other!
-    if isinstance(bound_fields[0].field, meta.BooleanField):
+    if isinstance(bound_fields[0].field, models.BooleanField):
         class_names.append('checkbox-row')
 
     return {
@@ -238,8 +229,4 @@ def admin_field_line(context, argument_val):
         'bound_fields': bound_fields,
         'class_names': " ".join(class_names),
     }
-admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
-
-def object_pk(bound_manip, ordered_obj):
-    return bound_manip.get_ordered_object_pk(ordered_obj)
-object_pk = register.simple_tag(object_pk)
+admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line)

+ 12 - 7
django/contrib/admin/templatetags/adminapplist.py

@@ -1,4 +1,5 @@
-from django.core import template
+from django import template
+from django.db.models import get_models
 
 register = template.Library()
 
@@ -7,20 +8,24 @@ class AdminApplistNode(template.Node):
         self.varname = varname
 
     def render(self, context):
-        from django.core import meta
+        from django.db import models
         from django.utils.text import capfirst
         app_list = []
         user = context['user']
 
-        for app in meta.get_installed_model_modules():
-            app_label = app.__name__[app.__name__.rindex('.')+1:]
+        for app in models.get_apps():
+            # Determine the app_label.
+            app_models = get_models(app)
+            if not app_models:
+                continue
+            app_label = app_models[0]._meta.app_label
+
             has_module_perms = user.has_module_perms(app_label)
 
             if has_module_perms:
                 model_list = []
-                for m in app._MODELS:
+                for m in app_models:
                     if m._meta.admin:
-                        module_name = m._meta.module_name
                         perms = {
                             'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())),
                             'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())),
@@ -32,7 +37,7 @@ class AdminApplistNode(template.Node):
                         if True in perms.values():
                             model_list.append({
                                 'name': capfirst(m._meta.verbose_name_plural),
-                                'admin_url': '%s/%s/' % (app_label, m._meta.module_name),
+                                'admin_url': '%s/%s/' % (app_label, m.__name__.lower()),
                                 'perms': perms,
                             })
 

+ 4 - 3
django/contrib/admin/templatetags/adminmedia.py

@@ -1,10 +1,11 @@
-from django.core.template import Library
+from django.template import Library
+
 register = Library()
 
 def admin_media_prefix():
     try:
-        from django.conf.settings import ADMIN_MEDIA_PREFIX
+        from django.conf import settings
     except ImportError:
         return ''
-    return ADMIN_MEDIA_PREFIX
+    return settings.ADMIN_MEDIA_PREFIX
 admin_media_prefix = register.simple_tag(admin_media_prefix)

+ 3 - 3
django/contrib/admin/templatetags/log.py

@@ -1,5 +1,5 @@
-from django.models.admin import log
-from django.core import template
+from django import template
+from django.contrib.admin.models import LogEntry
 
 register = template.Library()
 
@@ -13,7 +13,7 @@ class AdminLogNode(template.Node):
     def render(self, context):
         if self.user is not None and not self.user.isdigit():
             self.user = context[self.user].id
-        context[self.varname] = log.get_list(user__id__exact=self.user, limit=self.limit, select_related=True)
+        context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
         return ''
 
 class DoGetAdminLog:

+ 31 - 0
django/contrib/admin/urls.py

@@ -0,0 +1,31 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    ('^$', 'django.contrib.admin.views.main.index'),
+    ('^r/(\d+)/(\d+)/$', 'django.views.defaults.shortcut'),
+    ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}),
+    ('^logout/$', 'django.contrib.auth.views.logout'),
+    ('^password_change/$', 'django.contrib.auth.views.password_change'),
+    ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
+    ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
+
+    # Documentation
+    ('^doc/$', 'django.contrib.admin.views.doc.doc_index'),
+    ('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'),
+    ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'),
+    ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'),
+    ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'),
+    ('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'),
+    ('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
+    ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
+    ('^doc/models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
+#    ('^doc/templates/$', 'django.views.admin.doc.template_index'),
+    ('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'),
+
+    # Add/change/delete/history
+    ('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'),
+    ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
+    ('^([^/]+)/([^/]+)/(.+)/history/$', 'django.contrib.admin.views.main.history'),
+    ('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
+    ('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'),
+)

+ 0 - 58
django/contrib/admin/urls/admin.py

@@ -1,58 +0,0 @@
-from django.conf.urls.defaults import *
-from django.conf.settings import INSTALLED_APPS
-
-urlpatterns = (
-    ('^$', 'django.contrib.admin.views.main.index'),
-    ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}),
-    ('^logout/$', 'django.views.auth.login.logout'),
-    ('^password_change/$', 'django.views.registration.passwords.password_change'),
-    ('^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
-    ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
-
-    # Documentation
-    ('^doc/$', 'django.contrib.admin.views.doc.doc_index'),
-    ('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'),
-    ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'),
-    ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'),
-    ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'),
-    ('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'),
-    ('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
-    ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
-    ('^doc/models/(?P<model>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
-#    ('^doc/templates/$', 'django.views.admin.doc.template_index'),
-    ('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'),
-)
-
-if 'ellington.events' in INSTALLED_APPS:
-    urlpatterns += (
-        ("^events/usersubmittedevents/(?P<object_id>\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'),
-        ("^events/usersubmittedevents/(?P<object_id>\d+)/delete/$", 'ellington.events.views.admin.user_submitted_event_delete_stage'),
-    )
-
-if 'ellington.news' in INSTALLED_APPS:
-    urlpatterns += (
-        ("^stories/preview/$", 'ellington.news.views.admin.story_preview'),
-        ("^stories/js/inlinecontrols/$", 'ellington.news.views.admin.inlinecontrols_js'),
-        ("^stories/js/inlinecontrols/(?P<label>[-\w]+)/$", 'ellington.news.views.admin.inlinecontrols_js_specific'),
-    )
-
-if 'ellington.alerts' in INSTALLED_APPS:
-    urlpatterns += (
-        ("^alerts/send/$", 'ellington.alerts.views.admin.send_alert_form'),
-        ("^alerts/send/do/$", 'ellington.alerts.views.admin.send_alert_action'),
-    )
-
-if 'ellington.media' in INSTALLED_APPS:
-    urlpatterns += (
-        ('^media/photos/caption/(?P<photo_id>\d+)/$', 'ellington.media.views.admin.get_exif_caption'),
-    )
-
-urlpatterns += (
-    # Metasystem admin pages
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.contrib.admin.views.main.change_list'),
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.contrib.admin.views.main.history'),
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.contrib.admin.views.main.change_stage'),
-)
-urlpatterns = patterns('', *urlpatterns)

+ 3 - 3
django/contrib/admin/utils.py

@@ -82,18 +82,18 @@ ROLES = {
 
 def create_reference_role(rolename, urlbase):
     def _role(name, rawtext, text, lineno, inliner, options={}, content=[]):
-        node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text)), **options)
+        node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options)
         return [node], []
     docutils.parsers.rst.roles.register_canonical_role(rolename, _role)
 
 def default_reference_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
     context = inliner.document.settings.default_reference_context
-    node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text)), **options)
+    node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options)
     return [node], []
 
 if docutils_is_available:
     docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
     docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference'
 
-    for (name, urlbase) in ROLES.items():
+    for name, urlbase in ROLES.items():
         create_reference_role(name, urlbase)

+ 15 - 15
django/contrib/admin/views/decorators.py

@@ -1,7 +1,7 @@
-from django.core.extensions import DjangoContext, render_to_response
-from django.conf.settings import SECRET_KEY
-from django.models.auth import users
-from django.utils import httpwrappers
+from django import http, template
+from django.conf import settings
+from django.contrib.auth.models import User, SESSION_KEY
+from django.shortcuts import render_to_response
 from django.utils.translation import gettext_lazy
 import base64, datetime, md5
 import cPickle as pickle
@@ -19,22 +19,22 @@ def _display_login_form(request, error_message=''):
         post_data = _encode_post_data(request.POST)
     else:
         post_data = _encode_post_data({})
-    return render_to_response('admin/login', {
+    return render_to_response('admin/login.html', {
         'title': _('Log in'),
         'app_path': request.path,
         'post_data': post_data,
         'error_message': error_message
-    }, context_instance=DjangoContext(request))
+    }, context_instance=template.RequestContext(request))
 
 def _encode_post_data(post_data):
     pickled = pickle.dumps(post_data)
-    pickled_md5 = md5.new(pickled + SECRET_KEY).hexdigest()
+    pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
     return base64.encodestring(pickled + pickled_md5)
 
 def _decode_post_data(encoded_data):
     encoded_data = base64.decodestring(encoded_data)
     pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
-    if md5.new(pickled + SECRET_KEY).hexdigest() != tamper_check:
+    if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
         from django.core.exceptions import SuspiciousOperation
         raise SuspiciousOperation, "User may have tampered with session cookie."
     return pickle.loads(pickled)
@@ -53,7 +53,7 @@ def staff_member_required(view_func):
                 request.POST = _decode_post_data(request.POST['post_data'])
             return view_func(request, *args, **kwargs)
 
-        assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.middleware.sessions.SessionMiddleware'."
+        assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
 
         # If this isn't already the login page, display it.
         if not request.POST.has_key(LOGIN_FORM_KEY):
@@ -71,14 +71,14 @@ def staff_member_required(view_func):
         # Check the password.
         username = request.POST.get('username', '')
         try:
-            user = users.get_object(username__exact=username, is_staff__exact=True)
-        except users.UserDoesNotExist:
+            user = User.objects.get(username=username, is_staff=True)
+        except User.DoesNotExist:
             message = ERROR_MESSAGE
             if '@' in username:
                 # Mistakenly entered e-mail address instead of username? Look it up.
                 try:
-                    user = users.get_object(email__exact=username)
-                except users.UserDoesNotExist:
+                    user = User.objects.get(email=username)
+                except User.DoesNotExist:
                     message = _("Usernames cannot contain the '@' character.")
                 else:
                     message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username
@@ -87,7 +87,7 @@ def staff_member_required(view_func):
         # The user data is correct; log in the user in and continue.
         else:
             if user.check_password(request.POST.get('password', '')):
-                request.session[users.SESSION_KEY] = user.id
+                request.session[SESSION_KEY] = user.id
                 user.last_login = datetime.datetime.now()
                 user.save()
                 if request.POST.has_key('post_data'):
@@ -99,7 +99,7 @@ def staff_member_required(view_func):
                         return view_func(request, *args, **kwargs)
                     else:
                         request.session.delete_test_cookie()
-                        return httpwrappers.HttpResponseRedirect(request.path)
+                        return http.HttpResponseRedirect(request.path)
             else:
                 return _display_login_form(request, ERROR_MESSAGE)
 

+ 91 - 54
django/contrib/admin/views/doc.py

@@ -1,12 +1,14 @@
-from django.core import meta
-from django import templatetags
+from django import template, templatetags
+from django.template import RequestContext
 from django.conf import settings
 from django.contrib.admin.views.decorators import staff_member_required
-from django.models.core import sites
-from django.core.extensions import DjangoContext, render_to_response
-from django.core.exceptions import Http404, ViewDoesNotExist
-from django.core import template, urlresolvers
+from django.db import models
+from django.shortcuts import render_to_response
+from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+from django.http import Http404, get_host
+from django.core import urlresolvers
 from django.contrib.admin import utils
+from django.contrib.sites.models import Site
 import inspect, os, re
 
 # Exclude methods starting with these strings from documentation
@@ -15,15 +17,15 @@ MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
 def doc_index(request):
     if not utils.docutils_is_available:
         return missing_docutils_page(request)
-    return render_to_response('admin_doc/index', context_instance=DjangoContext(request))
+    return render_to_response('admin_doc/index.html', context_instance=RequestContext(request))
 doc_index = staff_member_required(doc_index)
 
 def bookmarklets(request):
     # Hack! This couples this view to the URL it lives at.
     admin_root = request.path[:-len('doc/bookmarklets/')]
-    return render_to_response('admin_doc/bookmarklets', {
-        'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', request.META['HTTP_HOST'], admin_root),
-    }, context_instance=DjangoContext(request))
+    return render_to_response('admin_doc/bookmarklets.html', {
+        'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', get_host(request), admin_root),
+    }, context_instance=RequestContext(request))
 bookmarklets = staff_member_required(bookmarklets)
 
 def template_tag_index(request):
@@ -54,7 +56,7 @@ def template_tag_index(request):
                 'library': tag_library,
             })
 
-    return render_to_response('admin_doc/template_tag_index', {'tags': tags}, context_instance=DjangoContext(request))
+    return render_to_response('admin_doc/template_tag_index.html', {'tags': tags}, context_instance=RequestContext(request))
 template_tag_index = staff_member_required(template_tag_index)
 
 def template_filter_index(request):
@@ -84,16 +86,20 @@ def template_filter_index(request):
                 'meta': metadata,
                 'library': tag_library,
             })
-    return render_to_response('admin_doc/template_filter_index', {'filters': filters}, context_instance=DjangoContext(request))
+    return render_to_response('admin_doc/template_filter_index.html', {'filters': filters}, context_instance=RequestContext(request))
 template_filter_index = staff_member_required(template_filter_index)
 
 def view_index(request):
     if not utils.docutils_is_available:
         return missing_docutils_page(request)
 
+    if settings.ADMIN_FOR:
+        settings_modules = [__import__(m, '', '', ['']) for m in settings.ADMIN_FOR]
+    else:
+        settings_modules = [settings]
+
     views = []
-    for site_settings_module in settings.ADMIN_FOR:
-        settings_mod = __import__(site_settings_module, '', '', [''])
+    for settings_mod in settings_modules:
         urlconf = __import__(settings_mod.ROOT_URLCONF, '', '', [''])
         view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
         for (func, regex) in view_functions:
@@ -101,10 +107,10 @@ def view_index(request):
                 'name': func.__name__,
                 'module': func.__module__,
                 'site_id': settings_mod.SITE_ID,
-                'site': sites.get_object(pk=settings_mod.SITE_ID),
+                'site': Site.objects.get(pk=settings_mod.SITE_ID),
                 'url': simplify_regex(regex),
             })
-    return render_to_response('admin_doc/view_index', {'views': views}, context_instance=DjangoContext(request))
+    return render_to_response('admin_doc/view_index.html', {'views': views}, context_instance=RequestContext(request))
 view_index = staff_member_required(view_index)
 
 def view_detail(request, view):
@@ -123,51 +129,63 @@ def view_detail(request, view):
         body = utils.parse_rst(body, 'view', 'view:' + view)
     for key in metadata:
         metadata[key] = utils.parse_rst(metadata[key], 'model', 'view:' + view)
-    return render_to_response('admin_doc/view_detail', {
+    return render_to_response('admin_doc/view_detail.html', {
         'name': view,
         'summary': title,
         'body': body,
         'meta': metadata,
-    }, context_instance=DjangoContext(request))
+    }, context_instance=RequestContext(request))
 view_detail = staff_member_required(view_detail)
 
 def model_index(request):
     if not utils.docutils_is_available:
         return missing_docutils_page(request)
 
-    models = []
-    for app in meta.get_installed_model_modules():
-        for model in app._MODELS:
-            opts = model._meta
-            models.append({
-                'name': '%s.%s' % (opts.app_label, opts.module_name),
-                'module': opts.app_label,
-                'class': opts.module_name,
-            })
-    return render_to_response('admin_doc/model_index', {'models': models}, context_instance=DjangoContext(request))
+    m_list = [m._meta for m in models.get_models()]
+    return render_to_response('admin_doc/model_index.html', {'models': m_list}, context_instance=RequestContext(request))
 model_index = staff_member_required(model_index)
 
-def model_detail(request, model):
+def model_detail(request, app_label, model_name):
     if not utils.docutils_is_available:
         return missing_docutils_page(request)
 
+    # Get the model class.
     try:
-        model = meta.get_app(model)
-    except ImportError:
-        raise Http404
-    opts = model.Klass._meta
+        app_mod = models.get_app(app_label)
+    except ImproperlyConfigured:
+        raise Http404, "App %r not found" % app_label
+    model = None
+    for m in models.get_models(app_mod):
+        if m._meta.object_name.lower() == model_name:
+            model = m
+            break
+    if model is None:
+        raise Http404, "Model %r not found in app %r" % (model_name, app_label)
 
-    # Gather fields/field descriptions
+    opts = model._meta
+
+    # Gather fields/field descriptions.
     fields = []
     for field in opts.fields:
+        # ForeignKey is a special case since the field will actually be a
+        # descriptor that returns the other object
+        if isinstance(field, models.ForeignKey):
+            data_type = related_object_name = field.rel.to.__name__
+            app_label = field.rel.to._meta.app_label
+            verbose = utils.parse_rst(("the related `%s.%s` object"  % (app_label, data_type)), 'model', 'model:' + data_type)
+        else:
+            data_type = get_readable_field_data_type(field)
+            verbose = field.verbose_name
         fields.append({
             'name': field.name,
-            'data_type': get_readable_field_data_type(field),
-            'verbose': field.verbose_name,
+            'data_type': data_type,
+            'verbose': verbose,
             'help': field.help_text,
         })
-    for func_name, func in model.Klass.__dict__.items():
-        if callable(func) and len(inspect.getargspec(func)[0]) == 0:
+
+    # Gather model methods.
+    for func_name, func in model.__dict__.items():
+        if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1):
             try:
                 for exclude in MODEL_METHODS_EXCLUDE:
                     if func_name.startswith(exclude):
@@ -182,12 +200,26 @@ def model_detail(request, model):
                 'data_type': get_return_data_type(func_name),
                 'verbose': verbose,
             })
-    return render_to_response('admin_doc/model_detail', {
-        'name': '%s.%s' % (opts.app_label, opts.module_name),
-        'summary': "Fields on %s objects" % opts.verbose_name,
+
+    # Gather related objects
+    for rel in opts.get_all_related_objects():
+        verbose = "related `%s.%s` objects" % (rel.opts.app_label, rel.opts.object_name)
+        accessor = rel.get_accessor_name()
+        fields.append({
+            'name' : "%s.all" % accessor,
+            'verbose' : utils.parse_rst("all " + verbose , 'model', 'model:' + opts.module_name),
+        })
+        fields.append({
+            'name' : "%s.count" % accessor,
+            'verbose' : utils.parse_rst("number of " + verbose , 'model', 'model:' + opts.module_name),
+        })
+
+    return render_to_response('admin_doc/model_detail.html', {
+        'name': '%s.%s' % (opts.app_label, opts.object_name),
+        'summary': "Fields on %s objects" % opts.object_name,
         'description': model.__doc__,
         'fields': fields,
-    }, context_instance=DjangoContext(request))
+    }, context_instance=RequestContext(request))
 model_detail = staff_member_required(model_detail)
 
 def template_detail(request, template):
@@ -201,13 +233,13 @@ def template_detail(request, template):
                 'exists': os.path.exists(template_file),
                 'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '',
                 'site_id': settings_mod.SITE_ID,
-                'site': sites.get_object(pk=settings_mod.SITE_ID),
+                'site': Site.objects.get(pk=settings_mod.SITE_ID),
                 'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
             })
-    return render_to_response('admin_doc/template_detail', {
+    return render_to_response('admin_doc/template_detail.html', {
         'name': template,
         'templates': templates,
-    }, context_instance=DjangoContext(request))
+    }, context_instance=RequestContext(request))
 template_detail = staff_member_required(template_detail)
 
 ####################
@@ -216,7 +248,7 @@ template_detail = staff_member_required(template_detail)
 
 def missing_docutils_page(request):
     """Display an error message for people without docutils"""
-    return render_to_response('admin_doc/missing_docutils')
+    return render_to_response('admin_doc/missing_docutils.html')
 
 def load_all_installed_template_libraries():
     # Load/register all template tag libraries from installed apps.
@@ -271,9 +303,6 @@ DATA_TYPE_MAPPING = {
 }
 
 def get_readable_field_data_type(field):
-    # ForeignKey is a special case. Use the field type of the relation.
-    if field.get_internal_type() == 'ForeignKey':
-        field = field.rel.get_related_field()
     return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
 
 def extract_views_from_urlpatterns(urlpatterns, base=''):
@@ -295,15 +324,23 @@ def extract_views_from_urlpatterns(urlpatterns, base=''):
             raise TypeError, "%s does not appear to be a urlpattern object" % p
     return views
 
-# Clean up urlpattern regexes into something somewhat readable by Mere Humans:
-# turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
-# into "<sport_slug>/athletes/<athlete_slug>/"
-
 named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)')
+non_named_group_matcher = re.compile(r'\(.*?\)')
 
 def simplify_regex(pattern):
+    """
+    Clean up urlpattern regexes into something somewhat readable by Mere Humans:
+    turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
+    into "<sport_slug>/athletes/<athlete_slug>/"
+    """
+    # handle named groups first
     pattern = named_group_matcher.sub(lambda m: m.group(1), pattern)
-    pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/')
+
+    # handle non-named groups
+    pattern = non_named_group_matcher.sub("<var>", pattern)
+
+    # clean up any outstanding regex-y characters.
+    pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '')
     if not pattern.startswith('/'):
         pattern = '/' + pattern
     return pattern

+ 462 - 391
django/contrib/admin/views/main.py

@@ -1,32 +1,34 @@
-# Generic admin views.
-from django.contrib.admin.views.decorators import staff_member_required
+from django import forms, template
+from django.conf import settings
 from django.contrib.admin.filterspecs import FilterSpec
-from django.core import formfields, meta, template
-from django.core.template import loader
-from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet
-from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
-from django.core.extensions import DjangoContext as Context
-from django.core.extensions import get_object_or_404, render_to_response
+from django.contrib.admin.views.decorators import staff_member_required
+from django.views.decorators.cache import never_cache
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
 from django.core.paginator import ObjectPaginator, InvalidPage
-from django.conf.settings import ADMIN_MEDIA_PREFIX
-try:
-    from django.models.admin import log
-except ImportError:
-    raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS."
-from django.utils.html import escape
-from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
-from django.utils.text import capfirst, get_text_list
+from django.shortcuts import get_object_or_404, render_to_response
+from django.db import models
+from django.db.models.query import handle_legacy_orderlist, QuerySet
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.template import loader
 from django.utils import dateformat
 from django.utils.dates import MONTHS
 from django.utils.html import escape
+from django.utils.text import capfirst, get_text_list
 import operator
 
-# The system will display a "Show all" link only if the total result count
-# is less than or equal to this setting.
-MAX_SHOW_ALL_ALLOWED = 200
+from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
+if not LogEntry._meta.installed:
+    raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application."
 
-DEFAULT_RESULTS_PER_PAGE = 100
+if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
+    raise ImproperlyConfigured, "You'll need to put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting before you can use the admin application."
+
+# The system will display a "Show all" link on the change list only if the
+# total result count is less than or equal to this setting.
+MAX_SHOW_ALL_ALLOWED = 200
 
+# Changelist settings
 ALL_VAR = 'all'
 ORDER_VAR = 'o'
 ORDER_TYPE_VAR = 'ot'
@@ -34,229 +36,59 @@ PAGE_VAR = 'p'
 SEARCH_VAR = 'q'
 IS_POPUP_VAR = 'pop'
 
-# Text to display within changelist table cells if the value is blank.
+# Text to display within change-list table cells if the value is blank.
 EMPTY_CHANGELIST_VALUE = '(None)'
 
-def _get_mod_opts(app_label, module_name):
-    "Helper function that returns a tuple of (module, opts), raising Http404 if necessary."
-    try:
-        mod = meta.get_module(app_label, module_name)
-    except ImportError:
-        raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS.
-    opts = mod.Klass._meta
-    if not opts.admin:
-        raise Http404 # This object is valid but has no admin interface.
-    return mod, opts
-
-def index(request):
-    return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request))
-index = staff_member_required(index)
+use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin
 
 class IncorrectLookupParameters(Exception):
     pass
 
-class ChangeList(object):
-    def __init__(self, request, app_label, module_name):
-        self.get_modules_and_options(app_label, module_name, request)
-        self.get_search_parameters(request)
-        self.get_ordering()
-        self.query = request.GET.get(SEARCH_VAR, '')
-        self.get_lookup_params()
-        self.get_results(request)
-        self.title = (self.is_popup
-                      and _('Select %s') % self.opts.verbose_name
-                      or _('Select %s to change') % self.opts.verbose_name)
-        self.get_filters(request)
-        self.pk_attname = self.lookup_opts.pk.attname
-
-    def get_filters(self, request):
-        self.filter_specs = []
-        if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
-            filter_fields = [self.lookup_opts.get_field(field_name) \
-                              for field_name in self.lookup_opts.admin.list_filter]
-            for f in filter_fields:
-                spec = FilterSpec.create(f, request, self.params)
-                if spec and spec.has_output():
-                    self.filter_specs.append(spec)
-        self.has_filters = bool(self.filter_specs)
-
-    def get_query_string(self, new_params={}, remove=[]):
-        p = self.params.copy()
-        for r in remove:
-            for k in p.keys():
-                if k.startswith(r):
-                    del p[k]
-        for k, v in new_params.items():
-            if p.has_key(k) and v is None:
-                del p[k]
-            elif v is not None:
-                p[k] = v
-        return '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
-
-    def get_modules_and_options(self, app_label, module_name, request):
-        self.mod, self.opts = _get_mod_opts(app_label, module_name)
-        if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()):
-            raise PermissionDenied
-
-        self.lookup_mod, self.lookup_opts = self.mod, self.opts
-
-    def get_search_parameters(self, request):
-        # Get search parameters from the query string.
-        try:
-            self.page_num = int(request.GET.get(PAGE_VAR, 0))
-        except ValueError:
-            self.page_num = 0
-        self.show_all = request.GET.has_key(ALL_VAR)
-        self.is_popup = request.GET.has_key(IS_POPUP_VAR)
-        self.params = dict(request.GET.items())
-        if self.params.has_key(PAGE_VAR):
-            del self.params[PAGE_VAR]
-
-    def get_results(self, request):
-        lookup_mod, lookup_params, show_all, page_num = \
-            self.lookup_mod, self.lookup_params, self.show_all, self.page_num
-        # Get the results.
-        try:
-            paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE)
-        # Naked except! Because we don't have any other way of validating "params".
-        # They might be invalid if the keyword arguments are incorrect, or if the
-        # values are not in the correct type (which would result in a database
-        # error).
-        except:
-            raise IncorrectLookupParameters()
-
-        # Get the total number of objects, with no filters applied.
-        real_lookup_params = lookup_params.copy()
-        del real_lookup_params['order_by']
-        if real_lookup_params:
-            full_result_count = lookup_mod.get_count()
-        else:
-            full_result_count = paginator.hits
-        del real_lookup_params
-        result_count = paginator.hits
-        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
-        multi_page = result_count > DEFAULT_RESULTS_PER_PAGE
-
-        # Get the list of objects to display on this page.
-        if (show_all and can_show_all) or not multi_page:
-            result_list = lookup_mod.get_list(**lookup_params)
-        else:
-            try:
-                result_list = paginator.get_page(page_num)
-            except InvalidPage:
-                result_list = []
-        (self.result_count, self.full_result_count, self.result_list,
-            self.can_show_all, self.multi_page, self.paginator) = (result_count,
-                  full_result_count, result_list, can_show_all, multi_page, paginator )
-
-    def url_for_result(self, result):
-        return "%s/" % getattr(result, self.pk_attname)
-
-    def get_ordering(self):
-        lookup_opts, params = self.lookup_opts, self.params
-        # For ordering, first check the "ordering" parameter in the admin options,
-        # then check the object's default ordering. If neither of those exist,
-        # order descending by ID by default. Finally, look for manually-specified
-        # ordering from the query string.
-        ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
-
-        # Normalize it to new-style ordering.
-        ordering = meta.handle_legacy_orderlist(ordering)
-
-        if ordering[0].startswith('-'):
-            order_field, order_type = ordering[0][1:], 'desc'
-        else:
-            order_field, order_type = ordering[0], 'asc'
-        if params.has_key(ORDER_VAR):
+def quote(s):
+    """
+    Ensure that primary key values do not confuse the admin URLs by escaping
+    any '/', '_' and ':' characters. Similar to urllib.quote, except that the
+    quoting is slightly different so that it doesn't get autoamtically
+    unquoted by the web browser.
+    """
+    if type(s) != type(''):
+        return s
+    res = list(s)
+    for i in range(len(res)):
+        c = res[i]
+        if c in ':/_':
+            res[i] = '_%02X' % ord(c)
+    return ''.join(res)
+
+def unquote(s):
+    """
+    Undo the effects of quote(). Based heavily on urllib.unquote().
+    """
+    mychr = chr
+    myatoi = int
+    list = s.split('_')
+    res = [list[0]]
+    myappend = res.append
+    del list[0]
+    for item in list:
+        if item[1:2]:
             try:
-                try:
-                    f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
-                except meta.FieldDoesNotExist:
-                    pass
-                else:
-                    if not isinstance(f.rel, meta.ManyToOneRel) or not f.null:
-                        order_field = f.name
-            except (IndexError, ValueError):
-                pass # Invalid ordering specified. Just use the default.
-        if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
-            order_type = params[ORDER_TYPE_VAR]
-        self.order_field, self.order_type = order_field, order_type
-
-    def get_lookup_params(self):
-        # Prepare the lookup parameters for the API lookup.
-        (params, order_field, lookup_opts, order_type, opts, query) = \
-           (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query)
-
-        lookup_params = params.copy()
-        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
-            if lookup_params.has_key(i):
-                del lookup_params[i]
-        # If the order-by field is a field with a relationship, order by the value
-        # in the related table.
-        lookup_order_field = order_field
-        try:
-            f = lookup_opts.get_field(order_field)
-        except meta.FieldDoesNotExist:
-            pass
-        else:
-            if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOneRel):
-                f = lookup_opts.get_field(order_field)
-                rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
-                lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
-        # Use select_related if one of the list_display options is a field with a
-        # relationship.
-        if lookup_opts.admin.list_select_related:
-            lookup_params['select_related'] = True
+                myappend(mychr(myatoi(item[:2], 16))
+                     + item[2:])
+            except ValueError:
+                myappend('_' + item)
         else:
-            for field_name in lookup_opts.admin.list_display:
-                try:
-                    f = lookup_opts.get_field(field_name)
-                except meta.FieldDoesNotExist:
-                    pass
-                else:
-                    if isinstance(f.rel, meta.ManyToOneRel):
-                        lookup_params['select_related'] = True
-                        break
-        lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,)
-        if lookup_opts.admin.search_fields and query:
-            complex_queries = []
-            for bit in query.split():
-                or_queries = []
-                for field_name in lookup_opts.admin.search_fields:
-                    or_queries.append(meta.Q(**{'%s__icontains' % field_name: bit}))
-                complex_queries.append(reduce(operator.or_, or_queries))
-            lookup_params['complex'] = reduce(operator.and_, complex_queries)
-        if opts.one_to_one_field:
-            lookup_params.update(opts.one_to_one_field.rel.limit_choices_to)
-        self.lookup_params = lookup_params
-
-def change_list(request, app_label, module_name):
-    try:
-        cl = ChangeList(request, app_label, module_name)
-    except IncorrectLookupParameters:
-        return HttpResponseRedirect(request.path)
-
-    c = Context(request, {
-        'title': cl.title,
-        'is_popup': cl.is_popup,
-        'cl' : cl
-    })
-    c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
-    return render_to_response(['admin/%s/%s/change_list' % (app_label, cl.opts.object_name.lower()),
-                               'admin/%s/change_list' % app_label,
-                               'admin/change_list'], context_instance=c)
-change_list = staff_member_required(change_list)
+            myappend('_' + item)
+    return "".join(res)
 
-use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) and field.rel.raw_id_admin
-
-def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets):
+def get_javascript_imports(opts, auto_populated_fields, field_sets):
 # Put in any necessary JavaScript imports.
     js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
     if auto_populated_fields:
         js.append('js/urlify.js')
-    if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
+    if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField):
         js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
-    if ordered_objects:
+    if opts.get_ordered_objects():
         js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
     if opts.admin.js:
         js.extend(opts.admin.js)
@@ -264,29 +96,30 @@ def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_se
     for field_set in field_sets:
         if not seen_collapse and 'collapse' in field_set.classes:
             seen_collapse = True
-            js.append('js/admin/CollapsedFieldsets.js' )
+            js.append('js/admin/CollapsedFieldsets.js')
 
         for field_line in field_set:
             try:
                 for f in field_line:
-                    if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
+                    if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface:
                         js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
                         raise StopIteration
             except StopIteration:
                 break
     return js
 
-class AdminBoundField(BoundField):
+class AdminBoundField(object):
     def __init__(self, field, field_mapping, original):
-        super(AdminBoundField, self).__init__(field, field_mapping, original)
-
+        self.field = field
+        self.original = original
+        self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
         self.element_id = self.form_fields[0].get_id()
-        self.has_label_first = not isinstance(self.field, meta.BooleanField)
+        self.has_label_first = not isinstance(self.field, models.BooleanField)
         self.raw_id_admin = use_raw_id_admin(field)
-        self.is_date_time = isinstance(field, meta.DateTimeField)
-        self.is_file_field = isinstance(field, meta.FileField)
-        self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOneRel) or isinstance(field.rel, meta.ManyToManyRel) and field.rel.to.admin
-        self.hidden = isinstance(self.field, meta.AutoField)
+        self.is_date_time = isinstance(field, models.DateTimeField)
+        self.is_file_field = isinstance(field, models.FileField)
+        self.needs_add_label = field.rel and isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel) and field.rel.to._meta.admin
+        self.hidden = isinstance(self.field, models.AutoField)
         self.first = False
 
         classes = []
@@ -298,26 +131,22 @@ class AdminBoundField(BoundField):
             self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
         self._repr_filled = False
 
-    def _fetch_existing_display(self, func_name):
-        class_dict = self.original.__class__.__dict__
-        func = class_dict.get(func_name)
-        return func(self.original)
-
-    def _fill_existing_display(self):
-        if getattr(self, '_display_filled', False):
-            return
-        # HACK
-        if isinstance(self.field.rel, meta.ManyToOneRel):
-             func_name = 'get_%s' % self.field.name
-             self._display = self._fetch_existing_display(func_name)
-        elif isinstance(self.field.rel, meta.ManyToManyRel):
-            func_name = 'get_%s_list' % self.field.rel.singular
-            self._display =  ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)])
-        self._display_filled = True
+        if field.rel:
+            self.related_url = '../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
+
+    def original_value(self):
+        if self.original:
+            return self.original.__dict__[self.field.column]
 
     def existing_display(self):
-        self._fill_existing_display()
-        return self._display
+        try:
+            return self._display
+        except AttributeError:
+            if isinstance(self.field.rel, models.ManyToOneRel):
+                self._display = getattr(self.original, 'get_%s' % self.field.name)()
+            elif isinstance(self.field.rel, models.ManyToManyRel):
+                self._display = ", ".join([str(obj) for obj in getattr(self.original, 'get_%s_list' % self.field.rel.singular)()])
+            return self._display
 
     def __repr__(self):
         return repr(self.__dict__)
@@ -325,94 +154,114 @@ class AdminBoundField(BoundField):
     def html_error_list(self):
         return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])
 
-class AdminBoundFieldLine(BoundFieldLine):
+    def original_url(self):
+        if self.is_file_field and self.original and self.field.attname:
+            url_method = getattr(self.original, 'get_%s_url' % self.field.attname)
+            if callable(url_method):
+                return url_method()
+        return ''
+
+class AdminBoundFieldLine(object):
     def __init__(self, field_line, field_mapping, original):
-        super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField)
+        self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line]
         for bound_field in self:
             bound_field.first = True
             break
 
-class AdminBoundFieldSet(BoundFieldSet):
+    def __iter__(self):
+        for bound_field in self.bound_fields:
+            yield bound_field
+
+    def __len__(self):
+        return len(self.bound_fields)
+
+class AdminBoundFieldSet(object):
     def __init__(self, field_set, field_mapping, original):
-        super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
-
-class BoundManipulator(object):
-    def __init__(self, opts, manipulator, field_mapping):
-        self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
-        self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None
-        self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet)
-                                 for field_set in opts.admin.get_field_sets(opts)]
-        self.ordered_objects = opts.get_ordered_objects()[:]
-
-class AdminBoundManipulator(BoundManipulator):
-    def __init__(self, opts, manipulator, field_mapping):
-        super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping)
-        field_sets = opts.admin.get_field_sets(opts)
-
-        self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
-        self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets);
-
-        self.coltype = self.ordered_objects and 'colMS' or 'colM'
-        self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
-        self.form_enc_attrib = opts.has_field_type(meta.FileField) and \
-                                'enctype="multipart/form-data" ' or ''
-
-        self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
-        self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects]
-
-        self.save_on_top = opts.admin.save_on_top
-        self.save_as = opts.admin.save_as
-
-        self.content_type_id = opts.get_content_type_id()
-        self.verbose_name_plural = opts.verbose_name_plural
-        self.verbose_name = opts.verbose_name
-        self.object_name = opts.object_name
-
-    def get_ordered_object_pk(self, ordered_obj):
-        for name in self.ordered_object_pk_names:
-            if hasattr(ordered_obj, name):
-                return str(getattr(ordered_obj, name))
-        return ""
-
-def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''):
+        self.name = field_set.name
+        self.classes = field_set.classes
+        self.description = field_set.description
+        self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set]
+
+    def __iter__(self):
+        for bound_field_line in self.bound_field_lines:
+            yield bound_field_line
+
+    def __len__(self):
+        return len(self.bound_field_lines)
+
+def render_change_form(model, manipulator, context, add=False, change=False, form_url=''):
+    opts = model._meta
+    app_label = opts.app_label
+    auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
+    field_sets = opts.admin.get_field_sets(opts)
+    original = getattr(manipulator, 'original_object', None)
+    bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets]
+    first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
+    ordered_objects = opts.get_ordered_objects()
+    inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
     extra_context = {
         'add': add,
         'change': change,
-        'bound_manipulator': AdminBoundManipulator(opts, manipulator, context['form']),
         'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()],
+        'has_change_permission': context['perms'][app_label][opts.get_change_permission()],
+        'has_file_field': opts.has_field_type(models.FileField),
+        'has_absolute_url': hasattr(model, 'get_absolute_url'),
+        'auto_populated_fields': auto_populated_fields,
+        'bound_field_sets': bound_field_sets,
+        'first_form_field_id': first_form_field_id,
+        'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets),
+        'ordered_objects': ordered_objects,
+        'inline_related_objects': inline_related_objects,
         'form_url': form_url,
-        'app_label': app_label,
+        'opts': opts,
+        'content_type_id': ContentType.objects.get_for_model(model).id,
     }
     context.update(extra_context)
-    return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ),
-                               "admin/%s/change_form" % app_label ,
-                               "admin/change_form"], context_instance=context)
+    return render_to_response([
+        "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
+        "admin/%s/change_form.html" % app_label,
+        "admin/change_form.html"], context_instance=context)
 
-def log_add_message(user, opts,manipulator,new_object):
-    pk_value = getattr(new_object, opts.pk.attname)
-    log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION)
+def index(request):
+    return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request))
+index = staff_member_required(never_cache(index))
+
+def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
+    model = models.get_model(app_label, model_name)
+    if model is None:
+        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+    opts = model._meta
 
-def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
-    mod, opts = _get_mod_opts(app_label, module_name)
     if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
         raise PermissionDenied
-    manipulator = mod.AddManipulator()
+
+    if post_url is None:
+        if request.user.has_perm(app_label + '.' + opts.get_change_permission()):
+            # redirect to list view
+            post_url = '../'
+        else:
+            # Object list will give 'Permission Denied', so go back to admin home
+            post_url = '../../../'
+
+    manipulator = model.AddManipulator()
     if request.POST:
         new_data = request.POST.copy()
-        if opts.has_field_type(meta.FileField):
+
+        if opts.has_field_type(models.FileField):
             new_data.update(request.FILES)
+
         errors = manipulator.get_validation_errors(new_data)
         manipulator.do_html2python(new_data)
 
-        if not errors and not request.POST.has_key("_preview"):
+        if not errors:
             new_object = manipulator.save(new_data)
-            log_add_message(request.user, opts,manipulator,new_object)
-            msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object}
-            pk_value = getattr(new_object,opts.pk.attname)
+            pk_value = new_object._get_pk_val()
+            LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
+            msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
             # Here, we distinguish between different save types by checking for
             # the presence of keys in request.POST.
             if request.POST.has_key("_continue"):
-                request.user.add_message(msg + ' ' + _("You may edit it again below."))
+                request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
                 if request.POST.has_key("_popup"):
                     post_url_continue += "?_popup=1"
                 return HttpResponseRedirect(post_url_continue % pk_value)
@@ -420,10 +269,10 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
                 return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
                     (pk_value, str(new_object).replace('"', '\\"')))
             elif request.POST.has_key("_addanother"):
-                request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+                request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
                 return HttpResponseRedirect(request.path)
             else:
-                request.user.add_message(msg)
+                request.user.message_set.create(message=msg)
                 return HttpResponseRedirect(post_url)
     else:
         # Add default data.
@@ -435,73 +284,80 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
         errors = {}
 
     # Populate the FormWrapper.
-    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
+    form = forms.FormWrapper(manipulator, new_data, errors)
 
-    c = Context(request, {
+    c = template.RequestContext(request, {
         'title': _('Add %s') % opts.verbose_name,
         'form': form,
         'is_popup': request.REQUEST.has_key('_popup'),
         'show_delete': show_delete,
     })
+
     if object_id_override is not None:
         c['object_id'] = object_id_override
 
-    return render_change_form(opts, manipulator, app_label, c, add=True)
-add_stage = staff_member_required(add_stage)
-
-def log_change_message(user, opts,manipulator,new_object):
-    pk_value = getattr(new_object, opts.pk.column)
-    # Construct the change message.
-    change_message = []
-    if manipulator.fields_added:
-        change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
-    if manipulator.fields_changed:
-        change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
-    if manipulator.fields_deleted:
-        change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
-    change_message = ' '.join(change_message)
-    if not change_message:
-        change_message = _('No fields changed.')
-    log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message)
-
-def change_stage(request, app_label, module_name, object_id):
-    mod, opts = _get_mod_opts(app_label, module_name)
+    return render_change_form(model, manipulator, c, add=True)
+add_stage = staff_member_required(never_cache(add_stage))
+
+def change_stage(request, app_label, model_name, object_id):
+    model = models.get_model(app_label, model_name)
+    object_id = unquote(object_id)
+    if model is None:
+        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+    opts = model._meta
+
     if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
         raise PermissionDenied
+
     if request.POST and request.POST.has_key("_saveasnew"):
-        return add_stage(request, app_label, module_name, form_url='../add/')
+        return add_stage(request, app_label, model_name, form_url='../../add/')
+
     try:
-        manipulator = mod.ChangeManipulator(object_id)
+        manipulator = model.ChangeManipulator(object_id)
     except ObjectDoesNotExist:
         raise Http404
 
     if request.POST:
         new_data = request.POST.copy()
-        if opts.has_field_type(meta.FileField):
+
+        if opts.has_field_type(models.FileField):
             new_data.update(request.FILES)
 
         errors = manipulator.get_validation_errors(new_data)
-
         manipulator.do_html2python(new_data)
-        if not errors and not request.POST.has_key("_preview"):
+
+        if not errors:
             new_object = manipulator.save(new_data)
-            log_change_message(request.user,opts,manipulator,new_object)
-            msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object}
-            pk_value = getattr(new_object,opts.pk.attname)
+            pk_value = new_object._get_pk_val()
+
+            # Construct the change message.
+            change_message = []
+            if manipulator.fields_added:
+                change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
+            if manipulator.fields_changed:
+                change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
+            if manipulator.fields_deleted:
+                change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
+            change_message = ' '.join(change_message)
+            if not change_message:
+                change_message = _('No fields changed.')
+            LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message)
+
+            msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
             if request.POST.has_key("_continue"):
-                request.user.add_message(msg + ' ' + _("You may edit it again below."))
+                request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
                 if request.REQUEST.has_key('_popup'):
                     return HttpResponseRedirect(request.path + "?_popup=1")
                 else:
                     return HttpResponseRedirect(request.path)
             elif request.POST.has_key("_saveasnew"):
-                request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
+                request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
                 return HttpResponseRedirect("../%s/" % pk_value)
             elif request.POST.has_key("_addanother"):
-                request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+                request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
                 return HttpResponseRedirect("../add/")
             else:
-                request.user.add_message(msg)
+                request.user.message_set.create(message=msg)
                 return HttpResponseRedirect("../")
     else:
         # Populate new_data with a "flattened" version of the current data.
@@ -519,7 +375,7 @@ def change_stage(request, app_label, module_name, object_id):
         errors = {}
 
     # Populate the FormWrapper.
-    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
+    form = forms.FormWrapper(manipulator, new_data, errors)
     form.original = manipulator.original_object
     form.order_objects = []
 
@@ -528,20 +384,19 @@ def change_stage(request, app_label, module_name, object_id):
         wrt = related.opts.order_with_respect_to
         if wrt and wrt.rel and wrt.rel.to == opts:
             func = getattr(manipulator.original_object, 'get_%s_list' %
-                    related.get_method_name_part())
+                    related.get_accessor_name())
             orig_list = func()
             form.order_objects.extend(orig_list)
 
-    c = Context(request, {
+    c = template.RequestContext(request, {
         'title': _('Change %s') % opts.verbose_name,
         'form': form,
         'object_id': object_id,
         'original': manipulator.original_object,
-        'is_popup' : request.REQUEST.has_key('_popup')
+        'is_popup': request.REQUEST.has_key('_popup'),
     })
-
-    return render_change_form(opts,manipulator, app_label, c, change=True)
-change_stage = staff_member_required(change_stage)
+    return render_change_form(model, manipulator, c, change=True)
+change_stage = staff_member_required(never_cache(change_stage))
 
 def _nest_help(obj, depth, val):
     current = obj
@@ -559,10 +414,10 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
         if related.opts in opts_seen:
             continue
         opts_seen.append(related.opts)
-        rel_opts_name = related.get_method_name_part()
-        if isinstance(related.field.rel, meta.OneToOneRel):
+        rel_opts_name = related.get_accessor_name()
+        if isinstance(related.field.rel, models.OneToOneRel):
             try:
-                sub_obj = getattr(obj, 'get_%s' % rel_opts_name)()
+                sub_obj = getattr(obj, rel_opts_name)
             except ObjectDoesNotExist:
                 pass
             else:
@@ -579,12 +434,12 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
                 else:
                     # Display a link to the admin page.
                     nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
-                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name,
-                        getattr(sub_obj, related.opts.pk.attname), sub_obj), []])
+                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(),
+                        sub_obj._get_pk_val(), sub_obj), []])
                 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
         else:
             has_related_objs = False
-            for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
+            for sub_obj in getattr(obj, rel_opts_name).all():
                 has_related_objs = True
                 if related.field.rel.edit_inline or not related.opts.admin:
                     # Don't display link to edit, because it either has no
@@ -593,7 +448,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
                 else:
                     # Display a link to the admin page.
                     nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
-                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj))), []])
+                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(str(sub_obj))), []])
                 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
             # If there were related objects, and the user doesn't have
             # permission to delete them, add the missing perm to perms_needed.
@@ -605,21 +460,21 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
         if related.opts in opts_seen:
             continue
         opts_seen.append(related.opts)
-        rel_opts_name = related.get_method_name_part()
+        rel_opts_name = related.get_accessor_name()
         has_related_objs = False
-        for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
+        for sub_obj in getattr(obj, rel_opts_name).all():
             has_related_objs = True
             if related.field.rel.edit_inline or not related.opts.admin:
                 # Don't display link to edit, because it either has no
                 # admin or is edited inline.
                 nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
-                    {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
+                    {'fieldname': related.field.verbose_name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
             else:
                 # Display a link to the admin page.
                 nh(deleted_objects, current_depth, [
-                    (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \
+                    (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.verbose_name, 'name':related.opts.verbose_name}) + \
                     (' <a href="../../../../%s/%s/%s/">%s</a>' % \
-                        (related.opts.app_label, related.opts.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj)))), []])
+                        (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(str(sub_obj)))), []])
         # If there were related objects, and the user doesn't have
         # permission to change them, add the missing perm to perms_needed.
         if related.opts.admin and has_related_objs:
@@ -627,12 +482,16 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
             if not user.has_perm(p):
                 perms_needed.add(related.opts.verbose_name)
 
-def delete_stage(request, app_label, module_name, object_id):
+def delete_stage(request, app_label, model_name, object_id):
     import sets
-    mod, opts = _get_mod_opts(app_label, module_name)
+    model = models.get_model(app_label, model_name)
+    object_id = unquote(object_id)
+    if model is None:
+        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+    opts = model._meta
     if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
         raise PermissionDenied
-    obj = get_object_or_404(mod, pk=object_id)
+    obj = get_object_or_404(model, pk=object_id)
 
     # Populate deleted_objects, a data structure of all related objects that
     # will also be deleted.
@@ -645,28 +504,240 @@ def delete_stage(request, app_label, module_name, object_id):
             raise PermissionDenied
         obj_display = str(obj)
         obj.delete()
-        log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, log.DELETION)
-        request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display})
+        LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION)
+        request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': opts.verbose_name, 'obj': obj_display})
         return HttpResponseRedirect("../../")
-    return render_to_response('admin/delete_confirmation', {
+    extra_context = {
         "title": _("Are you sure?"),
         "object_name": opts.verbose_name,
         "object": obj,
         "deleted_objects": deleted_objects,
         "perms_lacking": perms_needed,
-    }, context_instance=Context(request))
-delete_stage = staff_member_required(delete_stage)
-
-def history(request, app_label, module_name, object_id):
-    mod, opts = _get_mod_opts(app_label, module_name)
-    action_list = log.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(),
-        order_by=("action_time",), select_related=True)
+        "opts": model._meta,
+    }
+    return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ),
+                               "admin/%s/delete_confirmation.html" % app_label ,
+                               "admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request))
+delete_stage = staff_member_required(never_cache(delete_stage))
+
+def history(request, app_label, model_name, object_id):
+    model = models.get_model(app_label, model_name)
+    object_id = unquote(object_id)
+    if model is None:
+        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+    action_list = LogEntry.objects.filter(object_id=object_id,
+        content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
     # If no history was found, see whether this object even exists.
-    obj = get_object_or_404(mod, pk=object_id)
-    return render_to_response('admin/object_history', {
+    obj = get_object_or_404(model, pk=object_id)
+    extra_context = {
         'title': _('Change history: %s') % obj,
         'action_list': action_list,
-        'module_name': capfirst(opts.verbose_name_plural),
+        'module_name': capfirst(model._meta.verbose_name_plural),
         'object': obj,
-    }, context_instance=Context(request))
-history = staff_member_required(history)
+    }
+    return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()),
+                               "admin/%s/object_history.html" % app_label ,
+                               "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request))
+history = staff_member_required(never_cache(history))
+
+class ChangeList(object):
+    def __init__(self, request, model):
+        self.model = model
+        self.opts = model._meta
+        self.lookup_opts = self.opts
+        self.manager = self.opts.admin.manager
+
+        # Get search parameters from the query string.
+        try:
+            self.page_num = int(request.GET.get(PAGE_VAR, 0))
+        except ValueError:
+            self.page_num = 0
+        self.show_all = request.GET.has_key(ALL_VAR)
+        self.is_popup = request.GET.has_key(IS_POPUP_VAR)
+        self.params = dict(request.GET.items())
+        if self.params.has_key(PAGE_VAR):
+            del self.params[PAGE_VAR]
+
+        self.order_field, self.order_type = self.get_ordering()
+        self.query = request.GET.get(SEARCH_VAR, '')
+        self.query_set = self.get_query_set()
+        self.get_results(request)
+        self.title = (self.is_popup and _('Select %s') % self.opts.verbose_name or _('Select %s to change') % self.opts.verbose_name)
+        self.filter_specs, self.has_filters = self.get_filters(request)
+        self.pk_attname = self.lookup_opts.pk.attname
+
+    def get_filters(self, request):
+        filter_specs = []
+        if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
+            filter_fields = [self.lookup_opts.get_field(field_name) \
+                              for field_name in self.lookup_opts.admin.list_filter]
+            for f in filter_fields:
+                spec = FilterSpec.create(f, request, self.params)
+                if spec and spec.has_output():
+                    filter_specs.append(spec)
+        return filter_specs, bool(filter_specs)
+
+    def get_query_string(self, new_params={}, remove=[]):
+        p = self.params.copy()
+        for r in remove:
+            for k in p.keys():
+                if k.startswith(r):
+                    del p[k]
+        for k, v in new_params.items():
+            if p.has_key(k) and v is None:
+                del p[k]
+            elif v is not None:
+                p[k] = v
+        return '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
+
+    def get_results(self, request):
+        paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page)
+
+        # Get the number of objects, with admin filters applied.
+        try:
+            result_count = paginator.hits
+        # Naked except! Because we don't have any other way of validating
+        # "params". They might be invalid if the keyword arguments are
+        # incorrect, or if the values are not in the correct type (which would
+        # result in a database error).
+        except:
+            raise IncorrectLookupParameters
+
+        # Get the total number of objects, with no admin filters applied.
+        # Perform a slight optimization: Check to see whether any filters were
+        # given. If not, use paginator.hits to calculate the number of objects,
+        # because we've already done paginator.hits and the value is cached.
+        if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs:
+            full_result_count = result_count
+        else:
+            full_result_count = self.manager.count()
+
+        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
+        multi_page = result_count > self.lookup_opts.admin.list_per_page
+
+        # Get the list of objects to display on this page.
+        if (self.show_all and can_show_all) or not multi_page:
+            result_list = list(self.query_set)
+        else:
+            try:
+                result_list = paginator.get_page(self.page_num)
+            except InvalidPage:
+                result_list = ()
+
+        self.result_count = result_count
+        self.full_result_count = full_result_count
+        self.result_list = result_list
+        self.can_show_all = can_show_all
+        self.multi_page = multi_page
+        self.paginator = paginator
+
+    def get_ordering(self):
+        lookup_opts, params = self.lookup_opts, self.params
+        # For ordering, first check the "ordering" parameter in the admin options,
+        # then check the object's default ordering. If neither of those exist,
+        # order descending by ID by default. Finally, look for manually-specified
+        # ordering from the query string.
+        ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
+
+        # Normalize it to new-style ordering.
+        ordering = handle_legacy_orderlist(ordering)
+
+        if ordering[0].startswith('-'):
+            order_field, order_type = ordering[0][1:], 'desc'
+        else:
+            order_field, order_type = ordering[0], 'asc'
+        if params.has_key(ORDER_VAR):
+            try:
+                try:
+                    f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
+                except models.FieldDoesNotExist:
+                    pass
+                else:
+                    if not isinstance(f.rel, models.ManyToOneRel) or not f.null:
+                        order_field = f.name
+            except (IndexError, ValueError):
+                pass # Invalid ordering specified. Just use the default.
+        if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+            order_type = params[ORDER_TYPE_VAR]
+        return order_field, order_type
+
+    def get_query_set(self):
+        qs = self.manager.get_query_set()
+        lookup_params = self.params.copy() # a dictionary of the query string
+        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
+            if lookup_params.has_key(i):
+                del lookup_params[i]
+
+        # Apply lookup parameters from the query string.
+        qs = qs.filter(**lookup_params)
+
+        # Use select_related() if one of the list_display options is a field
+        # with a relationship.
+        if self.lookup_opts.admin.list_select_related:
+            qs = qs.select_related()
+        else:
+            for field_name in self.lookup_opts.admin.list_display:
+                try:
+                    f = self.lookup_opts.get_field(field_name)
+                except models.FieldDoesNotExist:
+                    pass
+                else:
+                    if isinstance(f.rel, models.ManyToOneRel):
+                        qs = qs.select_related()
+                        break
+
+        # Calculate lookup_order_field.
+        # If the order-by field is a field with a relationship, order by the
+        # value in the related table.
+        lookup_order_field = self.order_field
+        try:
+            f = self.lookup_opts.get_field(self.order_field, many_to_many=False)
+        except models.FieldDoesNotExist:
+            pass
+        else:
+            if isinstance(f.rel, models.OneToOneRel):
+                # For OneToOneFields, don't try to order by the related object's ordering criteria.
+                pass
+            elif isinstance(f.rel, models.ManyToOneRel):
+                rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column
+                lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering)
+
+        # Set ordering.
+        qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field)
+
+        # Apply keyword searches.
+        if self.lookup_opts.admin.search_fields and self.query:
+            for bit in self.query.split():
+                or_queries = [models.Q(**{'%s__icontains' % field_name: bit}) for field_name in self.lookup_opts.admin.search_fields]
+                other_qs = QuerySet(self.model)
+                other_qs = other_qs.filter(reduce(operator.or_, or_queries))
+                qs = qs & other_qs
+
+        if self.opts.one_to_one_field:
+            qs = qs.filter(**self.opts.one_to_one_field.rel.limit_choices_to)
+
+        return qs
+
+    def url_for_result(self, result):
+        return "%s/" % quote(getattr(result, self.pk_attname))
+
+def change_list(request, app_label, model_name):
+    model = models.get_model(app_label, model_name)
+    if model is None:
+        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+    if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
+        raise PermissionDenied
+    try:
+        cl = ChangeList(request, model)
+    except IncorrectLookupParameters:
+        return HttpResponseRedirect(request.path)
+    c = template.RequestContext(request, {
+        'title': cl.title,
+        'is_popup': cl.is_popup,
+        'cl': cl,
+    })
+    c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
+    return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()),
+                               'admin/%s/change_list.html' % app_label,
+                               'admin/change_list.html'], context_instance=c)
+change_list = staff_member_required(never_cache(change_list))

+ 12 - 12
django/contrib/admin/views/template.py

@@ -1,9 +1,9 @@
 from django.contrib.admin.views.decorators import staff_member_required
-from django.core import formfields, validators
-from django.core import template
-from django.core.template import loader
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.core import sites
+from django.core import validators
+from django import template, forms
+from django.template import loader
+from django.shortcuts import render_to_response
+from django.contrib.sites.models import Site
 from django.conf import settings
 
 def template_validator(request):
@@ -23,19 +23,19 @@ def template_validator(request):
         errors = manipulator.get_validation_errors(new_data)
         if not errors:
             request.user.add_message('The template is valid.')
-    return render_to_response('admin/template_validator', {
+    return render_to_response('admin/template_validator.html', {
         'title': 'Template validator',
-        'form': formfields.FormWrapper(manipulator, new_data, errors),
-    }, context_instance=DjangoContext(request))
+        'form': forms.FormWrapper(manipulator, new_data, errors),
+    }, context_instance=template.RequestContext(request))
 template_validator = staff_member_required(template_validator)
 
-class TemplateValidator(formfields.Manipulator):
+class TemplateValidator(forms.Manipulator):
     def __init__(self, settings_modules):
         self.settings_modules = settings_modules
-        site_list = sites.get_in_bulk(settings_modules.keys()).values()
+        site_list = Site.objects.get_in_bulk(settings_modules.keys()).values()
         self.fields = (
-            formfields.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
-            formfields.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
+            forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
+            forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
         )
 
     def isValidTemplate(self, field_data, all_data):

+ 2 - 0
django/contrib/auth/__init__.py

@@ -0,0 +1,2 @@
+LOGIN_URL = '/accounts/login/'
+REDIRECT_FIELD_NAME = 'next'

+ 84 - 0
django/contrib/auth/create_superuser.py

@@ -0,0 +1,84 @@
+"""
+Helper function for creating superusers in the authentication system.
+"""
+
+from django.core import validators
+from django.contrib.auth.models import User
+import getpass
+import os
+import sys
+
+def createsuperuser(username=None, email=None, password=None):
+    """
+    Helper function for creating a superuser from the command line. All
+    arguments are optional and will be prompted-for if invalid or not given.
+    """
+    try:
+        import pwd
+    except ImportError:
+        default_username = ''
+    else:
+        # Determine the current system user's username, to use as a default.
+        default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower()
+
+    # Determine whether the default username is taken, so we don't display
+    # it as an option.
+    if default_username:
+        try:
+            User.objects.get(username=default_username)
+        except User.DoesNotExist:
+            pass
+        else:
+            default_username = ''
+
+    try:
+        while 1:
+            if not username:
+                input_msg = 'Username'
+                if default_username:
+                    input_msg += ' (Leave blank to use %r)' % default_username
+                username = raw_input(input_msg + ': ')
+            if default_username and username == '':
+                username = default_username
+            if not username.isalnum():
+                sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
+                username = None
+            try:
+                User.objects.get(username=username)
+            except User.DoesNotExist:
+                break
+            else:
+                sys.stderr.write("Error: That username is already taken.\n")
+                username = None
+        while 1:
+            if not email:
+                email = raw_input('E-mail address: ')
+            try:
+                validators.isValidEmail(email, None)
+            except validators.ValidationError:
+                sys.stderr.write("Error: That e-mail address is invalid.\n")
+                email = None
+            else:
+                break
+        while 1:
+            if not password:
+                password = getpass.getpass()
+                password2 = getpass.getpass('Password (again): ')
+                if password != password2:
+                    sys.stderr.write("Error: Your passwords didn't match.\n")
+                    password = None
+                    continue
+            if password.strip() == '':
+                sys.stderr.write("Error: Blank passwords aren't allowed.\n")
+                password = None
+                continue
+            break
+    except KeyboardInterrupt:
+        sys.stderr.write("\nOperation cancelled.\n")
+        sys.exit(1)
+    u = User.objects.create_user(username, email, password)
+    u.is_staff = True
+    u.is_active = True
+    u.is_superuser = True
+    u.save()
+    print "Superuser created successfully."

+ 5 - 3
django/views/decorators/auth.py → django/contrib/auth/decorators.py

@@ -1,6 +1,7 @@
-from django.views.auth import login
+from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
+from django.http import HttpResponseRedirect
 
-def user_passes_test(test_func, login_url=login.LOGIN_URL):
+def user_passes_test(test_func, login_url=LOGIN_URL):
     """
     Decorator for views that checks that the user passes the given test,
     redirecting to the log-in page if necessary. The test should be a callable
@@ -10,7 +11,8 @@ def user_passes_test(test_func, login_url=login.LOGIN_URL):
         def _checklogin(request, *args, **kwargs):
             if test_func(request.user):
                 return view_func(request, *args, **kwargs)
-            return login.redirect_to_login(request.path, login_url)
+            return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.path))
+
         return _checklogin
     return _dec
 

+ 108 - 0
django/contrib/auth/forms.py

@@ -0,0 +1,108 @@
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.template import Context, loader
+from django.core import validators
+from django import forms
+
+class AuthenticationForm(forms.Manipulator):
+    """
+    Base class for authenticating users. Extend this to get a form that accepts
+    username/password logins.
+    """
+    def __init__(self, request=None):
+        """
+        If request is passed in, the manipulator will validate that cookies are
+        enabled. Note that the request (a HttpRequest object) must have set a
+        cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
+        running this validator.
+        """
+        self.request = request
+        self.fields = [
+            forms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
+                validator_list=[self.isValidUser, self.hasCookiesEnabled]),
+            forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
+                validator_list=[self.isValidPasswordForUser]),
+        ]
+        self.user_cache = None
+
+    def hasCookiesEnabled(self, field_data, all_data):
+        if self.request and not self.request.session.test_cookie_worked():
+            raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
+
+    def isValidUser(self, field_data, all_data):
+        try:
+            self.user_cache = User.objects.get(username=field_data)
+        except User.DoesNotExist:
+            raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
+
+    def isValidPasswordForUser(self, field_data, all_data):
+        if self.user_cache is not None and not self.user_cache.check_password(field_data):
+            self.user_cache = None
+            raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
+
+    def get_user_id(self):
+        if self.user_cache:
+            return self.user_cache.id
+        return None
+
+    def get_user(self):
+        return self.user_cache
+
+class PasswordResetForm(forms.Manipulator):
+    "A form that lets a user request a password reset"
+    def __init__(self):
+        self.fields = (
+            forms.EmailField(field_name="email", length=40, is_required=True,
+                validator_list=[self.isValidUserEmail]),
+        )
+
+    def isValidUserEmail(self, new_data, all_data):
+        "Validates that a user exists with the given e-mail address"
+        try:
+            self.user_cache = User.objects.get(email__iexact=new_data)
+        except User.DoesNotExist:
+            raise validators.ValidationError, "That e-mail address doesn't have an associated user acount. Are you sure you've registered?"
+
+    def save(self, domain_override=None):
+        "Calculates a new password randomly and sends it to the user"
+        from django.core.mail import send_mail
+        new_pass = User.objects.make_random_password()
+        self.user_cache.set_password(new_pass)
+        self.user_cache.save()
+        if not domain_override:
+            current_site = Site.objects.get_current()
+            site_name = current_site.name
+            domain = current_site.domain
+        else:
+            site_name = domain = domain_override
+        t = loader.get_template('registration/password_reset_email.html')
+        c = {
+            'new_password': new_pass,
+            'email': self.user_cache.email,
+            'domain': domain,
+            'site_name': site_name,
+            'user': self.user_cache,
+        }
+        send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [self.user_cache.email])
+
+class PasswordChangeForm(forms.Manipulator):
+    "A form that lets a user change his password."
+    def __init__(self, user):
+        self.user = user
+        self.fields = (
+            forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
+                validator_list=[self.isValidOldPassword]),
+            forms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True,
+                validator_list=[validators.AlwaysMatchesOtherField('new_password2', "The two 'new password' fields didn't match.")]),
+            forms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True),
+        )
+
+    def isValidOldPassword(self, new_data, all_data):
+        "Validates that the old_password field is correct."
+        if not self.user.check_password(new_data):
+            raise validators.ValidationError, "Your old password was entered incorrectly. Please enter it again."
+
+    def save(self, new_data):
+        "Saves the new password."
+        self.user.set_password(new_data['new_password1'])
+        self.user.save()

+ 6 - 6
django/contrib/auth/handlers/modpython.py

@@ -10,7 +10,7 @@ def authenhandler(req, **kwargs):
     # that so that the following import works
     os.environ.update(req.subprocess_env)
 
-    from django.models.auth import users
+    from django.contrib.auth.models import User
 
     # check for PythonOptions
     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
@@ -21,14 +21,14 @@ def authenhandler(req, **kwargs):
     superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
 
     # check that the username is valid
-    kwargs = {'username__exact': req.user, 'is_active__exact': True}
+    kwargs = {'username': req.user, 'is_active': True}
     if staff_only:
-        kwargs['is_staff__exact'] = True
+        kwargs['is_staff'] = True
     if superuser_only:
-        kwargs['is_superuser__exact'] = True
+        kwargs['is_superuser'] = True
     try:
-        user = users.get_object(**kwargs)
-    except users.UserDoesNotExist:
+        user = User.objects.get(**kwargs)
+    except User.DoesNotExist:
         return apache.HTTP_UNAUTHORIZED
 
     # check the password and any permission given

+ 53 - 0
django/contrib/auth/management.py

@@ -0,0 +1,53 @@
+"""
+Creates permissions for all installed apps that need permissions.
+"""
+
+from django.dispatch import dispatcher
+from django.db.models import get_models, signals
+from django.contrib.auth import models as auth_app
+
+def _get_permission_codename(action, opts):
+    return '%s_%s' % (action, opts.object_name.lower())
+
+def _get_all_permissions(opts):
+    "Returns (codename, name) for all permissions in the given opts."
+    perms = []
+    for action in ('add', 'change', 'delete'):
+        perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name)))
+    return perms + list(opts.permissions)
+
+def create_permissions(app, created_models):
+    from django.contrib.contenttypes.models import ContentType
+    from django.contrib.auth.models import Permission
+    app_models = get_models(app)
+    if not app_models:
+        return
+    for klass in app_models:
+        if not klass._meta.admin:
+            continue
+        ctype = ContentType.objects.get_for_model(klass)
+        for codename, name in _get_all_permissions(klass._meta):
+            try:
+                Permission.objects.get(name=name, codename=codename, content_type__pk=ctype.id)
+            except Permission.DoesNotExist:
+                p = Permission(name=name, codename=codename, content_type=ctype)
+                p.save()
+                print "Adding permission '%s'" % p
+
+def create_superuser(app, created_models):
+    from django.contrib.auth.models import User
+    from django.contrib.auth.create_superuser import createsuperuser as do_create
+    if User in created_models:
+        msg = "\nYou just installed Django's auth system, which means you don't have " \
+                "any superusers defined.\nWould you like to create one now? (yes/no): "
+        confirm = raw_input(msg)
+        while 1:
+            if confirm not in ('yes', 'no'):
+                confirm = raw_input('Please enter either "yes" or "no": ')
+                continue
+            if confirm == 'yes':
+                do_create()
+            break
+
+dispatcher.connect(create_permissions, signal=signals.post_syncdb)
+dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)

+ 19 - 0
django/contrib/auth/middleware.py

@@ -0,0 +1,19 @@
+class LazyUser(object):
+    def __init__(self):
+        self._user = None
+
+    def __get__(self, request, obj_type=None):
+        if self._user is None:
+            from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY
+            try:
+                user_id = request.session[SESSION_KEY]
+                self._user = User.objects.get(pk=user_id)
+            except (KeyError, User.DoesNotExist):
+                self._user = AnonymousUser()
+        return self._user
+
+class AuthenticationMiddleware:
+    def process_request(self, request):
+        assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+        request.__class__.user = LazyUser()
+        return None

+ 264 - 0
django/contrib/auth/models.py

@@ -0,0 +1,264 @@
+from django.core import validators
+from django.db import backend, connection, models
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext_lazy as _
+import datetime
+
+SESSION_KEY = '_auth_user_id'
+
+class SiteProfileNotAvailable(Exception):
+    pass
+
+class Permission(models.Model):
+    name = models.CharField(_('name'), maxlength=50)
+    content_type = models.ForeignKey(ContentType)
+    codename = models.CharField(_('codename'), maxlength=100)
+    class Meta:
+        verbose_name = _('permission')
+        verbose_name_plural = _('permissions')
+        unique_together = (('content_type', 'codename'),)
+        ordering = ('content_type', 'codename')
+
+    def __str__(self):
+        return "%r | %s" % (self.content_type, self.name)
+
+class Group(models.Model):
+    name = models.CharField(_('name'), maxlength=80, unique=True)
+    permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True, filter_interface=models.HORIZONTAL)
+    class Meta:
+        verbose_name = _('group')
+        verbose_name_plural = _('groups')
+        ordering = ('name',)
+    class Admin:
+        search_fields = ('name',)
+
+    def __str__(self):
+        return self.name
+
+class UserManager(models.Manager):
+    def create_user(self, username, email, password):
+        "Creates and saves a User with the given username, e-mail and password."
+        now = datetime.datetime.now()
+        user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
+        user.set_password(password)
+        user.save()
+        return user
+
+    def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
+        "Generates a random password with the given length and given allowed_chars"
+        # Note that default value of allowed_chars does not have "I" or letters
+        # that look like it -- just to avoid confusion.
+        from random import choice
+        return ''.join([choice(allowed_chars) for i in range(length)])
+
+class User(models.Model):
+    username = models.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])
+    first_name = models.CharField(_('first name'), maxlength=30, blank=True)
+    last_name = models.CharField(_('last name'), maxlength=30, blank=True)
+    email = models.EmailField(_('e-mail address'), blank=True)
+    password = models.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]'"))
+    is_staff = models.BooleanField(_('staff status'), help_text=_("Designates whether the user can log into this admin site."))
+    is_active = models.BooleanField(_('active'), default=True)
+    is_superuser = models.BooleanField(_('superuser status'))
+    last_login = models.DateTimeField(_('last login'), default=models.LazyDate())
+    date_joined = models.DateTimeField(_('date joined'), default=models.LazyDate())
+    groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
+        help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
+    user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True, filter_interface=models.HORIZONTAL)
+    objects = UserManager()
+    class Meta:
+        verbose_name = _('user')
+        verbose_name_plural = _('users')
+        ordering = ('username',)
+    class Admin:
+        fields = (
+            (None, {'fields': ('username', 'password')}),
+            (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
+            (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
+            (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
+            (_('Groups'), {'fields': ('groups',)}),
+        )
+        list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
+        list_filter = ('is_staff', 'is_superuser')
+        search_fields = ('username', 'first_name', 'last_name', 'email')
+
+    def __str__(self):
+        return self.username
+
+    def get_absolute_url(self):
+        return "/users/%s/" % self.username
+
+    def is_anonymous(self):
+        return False
+
+    def get_full_name(self):
+        full_name = '%s %s' % (self.first_name, self.last_name)
+        return full_name.strip()
+
+    def set_password(self, raw_password):
+        import sha, random
+        algo = 'sha1'
+        salt = sha.new(str(random.random())).hexdigest()[:5]
+        hsh = sha.new(salt+raw_password).hexdigest()
+        self.password = '%s$%s$%s' % (algo, salt, hsh)
+
+    def check_password(self, raw_password):
+        """
+        Returns a boolean of whether the raw_password was correct. Handles
+        encryption formats behind the scenes.
+        """
+        # Backwards-compatibility check. Older passwords won't include the
+        # algorithm or salt.
+        if '$' not in self.password:
+            import md5
+            is_correct = (self.password == md5.new(raw_password).hexdigest())
+            if is_correct:
+                # Convert the password to the new, more secure format.
+                self.set_password(raw_password)
+                self.save()
+            return is_correct
+        algo, salt, hsh = self.password.split('$')
+        if algo == 'md5':
+            import md5
+            return hsh == md5.new(salt+raw_password).hexdigest()
+        elif algo == 'sha1':
+            import sha
+            return hsh == sha.new(salt+raw_password).hexdigest()
+        raise ValueError, "Got unknown password algorithm type in password."
+
+    def get_group_permissions(self):
+        "Returns a list of permission strings that this user has through his/her groups."
+        if not hasattr(self, '_group_perm_cache'):
+            import sets
+            cursor = connection.cursor()
+            # The SQL below works out to the following, after DB quoting:
+            # cursor.execute("""
+            #     SELECT ct."app_label", p."codename"
+            #     FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct
+            #     WHERE p."id" = gp."permission_id"
+            #         AND gp."group_id" = ug."group_id"
+            #         AND ct."id" = p."content_type_id"
+            #         AND ug."user_id" = %s, [self.id])
+            sql = """
+                SELECT ct.%s, p.%s
+                FROM %s p, %s gp, %s ug, %s ct
+                WHERE p.%s = gp.%s
+                    AND gp.%s = ug.%s
+                    AND ct.%s = p.%s
+                    AND ug.%s = %%s""" % (
+                backend.quote_name('app_label'), backend.quote_name('codename'),
+                backend.quote_name('auth_permission'), backend.quote_name('auth_group_permissions'),
+                backend.quote_name('auth_user_groups'), backend.quote_name('django_content_type'),
+                backend.quote_name('id'), backend.quote_name('permission_id'),
+                backend.quote_name('group_id'), backend.quote_name('group_id'),
+                backend.quote_name('id'), backend.quote_name('content_type_id'),
+                backend.quote_name('user_id'),)
+            cursor.execute(sql, [self.id])
+            self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
+        return self._group_perm_cache
+
+    def get_all_permissions(self):
+        if not hasattr(self, '_perm_cache'):
+            import sets
+            self._perm_cache = sets.Set(["%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.all()])
+            self._perm_cache.update(self.get_group_permissions())
+        return self._perm_cache
+
+    def has_perm(self, perm):
+        "Returns True if the user has the specified permission."
+        if not self.is_active:
+            return False
+        if self.is_superuser:
+            return True
+        return perm in self.get_all_permissions()
+
+    def has_perms(self, perm_list):
+        "Returns True if the user has each of the specified permissions."
+        for perm in perm_list:
+            if not self.has_perm(perm):
+                return False
+        return True
+
+    def has_module_perms(self, app_label):
+        "Returns True if the user has any permissions in the given app label."
+        if self.is_superuser:
+            return True
+        return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == app_label]))
+
+    def get_and_delete_messages(self):
+        messages = []
+        for m in self.message_set.all():
+            messages.append(m.message)
+            m.delete()
+        return messages
+
+    def email_user(self, subject, message, from_email=None):
+        "Sends an e-mail to this User."
+        from django.core.mail import send_mail
+        send_mail(subject, message, from_email, [self.email])
+
+    def get_profile(self):
+        """
+        Returns site-specific profile for this user. Raises
+        SiteProfileNotAvailable if this site does not allow profiles.
+        """
+        if not hasattr(self, '_profile_cache'):
+            from django.conf import settings
+            if not settings.AUTH_PROFILE_MODULE:
+                raise SiteProfileNotAvailable
+            try:
+                app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
+                model = models.get_model(app_label, model_name)
+                self._profile_cache = model._default_manager.get(user__id__exact=self.id)
+            except ImportError, ImproperlyConfigured:
+                raise SiteProfileNotAvailable
+        return self._profile_cache
+
+class Message(models.Model):
+    user = models.ForeignKey(User)
+    message = models.TextField(_('message'))
+
+    def __str__(self):
+        return self.message
+
+class AnonymousUser(object):
+    id = None
+    username = ''
+
+    def __init__(self):
+        pass
+
+    def __str__(self):
+        return 'AnonymousUser'
+
+    def save(self):
+        raise NotImplementedError
+
+    def delete(self):
+        raise NotImplementedError
+
+    def set_password(self, raw_password):
+        raise NotImplementedError
+
+    def check_password(self, raw_password):
+        raise NotImplementedError
+
+    def _get_groups(self):
+        raise NotImplementedError
+    groups = property(_get_groups)
+
+    def _get_user_permissions(self):
+        raise NotImplementedError
+    user_permissions = property(_get_user_permissions)
+
+    def has_perm(self, perm):
+        return False
+
+    def has_module_perms(self, module):
+        return False
+
+    def get_and_delete_messages(self):
+        return []
+
+    def is_anonymous(self):
+        return True

+ 84 - 0
django/contrib/auth/views.py

@@ -0,0 +1,84 @@
+from django.contrib.auth.forms import AuthenticationForm
+from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
+from django import forms
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth.models import SESSION_KEY
+from django.contrib.sites.models import Site
+from django.http import HttpResponse, HttpResponseRedirect
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
+
+def login(request):
+    "Displays the login form and handles the login action."
+    manipulator = AuthenticationForm(request)
+    redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
+    if request.POST:
+        errors = manipulator.get_validation_errors(request.POST)
+        if not errors:
+            # Light security check -- make sure redirect_to isn't garbage.
+            if not redirect_to or '://' in redirect_to or ' ' in redirect_to:
+                redirect_to = '/accounts/profile/'
+            request.session[SESSION_KEY] = manipulator.get_user_id()
+            request.session.delete_test_cookie()
+            return HttpResponseRedirect(redirect_to)
+    else:
+        errors = {}
+    request.session.set_test_cookie()
+    return render_to_response('registration/login.html', {
+        'form': forms.FormWrapper(manipulator, request.POST, errors),
+        REDIRECT_FIELD_NAME: redirect_to,
+        'site_name': Site.objects.get_current().name,
+    }, context_instance=RequestContext(request))
+
+def logout(request, next_page=None):
+    "Logs out the user and displays 'You are logged out' message."
+    try:
+        del request.session[SESSION_KEY]
+    except KeyError:
+        return render_to_response('registration/logged_out.html', {'title': 'Logged out'}, context_instance=RequestContext(request))
+    else:
+        # Redirect to this page until the session has been cleared.
+        return HttpResponseRedirect(next_page or request.path)
+
+def logout_then_login(request, login_url=LOGIN_URL):
+    "Logs out the user if he is logged in. Then redirects to the log-in page."
+    return logout(request, login_url)
+
+def redirect_to_login(next, login_url=LOGIN_URL):
+    "Redirects the user to the login page, passing the given 'next' page"
+    return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next))
+
+def password_reset(request, is_admin_site=False):
+    new_data, errors = {}, {}
+    form = PasswordResetForm()
+    if request.POST:
+        new_data = request.POST.copy()
+        errors = form.get_validation_errors(new_data)
+        if not errors:
+            if is_admin_site:
+                form.save(request.META['HTTP_HOST'])
+            else:
+                form.save()
+            return HttpResponseRedirect('%sdone/' % request.path)
+    return render_to_response('registration/password_reset_form.html', {'form': forms.FormWrapper(form, new_data, errors)},
+        context_instance=RequestContext(request))
+
+def password_reset_done(request):
+    return render_to_response('registration/password_reset_done.html', context_instance=RequestContext(request))
+
+def password_change(request):
+    new_data, errors = {}, {}
+    form = PasswordChangeForm(request.user)
+    if request.POST:
+        new_data = request.POST.copy()
+        errors = form.get_validation_errors(new_data)
+        if not errors:
+            form.save(new_data)
+            return HttpResponseRedirect('%sdone/' % request.path)
+    return render_to_response('registration/password_change_form.html', {'form': forms.FormWrapper(form, new_data, errors)},
+        context_instance=RequestContext(request))
+password_change = login_required(password_change)
+
+def password_change_done(request):
+    return render_to_response('registration/password_change_done.html', context_instance=RequestContext(request))

+ 18 - 18
django/contrib/comments/feeds.py

@@ -1,48 +1,48 @@
 from django.conf import settings
+from django.contrib.comments.models import Comment, FreeComment
 from django.contrib.syndication.feeds import Feed
 from django.core.exceptions import ObjectDoesNotExist
-from django.models.core import sites
-from django.models.comments import comments, freecomments
+from django.contrib.sites.models import Site
 
 class LatestFreeCommentsFeed(Feed):
     """Feed of latest comments on the current site"""
-    
-    comments_module = freecomments
-    
+
+    comments_class = FreeComment
+
     def title(self):
         if not hasattr(self, '_site'):
-            self._site = sites.get_current()
+            self._site = Site.objects.get_current()
         return "%s comments" % self._site.name
-        
+
     def link(self):
         if not hasattr(self, '_site'):
-            self._site = sites.get_current()
+            self._site = Site.objects.get_current()
         return "http://%s/" % (self._site.domain)
-    
+
     def description(self):
         if not hasattr(self, '_site'):
-            self._site = sites.get_current()
+            self._site = Site.objects.get_current()
         return "Latest comments on %s" % self._site.name
 
     def items(self):
-        return self.comments_module.get_list(**self._get_lookup_kwargs())
+        return self.comments_class.objects.filter(**self._get_lookup_kwargs())
 
     def _get_lookup_kwargs(self):
         return {
-            'site__pk' : settings.SITE_ID,
-            'is_public__exact' : True,
-            'limit' : 40,
+            'site__pk': settings.SITE_ID,
+            'is_public__exact': True,
+            'limit': 40,
         }
 
 class LatestCommentsFeed(LatestFreeCommentsFeed):
     """Feed of latest free comments on the current site"""
-    
-    comments_module = comments
-    
+
+    comments_class = Comment
+
     def _get_lookup_kwargs(self):
         kwargs = LatestFreeCommentsFeed._get_lookup_kwargs(self)
         kwargs['is_removed__exact'] = False
         if settings.COMMENTS_BANNED_USERS_GROUP:
             kwargs['where'] = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)']
             kwargs['params'] = [COMMENTS_BANNED_USERS_GROUP]
-        return kwargs
+        return kwargs

+ 285 - 0
django/contrib/comments/models.py

@@ -0,0 +1,285 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sites.models import Site
+from django.contrib.auth.models import User
+from django.utils.translation import gettext_lazy as _
+from django.conf import settings
+import datetime
+
+MIN_PHOTO_DIMENSION = 5
+MAX_PHOTO_DIMENSION = 1000
+
+# option codes for comment-form hidden fields
+PHOTOS_REQUIRED = 'pr'
+PHOTOS_OPTIONAL = 'pa'
+RATINGS_REQUIRED = 'rr'
+RATINGS_OPTIONAL = 'ra'
+IS_PUBLIC = 'ip'
+
+# what users get if they don't have any karma
+DEFAULT_KARMA = 5
+KARMA_NEEDED_BEFORE_DISPLAYED = 3
+
+class CommentManager(models.Manager):
+    def get_security_hash(self, options, photo_options, rating_options, target):
+        """
+        Returns the MD5 hash of the given options (a comma-separated string such as
+        'pa,ra') and target (something like 'lcom.eventtimes:5157'). Used to
+        validate that submitted form options have not been tampered-with.
+        """
+        import md5
+        return md5.new(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest()
+
+    def get_rating_options(self, rating_string):
+        """
+        Given a rating_string, this returns a tuple of (rating_range, options).
+        >>> s = "scale:1-10|First_category|Second_category"
+        >>> get_rating_options(s)
+        ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
+        """
+        rating_range, options = rating_string.split('|', 1)
+        rating_range = range(int(rating_range[6:].split('-')[0]), int(rating_range[6:].split('-')[1])+1)
+        choices = [c.replace('_', ' ') for c in options.split('|')]
+        return rating_range, choices
+
+    def get_list_with_karma(self, **kwargs):
+        """
+        Returns a list of Comment objects matching the given lookup terms, with
+        _karma_total_good and _karma_total_bad filled.
+        """
+        extra_kwargs = {}
+        extra_kwargs.setdefault('select', {})
+        extra_kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=1'
+        extra_kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=-1'
+	return self.filter(**kwargs).extra(**extra_kwargs)
+
+    def user_is_moderator(self, user):
+        if user.is_superuser:
+            return True
+        for g in user.get_group_list():
+            if g.id == settings.COMMENTS_MODERATORS_GROUP:
+                return True
+        return False
+
+class Comment(models.Model):
+    user = models.ForeignKey(User, raw_id_admin=True)
+    content_type = models.ForeignKey(ContentType)
+    object_id = models.IntegerField(_('object ID'))
+    headline = models.CharField(_('headline'), maxlength=255, blank=True)
+    comment = models.TextField(_('comment'), maxlength=3000)
+    rating1 = models.PositiveSmallIntegerField(_('rating #1'), blank=True, null=True)
+    rating2 = models.PositiveSmallIntegerField(_('rating #2'), blank=True, null=True)
+    rating3 = models.PositiveSmallIntegerField(_('rating #3'), blank=True, null=True)
+    rating4 = models.PositiveSmallIntegerField(_('rating #4'), blank=True, null=True)
+    rating5 = models.PositiveSmallIntegerField(_('rating #5'), blank=True, null=True)
+    rating6 = models.PositiveSmallIntegerField(_('rating #6'), blank=True, null=True)
+    rating7 = models.PositiveSmallIntegerField(_('rating #7'), blank=True, null=True)
+    rating8 = models.PositiveSmallIntegerField(_('rating #8'), blank=True, null=True)
+    # This field designates whether to use this row's ratings in aggregate
+    # functions (summaries). We need this because people are allowed to post
+    # multiple reviews on the same thing, but the system will only use the
+    # latest one (with valid_rating=True) in tallying the reviews.
+    valid_rating = models.BooleanField(_('is valid rating'))
+    submit_date = models.DateTimeField(_('date/time submitted'), auto_now_add=True)
+    is_public = models.BooleanField(_('is public'))
+    ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
+    is_removed = models.BooleanField(_('is removed'), help_text=_('Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'))
+    site = models.ForeignKey(Site)
+    objects = CommentManager()
+    class Meta:
+        verbose_name = _('comment')
+        verbose_name_plural = _('comments')
+        ordering = ('-submit_date',)
+    class Admin:
+        fields = (
+            (None, {'fields': ('content_type', 'object_id', 'site')}),
+            ('Content', {'fields': ('user', 'headline', 'comment')}),
+            ('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
+            ('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
+        )
+        list_display = ('user', 'submit_date', 'content_type', 'get_content_object')
+        list_filter = ('submit_date',)
+        date_hierarchy = 'submit_date'
+        search_fields = ('comment', 'user__username')
+
+    def __repr__(self):
+        return "%s: %s..." % (self.user.username, self.comment[:100])
+
+    def get_absolute_url(self):
+        return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
+
+    def get_crossdomain_url(self):
+        return "/r/%d/%d/" % (self.content_type_id, self.object_id)
+
+    def get_flag_url(self):
+        return "/comments/flag/%s/" % self.id
+
+    def get_deletion_url(self):
+        return "/comments/delete/%s/" % self.id
+
+    def get_content_object(self):
+        """
+        Returns the object that this comment is a comment on. Returns None if
+        the object no longer exists.
+        """
+        from django.core.exceptions import ObjectDoesNotExist
+        try:
+            return self.get_content_type().get_object_for_this_type(pk=self.object_id)
+        except ObjectDoesNotExist:
+            return None
+
+    get_content_object.short_description = _('Content object')
+
+    def _fill_karma_cache(self):
+        "Helper function that populates good/bad karma caches"
+        good, bad = 0, 0
+        for k in self.get_karmascore_list():
+            if k.score == -1:
+                bad +=1
+            elif k.score == 1:
+                good +=1
+        self._karma_total_good, self._karma_total_bad = good, bad
+
+    def get_good_karma_total(self):
+        if not hasattr(self, "_karma_total_good"):
+            self._fill_karma_cache()
+        return self._karma_total_good
+
+    def get_bad_karma_total(self):
+        if not hasattr(self, "_karma_total_bad"):
+            self._fill_karma_cache()
+        return self._karma_total_bad
+
+    def get_karma_total(self):
+        if not hasattr(self, "_karma_total_good") or not hasattr(self, "_karma_total_bad"):
+            self._fill_karma_cache()
+        return self._karma_total_good + self._karma_total_bad
+
+    def get_as_text(self):
+        return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % \
+            {'user': self.user.username, 'date': self.submit_date,
+            'comment': self.comment, 'domain': self.get_site().domain, 'url': self.get_absolute_url()}
+
+class FreeComment(models.Model):
+    # A FreeComment is a comment by a non-registered user.
+    content_type = models.ForeignKey(ContentType)
+    object_id = models.IntegerField(_('object ID'))
+    comment = models.TextField(_('comment'), maxlength=3000)
+    person_name = models.CharField(_("person's name"), maxlength=50)
+    submit_date = models.DateTimeField(_('date/time submitted'), auto_now_add=True)
+    is_public = models.BooleanField(_('is public'))
+    ip_address = models.IPAddressField(_('ip address'))
+    # TODO: Change this to is_removed, like Comment
+    approved = models.BooleanField(_('approved by staff'))
+    site = models.ForeignKey(Site)
+    class Meta:
+        verbose_name = _('free comment')
+        verbose_name_plural = _('free comments')
+        ordering = ('-submit_date',)
+    class Admin:
+        fields = (
+            (None, {'fields': ('content_type', 'object_id', 'site')}),
+            ('Content', {'fields': ('person_name', 'comment')}),
+            ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
+        )
+        list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
+        list_filter = ('submit_date',)
+        date_hierarchy = 'submit_date'
+        search_fields = ('comment', 'person_name')
+
+    def __repr__(self):
+        return "%s: %s..." % (self.person_name, self.comment[:100])
+
+    def get_absolute_url(self):
+        return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
+
+    def get_content_object(self):
+        """
+        Returns the object that this comment is a comment on. Returns None if
+        the object no longer exists.
+        """
+        from django.core.exceptions import ObjectDoesNotExist
+        try:
+            return self.get_content_type().get_object_for_this_type(pk=self.object_id)
+        except ObjectDoesNotExist:
+            return None
+
+    get_content_object.short_description = _('Content object')
+
+class KarmaScoreManager(models.Manager):
+    def vote(self, user_id, comment_id, score):
+        try:
+            karma = self.objects.get(comment__id__exact=comment_id, user__id__exact=user_id)
+        except self.model.DoesNotExist:
+            karma = self.model(None, user_id, comment_id, score, datetime.datetime.now())
+            karma.save()
+        else:
+            karma.score = score
+            karma.scored_date = datetime.datetime.now()
+            karma.save()
+
+    def get_pretty_score(self, score):
+        """
+        Given a score between -1 and 1 (inclusive), returns the same score on a
+        scale between 1 and 10 (inclusive), as an integer.
+        """
+        if score is None:
+            return DEFAULT_KARMA
+        return int(round((4.5 * score) + 5.5))
+
+class KarmaScore(models.Model):
+    user = models.ForeignKey(User)
+    comment = models.ForeignKey(Comment)
+    score = models.SmallIntegerField(_('score'), db_index=True)
+    scored_date = models.DateTimeField(_('score date'), auto_now=True)
+    objects = KarmaScoreManager()
+    class Meta:
+        verbose_name = _('karma score')
+        verbose_name_plural = _('karma scores')
+        unique_together = (('user', 'comment'),)
+
+    def __repr__(self):
+        return _("%(score)d rating by %(user)s") % {'score': self.score, 'user': self.user}
+
+class UserFlagManager(models.Manager):
+    def flag(self, comment, user):
+        """
+        Flags the given comment by the given user. If the comment has already
+        been flagged by the user, or it was a comment posted by the user,
+        nothing happens.
+        """
+        if int(comment.user_id) == int(user.id):
+            return # A user can't flag his own comment. Fail silently.
+        try:
+            f = self.objects.get(user__id__exact=user.id, comment__id__exact=comment.id)
+        except self.model.DoesNotExist:
+            from django.core.mail import mail_managers
+            f = self.model(None, user.id, comment.id, None)
+            message = _('This comment was flagged by %(user)s:\n\n%(text)s') % {'user': user.username, 'text': comment.get_as_text()}
+            mail_managers('Comment flagged', message, fail_silently=True)
+            f.save()
+
+class UserFlag(models.Model):
+    user = models.ForeignKey(User)
+    comment = models.ForeignKey(Comment)
+    flag_date = models.DateTimeField(_('flag date'), auto_now_add=True)
+    objects = UserFlagManager()
+    class Meta:
+        verbose_name = _('user flag')
+        verbose_name_plural = _('user flags')
+        unique_together = (('user', 'comment'),)
+
+    def __repr__(self):
+        return _("Flag by %r") % self.user
+
+class ModeratorDeletion(models.Model):
+    user = models.ForeignKey(User, verbose_name='moderator')
+    comment = models.ForeignKey(Comment)
+    deletion_date = models.DateTimeField(_('deletion date'), auto_now_add=True)
+    class Meta:
+        verbose_name = _('moderator deletion')
+        verbose_name_plural = _('moderator deletions')
+        unique_together = (('user', 'comment'),)
+
+    def __repr__(self):
+        return _("Moderator deletion by %r") % self.user

+ 0 - 1
django/contrib/comments/models/__init__.py

@@ -1 +0,0 @@
-__all__ = ['comments']

+ 0 - 287
django/contrib/comments/models/comments.py

@@ -1,287 +0,0 @@
-from django.core import meta
-from django.models import auth, core
-from django.utils.translation import gettext_lazy as _
-
-class Comment(meta.Model):
-    user = meta.ForeignKey(auth.User, raw_id_admin=True)
-    content_type = meta.ForeignKey(core.ContentType)
-    object_id = meta.IntegerField(_('object ID'))
-    headline = meta.CharField(_('headline'), maxlength=255, blank=True)
-    comment = meta.TextField(_('comment'), maxlength=3000)
-    rating1 = meta.PositiveSmallIntegerField(_('rating #1'), blank=True, null=True)
-    rating2 = meta.PositiveSmallIntegerField(_('rating #2'), blank=True, null=True)
-    rating3 = meta.PositiveSmallIntegerField(_('rating #3'), blank=True, null=True)
-    rating4 = meta.PositiveSmallIntegerField(_('rating #4'), blank=True, null=True)
-    rating5 = meta.PositiveSmallIntegerField(_('rating #5'), blank=True, null=True)
-    rating6 = meta.PositiveSmallIntegerField(_('rating #6'), blank=True, null=True)
-    rating7 = meta.PositiveSmallIntegerField(_('rating #7'), blank=True, null=True)
-    rating8 = meta.PositiveSmallIntegerField(_('rating #8'), blank=True, null=True)
-    # This field designates whether to use this row's ratings in aggregate
-    # functions (summaries). We need this because people are allowed to post
-    # multiple reviews on the same thing, but the system will only use the
-    # latest one (with valid_rating=True) in tallying the reviews.
-    valid_rating = meta.BooleanField(_('is valid rating'))
-    submit_date = meta.DateTimeField(_('date/time submitted'), auto_now_add=True)
-    is_public = meta.BooleanField(_('is public'))
-    ip_address = meta.IPAddressField(_('IP address'), blank=True, null=True)
-    is_removed = meta.BooleanField(_('is removed'), help_text=_('Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'))
-    site = meta.ForeignKey(core.Site)
-    class META:
-        db_table = 'comments'
-        verbose_name = _('Comment')
-        verbose_name_plural = _('Comments')
-        module_constants = {
-            # min. and max. allowed dimensions for photo resizing (in pixels)
-            'MIN_PHOTO_DIMENSION': 5,
-            'MAX_PHOTO_DIMENSION': 1000,
-
-            # option codes for comment-form hidden fields
-            'PHOTOS_REQUIRED': 'pr',
-            'PHOTOS_OPTIONAL': 'pa',
-            'RATINGS_REQUIRED': 'rr',
-            'RATINGS_OPTIONAL': 'ra',
-            'IS_PUBLIC': 'ip',
-        }
-        ordering = ('-submit_date',)
-        admin = meta.Admin(
-            fields = (
-                (None, {'fields': ('content_type', 'object_id', 'site')}),
-                ('Content', {'fields': ('user', 'headline', 'comment')}),
-                ('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
-                ('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
-            ),
-            list_display = ('user', 'submit_date', 'content_type', 'get_content_object'),
-            list_filter = ('submit_date',),
-            date_hierarchy = 'submit_date',
-            search_fields = ('comment', 'user__username'),
-        )
-
-    def __repr__(self):
-        return "%s: %s..." % (self.get_user().username, self.comment[:100])
-
-    def get_absolute_url(self):
-        return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
-
-    def get_crossdomain_url(self):
-        return "/r/%d/%d/" % (self.content_type_id, self.object_id)
-
-    def get_flag_url(self):
-        return "/comments/flag/%s/" % self.id
-
-    def get_deletion_url(self):
-        return "/comments/delete/%s/" % self.id
-
-    def get_content_object(self):
-        """
-        Returns the object that this comment is a comment on. Returns None if
-        the object no longer exists.
-        """
-        from django.core.exceptions import ObjectDoesNotExist
-        try:
-            return self.get_content_type().get_object_for_this_type(pk=self.object_id)
-        except ObjectDoesNotExist:
-            return None
-
-    get_content_object.short_description = _('Content object')
-
-    def _fill_karma_cache(self):
-        "Helper function that populates good/bad karma caches"
-        good, bad = 0, 0
-        for k in self.get_karmascore_list():
-            if k.score == -1:
-                bad +=1
-            elif k.score == 1:
-                good +=1
-        self._karma_total_good, self._karma_total_bad = good, bad
-
-    def get_good_karma_total(self):
-        if not hasattr(self, "_karma_total_good"):
-            self._fill_karma_cache()
-        return self._karma_total_good
-
-    def get_bad_karma_total(self):
-        if not hasattr(self, "_karma_total_bad"):
-            self._fill_karma_cache()
-        return self._karma_total_bad
-
-    def get_karma_total(self):
-        if not hasattr(self, "_karma_total_good") or not hasattr(self, "_karma_total_bad"):
-            self._fill_karma_cache()
-        return self._karma_total_good + self._karma_total_bad
-
-    def get_as_text(self):
-        return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % \
-            {'user': self.get_user().username, 'date': self.submit_date,
-            'comment': self.comment, 'domain': self.get_site().domain, 'url': self.get_absolute_url()}
-
-    def _module_get_security_hash(options, photo_options, rating_options, target):
-        """
-        Returns the MD5 hash of the given options (a comma-separated string such as
-        'pa,ra') and target (something like 'lcom.eventtimes:5157'). Used to
-        validate that submitted form options have not been tampered-with.
-        """
-        from django.conf.settings import SECRET_KEY
-        import md5
-        return md5.new(options + photo_options + rating_options + target + SECRET_KEY).hexdigest()
-
-    def _module_get_rating_options(rating_string):
-        """
-        Given a rating_string, this returns a tuple of (rating_range, options).
-        >>> s = "scale:1-10|First_category|Second_category"
-        >>> get_rating_options(s)
-        ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
-        """
-        rating_range, options = rating_string.split('|', 1)
-        rating_range = range(int(rating_range[6:].split('-')[0]), int(rating_range[6:].split('-')[1])+1)
-        choices = [c.replace('_', ' ') for c in options.split('|')]
-        return rating_range, choices
-
-    def _module_get_list_with_karma(**kwargs):
-        """
-        Returns a list of Comment objects matching the given lookup terms, with
-        _karma_total_good and _karma_total_bad filled.
-        """
-        kwargs.setdefault('select', {})
-        kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karma WHERE comments_karma.comment_id=comments.id AND score=1'
-        kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karma WHERE comments_karma.comment_id=comments.id AND score=-1'
-        return get_list(**kwargs)
-
-    def _module_user_is_moderator(user):
-        from django.conf.settings import COMMENTS_MODERATORS_GROUP
-        if user.is_superuser:
-            return True
-        for g in user.get_group_list():
-            if g.id == COMMENTS_MODERATORS_GROUP:
-                return True
-        return False
-
-class FreeComment(meta.Model):
-    # A FreeComment is a comment by a non-registered user.
-    content_type = meta.ForeignKey(core.ContentType)
-    object_id = meta.IntegerField(_('object ID'))
-    comment = meta.TextField(_('comment'), maxlength=3000)
-    person_name = meta.CharField(_("person's name"), maxlength=50)
-    submit_date = meta.DateTimeField(_('date/time submitted'), auto_now_add=True)
-    is_public = meta.BooleanField(_('is public'))
-    ip_address = meta.IPAddressField(_('ip address'))
-    # TODO: Change this to is_removed, like Comment
-    approved = meta.BooleanField(_('approved by staff'))
-    site = meta.ForeignKey(core.Site)
-    class META:
-        db_table = 'comments_free'
-        verbose_name = _('Free comment')
-        verbose_name_plural = _('Free comments')
-        ordering = ('-submit_date',)
-        admin = meta.Admin(
-            fields = (
-                (None, {'fields': ('content_type', 'object_id', 'site')}),
-                ('Content', {'fields': ('person_name', 'comment')}),
-                ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
-            ),
-            list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object'),
-            list_filter = ('submit_date',),
-            date_hierarchy = 'submit_date',
-            search_fields = ('comment', 'person_name'),
-        )
-
-    def __repr__(self):
-        return "%s: %s..." % (self.person_name, self.comment[:100])
-
-    def get_absolute_url(self):
-        return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
-
-    def get_content_object(self):
-        """
-        Returns the object that this comment is a comment on. Returns None if
-        the object no longer exists.
-        """
-        from django.core.exceptions import ObjectDoesNotExist
-        try:
-            return self.get_content_type().get_object_for_this_type(pk=self.object_id)
-        except ObjectDoesNotExist:
-            return None
-
-    get_content_object.short_description = _('Content object')
-
-class KarmaScore(meta.Model):
-    user = meta.ForeignKey(auth.User)
-    comment = meta.ForeignKey(Comment)
-    score = meta.SmallIntegerField(_('score'), db_index=True)
-    scored_date = meta.DateTimeField(_('score date'), auto_now=True)
-    class META:
-        module_name = 'karma'
-        verbose_name = _('Karma score')
-        verbose_name_plural = _('Karma scores')
-        unique_together = (('user', 'comment'),)
-        module_constants = {
-            # what users get if they don't have any karma
-            'DEFAULT_KARMA': 5,
-            'KARMA_NEEDED_BEFORE_DISPLAYED': 3,
-        }
-
-    def __repr__(self):
-        return _("%(score)d rating by %(user)s") % {'score': self.score, 'user': self.get_user()}
-
-    def _module_vote(user_id, comment_id, score):
-        try:
-            karma = get_object(comment__id__exact=comment_id, user__id__exact=user_id)
-        except KarmaScoreDoesNotExist:
-            karma = KarmaScore(None, user_id, comment_id, score, datetime.datetime.now())
-            karma.save()
-        else:
-            karma.score = score
-            karma.scored_date = datetime.datetime.now()
-            karma.save()
-
-    def _module_get_pretty_score(score):
-        """
-        Given a score between -1 and 1 (inclusive), returns the same score on a
-        scale between 1 and 10 (inclusive), as an integer.
-        """
-        if score is None:
-            return DEFAULT_KARMA
-        return int(round((4.5 * score) + 5.5))
-
-class UserFlag(meta.Model):
-    user = meta.ForeignKey(auth.User)
-    comment = meta.ForeignKey(Comment)
-    flag_date = meta.DateTimeField(_('flag date'), auto_now_add=True)
-    class META:
-        db_table = 'comments_user_flags'
-        verbose_name = _('User flag')
-        verbose_name_plural = _('User flags')
-        unique_together = (('user', 'comment'),)
-
-    def __repr__(self):
-        return _("Flag by %r") % self.get_user()
-
-    def _module_flag(comment, user):
-        """
-        Flags the given comment by the given user. If the comment has already
-        been flagged by the user, or it was a comment posted by the user,
-        nothing happens.
-        """
-        if int(comment.user_id) == int(user.id):
-            return # A user can't flag his own comment. Fail silently.
-        try:
-            f = get_object(user__id__exact=user.id, comment__id__exact=comment.id)
-        except UserFlagDoesNotExist:
-            from django.core.mail import mail_managers
-            f = UserFlag(None, user.id, comment.id, None)
-            message = _('This comment was flagged by %(user)s:\n\n%(text)s') % {'user': user.username, 'text': comment.get_as_text()}
-            mail_managers('Comment flagged', message, fail_silently=True)
-            f.save()
-
-class ModeratorDeletion(meta.Model):
-    user = meta.ForeignKey(auth.User, verbose_name='moderator')
-    comment = meta.ForeignKey(Comment)
-    deletion_date = meta.DateTimeField(_('deletion date'), auto_now_add=True)
-    class META:
-        db_table = 'comments_moderator_deletions'
-        verbose_name = _('Moderator deletion')
-        verbose_name_plural = _('Moderator deletions')
-        unique_together = (('user', 'comment'),)
-
-    def __repr__(self):
-        return _("Moderator deletion by %r") % self.get_user()
-

+ 39 - 41
django/contrib/comments/templatetags/comments.py

@@ -1,16 +1,16 @@
-"Custom template tags for user comments"
-
-from django.core import template
-from django.core.template import loader
+from django.contrib.comments.models import Comment, FreeComment
+from django.contrib.comments.models import PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
+from django.contrib.comments.models import MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION
+from django import template
+from django.template import loader
 from django.core.exceptions import ObjectDoesNotExist
-from django.models.comments import comments, freecomments
-from django.models.core import contenttypes
+from django.contrib.contenttypes.models import ContentType
 import re
 
 register = template.Library()
 
-COMMENT_FORM = 'comments/form'
-FREE_COMMENT_FORM = 'comments/freeform'
+COMMENT_FORM = 'comments/form.html'
+FREE_COMMENT_FORM = 'comments/freeform.html'
 
 class CommentFormNode(template.Node):
     def __init__(self, content_type, obj_id_lookup_var, obj_id, free,
@@ -46,24 +46,24 @@ class CommentFormNode(template.Node):
             context['display_form'] = True
         context['target'] = '%s:%s' % (self.content_type.id, self.obj_id)
         options = []
-        for var, abbr in (('photos_required', comments.PHOTOS_REQUIRED),
-                          ('photos_optional', comments.PHOTOS_OPTIONAL),
-                          ('ratings_required', comments.RATINGS_REQUIRED),
-                          ('ratings_optional', comments.RATINGS_OPTIONAL),
-                          ('is_public', comments.IS_PUBLIC)):
+        for var, abbr in (('photos_required', PHOTOS_REQUIRED),
+                          ('photos_optional', PHOTOS_OPTIONAL),
+                          ('ratings_required', RATINGS_REQUIRED),
+                          ('ratings_optional', RATINGS_OPTIONAL),
+                          ('is_public', IS_PUBLIC)):
             context[var] = getattr(self, var)
             if getattr(self, var):
                 options.append(abbr)
         context['options'] = ','.join(options)
         if self.free:
-            context['hash'] = comments.get_security_hash(context['options'], '', '', context['target'])
+            context['hash'] = Comment.objects.get_security_hash(context['options'], '', '', context['target'])
             default_form = loader.get_template(FREE_COMMENT_FORM)
         else:
             context['photo_options'] = self.photo_options
             context['rating_options'] = normalize_newlines(base64.encodestring(self.rating_options).strip())
             if self.rating_options:
-                context['rating_range'], context['rating_choices'] = comments.get_rating_options(self.rating_options)
-            context['hash'] = comments.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
+                context['rating_range'], context['rating_choices'] = Comment.objects.get_rating_options(self.rating_options)
+            context['hash'] = Comment.objects.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
             default_form = loader.get_template(COMMENT_FORM)
         output = default_form.render(context)
         context.pop()
@@ -76,13 +76,13 @@ class CommentCountNode(template.Node):
         self.var_name, self.free = var_name, free
 
     def render(self, context):
-        from django.conf.settings import SITE_ID
-        get_count_function = self.free and freecomments.get_count or comments.get_count
+        from django.conf import settings
+        manager = self.free and FreeComment.objects or Comment.objects
         if self.context_var_name is not None:
             self.obj_id = template.resolve_variable(self.context_var_name, context)
-        comment_count = get_count_function(object_id__exact=self.obj_id,
-            content_type__package__label__exact=self.package,
-            content_type__python_module_name__exact=self.module, site__id__exact=SITE_ID)
+        comment_count = manager.filter(object_id__exact=self.obj_id,
+            content_type__app_label__exact=self.package,
+            content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count()
         context[self.var_name] = comment_count
         return ''
 
@@ -95,8 +95,8 @@ class CommentListNode(template.Node):
         self.extra_kwargs = extra_kwargs or {}
 
     def render(self, context):
-        from django.conf.settings import COMMENTS_BANNED_USERS_GROUP, SITE_ID
-        get_list_function = self.free and freecomments.get_list or comments.get_list_with_karma
+        from django.conf import settings
+        get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma
         if self.context_var_name is not None:
             try:
                 self.obj_id = template.resolve_variable(self.context_var_name, context)
@@ -104,26 +104,24 @@ class CommentListNode(template.Node):
                 return ''
         kwargs = {
             'object_id__exact': self.obj_id,
-            'content_type__package__label__exact': self.package,
-            'content_type__python_module_name__exact': self.module,
-            'site__id__exact': SITE_ID,
-            'select_related': True,
-            'order_by': (self.ordering + 'submit_date',),
+            'content_type__app_label__exact': self.package,
+            'content_type__model__exact': self.module,
+            'site__id__exact': settings.SITE_ID,
         }
         kwargs.update(self.extra_kwargs)
-        if not self.free and COMMENTS_BANNED_USERS_GROUP:
-            kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_users_groups WHERE group_id = %s)' % COMMENTS_BANNED_USERS_GROUP}
-        comment_list = get_list_function(**kwargs)
+        if not self.free and settings.COMMENTS_BANNED_USERS_GROUP:
+            kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_user_groups WHERE group_id = %s)' % settings.COMMENTS_BANNED_USERS_GROUP}
+        comment_list = get_list_function(**kwargs).order_by(self.ordering + 'submit_date').select_related()
 
         if not self.free:
             if context.has_key('user') and not context['user'].is_anonymous():
                 user_id = context['user'].id
-                context['user_can_moderate_comments'] = comments.user_is_moderator(context['user'])
+                context['user_can_moderate_comments'] = Comment.objects.user_is_moderator(context['user'])
             else:
                 user_id = None
                 context['user_can_moderate_comments'] = False
             # Only display comments by banned users to those users themselves.
-            if COMMENTS_BANNED_USERS_GROUP:
+            if settings.COMMENTS_BANNED_USERS_GROUP:
                 comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)]
 
         context[self.var_name] = comment_list
@@ -157,8 +155,8 @@ class DoCommentForm:
         except ValueError: # unpack list of wrong size
             raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
         try:
-            content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
-        except contenttypes.ContentTypeDoesNotExist:
+            content_type = ContentType.objects.get(app_label__exact=package, model__exact=module)
+        except ContentType.DoesNotExist:
             raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
         obj_id_lookup_var, obj_id = None, None
         if tokens[3].isdigit():
@@ -183,8 +181,8 @@ class DoCommentForm:
                         if not opt.isalnum():
                             raise template.TemplateSyntaxError, "Invalid photo directory name in %r tag: '%s'" % (tokens[0], opt)
                     for opt in option_list[1::3] + option_list[2::3]:
-                        if not opt.isdigit() or not (comments.MIN_PHOTO_DIMENSION <= int(opt) <= comments.MAX_PHOTO_DIMENSION):
-                            raise template.TemplateSyntaxError, "Invalid photo dimension in %r tag: '%s'. Only values between %s and %s are allowed." % (tokens[0], opt, comments.MIN_PHOTO_DIMENSION, comments.MAX_PHOTO_DIMENSION)
+                        if not opt.isdigit() or not (MIN_PHOTO_DIMENSION <= int(opt) <= MAX_PHOTO_DIMENSION):
+                            raise template.TemplateSyntaxError, "Invalid photo dimension in %r tag: '%s'. Only values between %s and %s are allowed." % (tokens[0], opt, MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION)
                     # VALIDATION ENDS #########################################
                     kwargs[option] = True
                     kwargs['photo_options'] = args
@@ -237,8 +235,8 @@ class DoCommentCount:
         except ValueError: # unpack list of wrong size
             raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
         try:
-            content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
-        except contenttypes.ContentTypeDoesNotExist:
+            content_type = ContentType.objects.get(app_label__exact=package, model__exact=module)
+        except ContentType.DoesNotExist:
             raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
         var_name, obj_id = None, None
         if tokens[3].isdigit():
@@ -292,8 +290,8 @@ class DoGetCommentList:
         except ValueError: # unpack list of wrong size
             raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
         try:
-            content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
-        except contenttypes.ContentTypeDoesNotExist:
+            content_type = ContentType.objects.get(app_label__exact=package,model__exact=module)
+        except ContentType.DoesNotExist:
             raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
         var_name, obj_id = None, None
         if tokens[3].isdigit():

+ 57 - 54
django/contrib/comments/views/comments.py

@@ -1,14 +1,17 @@
-from django.core import formfields, validators
+from django.core import validators
+from django import forms
 from django.core.mail import mail_admins, mail_managers
-from django.core.exceptions import Http404, ObjectDoesNotExist
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.auth import users
-from django.models.comments import comments, freecomments
-from django.models.core import contenttypes
-from django.parts.auth.formfields import AuthenticationForm
-from django.utils.httpwrappers import HttpResponseRedirect
+from django.http import Http404
+from django.core.exceptions import ObjectDoesNotExist
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth.models import SESSION_KEY
+from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.forms import AuthenticationForm
+from django.http import HttpResponseRedirect
 from django.utils.text import normalize_newlines
-from django.conf.settings import BANNED_IPS, COMMENTS_ALLOW_PROFANITIES, COMMENTS_SKETCHY_USERS_GROUP, COMMENTS_FIRST_FEW, SITE_ID
+from django.conf import settings
 from django.utils.translation import ngettext
 import base64, datetime
 
@@ -26,37 +29,37 @@ class PublicCommentManipulator(AuthenticationForm):
             else:
                 return []
         self.fields.extend([
-            formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+            forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
                 validator_list=[self.hasNoProfanities]),
-            formfields.RadioSelectField(field_name="rating1", choices=choices,
+            forms.RadioSelectField(field_name="rating1", choices=choices,
                 is_required=ratings_required and num_rating_choices > 0,
                 validator_list=get_validator_list(1),
             ),
-            formfields.RadioSelectField(field_name="rating2", choices=choices,
+            forms.RadioSelectField(field_name="rating2", choices=choices,
                 is_required=ratings_required and num_rating_choices > 1,
                 validator_list=get_validator_list(2),
             ),
-            formfields.RadioSelectField(field_name="rating3", choices=choices,
+            forms.RadioSelectField(field_name="rating3", choices=choices,
                 is_required=ratings_required and num_rating_choices > 2,
                 validator_list=get_validator_list(3),
             ),
-            formfields.RadioSelectField(field_name="rating4", choices=choices,
+            forms.RadioSelectField(field_name="rating4", choices=choices,
                 is_required=ratings_required and num_rating_choices > 3,
                 validator_list=get_validator_list(4),
             ),
-            formfields.RadioSelectField(field_name="rating5", choices=choices,
+            forms.RadioSelectField(field_name="rating5", choices=choices,
                 is_required=ratings_required and num_rating_choices > 4,
                 validator_list=get_validator_list(5),
             ),
-            formfields.RadioSelectField(field_name="rating6", choices=choices,
+            forms.RadioSelectField(field_name="rating6", choices=choices,
                 is_required=ratings_required and num_rating_choices > 5,
                 validator_list=get_validator_list(6),
             ),
-            formfields.RadioSelectField(field_name="rating7", choices=choices,
+            forms.RadioSelectField(field_name="rating7", choices=choices,
                 is_required=ratings_required and num_rating_choices > 6,
                 validator_list=get_validator_list(7),
             ),
-            formfields.RadioSelectField(field_name="rating8", choices=choices,
+            forms.RadioSelectField(field_name="rating8", choices=choices,
                 is_required=ratings_required and num_rating_choices > 7,
                 validator_list=get_validator_list(8),
             ),
@@ -69,25 +72,25 @@ class PublicCommentManipulator(AuthenticationForm):
             self.user_cache = user
 
     def hasNoProfanities(self, field_data, all_data):
-        if COMMENTS_ALLOW_PROFANITIES:
+        if settings.COMMENTS_ALLOW_PROFANITIES:
             return
         return validators.hasNoProfanities(field_data, all_data)
 
     def get_comment(self, new_data):
         "Helper function"
-        return comments.Comment(None, self.get_user_id(), new_data["content_type_id"],
+        return Comment(None, self.get_user_id(), new_data["content_type_id"],
             new_data["object_id"], new_data.get("headline", "").strip(),
             new_data["comment"].strip(), new_data.get("rating1", None),
             new_data.get("rating2", None), new_data.get("rating3", None),
             new_data.get("rating4", None), new_data.get("rating5", None),
             new_data.get("rating6", None), new_data.get("rating7", None),
             new_data.get("rating8", None), new_data.get("rating1", None) is not None,
-            datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, SITE_ID)
+            datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, settings.SITE_ID)
 
     def save(self, new_data):
         today = datetime.date.today()
         c = self.get_comment(new_data)
-        for old in comments.get_list(content_type__id__exact=new_data["content_type_id"],
+        for old in Comment.objects.filter(content_type__id__exact=new_data["content_type_id"],
             object_id__exact=new_data["object_id"], user__id__exact=self.get_user_id()):
             # Check that this comment isn't duplicate. (Sometimes people post
             # comments twice by mistake.) If it is, fail silently by pretending
@@ -105,37 +108,37 @@ class PublicCommentManipulator(AuthenticationForm):
         c.save()
         # If the commentor has posted fewer than COMMENTS_FIRST_FEW comments,
         # send the comment to the managers.
-        if self.user_cache.get_comments_comment_count() <= COMMENTS_FIRST_FEW:
+        if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW:
             message = ngettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s',
                 'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s') % \
-                {'count': COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
+                {'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
             mail_managers("Comment posted by rookie user", message)
-        if COMMENTS_SKETCHY_USERS_GROUP and COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_group_list()]:
+        if settings.COMMENTS_SKETCHY_USERS_GROUP and settings.COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_group_list()]:
             message = _('This comment was posted by a sketchy user:\n\n%(text)s') % {'text': c.get_as_text()}
             mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text())
         return c
 
-class PublicFreeCommentManipulator(formfields.Manipulator):
+class PublicFreeCommentManipulator(forms.Manipulator):
     "Manipulator that handles public free (unregistered) comments"
     def __init__(self):
         self.fields = (
-            formfields.TextField(field_name="person_name", maxlength=50, is_required=True,
+            forms.TextField(field_name="person_name", maxlength=50, is_required=True,
                 validator_list=[self.hasNoProfanities]),
-            formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+            forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
                 validator_list=[self.hasNoProfanities]),
         )
 
     def hasNoProfanities(self, field_data, all_data):
-        if COMMENTS_ALLOW_PROFANITIES:
+        if settings.COMMENTS_ALLOW_PROFANITIES:
             return
         return validators.hasNoProfanities(field_data, all_data)
 
     def get_comment(self, new_data):
         "Helper function"
-        return freecomments.FreeComment(None, new_data["content_type_id"],
+        return FreeComment(None, new_data["content_type_id"],
             new_data["object_id"], new_data["comment"].strip(),
             new_data["person_name"].strip(), datetime.datetime.now(), new_data["is_public"],
-            new_data["ip_address"], False, SITE_ID)
+            new_data["ip_address"], False, settings.SITE_ID)
 
     def save(self, new_data):
         today = datetime.date.today()
@@ -143,7 +146,7 @@ class PublicFreeCommentManipulator(formfields.Manipulator):
         # Check that this comment isn't duplicate. (Sometimes people post
         # comments twice by mistake.) If it is, fail silently by pretending
         # the comment was posted successfully.
-        for old_comment in freecomments.get_list(content_type__id__exact=new_data["content_type_id"],
+        for old_comment in FreeComment.objects.filter(content_type__id__exact=new_data["content_type_id"],
             object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"],
             submit_date__year=today.year, submit_date__month=today.month,
             submit_date__day=today.day):
@@ -190,16 +193,16 @@ def post_comment(request):
         raise Http404, _("One or more of the required fields wasn't submitted")
     photo_options = request.POST.get('photo_options', '')
     rating_options = normalize_newlines(request.POST.get('rating_options', ''))
-    if comments.get_security_hash(options, photo_options, rating_options, target) != security_hash:
+    if Comment.objects.get_security_hash(options, photo_options, rating_options, target) != security_hash:
         raise Http404, _("Somebody tampered with the comment form (security violation)")
     # Now we can be assured the data is valid.
     if rating_options:
-        rating_range, rating_choices = comments.get_rating_options(base64.decodestring(rating_options))
+        rating_range, rating_choices = Comment.objects.get_rating_options(base64.decodestring(rating_options))
     else:
         rating_range, rating_choices = [], []
     content_type_id, object_id = target.split(':') # target is something like '52:5157'
     try:
-        obj = contenttypes.get_object(pk=content_type_id).get_object_for_this_type(pk=object_id)
+        obj = ContentType.objects.get(pk=content_type_id).get_object_for_this_type(pk=object_id)
     except ObjectDoesNotExist:
         raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid")
     option_list = options.split(',') # options is something like 'pa,ra'
@@ -207,20 +210,20 @@ def post_comment(request):
     new_data['content_type_id'] = content_type_id
     new_data['object_id'] = object_id
     new_data['ip_address'] = request.META.get('REMOTE_ADDR')
-    new_data['is_public'] = comments.IS_PUBLIC in option_list
+    new_data['is_public'] = IS_PUBLIC in option_list
     manipulator = PublicCommentManipulator(request.user,
-        ratings_required=comments.RATINGS_REQUIRED in option_list,
+        ratings_required=RATINGS_REQUIRED in option_list,
         ratings_range=rating_range,
         num_rating_choices=len(rating_choices))
     errors = manipulator.get_validation_errors(new_data)
     # If user gave correct username/password and wasn't already logged in, log them in
     # so they don't have to enter a username/password again.
     if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
-        request.session[users.SESSION_KEY] = manipulator.get_user_id()
+        request.session[SESSION_KEY] = manipulator.get_user_id()
     if errors or request.POST.has_key('preview'):
-        class CommentFormWrapper(formfields.FormWrapper):
+        class CommentFormWrapper(forms.FormWrapper):
             def __init__(self, manipulator, new_data, errors, rating_choices):
-                formfields.FormWrapper.__init__(self, manipulator, new_data, errors)
+                forms.FormWrapper.__init__(self, manipulator, new_data, errors)
                 self.rating_choices = rating_choices
             def ratings(self):
                 field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))]
@@ -229,22 +232,22 @@ def post_comment(request):
                 return field_list
         comment = errors and '' or manipulator.get_comment(new_data)
         comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices)
-        return render_to_response('comments/preview', {
+        return render_to_response('comments/preview.html', {
             'comment': comment,
             'comment_form': comment_form,
             'options': options,
             'target': target,
             'hash': security_hash,
             'rating_options': rating_options,
-            'ratings_optional': comments.RATINGS_OPTIONAL in option_list,
-            'ratings_required': comments.RATINGS_REQUIRED in option_list,
+            'ratings_optional': RATINGS_OPTIONAL in option_list,
+            'ratings_required': RATINGS_REQUIRED in option_list,
             'rating_range': rating_range,
             'rating_choices': rating_choices,
-        }, context_instance=DjangoContext(request))
+        }, context_instance=RequestContext(request))
     elif request.POST.has_key('post'):
         # If the IP is banned, mail the admins, do NOT save the comment, and
         # serve up the "Thanks for posting" page as if the comment WAS posted.
-        if request.META['REMOTE_ADDR'] in BANNED_IPS:
+        if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
             mail_admins("Banned IP attempted to post comment", str(request.POST) + "\n\n" + str(request.META))
         else:
             manipulator.do_html2python(new_data)
@@ -279,10 +282,10 @@ def post_free_comment(request):
         options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
     except KeyError:
         raise Http404, _("One or more of the required fields wasn't submitted")
-    if comments.get_security_hash(options, '', '', target) != security_hash:
+    if Comment.objects.get_security_hash(options, '', '', target) != security_hash:
         raise Http404, _("Somebody tampered with the comment form (security violation)")
     content_type_id, object_id = target.split(':') # target is something like '52:5157'
-    content_type = contenttypes.get_object(pk=content_type_id)
+    content_type = ContentType.objects.get(pk=content_type_id)
     try:
         obj = content_type.get_object_for_this_type(pk=object_id)
     except ObjectDoesNotExist:
@@ -292,22 +295,22 @@ def post_free_comment(request):
     new_data['content_type_id'] = content_type_id
     new_data['object_id'] = object_id
     new_data['ip_address'] = request.META['REMOTE_ADDR']
-    new_data['is_public'] = comments.IS_PUBLIC in option_list
+    new_data['is_public'] = IS_PUBLIC in option_list
     manipulator = PublicFreeCommentManipulator()
     errors = manipulator.get_validation_errors(new_data)
     if errors or request.POST.has_key('preview'):
         comment = errors and '' or manipulator.get_comment(new_data)
-        return render_to_response('comments/free_preview', {
+        return render_to_response('comments/free_preview.html', {
             'comment': comment,
-            'comment_form': formfields.FormWrapper(manipulator, new_data, errors),
+            'comment_form': forms.FormWrapper(manipulator, new_data, errors),
             'options': options,
             'target': target,
             'hash': security_hash,
-        }, context_instance=DjangoContext(request))
+        }, context_instance=RequestContext(request))
     elif request.POST.has_key('post'):
         # If the IP is banned, mail the admins, do NOT save the comment, and
         # serve up the "Thanks for posting" page as if the comment WAS posted.
-        if request.META['REMOTE_ADDR'] in BANNED_IPS:
+        if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
             from django.core.mail import mail_admins
             mail_admins("Practical joker", str(request.POST) + "\n\n" + str(request.META))
         else:
@@ -330,8 +333,8 @@ def comment_was_posted(request):
     if request.GET.has_key('c'):
         content_type_id, object_id = request.GET['c'].split(':')
         try:
-            content_type = contenttypes.get_object(pk=content_type_id)
+            content_type = ContentType.objects.get(pk=content_type_id)
             obj = content_type.get_object_for_this_type(pk=object_id)
         except ObjectDoesNotExist:
             pass
-    return render_to_response('comments/posted', {'object': obj}, context_instance=DjangoContext(request))
+    return render_to_response('comments/posted.html', {'object': obj}, context_instance=RequestContext(request))

+ 10 - 9
django/contrib/comments/views/karma.py

@@ -1,6 +1,7 @@
-from django.core.exceptions import Http404
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.comments import comments, karma
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.comments.models import Comment, KarmaScore
 
 def vote(request, comment_id, vote):
     """
@@ -17,12 +18,12 @@ def vote(request, comment_id, vote):
     if request.user.is_anonymous():
         raise Http404, _("Anonymous users cannot vote")
     try:
-        comment = comments.get_object(pk=comment_id)
-    except comments.CommentDoesNotExist:
+        comment = Comment.objects.get(pk=comment_id)
+    except Comment.DoesNotExist:
         raise Http404, _("Invalid comment ID")
-    if comment.user_id == request.user.id:
+    if comment.user.id == request.user.id:
         raise Http404, _("No voting for yourself")
-    karma.vote(request.user.id, comment_id, rating)
+    KarmaScore.objects.vote(request.user.id, comment_id, rating)
     # Reload comment to ensure we have up to date karma count
-    comment = comments.get_object(pk=comment_id)
-    return render_to_response('comments/karma_vote_accepted', {'comment': comment}, context_instance=DjangoContext(request))
+    comment = Comment.objects.get(pk=comment_id)
+    return render_to_response('comments/karma_vote_accepted.html', {'comment': comment}, context_instance=RequestContext(request))

+ 18 - 29
django/contrib/comments/views/userflags.py

@@ -1,9 +1,10 @@
-from django.core.extensions import DjangoContext, render_to_response
-from django.core.exceptions import Http404
-from django.models.comments import comments, moderatordeletions, userflags
-from django.views.decorators.auth import login_required
-from django.utils.httpwrappers import HttpResponseRedirect
-from django.conf.settings import SITE_ID
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.http import Http404
+from django.contrib.comments.models import Comment, ModeratorDeletion, UserFlag
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect
+from django.conf import settings
 
 def flag(request, comment_id):
     """
@@ -14,22 +15,16 @@ def flag(request, comment_id):
         comment
             the flagged `comments.comments` object
     """
-    try:
-        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
-    except comments.CommentDoesNotExist:
-        raise Http404
+    comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
     if request.POST:
-        userflags.flag(comment, request.user)
+        UserFlag.objects.flag(comment, request.user)
         return HttpResponseRedirect('%sdone/' % request.path)
-    return render_to_response('comments/flag_verify', {'comment': comment}, context_instance=DjangoContext(request))
+    return render_to_response('comments/flag_verify.html', {'comment': comment}, context_instance=RequestContext(request))
 flag = login_required(flag)
 
 def flag_done(request, comment_id):
-    try:
-        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
-    except comments.CommentDoesNotExist:
-        raise Http404
-    return render_to_response('comments/flag_done', {'comment': comment}, context_instance=DjangoContext(request))
+    comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+    return render_to_response('comments/flag_done.html', {'comment': comment}, context_instance=RequestContext(request))
 
 def delete(request, comment_id):
     """
@@ -40,26 +35,20 @@ def delete(request, comment_id):
         comment
             the flagged `comments.comments` object
     """
-    try:
-        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
-    except comments.CommentDoesNotExist:
-        raise Http404
-    if not comments.user_is_moderator(request.user):
+    comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+    if not Comment.objects.user_is_moderator(request.user):
         raise Http404
     if request.POST:
         # If the comment has already been removed, silently fail.
         if not comment.is_removed:
             comment.is_removed = True
             comment.save()
-            m = moderatordeletions.ModeratorDeletion(None, request.user.id, comment.id, None)
+            m = ModeratorDeletion(None, request.user.id, comment.id, None)
             m.save()
         return HttpResponseRedirect('%sdone/' % request.path)
-    return render_to_response('comments/delete_verify', {'comment': comment}, context_instance=DjangoContext(request))
+    return render_to_response('comments/delete_verify.html', {'comment': comment}, context_instance=RequestContext(request))
 delete = login_required(delete)
 
 def delete_done(request, comment_id):
-    try:
-        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
-    except comments.CommentDoesNotExist:
-        raise Http404
-    return render_to_response('comments/delete_done', {'comment': comment}, context_instance=DjangoContext(request))
+    comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+    return render_to_response('comments/delete_done.html', {'comment': comment}, context_instance=RequestContext(request))

+ 0 - 0
django/contrib/admin/urls/__init__.py → django/contrib/contenttypes/__init__.py


+ 49 - 0
django/contrib/contenttypes/models.py

@@ -0,0 +1,49 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+class ContentTypeManager(models.Manager):
+    def get_for_model(self, model):
+        """
+        Returns the ContentType object for the given model, creating the
+        ContentType if necessary.
+        """
+        opts = model._meta
+        try:
+            return self.model._default_manager.get(app_label=opts.app_label,
+                model=opts.object_name.lower())
+        except self.model.DoesNotExist:
+            # The str() is needed around opts.verbose_name because it's a
+            # django.utils.functional.__proxy__ object.
+            ct = self.model(name=str(opts.verbose_name),
+                app_label=opts.app_label, model=opts.object_name.lower())
+            ct.save()
+            return ct
+
+class ContentType(models.Model):
+    name = models.CharField(maxlength=100)
+    app_label = models.CharField(maxlength=100)
+    model = models.CharField(_('python model class name'), maxlength=100)
+    objects = ContentTypeManager()
+    class Meta:
+        verbose_name = _('content type')
+        verbose_name_plural = _('content types')
+        db_table = 'django_content_type'
+        ordering = ('name',)
+        unique_together = (('app_label', 'model'),)
+
+    def __repr__(self):
+        return self.name
+
+    def model_class(self):
+        "Returns the Python model class for this type of content."
+        from django.db import models
+        return models.get_model(self.app_label, self.model)
+
+    def get_object_for_this_type(self, **kwargs):
+        """
+        Returns an object of this type for the keyword arguments given.
+        Basically, this is a proxy around this object_type's get_object() model
+        method. The ObjectNotExist exception, if thrown, will not be caught,
+        so code that calls this method should catch it.
+        """
+        return self.model_class()._default_manager.get(**kwargs)

+ 3 - 3
django/contrib/flatpages/middleware.py

@@ -1,6 +1,6 @@
 from django.contrib.flatpages.views import flatpage
-from django.core.extensions import Http404
-from django.conf.settings import DEBUG
+from django.http import Http404
+from django.conf import settings
 
 class FlatpageFallbackMiddleware:
     def process_response(self, request, response):
@@ -13,6 +13,6 @@ class FlatpageFallbackMiddleware:
         except Http404:
             return response
         except:
-            if DEBUG:
+            if settings.DEBUG:
                 raise
             return response

+ 33 - 0
django/contrib/flatpages/models.py

@@ -0,0 +1,33 @@
+from django.core import validators
+from django.db import models
+from django.contrib.sites.models import Site
+from django.utils.translation import gettext_lazy as _
+
+class FlatPage(models.Model):
+    url = models.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
+        help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
+    title = models.CharField(_('title'), maxlength=200)
+    content = models.TextField(_('content'))
+    enable_comments = models.BooleanField(_('enable comments'))
+    template_name = models.CharField(_('template name'), maxlength=70, blank=True,
+        help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."))
+    registration_required = models.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
+    sites = models.ManyToManyField(Site)
+    class Meta:
+        db_table = 'django_flatpage'
+        verbose_name = _('flat page')
+        verbose_name_plural = _('flat pages')
+        ordering = ('url',)
+    class Admin:
+        fields = (
+            (None, {'fields': ('url', 'title', 'content', 'sites')}),
+            ('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
+        )
+        list_filter = ('sites',)
+        search_fields = ('url', 'title')
+
+    def __repr__(self):
+        return "%s -- %s" % (self.url, self.title)
+
+    def get_absolute_url(self):
+        return self.url

+ 0 - 1
django/contrib/flatpages/models/__init__.py

@@ -1 +0,0 @@
-__all__ = ['flatpages']

+ 0 - 33
django/contrib/flatpages/models/flatpages.py

@@ -1,33 +0,0 @@
-from django.core import meta, validators
-from django.models.core import Site
-from django.utils.translation import gettext_lazy as _
-
-class FlatPage(meta.Model):
-    url = meta.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
-        help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
-    title = meta.CharField(_('title'), maxlength=200)
-    content = meta.TextField(_('content'))
-    enable_comments = meta.BooleanField(_('enable comments'))
-    template_name = meta.CharField(_('template name'), maxlength=70, blank=True,
-        help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."))
-    registration_required = meta.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
-    sites = meta.ManyToManyField(Site)
-    class META:
-        db_table = 'django_flatpages'
-        verbose_name = _('flat page')
-        verbose_name_plural = _('flat pages')
-        ordering = ('url',)
-        admin = meta.Admin(
-            fields = (
-                (None, {'fields': ('url', 'title', 'content', 'sites')}),
-                ('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
-            ),
-            list_filter = ('sites',),
-            search_fields = ('url', 'title'),
-        )
-
-    def __repr__(self):
-        return "%s -- %s" % (self.url, self.title)
-
-    def get_absolute_url(self):
-        return self.url

+ 12 - 12
django/contrib/flatpages/views.py

@@ -1,10 +1,10 @@
-from django.core import template_loader
-from django.core.extensions import get_object_or_404, DjangoContext
-from django.models.flatpages import flatpages
-from django.utils.httpwrappers import HttpResponse
-from django.conf.settings import SITE_ID
+from django.contrib.flatpages.models import FlatPage
+from django.template import loader, RequestContext
+from django.shortcuts import get_object_or_404
+from django.http import HttpResponse
+from django.conf import settings
 
-DEFAULT_TEMPLATE = 'flatpages/default'
+DEFAULT_TEMPLATE = 'flatpages/default.html'
 
 def flatpage(request, url):
     """
@@ -12,24 +12,24 @@ def flatpage(request, url):
 
     Models: `flatpages.flatpages`
     Templates: Uses the template defined by the ``template_name`` field,
-        or `flatpages/default` if template_name is not defined.
+        or `flatpages/default.html` if template_name is not defined.
     Context:
         flatpage
             `flatpages.flatpages` object
     """
     if not url.startswith('/'):
         url = "/" + url
-    f = get_object_or_404(flatpages, url__exact=url, sites__id__exact=SITE_ID)
+    f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID)
     # If registration is required for accessing this page, and the user isn't
     # logged in, redirect to the login page.
     if f.registration_required and request.user.is_anonymous():
-        from django.views.auth.login import redirect_to_login
+        from django.contrib.auth.views import redirect_to_login
         return redirect_to_login(request.path)
     if f.template_name:
-        t = template_loader.select_template((f.template_name, DEFAULT_TEMPLATE))
+        t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
     else:
-        t = template_loader.get_template(DEFAULT_TEMPLATE)
-    c = DjangoContext(request, {
+        t = loader.get_template(DEFAULT_TEMPLATE)
+    c = RequestContext(request, {
         'flatpage': f,
     })
     return HttpResponse(t.render(c))

+ 8 - 1
django/contrib/markup/templatetags/markup.py

@@ -14,7 +14,8 @@ In each case, if the required library is not installed, the filter will
 silently fail and return the un-marked-up text.
 """
 
-from django.core import template
+from django import template
+from django.conf import settings
 
 register = template.Library()
 
@@ -22,6 +23,8 @@ def textile(value):
     try:
         import textile
     except ImportError:
+        if settings.DEBUG:
+            raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
         return value
     else:
         return textile.textile(value)
@@ -30,6 +33,8 @@ def markdown(value):
     try:
         import markdown
     except ImportError:
+        if settings.DEBUG:
+            raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
         return value
     else:
         return markdown.markdown(value)
@@ -38,6 +43,8 @@ def restructuredtext(value):
     try:
         from docutils.core import publish_parts
     except ImportError:
+        if settings.DEBUG:
+            raise template.TemplateSyntaxError, "Error in {% restructuredtext %} filter: The Python docutils library isn't installed."
         return value
     else:
         parts = publish_parts(source=value, writer_name="html4css1")

Some files were not shown because too many files changed in this diff