From 25264c86048d442a4885dfebae94510e2fa0c1e4 Mon Sep 17 00:00:00 2001
From: Adrian Holovaty <adrian@holovaty.com>
Date: Thu, 25 Aug 2005 22:51:30 +0000
Subject: [PATCH] Fixed #122 -- BIG, BACKWARDS-INCOMPATIBLE CHANGE. Changed
 model syntax to use fieldname=FieldClass() syntax. See
 ModelSyntaxChangeInstructions for important information on how to change your
 models

git-svn-id: http://code.djangoproject.com/svn/django/trunk@549 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/contrib/comments/models/comments.py    | 207 +++++++------
 .../contrib/comments/templatetags/comments.py |   4 +-
 django/contrib/comments/views/comments.py     |   6 +-
 django/contrib/comments/views/userflags.py    |   8 +-
 django/core/management.py                     |  18 +-
 django/core/meta/__init__.py                  | 283 +++++++++++-------
 django/core/meta/fields.py                    |  98 +++---
 django/models/auth.py                         | 136 ++++-----
 django/models/core.py                         | 132 ++++----
 django/templatetags/log.py                    |  12 +-
 django/views/admin/main.py                    |  20 +-
 django/views/defaults.py                      |   4 +-
 docs/db-api.txt                               |  49 ++-
 docs/faq.txt                                  |   9 +-
 docs/forms.txt                                |  16 +-
 docs/model-api.txt                            | 104 +++----
 docs/overview.txt                             |  31 +-
 docs/tutorial01.txt                           |  46 ++-
 docs/tutorial02.txt                           |  12 +-
 tests/testapp/models/__init__.py              |   3 +-
 tests/testapp/models/basic.py                 |   6 +-
 tests/testapp/models/custom_methods.py        |   6 +-
 tests/testapp/models/custom_pk.py             |  11 +-
 tests/testapp/models/get_latest.py            |   9 +-
 tests/testapp/models/lookup.py                |   9 +-
 tests/testapp/models/m2m_intermediary.py      |  30 +-
 tests/testapp/models/m2o_recursive.py         |  18 +-
 tests/testapp/models/m2o_recursive2.py        |  22 +-
 tests/testapp/models/many_to_many.py          |  10 +-
 tests/testapp/models/many_to_one.py           |  54 +++-
 tests/testapp/models/many_to_one_null.py      |  77 +++++
 tests/testapp/models/one_to_one.py            |  27 +-
 tests/testapp/models/ordering.py              |  17 +-
 tests/testapp/models/repr.py                  |   8 +-
 tests/testapp/models/save_delete_hooks.py     |   6 +-
 tests/testapp/models/subclassing.py           | 168 +++++++++++
 36 files changed, 956 insertions(+), 720 deletions(-)
 create mode 100644 tests/testapp/models/many_to_one_null.py
 create mode 100644 tests/testapp/models/subclassing.py

diff --git a/django/contrib/comments/models/comments.py b/django/contrib/comments/models/comments.py
index 61cb6664fc..33a44c8494 100644
--- a/django/contrib/comments/models/comments.py
+++ b/django/contrib/comments/models/comments.py
@@ -2,59 +2,56 @@ from django.core import meta
 from django.models import auth, core
 
 class Comment(meta.Model):
-    db_table = 'comments'
-    fields = (
-        meta.ForeignKey(auth.User, raw_id_admin=True),
-        meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type'),
-        meta.IntegerField('object_id', 'object ID'),
-        meta.CharField('headline', 'headline', maxlength=255, blank=True),
-        meta.TextField('comment', 'comment', maxlength=3000),
-        meta.PositiveSmallIntegerField('rating1', 'rating #1', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating2', 'rating #2', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating3', 'rating #3', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating4', 'rating #4', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating5', 'rating #5', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating6', 'rating #6', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating7', 'rating #7', blank=True, null=True),
-        meta.PositiveSmallIntegerField('rating8', '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 review on the same thing, but the system
-        # will only use the latest one (with valid_rating=True) in tallying
-        # the reviews.
-        meta.BooleanField('valid_rating', 'is valid rating'),
-        meta.DateTimeField('submit_date', 'date/time submitted', auto_now_add=True),
-        meta.BooleanField('is_public', 'is public'),
-        meta.IPAddressField('ip_address', 'IP address', blank=True, null=True),
-        meta.BooleanField('is_removed', 'is removed',
-            help_text='Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'),
-        meta.ForeignKey(core.Site),
-    )
-    module_constants = {
-        # min. and max. allowed dimensions for photo resizing (in pixels)
-        'MIN_PHOTO_DIMENSION': 5,
-        'MAX_PHOTO_DIMENSION': 1000,
+    user = meta.ForeignKey(auth.User, raw_id_admin=True)
+    content_type = meta.ForeignKey(core.ContentType)
+    object_id = meta.IntegerField('object ID')
+    headline = meta.CharField(maxlength=255, blank=True)
+    comment = meta.TextField(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()
+    ip_address = meta.IPAddressField('IP address', blank=True, null=True)
+    is_removed = meta.BooleanField(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'
+        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_id', 'object_id', 'site_id')}),
-            ('Content', {'fields': ('user_id', '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_id', 'submit_date', 'content_type_id', 'get_content_object'),
-        list_filter = ('submit_date',),
-        date_hierarchy = 'submit_date',
-        search_fields = ('comment', 'user__username'),
-    )
+            # 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])
@@ -156,32 +153,31 @@ class Comment(meta.Model):
         return False
 
 class FreeComment(meta.Model):
-    "A FreeComment is a comment by a non-registered user"
-    db_table = 'comments_free'
-    fields = (
-        meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type'),
-        meta.IntegerField('object_id', 'object ID'),
-        meta.TextField('comment', 'comment', maxlength=3000),
-        meta.CharField('person_name', "person's name", maxlength=50),
-        meta.DateTimeField('submit_date', 'date/time submitted', auto_now_add=True),
-        meta.BooleanField('is_public', 'is public'),
-        meta.IPAddressField('ip_address', 'IP address'),
-        # TODO: Change this to is_removed, like Comment
-        meta.BooleanField('approved', 'approved by staff'),
-        meta.ForeignKey(core.Site),
-    )
-    ordering = ('-submit_date',)
-    admin = meta.Admin(
-        fields = (
-            (None, {'fields': ('content_type_id', 'object_id', 'site_id')}),
-            ('Content', {'fields': ('person_name', 'comment')}),
-            ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
-        ),
-        list_display = ('person_name', 'submit_date', 'content_type_id', 'get_content_object'),
-        list_filter = ('submit_date',),
-        date_hierarchy = 'submit_date',
-        search_fields = ('comment', 'person_name'),
-    )
+    # 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(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()
+    ip_address = meta.IPAddressField()
+    # 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'
+        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])
@@ -203,26 +199,25 @@ class FreeComment(meta.Model):
     get_content_object.short_description = 'Content object'
 
 class KarmaScore(meta.Model):
-    module_name = 'karma'
-    fields = (
-        meta.ForeignKey(auth.User),
-        meta.ForeignKey(Comment),
-        meta.SmallIntegerField('score', 'score', db_index=True),
-        meta.DateTimeField('scored_date', 'date scored', auto_now=True),
-    )
-    unique_together = (('user_id', 'comment_id'),)
-    module_constants = {
-        # what users get if they don't have any karma
-        'DEFAULT_KARMA': 5,
-        'KARMA_NEEDED_BEFORE_DISPLAYED': 3,
-    }
+    user = meta.ForeignKey(auth.User)
+    comment = meta.ForeignKey(Comment)
+    score = meta.SmallIntegerField(db_index=True)
+    scored_date = meta.DateTimeField(auto_now=True)
+    class META:
+        module_name = 'karma'
+        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 "%d rating by %s" % (self.score, 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)
+            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()
@@ -241,13 +236,12 @@ class KarmaScore(meta.Model):
         return int(round((4.5 * score) + 5.5))
 
 class UserFlag(meta.Model):
-    db_table = 'comments_user_flags'
-    fields = (
-        meta.ForeignKey(auth.User),
-        meta.ForeignKey(Comment),
-        meta.DateTimeField('flag_date', 'date flagged', auto_now_add=True),
-    )
-    unique_together = (('user_id', 'comment_id'),)
+    user = meta.ForeignKey(auth.User)
+    comment = meta.ForeignKey(Comment)
+    flag_date = meta.DateTimeField(auto_now_add=True)
+    class META:
+        db_table = 'comments_user_flags'
+        unique_together = (('user', 'comment'),)
 
     def __repr__(self):
         return "Flag by %r" % self.get_user()
@@ -261,7 +255,7 @@ class UserFlag(meta.Model):
         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)
+            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)
@@ -270,13 +264,12 @@ class UserFlag(meta.Model):
             f.save()
 
 class ModeratorDeletion(meta.Model):
-    db_table = 'comments_moderator_deletions'
-    fields = (
-        meta.ForeignKey(auth.User, verbose_name='moderator'),
-        meta.ForeignKey(Comment),
-        meta.DateTimeField('deletion_date', 'date deleted', auto_now_add=True),
-    )
-    unique_together = (('user_id', 'comment_id'),)
+    user = meta.ForeignKey(auth.User, verbose_name='moderator')
+    comment = meta.ForeignKey(Comment)
+    deletion_date = meta.DateTimeField(auto_now_add=True)
+    class META:
+        db_table = 'comments_moderator_deletions'
+        unique_together = (('user', 'comment'),)
 
     def __repr__(self):
         return "Moderator deletion by %r" % self.get_user()
diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py
index f54ef1e29e..f7da28b19e 100644
--- a/django/contrib/comments/templatetags/comments.py
+++ b/django/contrib/comments/templatetags/comments.py
@@ -123,7 +123,7 @@ class CommentCountNode(template.Node):
             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)
+            content_type__python_module_name__exact=self.module, site__id__exact=SITE_ID)
         context[self.var_name] = comment_count
         return ''
 
@@ -146,7 +146,7 @@ class CommentListNode(template.Node):
             '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,
+            'site__id__exact': SITE_ID,
             'select_related': True,
             'order_by': (self.ordering + 'submit_date',),
         }
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 33972a4c52..8798c05a01 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -86,8 +86,8 @@ class PublicCommentManipulator(AuthenticationForm):
     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"],
-            object_id__exact=new_data["object_id"], user_id__exact=self.get_user_id()):
+        for old in comments.get_list(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
             # the comment was posted successfully.
@@ -141,7 +141,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 freecomments.get_list(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):
diff --git a/django/contrib/comments/views/userflags.py b/django/contrib/comments/views/userflags.py
index 59664f3cab..3395fe017e 100644
--- a/django/contrib/comments/views/userflags.py
+++ b/django/contrib/comments/views/userflags.py
@@ -16,7 +16,7 @@ def flag(request, comment_id):
             the flagged `comments.comments` object
     """
     try:
-        comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
+        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
     except comments.CommentDoesNotExist:
         raise Http404
     if request.POST:
@@ -31,7 +31,7 @@ flag = login_required(flag)
 
 def flag_done(request, comment_id):
     try:
-        comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
+        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
     except comments.CommentDoesNotExist:
         raise Http404
     t = template_loader.get_template('comments/flag_done')
@@ -50,7 +50,7 @@ def delete(request, comment_id):
             the flagged `comments.comments` object
     """
     try:
-        comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
+        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):
@@ -72,7 +72,7 @@ delete = login_required(delete)
 
 def delete_done(request, comment_id):
     try:
-        comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
+        comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
     except comments.CommentDoesNotExist:
         raise Http404
     t = template_loader.get_template('comments/delete_done')
diff --git a/django/core/management.py b/django/core/management.py
index 8d7d7e2a93..9e3c84e9f1 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -67,7 +67,7 @@ def get_sql_create(mod):
                 data_type = f.__class__.__name__
             col_type = db.DATA_TYPES[data_type]
             if col_type is not None:
-                field_output = [f.name, col_type % rel_field.__dict__]
+                field_output = [f.column, col_type % rel_field.__dict__]
                 field_output.append('%sNULL' % (not f.null and 'NOT ' or ''))
                 if f.unique:
                     field_output.append('UNIQUE')
@@ -75,12 +75,12 @@ def get_sql_create(mod):
                     field_output.append('PRIMARY KEY')
                 if f.rel:
                     field_output.append('REFERENCES %s (%s)' % \
-                        (f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).name))
+                        (f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).column))
                 table_output.append(' '.join(field_output))
         if opts.order_with_respect_to:
             table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField'])
         for field_constraints in opts.unique_together:
-            table_output.append('UNIQUE (%s)' % ", ".join(field_constraints))
+            table_output.append('UNIQUE (%s)' % ", ".join([opts.get_field(f).column for f in field_constraints]))
 
         full_statement = ['CREATE TABLE %s (' % opts.db_table]
         for i, line in enumerate(table_output): # Combine and add commas.
@@ -94,9 +94,9 @@ def get_sql_create(mod):
             table_output = ['CREATE TABLE %s (' % f.get_m2m_db_table(opts)]
             table_output.append('    id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField'])
             table_output.append('    %s_id %s NOT NULL REFERENCES %s (%s),' % \
-                (opts.object_name.lower(), db.DATA_TYPES['IntegerField'], opts.db_table, opts.pk.name))
+                (opts.object_name.lower(), db.DATA_TYPES['IntegerField'], opts.db_table, opts.pk.column))
             table_output.append('    %s_id %s NOT NULL REFERENCES %s (%s),' % \
-                (f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.name))
+                (f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.column))
             table_output.append('    UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower()))
             table_output.append(');')
             final_output.append('\n'.join(table_output))
@@ -186,7 +186,7 @@ def get_sql_sequence_reset(mod):
     for klass in mod._MODELS:
         for f in klass._meta.fields:
             if isinstance(f, meta.AutoField):
-                output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.name, f.name, klass._meta.db_table))
+                output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.column, f.column, klass._meta.db_table))
     return output
 get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app(s)."
 get_sql_sequence_reset.args = APP_ARGS
@@ -199,7 +199,7 @@ def get_sql_indexes(mod):
             if f.db_index:
                 unique = f.unique and "UNIQUE " or ""
                 output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
-                    (unique, klass._meta.db_table, f.name, klass._meta.db_table, f.name))
+                    (unique, klass._meta.db_table, f.column, klass._meta.db_table, f.column))
     return output
 get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given app(s)."
 get_sql_indexes.args = APP_ARGS
@@ -490,7 +490,7 @@ class ModelErrorCollection:
 
     def add(self, opts, error):
         self.errors.append((opts, error))
-        self.outfile.write("%s.%s: %s\n" % (opts.module_name, opts.object_name, error))
+        self.outfile.write("%s.%s: %s\n" % (opts.app_label, opts.module_name, error))
 
 def validate():
     "Validates all installed models."
@@ -524,6 +524,8 @@ def validate():
                     if field_name == '?': continue
                     if field_name.startswith('-'):
                         field_name = field_name[1:]
+                    if opts.order_with_respect_to and field_name == '_order':
+                        continue
                     try:
                         opts.get_field(field_name, many_to_many=False)
                     except meta.FieldDoesNotExist:
diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py
index 5846fdcc5c..6f72090e84 100644
--- a/django/core/meta/__init__.py
+++ b/django/core/meta/__init__.py
@@ -50,15 +50,21 @@ def handle_legacy_orderlist(order_list):
         warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning)
         return new_order_list
 
-def orderlist2sql(order_list, prefix=''):
+def orderfield2column(f, opts):
+    try:
+        return opts.get_field(f, False).column
+    except FieldDoesNotExist:
+        return f
+
+def orderlist2sql(order_list, opts, prefix=''):
     output = []
     for f in handle_legacy_orderlist(order_list):
         if f.startswith('-'):
-            output.append('%s%s DESC' % (prefix, f[1:]))
+            output.append('%s%s DESC' % (prefix, orderfield2column(f[1:], opts)))
         elif f == '?':
             output.append('RANDOM()')
         else:
-            output.append('%s%s ASC' % (prefix, f))
+            output.append('%s%s ASC' % (prefix, orderfield2column(f, opts)))
     return ', '.join(output)
 
 def get_module(app_label, module_name):
@@ -206,7 +212,7 @@ class Options:
         # If a primary_key field hasn't been specified, add an
         # auto-incrementing primary-key ID field automatically.
         if self.pk is None:
-            self.fields.insert(0, AutoField('id', 'ID', primary_key=True))
+            self.fields.insert(0, AutoField(name='id', verbose_name='ID', primary_key=True))
             self.pk = self.fields[0]
         # Cache whether this has an AutoField.
         self.has_auto_field = False
@@ -249,7 +255,7 @@ class Options:
         "Returns the full 'ORDER BY' clause for this object, according to self.ordering."
         if not self.ordering: return ''
         pre = table_prefix and (table_prefix + '.') or ''
-        return 'ORDER BY ' + orderlist2sql(self.ordering, pre)
+        return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
 
     def get_add_permission(self):
         return 'add_%s' % self.object_name.lower()
@@ -298,7 +304,7 @@ class Options:
                 # subsequently loaded object with related links will override this
                 # relationship we're adding.
                 link_field = copy.copy(core.RelatedLink._meta.get_field('object_id'))
-                link_field.rel = ManyToOne(self.get_model_module().Klass, 'related_links', 'id',
+                link_field.rel = ManyToOne(self.get_model_module().Klass, 'id',
                     num_in_admin=3, min_num_in_admin=3, edit_inline=TABULAR,
                     lookup_overrides={
                         'content_type__package__label__exact': self.app_label,
@@ -386,34 +392,48 @@ class ModelBase(type):
         if not bases:
             return type.__new__(cls, name, bases, attrs)
 
-        # If this model is a subclass of another Model, create an Options
+        try:
+            meta_attrs = attrs.pop('META').__dict__
+            del meta_attrs['__module__']
+            del meta_attrs['__doc__']
+        except KeyError:
+            meta_attrs = {}
+
+        # Gather all attributes that are Field instances.
+        fields = []
+        for obj_name, obj in attrs.items():
+            if isinstance(obj, Field):
+                obj.set_name(obj_name)
+                fields.append(obj)
+                del attrs[obj_name]
+
+        # Sort the fields in the order that they were created. The
+        # "creation_counter" is needed because metaclasses don't preserve the
+        # attribute order.
+        fields.sort(lambda x, y: x.creation_counter - y.creation_counter)
+
+        # If this model is a subclass of another model, create an Options
         # object by first copying the base class's _meta and then updating it
         # with the overrides from this class.
         replaces_module = None
         if bases[0] != Model:
-            if not attrs.has_key('fields'):
-                attrs['fields'] = list(bases[0]._meta._orig_init_args['fields'][:])
-            if attrs.has_key('ignore_fields'):
-                ignore_fields = attrs.pop('ignore_fields')
-                new_fields = []
-                for i, f in enumerate(attrs['fields']):
-                    if f.name not in ignore_fields:
-                        new_fields.append(f)
-                attrs['fields'] = new_fields
-            if attrs.has_key('add_fields'):
-                attrs['fields'].extend(attrs.pop('add_fields'))
-            if attrs.has_key('replaces_module'):
+            field_names = [f.name for f in fields]
+            remove_fields = meta_attrs.pop('remove_fields', [])
+            for f in bases[0]._meta._orig_init_args['fields']:
+                if f.name not in field_names and f.name not in remove_fields:
+                    fields.insert(0, f)
+            if meta_attrs.has_key('replaces_module'):
                 # Set the replaces_module variable for now. We can't actually
                 # do anything with it yet, because the module hasn't yet been
                 # created.
-                replaces_module = attrs.pop('replaces_module').split('.')
+                replaces_module = meta_attrs.pop('replaces_module').split('.')
             # Pass any Options overrides to the base's Options instance, and
             # simultaneously remove them from attrs. When this is done, attrs
             # will be a dictionary of custom methods, plus __module__.
-            meta_overrides = {}
-            for k, v in attrs.items():
+            meta_overrides = {'fields': fields}
+            for k, v in meta_attrs.items():
                 if not callable(v) and k != '__module__':
-                    meta_overrides[k] = attrs.pop(k)
+                    meta_overrides[k] = meta_attrs.pop(k)
             opts = bases[0]._meta.copy(**meta_overrides)
             opts.object_name = name
             del meta_overrides
@@ -422,28 +442,31 @@ class ModelBase(type):
                 # If the module_name wasn't given, use the class name
                 # in lowercase, plus a trailing "s" -- a poor-man's
                 # pluralization.
-                module_name = attrs.pop('module_name', name.lower() + 's'),
+                module_name = meta_attrs.pop('module_name', name.lower() + 's'),
                 # If the verbose_name wasn't given, use the class name,
                 # converted from InitialCaps to "lowercase with spaces".
-                verbose_name = attrs.pop('verbose_name',
+                verbose_name = meta_attrs.pop('verbose_name',
                     re.sub('([A-Z])', ' \\1', name).lower().strip()),
-                verbose_name_plural = attrs.pop('verbose_name_plural', ''),
-                db_table = attrs.pop('db_table', ''),
-                fields = attrs.pop('fields'),
-                ordering = attrs.pop('ordering', None),
-                unique_together = attrs.pop('unique_together', None),
-                admin = attrs.pop('admin', None),
-                has_related_links = attrs.pop('has_related_links', False),
-                where_constraints = attrs.pop('where_constraints', None),
+                verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''),
+                db_table = meta_attrs.pop('db_table', ''),
+                fields = fields,
+                ordering = meta_attrs.pop('ordering', None),
+                unique_together = meta_attrs.pop('unique_together', None),
+                admin = meta_attrs.pop('admin', None),
+                has_related_links = meta_attrs.pop('has_related_links', False),
+                where_constraints = meta_attrs.pop('where_constraints', None),
                 object_name = name,
-                app_label = attrs.pop('app_label', None),
-                exceptions = attrs.pop('exceptions', None),
-                permissions = attrs.pop('permissions', None),
-                get_latest_by = attrs.pop('get_latest_by', None),
-                order_with_respect_to = attrs.pop('order_with_respect_to', None),
-                module_constants = attrs.pop('module_constants', None),
+                app_label = meta_attrs.pop('app_label', None),
+                exceptions = meta_attrs.pop('exceptions', None),
+                permissions = meta_attrs.pop('permissions', None),
+                get_latest_by = meta_attrs.pop('get_latest_by', None),
+                order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
+                module_constants = meta_attrs.pop('module_constants', None),
             )
 
+        if meta_attrs != {}:
+            raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
+
         # Dynamically create the module that will contain this class and its
         # associated helper functions.
         if replaces_module is not None:
@@ -511,7 +534,7 @@ class ModelBase(type):
             # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
             if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
                 f.rel.to = opts
-                f.name = f.name or ((f.rel.name or f.rel.to.object_name.lower()) + '_' + f.rel.to.pk.name)
+                f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name)
                 f.verbose_name = f.verbose_name or f.rel.to.verbose_name
                 f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
             # Add "get_thingie" methods for many-to-one related objects.
@@ -519,14 +542,14 @@ class ModelBase(type):
             if isinstance(f.rel, ManyToOne):
                 func = curry(method_get_many_to_one, f)
                 func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name)
-                attrs['get_%s' % f.rel.name] = func
+                attrs['get_%s' % f.name] = func
 
         for f in opts.many_to_many:
             # Add "get_thingie" methods for many-to-many related objects.
             # EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
             func = curry(method_get_many_to_many, f)
             func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name)
-            attrs['get_%s_list' % f.rel.name] = func
+            attrs['get_%s_list' % f.rel.singular] = func
             # Add "set_thingie" methods for many-to-many related objects.
             # EXAMPLES: Poll.set_sites(), Story.set_bylines()
             func = curry(method_set_many_to_many, f)
@@ -711,14 +734,36 @@ class Model:
 def method_init(opts, self, *args, **kwargs):
     if kwargs:
         for f in opts.fields:
-            setattr(self, f.name, kwargs.pop(f.name, f.get_default()))
+            if isinstance(f.rel, ManyToOne):
+                try:
+                    # Assume object instance was passed in.
+                    rel_obj = kwargs.pop(f.name)
+                except KeyError:
+                    try:
+                        # Object instance wasn't passed in -- must be an ID.
+                        val = kwargs.pop(f.column)
+                    except KeyError:
+                        val = f.get_default()
+                else:
+                    # Special case: You can pass in "None" for related objects if it's allowed.
+                    if rel_obj is None and f.null:
+                        val = None
+                    else:
+                        try:
+                            val = getattr(rel_obj, f.rel.field_name)
+                        except AttributeError:
+                            raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
+                setattr(self, f.column, val)
+            else:
+                val = kwargs.pop(f.name, f.get_default())
+                setattr(self, f.name, val)
         if kwargs:
             raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
     for i, arg in enumerate(args):
-        setattr(self, opts.fields[i].name, arg)
+        setattr(self, opts.fields[i].column, arg)
 
 def method_eq(opts, self, other):
-    return isinstance(other, self.__class__) and getattr(self, opts.pk.name) == getattr(other, opts.pk.name)
+    return isinstance(other, self.__class__) and getattr(self, opts.pk.column) == getattr(other, opts.pk.column)
 
 def method_save(opts, self):
     # Run any pre-save hooks.
@@ -728,41 +773,41 @@ def method_save(opts, self):
     cursor = db.db.cursor()
 
     # First, try an UPDATE. If that doesn't update anything, do an INSERT.
-    pk_val = getattr(self, opts.pk.name)
+    pk_val = getattr(self, opts.pk.column)
     pk_set = bool(pk_val)
     record_exists = True
     if pk_set:
         # Determine whether a record with the primary key already exists.
-        cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.name), [pk_val])
+        cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.column), [pk_val])
         # If it does already exist, do an UPDATE.
         if cursor.fetchone():
-            db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.name), False)) for f in non_pks]
+            db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
             cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
-                ','.join(['%s=%%s' % f.name for f in non_pks]), opts.pk.name),
+                ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
                 db_values + [pk_val])
         else:
             record_exists = False
     if not pk_set or not record_exists:
-        field_names = [f.name for f in opts.fields if not isinstance(f, AutoField)]
+        field_names = [f.column for f in opts.fields if not isinstance(f, AutoField)]
         placeholders = ['%s'] * len(field_names)
-        db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.name), True)) for f in opts.fields if not isinstance(f, AutoField)]
+        db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in opts.fields if not isinstance(f, AutoField)]
         if opts.order_with_respect_to:
             field_names.append('_order')
             # TODO: This assumes the database supports subqueries.
             placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
-                (opts.db_table, opts.order_with_respect_to.name))
-            db_values.append(getattr(self, opts.order_with_respect_to.name))
+                (opts.db_table, opts.order_with_respect_to.column))
+            db_values.append(getattr(self, opts.order_with_respect_to.column))
         cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table,
             ','.join(field_names), ','.join(placeholders)), db_values)
         if opts.has_auto_field:
-            setattr(self, opts.pk.name, db.get_last_insert_id(cursor, opts.db_table, opts.pk.name))
+            setattr(self, opts.pk.column, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column))
     db.db.commit()
     # Run any post-save hooks.
     if hasattr(self, '_post_save'):
         self._post_save()
 
 def method_delete(opts, self):
-    assert getattr(self, opts.pk.name) is not None, "%r can't be deleted because it doesn't have an ID."
+    assert getattr(self, opts.pk.column) is not None, "%r can't be deleted because it doesn't have an ID."
     # Run any pre-delete hooks.
     if hasattr(self, '_pre_delete'):
         self._pre_delete()
@@ -781,15 +826,15 @@ def method_delete(opts, self):
                 sub_obj.delete()
     for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
         cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts),
-            self._meta.object_name.lower()), [getattr(self, opts.pk.name)])
+            self._meta.object_name.lower()), [getattr(self, opts.pk.column)])
     for f in opts.many_to_many:
         cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()),
-            [getattr(self, opts.pk.name)])
-    cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.name), [getattr(self, opts.pk.name)])
+            [getattr(self, opts.pk.column)])
+    cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [getattr(self, opts.pk.column)])
     db.db.commit()
-    setattr(self, opts.pk.name, None)
+    setattr(self, opts.pk.column, None)
     for f in opts.fields:
-        if isinstance(f, FileField) and getattr(self, f.name):
+        if isinstance(f, FileField) and getattr(self, f.column):
             file_name = getattr(self, 'get_%s_filename' % f.name)()
             # If the file exists and no other object of this type references it,
             # delete it from the filesystem.
@@ -802,26 +847,26 @@ def method_delete(opts, self):
 def method_get_next_in_order(opts, order_field, self):
     if not hasattr(self, '_next_in_order_cache'):
         self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',),
-            where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.name),
-                '%s=%%s' % order_field.name], limit=1,
-            params=[getattr(self, opts.pk.name), getattr(self, order_field.name)])
+            where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column),
+                '%s=%%s' % order_field.column], limit=1,
+            params=[getattr(self, opts.pk.column), getattr(self, order_field.name)])
     return self._next_in_order_cache
 
 def method_get_previous_in_order(opts, order_field, self):
     if not hasattr(self, '_previous_in_order_cache'):
         self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',),
-            where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.name),
-                '%s=%%s' % order_field.name], limit=1,
-            params=[getattr(self, opts.pk.name), getattr(self, order_field.name)])
+            where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column),
+                '%s=%%s' % order_field.column], limit=1,
+            params=[getattr(self, opts.pk.column), getattr(self, order_field.name)])
     return self._previous_in_order_cache
 
 # RELATIONSHIP METHODS #####################
 
 # Example: Story.get_dateline()
 def method_get_many_to_one(field_with_rel, self):
-    cache_var = field_with_rel.rel.get_cache_name()
+    cache_var = field_with_rel.get_cache_name()
     if not hasattr(self, cache_var):
-        val = getattr(self, field_with_rel.name)
+        val = getattr(self, field_with_rel.column)
         mod = field_with_rel.rel.to.get_model_module()
         if val is None:
             raise getattr(mod, '%sDoesNotExist' % field_with_rel.rel.to.object_name)
@@ -837,11 +882,11 @@ def method_get_many_to_many(field_with_rel, self):
     if not hasattr(self, cache_var):
         mod = rel.get_model_module()
         sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s_id AND b.%s_id = %%s %s" % \
-            (','.join(['a.%s' % f.name for f in rel.fields]), rel.db_table,
-            field_with_rel.get_m2m_db_table(self._meta), rel.pk.name,
+            (','.join(['a.%s' % f.column for f in rel.fields]), rel.db_table,
+            field_with_rel.get_m2m_db_table(self._meta), rel.pk.column,
             rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a'))
         cursor = db.db.cursor()
-        cursor.execute(sql, [getattr(self, self._meta.pk.name)])
+        cursor.execute(sql, [getattr(self, self._meta.pk.column)])
         setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
     return getattr(self, cache_var)
 
@@ -863,7 +908,7 @@ def method_set_many_to_many(rel_field, self, id_list):
     rel = rel_field.rel.to
     m2m_table = rel_field.get_m2m_db_table(self._meta)
     cursor = db.db.cursor()
-    this_id = getattr(self, self._meta.pk.name)
+    this_id = getattr(self, self._meta.pk.column)
     if ids_to_delete:
         sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete)))
         cursor.execute(sql, [this_id])
@@ -880,7 +925,7 @@ def method_set_many_to_many(rel_field, self, id_list):
 # Handles related-object retrieval.
 # Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()
 def method_get_related(method_name, rel_mod, rel_field, self, **kwargs):
-    kwargs['%s__exact' % rel_field.name] = getattr(self, rel_field.rel.field_name)
+    kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to.pk.name)] = getattr(self, rel_field.rel.field_name)
     kwargs.update(rel_field.rel.lookup_overrides)
     return getattr(rel_mod, method_name)(**kwargs)
 
@@ -892,7 +937,7 @@ def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs):
     for f in rel_obj.fields:
         if isinstance(f, AutoField):
             init_kwargs[f.name] = None
-    init_kwargs[rel_field.name] = getattr(self, rel_field.rel.field_name)
+    init_kwargs[rel_field.name] = self
     obj = rel_mod.Klass(**init_kwargs)
     obj.save()
     return obj
@@ -909,7 +954,7 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
     id_list = map(int, id_list) # normalize to integers
     rel = rel_field.rel.to
     m2m_table = rel_field.get_m2m_db_table(rel_opts)
-    this_id = getattr(self, self._meta.pk.name)
+    this_id = getattr(self, self._meta.pk.column)
     cursor = db.db.cursor()
     cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id])
     sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower())
@@ -921,7 +966,7 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
 def method_set_order(ordered_obj, self, id_list):
     cursor = db.db.cursor()
     # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
-    sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.name, ordered_obj.pk.name)
+    sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.column, ordered_obj.pk.column)
     rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
     cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
     db.db.commit()
@@ -929,7 +974,7 @@ def method_set_order(ordered_obj, self, id_list):
 def method_get_order(ordered_obj, self):
     cursor = db.db.cursor()
     # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
-    sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.name, ordered_obj.db_table, ordered_obj.order_with_respect_to.name)
+    sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.column, ordered_obj.db_table, ordered_obj.order_with_respect_to.column)
     rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
     cursor.execute(sql, [rel_val])
     return [r[0] for r in cursor.fetchall()]
@@ -937,7 +982,7 @@ def method_get_order(ordered_obj, self):
 # DATE-RELATED METHODS #####################
 
 def method_get_next_or_previous(get_object_func, field, is_next, self, **kwargs):
-    kwargs.setdefault('where', []).append('%s %s %%s' % (field.name, (is_next and '>' or '<')))
+    kwargs.setdefault('where', []).append('%s %s %%s' % (field.column, (is_next and '>' or '<')))
     kwargs.setdefault('params', []).append(str(getattr(self, field.name)))
     kwargs['order_by'] = [(not is_next and '-' or '') + field.name]
     kwargs['limit'] = 1
@@ -1045,7 +1090,7 @@ def _get_cached_row(opts, row, index_start):
     for f in opts.fields:
         if f.rel and not f.null:
             rel_obj, index_end = _get_cached_row(f.rel.to, row, index_end)
-            setattr(obj, f.rel.get_cache_name(), rel_obj)
+            setattr(obj, f.get_cache_name(), rel_obj)
     return obj, index_end
 
 def function_get_iterator(opts, klass, **kwargs):
@@ -1091,9 +1136,9 @@ def function_get_values_iterator(opts, klass, **kwargs):
 
     # 'fields' is a list of field names to fetch.
     try:
-        fields = kwargs.pop('fields')
+        fields = [opts.get_field(f).column for f in kwargs.pop('fields')]
     except KeyError: # Default to all fields.
-        fields = [f.name for f in opts.fields]
+        fields = [f.column for f in opts.fields]
 
     cursor = db.db.cursor()
     _, sql, params = function_get_sql_clause(opts, **kwargs)
@@ -1124,8 +1169,8 @@ def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen
                 tables.append('%s %s' % (db_table, new_prefix))
                 db_table = new_prefix
             cache_tables_seen.append(db_table)
-            where.append('%s.%s = %s.%s' % (old_prefix, f.name, db_table, f.rel.field_name))
-            select.extend(['%s.%s' % (db_table, f2.name) for f2 in f.rel.to.fields])
+            where.append('%s.%s = %s.%s' % (old_prefix, f.column, db_table, f.rel.get_related_field().column))
+            select.extend(['%s.%s' % (db_table, f2.column) for f2 in f.rel.to.fields])
             _fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen)
 
 def _throw_bad_kwarg_error(kwarg):
@@ -1158,7 +1203,10 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
         lookup_list = kwarg.split(LOOKUP_SEPARATOR)
         # pk="value" is shorthand for (primary key)__exact="value"
         if lookup_list[-1] == 'pk':
-            lookup_list = lookup_list[:-1] + [opts.pk.name, 'exact']
+            if opts.pk.rel:
+                lookup_list = lookup_list[:-1] + [opts.pk.name, opts.pk.rel.field_name, 'exact']
+            else:
+                lookup_list = lookup_list[:-1] + [opts.pk.name, 'exact']
         if len(lookup_list) == 1:
             _throw_bad_kwarg_error(kwarg)
         lookup_type = lookup_list.pop()
@@ -1184,7 +1232,7 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
                         rel_table_alias = 't%s' % table_count
                         table_count += 1
                         tables.append('%s %s' % (f.get_m2m_db_table(current_opts), rel_table_alias))
-                        join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.name,
+                        join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.column,
                             rel_table_alias, current_opts.object_name.lower()))
                         # Optimization: In the case of primary-key lookups, we
                         # don't have to do an extra join.
@@ -1198,32 +1246,39 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
                             new_table_alias = 't%s' % table_count
                             tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
                             join_where.append('%s.%s_id = %s.%s' % (rel_table_alias, f.rel.to.object_name.lower(),
-                                new_table_alias, f.rel.to.pk.name))
+                                new_table_alias, f.rel.to.pk.column))
                             current_table_alias = new_table_alias
                             param_required = True
                         current_opts = f.rel.to
                         raise StopIteration
                 for f in current_opts.fields:
                     # Try many-to-one relationships...
-                    if f.rel and f.rel.name == current:
+                    if f.rel and f.name == current:
                         # Optimization: In the case of primary-key lookups, we
                         # don't have to do an extra join.
                         if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
-                            where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.name, kwarg_value))
+                            where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
                             params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
                             lookup_list.pop()
                             param_required = False
+                        # 'isnull' lookups in many-to-one relationships are a special case,
+                        # because we don't want to do a join. We just want to find out
+                        # whether the foreign key field is NULL.
+                        elif lookup_type == 'isnull' and not lookup_list:
+                            where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
+                            params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
                         else:
                             new_table_alias = 't%s' % table_count
                             tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
-                            join_where.append('%s.%s = %s.%s' % (current_table_alias, f.name, new_table_alias, f.rel.to.pk.name))
+                            join_where.append('%s.%s = %s.%s' % (current_table_alias, f.column, \
+                                new_table_alias, f.rel.to.pk.column))
                             current_table_alias = new_table_alias
                             param_required = True
                         current_opts = f.rel.to
                         raise StopIteration
                     # Try direct field-name lookups...
                     if f.name == current:
-                        where.append(_get_where_clause(lookup_type, current_table_alias+'.', current, kwarg_value))
+                        where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
                         params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
                         param_required = False
                         raise StopIteration
@@ -1235,7 +1290,7 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
     return tables, join_where, where, params, table_count
 
 def function_get_sql_clause(opts, **kwargs):
-    select = ["%s.%s" % (opts.db_table, f.name) for f in opts.fields]
+    select = ["%s.%s" % (opts.db_table, f.column) for f in opts.fields]
     tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
     where = kwargs.get('where') and kwargs['where'][:] or []
     params = kwargs.get('params') and kwargs['params'][:] or []
@@ -1270,9 +1325,9 @@ def function_get_sql_clause(opts, **kwargs):
             else:
                 table_prefix = ''
             if f.startswith('-'):
-                order_by.append('%s%s DESC' % (table_prefix, f[1:]))
+                order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts)))
             else:
-                order_by.append('%s%s ASC' % (table_prefix, f))
+                order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
     order_by = ", ".join(order_by)
 
     # LIMIT and OFFSET clauses
@@ -1307,9 +1362,9 @@ def function_get_date_list(opts, field, *args, **kwargs):
     assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
     kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
     if field.null:
-        kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.name))
+        kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.column))
     select, sql, params = function_get_sql_clause(opts, **kwargs)
-    sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.name)), sql)
+    sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.column)), sql)
     cursor = db.db.cursor()
     cursor.execute(sql, params)
     # We have to manually run typecast_timestamp(str()) on the results, because
@@ -1359,8 +1414,8 @@ def manipulator_init(opts, add, change, self, obj_key=None):
                 lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
                 lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
                 _ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs)
-                params = dict([(f.name, f.get_default()) for f in opts.fields])
-                params[opts.pk.name] = obj_key
+                params = dict([(f.column, f.get_default()) for f in opts.fields])
+                params[opts.pk.column] = obj_key
                 self.original_object = opts.get_model_module().Klass(**params)
             else:
                 raise
@@ -1396,9 +1451,9 @@ def manipulator_save(opts, klass, add, change, self, new_data):
         # Fields with auto_now_add are another special case; they should keep
         # their original value in the change stage.
         if change and getattr(f, 'auto_now_add', False):
-            params[f.name] = getattr(self.original_object, f.name)
+            params[f.column] = getattr(self.original_object, f.name)
         else:
-            params[f.name] = f.get_manipulator_new_data(new_data)
+            params[f.column] = f.get_manipulator_new_data(new_data)
 
     if change:
         params[opts.pk.name] = self.obj_key
@@ -1416,7 +1471,7 @@ def manipulator_save(opts, klass, add, change, self, new_data):
     if change:
         self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
         for f in opts.fields:
-            if not f.primary_key and str(getattr(self.original_object, f.name)) != str(getattr(new_object, f.name)):
+            if not f.primary_key and str(getattr(self.original_object, f.column)) != str(getattr(new_object, f.column)):
                 self.fields_changed.append(f.verbose_name)
 
     # Save many-to-many objects. Example: Poll.set_sites()
@@ -1467,15 +1522,15 @@ def manipulator_save(opts, klass, add, change, self, new_data):
                 # case, because they'll be dealt with later.
                 if change and (isinstance(f, FileField) or not f.editable):
                     if rel_new_data.get(rel_opts.pk.name, False) and rel_new_data[rel_opts.pk.name][0]:
-                        params[f.name] = getattr(old_rel_obj, f.name)
+                        params[f.column] = getattr(old_rel_obj, f.column)
                     else:
-                        params[f.name] = f.get_default()
+                        params[f.column] = f.get_default()
                 elif f == rel_field:
-                    params[f.name] = getattr(new_object, rel_field.rel.field_name)
+                    params[f.column] = getattr(new_object, rel_field.rel.field_name)
                 elif add and isinstance(f, AutoField):
-                    params[f.name] = None
+                    params[f.column] = None
                 else:
-                    params[f.name] = f.get_manipulator_new_data(rel_new_data, rel=True)
+                    params[f.column] = f.get_manipulator_new_data(rel_new_data, rel=True)
                 # Related links are a special case, because we have to
                 # manually set the "content_type_id" field.
                 if opts.has_related_links and rel_opts.module_name == 'relatedlinks':
@@ -1501,7 +1556,7 @@ def manipulator_save(opts, klass, add, change, self, new_data):
                         self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj))
                     else:
                         for f in rel_opts.fields:
-                            if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.name)) != str(getattr(new_rel_obj, f.name)):
+                            if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.column)) != str(getattr(new_rel_obj, f.column)):
                                 self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj))
 
                 # Save many-to-many objects.
@@ -1527,20 +1582,26 @@ def manipulator_save(opts, klass, add, change, self, new_data):
 def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
     from django.utils.text import get_text_list
     field_list = [opts.get_field(field_name) for field_name in field_name_list]
-    kwargs = {'%s__iexact' % field_name_list[0]: field_data}
+    if isinstance(field_list[0].rel, ManyToOne):
+        kwargs = {'%s__%s__iexact' % (field_name_list[0], field_list[0].rel.field_name): field_data}
+    else:
+        kwargs = {'%s__iexact' % field_name_list[0]: field_data}
     for f in field_list[1:]:
-        field_val = all_data.get(f.name, None)
+        field_val = all_data.get(f.column, None)
         if field_val is None:
             # This will be caught by another validator, assuming the field
             # doesn't have blank=True.
             return
-        kwargs['%s__iexact' % f.name] = field_val
+        if isinstance(f.rel, ManyToOne):
+            kwargs['%s__pk' % f.name] = field_val
+        else:
+            kwargs['%s__iexact' % f.name] = field_val
     mod = opts.get_model_module()
     try:
         old_obj = mod.get_object(**kwargs)
     except ObjectDoesNotExist:
         return
-    if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name):
+    if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column):
         pass
     else:
         raise validators.ValidationError, "%s with this %s already exists for the given %s." % \
@@ -1562,7 +1623,7 @@ def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_t
     except ObjectDoesNotExist:
         return
     else:
-        if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name):
+        if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column):
             pass
         else:
             format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y'
diff --git a/django/core/meta/fields.py b/django/core/meta/fields.py
index 774c459279..da201ef8f3 100644
--- a/django/core/meta/fields.py
+++ b/django/core/meta/fields.py
@@ -40,7 +40,7 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data):
         old_obj = opts.get_model_module().get_object(**{'%s__exact' % f.name: field_data})
     except ObjectDoesNotExist:
         return
-    if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name):
+    if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column):
         return
     raise validators.ValidationError, "%s with this %s already exists." % (capfirst(opts.verbose_name), f.verbose_name)
 
@@ -50,14 +50,17 @@ class Field(object):
     # database level.
     empty_strings_allowed = True
 
-    def __init__(self, name, verbose_name=None, primary_key=False,
+    # Tracks each time a Field instance is created. Used to retain order.
+    creation_counter = 0
+
+    def __init__(self, verbose_name=None, name=None, primary_key=False,
         maxlength=None, unique=False, blank=False, null=False, db_index=None,
         core=False, rel=None, default=NOT_PROVIDED, editable=True,
         prepopulate_from=None, unique_for_date=None, unique_for_month=None,
         unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
-        help_text=''):
+        help_text='', db_column=None):
         self.name = name
-        self.verbose_name = verbose_name or name.replace('_', ' ')
+        self.verbose_name = verbose_name or (name and name.replace('_', ' '))
         self.primary_key = primary_key
         self.maxlength, self.unique = maxlength, unique
         self.blank, self.null = blank, null
@@ -70,6 +73,7 @@ class Field(object):
         self.choices = choices or []
         self.radio_admin = radio_admin
         self.help_text = help_text
+        self.db_column = db_column
         if rel and isinstance(rel, ManyToMany):
             if rel.raw_id_admin:
                 self.help_text += ' Separate multiple IDs with commas.'
@@ -85,6 +89,27 @@ class Field(object):
         else:
             self.db_index = db_index
 
+        # Increase the creation counter, and save our local copy.
+        self.creation_counter = Field.creation_counter
+        Field.creation_counter += 1
+
+        # Set the name of the database column.
+        self.column = self.get_db_column()
+
+    def set_name(self, name):
+        self.name = name
+        self.verbose_name = self.verbose_name or name.replace('_', ' ')
+        self.column = self.get_db_column()
+
+    def get_db_column(self):
+        if self.db_column: return self.db_column
+        if isinstance(self.rel, ManyToOne):
+            return '%s_id' % self.name
+        return self.name
+
+    def get_cache_name(self):
+        return '_%s_cache' % self.name
+
     def pre_save(self, value, add):
         "Returns field's value just before saving."
         return value
@@ -232,7 +257,7 @@ class Field(object):
         if self.choices:
             return first_choice + list(self.choices)
         rel_obj = self.rel.to
-        return first_choice + [(getattr(x, rel_obj.pk.name), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
+        return first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
 
 class AutoField(Field):
     empty_strings_allowed = False
@@ -271,11 +296,11 @@ class CommaSeparatedIntegerField(CharField):
 
 class DateField(Field):
     empty_strings_allowed = False
-    def __init__(self, name, verbose_name=None, auto_now=False, auto_now_add=False, **kwargs):
+    def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
         if auto_now or auto_now_add:
             kwargs['editable'] = False
-        Field.__init__(self, name, verbose_name, **kwargs)
+        Field.__init__(self, verbose_name, name, **kwargs)
 
     def get_db_prep_lookup(self, lookup_type, value):
         if lookup_type == 'range':
@@ -332,9 +357,9 @@ class EmailField(Field):
         return [formfields.EmailField]
 
 class FileField(Field):
-    def __init__(self, name, verbose_name=None, upload_to='', **kwargs):
+    def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
         self.upload_to = upload_to
-        Field.__init__(self, name, verbose_name, **kwargs)
+        Field.__init__(self, verbose_name, name, **kwargs)
 
     def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
         field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
@@ -397,17 +422,17 @@ class FileField(Field):
 
 class FloatField(Field):
     empty_strings_allowed = False
-    def __init__(self, name, verbose_name=None, max_digits=None, decimal_places=None, **kwargs):
+    def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
         self.max_digits, self.decimal_places = max_digits, decimal_places
-        Field.__init__(self, name, verbose_name, **kwargs)
+        Field.__init__(self, verbose_name, name, **kwargs)
 
     def get_manipulator_field_objs(self):
         return [curry(formfields.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
 
 class ImageField(FileField):
-    def __init__(self, name, verbose_name=None, width_field=None, height_field=None, **kwargs):
+    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
         self.width_field, self.height_field = width_field, height_field
-        FileField.__init__(self, name, verbose_name, **kwargs)
+        FileField.__init__(self, verbose_name, name, **kwargs)
 
     def get_manipulator_field_objs(self):
         return [formfields.ImageUploadField, formfields.HiddenField]
@@ -479,11 +504,11 @@ class TextField(Field):
 
 class TimeField(Field):
     empty_strings_allowed = False
-    def __init__(self, name, verbose_name=None, auto_now=False, auto_now_add=False, **kwargs):
+    def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
         self.auto_now, self.auto_now_add  = auto_now, auto_now_add
         if auto_now or auto_now_add:
             kwargs['editable'] = False
-        Field.__init__(self, name, verbose_name, **kwargs)
+        Field.__init__(self, verbose_name, name, **kwargs)
 
     def get_db_prep_lookup(self, lookup_type, value):
         if lookup_type == 'range':
@@ -511,10 +536,10 @@ class TimeField(Field):
         return [formfields.TimeField]
 
 class URLField(Field):
-    def __init__(self, name, verbose_name=None, verify_exists=True, **kwargs):
+    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
         if verify_exists:
             kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
-        Field.__init__(self, name, verbose_name, **kwargs)
+        Field.__init__(self, verbose_name, name, **kwargs)
 
     def get_manipulator_field_objs(self):
         return [formfields.URLField]
@@ -524,34 +549,31 @@ class USStateField(Field):
         return [formfields.USStateField]
 
 class XMLField(Field):
-    def __init__(self, name, verbose_name=None, schema_path=None, **kwargs):
+    def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
         self.schema_path = schema_path
-        Field.__init__(self, name, verbose_name, **kwargs)
+        Field.__init__(self, verbose_name, name, **kwargs)
 
     def get_manipulator_field_objs(self):
         return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
 
 class ForeignKey(Field):
     empty_strings_allowed = False
-    def __init__(self, to, to_field=None, rel_name=None, **kwargs):
+    def __init__(self, to, to_field=None, **kwargs):
         try:
             to_name = to._meta.object_name.lower()
         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
             assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
-            kwargs['name'] = kwargs.get('name', '')
             kwargs['verbose_name'] = kwargs.get('verbose_name', '')
         else:
             to_field = to_field or to._meta.pk.name
-            kwargs['name'] = kwargs.get('name', to_name + '_id')
             kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name)
-            rel_name = rel_name or to_name
 
         if kwargs.has_key('edit_inline_type'):
             import warnings
             warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
             kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
 
-        kwargs['rel'] = ManyToOne(to, rel_name, to_field,
+        kwargs['rel'] = ManyToOne(to, to_field,
             num_in_admin=kwargs.pop('num_in_admin', 3),
             min_num_in_admin=kwargs.pop('min_num_in_admin', None),
             max_num_in_admin=kwargs.pop('max_num_in_admin', None),
@@ -567,11 +589,9 @@ class ForeignKey(Field):
         return [formfields.IntegerField]
 
 class ManyToManyField(Field):
-    def __init__(self, to, rel_name=None, **kwargs):
-        kwargs['name'] = kwargs.get('name', to._meta.module_name)
+    def __init__(self, to, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
-        rel_name = rel_name or to._meta.object_name.lower()
-        kwargs['rel'] = ManyToMany(to, rel_name,
+        kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
             num_in_admin=kwargs.pop('num_in_admin', 0),
             related_name=kwargs.pop('related_name', None),
             filter_interface=kwargs.pop('filter_interface', None),
@@ -609,18 +629,16 @@ class ManyToManyField(Field):
                 len(badkeys) == 1 and "is" or "are")
 
 class OneToOneField(IntegerField):
-    def __init__(self, to, to_field=None, rel_name=None, **kwargs):
-        kwargs['name'] = kwargs.get('name', 'id')
+    def __init__(self, to, to_field=None, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
         to_field = to_field or to._meta.pk.name
-        rel_name = rel_name or to._meta.object_name.lower()
 
         if kwargs.has_key('edit_inline_type'):
             import warnings
             warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
             kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
 
-        kwargs['rel'] = OneToOne(to, rel_name, to_field,
+        kwargs['rel'] = OneToOne(to, to_field,
             num_in_admin=kwargs.pop('num_in_admin', 0),
             edit_inline=kwargs.pop('edit_inline', False),
             related_name=kwargs.pop('related_name', None),
@@ -631,7 +649,7 @@ class OneToOneField(IntegerField):
         IntegerField.__init__(self, **kwargs)
 
 class ManyToOne:
-    def __init__(self, to, name, field_name, num_in_admin=3, min_num_in_admin=None,
+    def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
         max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
         related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
         try:
@@ -639,7 +657,7 @@ class ManyToOne:
         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
             assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
             self.to = to
-        self.name, self.field_name = name, field_name
+        self.field_name = field_name
         self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
         self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
         self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
@@ -647,17 +665,15 @@ class ManyToOne:
         self.lookup_overrides = lookup_overrides or {}
         self.raw_id_admin = raw_id_admin
 
-    def get_cache_name(self):
-        return '_%s_cache' % self.name
-
     def get_related_field(self):
         "Returns the Field in the 'to' object to which this relationship is tied."
         return self.to.get_field(self.field_name)
 
 class ManyToMany:
-    def __init__(self, to, name, num_in_admin=0, related_name=None,
+    def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
         filter_interface=None, limit_choices_to=None, raw_id_admin=False):
-        self.to, self.name = to._meta, name
+        self.to = to._meta
+        self.singular = singular or to._meta.object_name.lower()
         self.num_in_admin = num_in_admin
         self.related_name = related_name
         self.filter_interface = filter_interface
@@ -667,10 +683,10 @@ class ManyToMany:
         assert not (self.raw_id_admin and self.filter_interface), "ManyToMany relationships may not use both raw_id_admin and filter_interface"
 
 class OneToOne(ManyToOne):
-    def __init__(self, to, name, field_name, num_in_admin=0, edit_inline=False,
+    def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
         related_name=None, limit_choices_to=None, lookup_overrides=None,
         raw_id_admin=False):
-        self.to, self.name, self.field_name = to._meta, name, field_name
+        self.to, self.field_name = to._meta, field_name
         self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
         self.related_name = related_name
         self.limit_choices_to = limit_choices_to or {}
diff --git a/django/models/auth.py b/django/models/auth.py
index 9ab403af34..430cad192a 100644
--- a/django/models/auth.py
+++ b/django/models/auth.py
@@ -2,65 +2,60 @@ from django.core import meta, validators
 from django.models import core
 
 class Permission(meta.Model):
-    fields = (
-        meta.CharField('name', maxlength=50),
-        meta.ForeignKey(core.Package, name='package'),
-        meta.CharField('codename', maxlength=100),
-    )
-    unique_together = (('package', 'codename'),)
-    ordering = ('package', 'codename')
+    name = meta.CharField(maxlength=50)
+    package = meta.ForeignKey(core.Package, db_column='package')
+    codename = meta.CharField(maxlength=100)
+    class META:
+        unique_together = (('package', 'codename'),)
+        ordering = ('package', 'codename')
 
     def __repr__(self):
         return "%s | %s" % (self.package, self.name)
 
 class Group(meta.Model):
-    fields = (
-        meta.CharField('name', maxlength=80, unique=True),
-        meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL),
-    )
-    ordering = ('name',)
-    admin = meta.Admin(
-        search_fields = ('name',),
-    )
+    name = meta.CharField(maxlength=80, unique=True)
+    permissions = meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL)
+    class META:
+        ordering = ('name',)
+        admin = meta.Admin(
+            search_fields = ('name',),
+        )
 
     def __repr__(self):
         return self.name
 
 class User(meta.Model):
-    fields = (
-        meta.CharField('username', maxlength=30, unique=True,
-            validator_list=[validators.isAlphaNumeric]),
-        meta.CharField('first_name', maxlength=30, blank=True),
-        meta.CharField('last_name', maxlength=30, blank=True),
-        meta.EmailField('email', 'e-mail address', blank=True),
-        meta.CharField('password_md5', 'password', maxlength=32, help_text="Use an MD5 hash -- not the raw password."),
-        meta.BooleanField('is_staff', 'staff status',
-            help_text="Designates whether the user can log into this admin site."),
-        meta.BooleanField('is_active', 'active', default=True),
-        meta.BooleanField('is_superuser', 'superuser status'),
-        meta.DateTimeField('last_login', default=meta.LazyDate()),
-        meta.DateTimeField('date_joined', default=meta.LazyDate()),
-        meta.ManyToManyField(Group, 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."),
-        meta.ManyToManyField(Permission, name='user_permissions', blank=True, filter_interface=meta.HORIZONTAL),
-    )
-    module_constants = {
-        'SESSION_KEY': '_auth_user_id',
-    }
-    ordering = ('username',)
-    exceptions = ('SiteProfileNotAvailable',)
-    admin = meta.Admin(
-        fields = (
-            (None, {'fields': ('username', 'password_md5')}),
-            ('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'),
-    )
+    username = meta.CharField(maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])
+    first_name = meta.CharField(maxlength=30, blank=True)
+    last_name = meta.CharField(maxlength=30, blank=True)
+    email = meta.EmailField('e-mail address', blank=True)
+    password_md5 = meta.CharField('password', maxlength=32, help_text="Use an MD5 hash -- not the raw password.")
+    is_staff = meta.BooleanField('staff status', help_text="Designates whether the user can log into this admin site.")
+    is_active = meta.BooleanField('active', default=True)
+    is_superuser = meta.BooleanField('superuser status')
+    last_login = meta.DateTimeField(default=meta.LazyDate())
+    date_joined = meta.DateTimeField(default=meta.LazyDate())
+    groups = meta.ManyToManyField(Group, 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 = meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL)
+    class META:
+        module_constants = {
+            'SESSION_KEY': '_auth_user_id',
+        }
+        ordering = ('username',)
+        exceptions = ('SiteProfileNotAvailable',)
+        admin = meta.Admin(
+            fields = (
+                (None, {'fields': ('username', 'password_md5')}),
+                ('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 __repr__(self):
         return self.username
@@ -154,7 +149,7 @@ class User(meta.Model):
             except ImportError:
                 try:
                     module = __import__('django.models.%s' % AUTH_PROFILE_MODULE, [], [], [''])
-                    self._profile_cache = module.get_object(user_id__exact=self.id)
+                    self._profile_cache = module.get_object(user__id__exact=self.id)
                 except ImportError:
                     raise SiteProfileNotAvailable
         return self._profile_cache
@@ -176,33 +171,30 @@ class User(meta.Model):
         return ''.join([choice(allowed_chars) for i in range(length)])
 
 class Message(meta.Model):
-    fields = (
-        meta.ForeignKey(User),
-        meta.TextField('message'),
-    )
+    user = meta.ForeignKey(User)
+    message = meta.TextField()
 
     def __repr__(self):
         return self.message
 
 class LogEntry(meta.Model):
-    module_name = 'log'
-    verbose_name_plural = 'log entries'
-    db_table = 'auth_admin_log'
-    fields = (
-        meta.DateTimeField('action_time', auto_now=True),
-        meta.ForeignKey(User),
-        meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type', blank=True, null=True),
-        meta.TextField('object_id', blank=True, null=True),
-        meta.CharField('object_repr', maxlength=200),
-        meta.PositiveSmallIntegerField('action_flag'),
-        meta.TextField('change_message', blank=True),
-    )
-    ordering = ('-action_time',)
-    module_constants = {
-        'ADDITION': 1,
-        'CHANGE': 2,
-        'DELETION': 3,
-    }
+    action_time = meta.DateTimeField(auto_now=True)
+    user = meta.ForeignKey(User)
+    content_type = meta.ForeignKey(core.ContentType, blank=True, null=True) # TODO: content_type_id name?
+    object_id = meta.TextField(blank=True, null=True)
+    object_repr = meta.CharField(maxlength=200)
+    action_flag = meta.PositiveSmallIntegerField()
+    change_message = meta.TextField(blank=True)
+    class META:
+        module_name = 'log'
+        verbose_name_plural = 'log entries'
+        db_table = 'auth_admin_log'
+        ordering = ('-action_time',)
+        module_constants = {
+            'ADDITION': 1,
+            'CHANGE': 2,
+            'DELETION': 3,
+        }
 
     def __repr__(self):
         return str(self.action_time)
diff --git a/django/models/core.py b/django/models/core.py
index e94a35b694..939373f7ec 100644
--- a/django/models/core.py
+++ b/django/models/core.py
@@ -1,12 +1,11 @@
 from django.core import meta, validators
 
 class Site(meta.Model):
-    db_table = 'sites'
-    fields = (
-        meta.CharField('domain', 'domain name', maxlength=100),
-        meta.CharField('name', 'display name', maxlength=50),
-    )
-    ordering = ('domain',)
+    domain = meta.CharField('domain name', maxlength=100)
+    name = meta.CharField('display name', maxlength=50)
+    class META:
+        db_table = 'sites'
+        ordering = ('domain',)
 
     def __repr__(self):
         return self.domain
@@ -17,25 +16,23 @@ class Site(meta.Model):
         return get_object(pk=SITE_ID)
 
 class Package(meta.Model):
-    db_table = 'packages'
-    fields = (
-        meta.CharField('label', maxlength=20, primary_key=True),
-        meta.CharField('name', maxlength=30, unique=True),
-    )
-    ordering = ('name',)
+    label = meta.CharField(maxlength=20, primary_key=True)
+    name = meta.CharField(maxlength=30, unique=True)
+    class META:
+        db_table = 'packages'
+        ordering = ('name',)
 
     def __repr__(self):
         return self.name
 
 class ContentType(meta.Model):
-    db_table = 'content_types'
-    fields = (
-        meta.CharField('name', maxlength=100),
-        meta.ForeignKey(Package, name='package'),
-        meta.CharField('python_module_name', maxlength=50),
-    )
-    ordering = ('package', 'name')
-    unique_together = (('package', 'python_module_name'),)
+    name = meta.CharField(maxlength=100)
+    package = meta.ForeignKey(Package, db_column='package')
+    python_module_name = meta.CharField(maxlength=50)
+    class META:
+        db_table = 'content_types'
+        ordering = ('package', 'name')
+        unique_together = (('package', 'python_module_name'),)
 
     def __repr__(self):
         return "%s | %s" % (self.package, self.name)
@@ -54,49 +51,45 @@ class ContentType(meta.Model):
         return self.get_model_module().get_object(**kwargs)
 
 class Redirect(meta.Model):
-    db_table = 'redirects'
-    fields = (
-        meta.ForeignKey(Site, radio_admin=meta.VERTICAL),
-        meta.CharField('old_path', 'redirect from', maxlength=200, db_index=True,
-            help_text="This should be an absolute path, excluding the domain name. Example: '/events/search/'."),
-        meta.CharField('new_path', 'redirect to', maxlength=200, blank=True,
-            help_text="This can be either an absolute path (as above) or a full URL starting with 'http://'."),
-    )
-    unique_together=(('site_id', 'old_path'),)
-    ordering = ('old_path',)
-    admin = meta.Admin(
-        list_display = ('__repr__',),
-        list_filter = ('site_id',),
-        search_fields = ('old_path', 'new_path'),
-    )
+    site = meta.ForeignKey(Site, radio_admin=meta.VERTICAL)
+    old_path = meta.CharField('redirect from', maxlength=200, db_index=True,
+        help_text="This should be an absolute path, excluding the domain name. Example: '/events/search/'.")
+    new_path = meta.CharField('redirect to', maxlength=200, blank=True,
+        help_text="This can be either an absolute path (as above) or a full URL starting with 'http://'.")
+    class META:
+        db_table = 'redirects'
+        unique_together=(('site', 'old_path'),)
+        ordering = ('old_path',)
+        admin = meta.Admin(
+            list_filter = ('site',),
+            search_fields = ('old_path', 'new_path'),
+        )
 
     def __repr__(self):
         return "%s ---> %s" % (self.old_path, self.new_path)
 
 class FlatFile(meta.Model):
-    db_table = 'flatfiles'
-    verbose_name = 'flat page'
-    fields = (
-        meta.CharField('url', 'URL', maxlength=100, validator_list=[validators.isAlphaNumericURL],
-            help_text="Example: '/about/contact/'. Make sure to have leading and trailing slashes."),
-        meta.CharField('title', maxlength=200),
-        meta.TextField('content', help_text="Full HTML is allowed."),
-        meta.BooleanField('enable_comments'),
-        meta.CharField('template_name', maxlength=70, blank=True,
-            help_text="Example: 'flatfiles/contact_page'. If this isn't provided, the system will use 'flatfiles/default'."),
-        meta.BooleanField('registration_required',
-            help_text="If this is checked, only logged-in users will be able to view the page."),
-        meta.ManyToManyField(Site),
-    )
-    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'),
-    )
+    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(maxlength=200)
+    content = meta.TextField()
+    enable_comments = meta.BooleanField()
+    template_name = meta.CharField(maxlength=70, blank=True,
+        help_text="Example: 'flatfiles/contact_page'. If this isn't provided, the system will use 'flatfiles/default'.")
+    registration_required = meta.BooleanField(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 = 'flatfiles'
+        verbose_name = 'flat page'
+        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)
@@ -108,18 +101,17 @@ import base64, md5, random, sys
 import cPickle as pickle
 
 class Session(meta.Model):
-    fields = (
-        meta.CharField('session_key', maxlength=40, primary_key=True),
-        meta.TextField('session_data'),
-        meta.DateTimeField('expire_date'),
-    )
-    module_constants = {
-        'base64': base64,
-        'md5': md5,
-        'pickle': pickle,
-        'random': random,
-        'sys': sys,
-    }
+    session_key = meta.CharField(maxlength=40, primary_key=True)
+    session_data = meta.TextField()
+    expire_date = meta.DateTimeField()
+    class META:
+        module_constants = {
+            'base64': base64,
+            'md5': md5,
+            'pickle': pickle,
+            'random': random,
+            'sys': sys,
+        }
 
     def get_decoded(self):
         from django.conf.settings import SECRET_KEY
diff --git a/django/templatetags/log.py b/django/templatetags/log.py
index 564d8fbfa0..c88e28dabe 100644
--- a/django/templatetags/log.py
+++ b/django/templatetags/log.py
@@ -11,23 +11,23 @@ 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] = log.get_list(user__id__exact=self.user, limit=self.limit, select_related=True)
         return ''
 
 class DoGetAdminLog:
     """
     Populates a template variable with the admin log for the given criteria.
-    
+
     Usage::
-    
+
         {% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %}
-        
+
     Examples::
-        
+
         {% get_admin_log 10 as admin_log for_user 23 %}
         {% get_admin_log 10 as admin_log for_user user %}
         {% get_admin_log 10 as admin_log %}
-    
+
     Note that ``context_var_containing_user_obj`` can be a hard-coded integer
     (user ID) or the name of a template context variable containing the user
     object whose ID you want.
diff --git a/django/views/admin/main.py b/django/views/admin/main.py
index c0a177a928..febe0adfad 100644
--- a/django/views/admin/main.py
+++ b/django/views/admin/main.py
@@ -387,12 +387,12 @@ def change_list(request, app_label, module_name):
                     except ObjectDoesNotExist:
                         result_repr = EMPTY_CHANGELIST_VALUE
                 else:
-                    field_val = getattr(result, f.name)
+                    field_val = getattr(result, f.column)
                     # Foreign-key fields are special: Use the repr of the
                     # related object.
                     if isinstance(f.rel, meta.ManyToOne):
                         if field_val is not None:
-                            result_repr = getattr(result, 'get_%s' % f.rel.name)()
+                            result_repr = getattr(result, 'get_%s' % f.name)()
                         else:
                             result_repr = EMPTY_CHANGELIST_VALUE
                     # Dates are special: They're formatted in a certain way.
@@ -723,10 +723,10 @@ def _get_admin_field(field_list, name_prefix, rel, add, change):
             t.append('{{ %soriginal.%s }}' % ((rel and name_prefix or ''), field.name))
         if change and use_raw_id_admin(field):
             if isinstance(field.rel, meta.ManyToOne):
-                if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.rel.name)
+                if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.name)
                 obj_repr = if_bit + '|truncatewords:"14"'
             elif isinstance(field.rel, meta.ManyToMany):
-                if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.rel.name)
+                if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.name)
                 obj_repr = if_bit + '|join:", "|truncatewords:"14"'
             t.append('{%% if %s %%}&nbsp;<strong>{{ %s }}</strong>{%% endif %%}' % (if_bit, obj_repr))
         if field.help_text:
@@ -915,21 +915,21 @@ def change_stage(request, app_label, module_name, object_id):
         new_data = {}
         obj = manipulator.original_object
         for f in opts.fields:
-            new_data.update(_get_flattened_data(f, getattr(obj, f.name)))
+            new_data.update(_get_flattened_data(f, getattr(obj, f.column)))
         for f in opts.many_to_many:
             if f.rel.raw_id_admin:
-                new_data[f.name] = ",".join([str(i.id) for i in getattr(obj, 'get_%s_list' % f.rel.name)()])
+                new_data[f.name] = ",".join([str(i.id) for i in getattr(obj, 'get_%s_list' % f.rel.singular)()])
             elif not f.rel.edit_inline:
-                new_data[f.name] = [i.id for i in getattr(obj, 'get_%s_list' % f.rel.name)()]
+                new_data[f.name] = [i.id for i in getattr(obj, 'get_%s_list' % f.rel.singular)()]
         for rel_obj, rel_field in inline_related_objects:
             var_name = rel_obj.object_name.lower()
             for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
                 for f in rel_obj.fields:
                     if f.editable and f != rel_field:
-                        for k, v in _get_flattened_data(f, getattr(rel_instance, f.name)).items():
+                        for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items():
                             new_data['%s.%d.%s' % (var_name, i, k)] = v
                 for f in rel_obj.many_to_many:
-                    new_data['%s.%d.%s' % (var_name, i, f.name)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.name)()]
+                    new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
 
         # If the object has ordered objects on its admin page, get the existing
         # order and flatten it into a comma-separated list of IDs.
@@ -1095,7 +1095,7 @@ def delete_stage(request, app_label, module_name, object_id):
 
 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(),
+    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)
     # If no history was found, see whether this object even exists.
     try:
diff --git a/django/views/defaults.py b/django/views/defaults.py
index 4da2643c3d..c75bc6880e 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -43,13 +43,13 @@ def page_not_found(request):
     from django.conf.settings import APPEND_SLASH, SITE_ID
     path = request.get_full_path()
     try:
-        r = redirects.get_object(site_id__exact=SITE_ID, old_path__exact=path)
+        r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path)
     except redirects.RedirectDoesNotExist:
         r = None
     if r is None and APPEND_SLASH:
         # Try removing the trailing slash.
         try:
-            r = redirects.get_object(site_id__exact=SITE_ID, old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
+            r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
         except redirects.RedirectDoesNotExist:
             pass
     if r is not None:
diff --git a/docs/db-api.txt b/docs/db-api.txt
index 40fe40a64d..3d9cc9b9c5 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -11,20 +11,16 @@ models, and how to create, retrieve, and update objects.
 Throughout this reference, we'll refer to the following Poll application::
 
     class Poll(meta.Model):
-        fields = (
-            meta.SlugField('slug', unique_for_month='pub_date'),
-            meta.CharField('question', maxlength=255),
-            meta.DateTimeField('pub_date'),
-            meta.DateTimeField('expire_date'),
-        )
+        slug = meta.SlugField(unique_for_month='pub_date')
+        question = meta.CharField(maxlength=255)
+        pub_date = meta.DateTimeField()
+        expire_date = meta.DateTimeField()
 
     class Choice(meta.Model):
-        fields = (
-            meta.ForeignKey(Poll, edit_inline=meta.TABULAR,
-                num_in_admin=10, min_num_in_admin=5),
-            meta.CharField('choice', maxlength=255, core=True),
-            meta.IntegerField('votes', editable=False, default=0),
-        )
+        poll = meta.ForeignKey(Poll, edit_inline=meta.TABULAR,
+            num_in_admin=10, min_num_in_admin=5)
+        choice = meta.CharField(maxlength=255, core=True)
+        votes = meta.IntegerField(editable=False, default=0)
 
 Basic lookup functions
 ======================
@@ -163,23 +159,18 @@ automatically.
 One-to-one relations
 --------------------
 
-Each object in a one-to-one relationship will have a ``get_relatedobject()``
+Each object in a one-to-one relationship will have a ``get_relatedobjectname()``
 method. For example::
 
     class Place(meta.Model):
-        fields = (
-            ...
-        )
+        # ...
 
     class Restaurant(meta.Model):
-        ...
-        fields = (
-            meta.OneToOneField(places.Place),
-            ...
-        )
+        # ...
+        the_place = meta.OneToOneField(places.Place)
 
 In the above example, each ``Place`` will have a ``get_restaurant()`` method,
-and each ``Restaurant`` will have a ``get_place()`` method.
+and each ``Restaurant`` will have a ``get_theplace()`` method.
 
 Many-to-one relations
 ---------------------
@@ -236,19 +227,15 @@ Note that ``select_related`` follows foreign keys as far as possible. If you hav
 following models::
 
     class Poll(meta.Model):
-        ...
+        # ...
 
     class Choice(meta.Model):
-        fields = (
-            meta.ForeignKey(Poll),
-            ...
-        )
+        # ...
+        poll = meta.ForeignKey(Poll)
 
     class SingleVote(meta.Model):
-        fields = (
-            meta.ForeignKey(Choice),
-            ...
-        )
+        # ...
+        choice = meta.ForeignKey(Choice)
 
 then a call to ``singlevotes.get_object(id__exact=4, select_related=True)`` will
 cache the related choice *and* the related poll::
diff --git a/docs/faq.txt b/docs/faq.txt
index d5400dd98c..e1f909cea3 100644
--- a/docs/faq.txt
+++ b/docs/faq.txt
@@ -291,14 +291,9 @@ dictionaries in order of query execution. Each dictionary has the following::
 Can I use Django with a pre-existing database?
 ----------------------------------------------
 
-Yes. You have two options:
+Yes. See `Integrating with a legacy database`_.
 
-    * Write models that describe your already-existing database layout, and
-      just point Django at your database.
-    * Use the alpha ``django-admin.py inspectdb`` function to automatically
-      create models by introspecting a given database. See `Ticket 90`_.
-
-.. _`Ticket 90`: http://code.djangoproject.com/ticket/90
+.. _`Integrating with a legacy database`: http://www.djangoproject.com/documentation/legacy_databases/
 
 The admin site
 ==============
diff --git a/docs/forms.txt b/docs/forms.txt
index 5e2ddcdeed..e3a98feb32 100644
--- a/docs/forms.txt
+++ b/docs/forms.txt
@@ -26,14 +26,14 @@ this document, we'll be working with the following model, a "place" object::
     )
 
     class Place(meta.Model):
-        fields = (
-            meta.CharField('name', maxlength=100),
-            meta.CharField('address', maxlength=100, blank=True),
-            meta.CharField('city', maxlength=50, blank=True),
-            meta.USStateField('state'),
-            meta.CharField('zip_code', maxlength=5, blank=True),
-            meta.IntegerField('place_type', choices=PLACE_TYPES)
-        )
+        name = meta.CharField(maxlength=100),
+        address = meta.CharField(maxlength=100, blank=True),
+        city = meta.CharField(maxlength=50, blank=True),
+        state = meta.USStateField(),
+        zip_code = meta.CharField(maxlength=5, blank=True),
+        place_type = meta.IntegerField(choices=PLACE_TYPES)
+        class META:
+            admin = meta.Admin()
 
         def __repr__(self):
             return self.name
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 02d7d7a509..bbe38929bf 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -6,15 +6,24 @@ Django's models are the bread and butter of the framework.  There's a huge
 array of options available to you when defining your data models. This
 document explains them.
 
-Options for models
-==================
+META options
+============
 
-A list of all possible options for a model object follows. Although there's a
-wide array of options, only ``fields`` is required.
+Give your model metadata by using an inner ``"class META"``, like so::
+
+    class Foo(meta.Model):
+        bar = meta.CharField(maxlength=30)
+        # ...
+        class META:
+            admin = meta.Admin()
+            # ...
+
+Here's a list of all possible ``META`` options. No options are required.
 
 ``admin``
-    A ``meta.Admin`` object; see `Admin options`_.  If this field isn't given,
-    the object will not have an admin interface.
+    A ``meta.Admin`` object; see `Admin options`_. If this field is given, the
+    object will have an admin interface. If it isn't given, the object won't
+    have one.
 
 ``db_table``
     The name of the database table to use for the module::
@@ -30,16 +39,6 @@ wide array of options, only ``fields`` is required.
 
         exceptions = ("DisgustingToppingsException", "BurntCrust")
 
-``fields``
-    A list of field objects. See `Field objects`_. For example::
-
-        fields = (
-            meta.CharField('customer_name', maxlength=15),
-            meta.BooleanField('use_extra_cheese'),
-            meta.IntegerField('customer_type', choices=CUSTOMER_TYPE_CHOICES),
-            ...
-        )
-
 ``get_latest_by``
     The name of a ``DateField`` or ``DateTimeField``; if given, the module will
     have a ``get_latest()`` function that fetches the "latest" object according
@@ -69,7 +68,7 @@ wide array of options, only ``fields`` is required.
     respect to a parent object.  For example, if a ``PizzaToppping`` relates to
     a ``Pizza`` object, you might use::
 
-        order_with_respect_to = 'pizza_id'
+        order_with_respect_to = 'pizza'
 
     to allow the toppings to be ordered with respect to the associated pizza.
 
@@ -95,7 +94,7 @@ wide array of options, only ``fields`` is required.
 ``unique_together``
     Sets of field names that, taken together, must be unique::
 
-        unique_together = (("driver_id", "restaurant_id"),)
+        unique_together = (("driver", "restaurant"),)
 
     This is a list of lists of fields that must be unique when considered
     together. It's used in the Django admin.
@@ -118,18 +117,14 @@ wide array of options, only ``fields`` is required.
 Field objects
 =============
 
-The list of fields is the most important part of a data model.  Each item in
-the ``fields`` list is an instance of a ``meta.Field`` subclass and maps to
-a database field.
+The list of fields is the most important part of a data model. Each class
+variable in a model, aside from the optional inner ``class META``, should be
+an instance of a ``meta.Field`` subclass.
 
-All field objects -- except for ``ForeignKey`` and ``ManyToManyField`` (see
-below) -- require the field's machine-readable name as the first positional
-argument. This must be a valid Python identifier -- no spaces, punctuation,
-etc., are allowed.
-
-The second positional argument, a human-readable name, is optional. If the
-human-readable name isn't given, Django will use the machine-readable name,
-coverting underscores to spaces.
+Each field type, except for ``ForeignKey``, ``ManyToManyField`` and
+``OneToOneField``, takes an optional first positional argument, a
+human-readable name. If the human-readable name isn't given, Django will use
+the machine-readable name, converting underscores to spaces.
 
 General field options
 ---------------------
@@ -173,6 +168,10 @@ common to all field types. These arguments are:
                             It is an error to have an inline-editable
                             relation without at least one core field.
 
+    ``db_column``           The name of the database column to use for this
+                            field. If this isn't given, Django will use the
+                            field's name.
+
     ``db_index``            If ``True``, the SQL generator will create a database
                             index on this field.
 
@@ -229,7 +228,7 @@ Field Types
     use this directly; a primary key field will automatically be added to your
     model if you don't specify otherwise. That automatically-added field is::
 
-        meta.AutoField('id', primary_key=True)
+        id = meta.AutoField(primary_key=True)
 
 ``BooleanField``
     A true/false field.
@@ -370,13 +369,6 @@ Field Types
 
                              Not used with ``edit_inline``.
 
-    ``rel_name``             The name of the relation. In the above example,
-                             this would default to 'pizza' (so that the
-                             ``Toppings`` object would have a ``get_pizza()``
-                             function. If you set ``rel_name`` to "pie", then
-                             the function would be called ``get_pie()`` and the
-                             field name would be ``pie_id``.
-
     ``related_name``         The name to use for the relation from the related
                              object back to this one.  For example, when if
                              ``Topping`` has this field::
@@ -405,11 +397,9 @@ Field Types
 
                                     ...
                                     meta.ForeignKey(Category, name="primary_category_id",
-                                                    rel_name="primary_category",
                                                     related_name="primary_story"),
 
                                     meta.ForeignKey(Category, name="secondary_category_id",
-                                                    rel_name="secondary_category",
                                                     related_name="secondary_story"),
                                     ...
 
@@ -445,24 +435,18 @@ Field Types
     ``core.flatfiles`` object::
 
         class FlatFile(meta.Model):
-            fields = (
-                ...
-                meta.ManyToManyField(Site),
-            )
+            # ...
+            sites = meta.ManyToManyField(Site)
 
     Many-to-many relations are a bit different from other fields.  First, they
     aren't actually a field per se, because they use a intermediary join table.
     Second, they don't take the same options as the rest of the fields. The
-    only arguments taken are:
+    first position argument is required and should be a model class. Other
+    available arguments, all of which are optional, are:
 
     =======================  ============================================================
     Argument                 Description
     =======================  ============================================================
-    ``rel_name``             Use this if you have more than one
-                             ``ForeignKey``  in the same model that relate
-                             to the same model. Django will use ``rel_name`` in
-                             the generated API.
-
     ``related_name``         See the description of ``related_name`` in
                              ``ForeignKey``, above.
 
@@ -475,14 +459,6 @@ Field Types
 
     ``limit_choices_to``     See the description under ``ForeignKey`` above.
 
-    ``name``                 An alphanumeric name for the relationship. If this
-                             isn't provided, Django uses the ``module_name`` of
-                             the related object.
-
-                             This is only really useful when you have a single
-                             object that relates to the same object more than
-                             once.
-
     ``verbose_name``         A human-readable name for the object, singular. If
                              this isn't provided, Django uses the
                              ``verbose_name`` for the related object.
@@ -534,7 +510,7 @@ Field Types
     from which to auto-populate the slug, via JavaScript, in the object's admin
     form::
 
-        meta.SlugField("slug", prepopulate_from=("pre_name", "name")),
+        meta.SlugField(prepopulate_from=("pre_name", "name")),
 
 ``SmallIntegerField``
     Like an ``IntegerField``, but only allows values under a certain
@@ -691,9 +667,7 @@ object's behavior.  First, any methods you define will be available as methods
 of object instances. For example::
 
     class Pizza(meta.Model):
-        fields = (
-            ...
-        )
+        # ...
 
         def is_disgusting(self):
             return "anchovies" in [topping.name for topping in self.get_topping_list()]
@@ -742,9 +716,7 @@ that module.  Any model method that begins with "_module_" is turned into a
 module-level function::
 
     class Pizza(meta.Model):
-        fields = (
-            ...
-        )
+        # ...
 
         def _module_get_pizzas_to_deliver():
             return get_list(delivered__exact=False)
@@ -769,9 +741,7 @@ fields because manipulators automatically call any method that begins with
 "validate"::
 
     class Pizza(meta.Model):
-        fields = (
-            ...
-        )
+        # ...
 
         def _manipulator_validate_customer_id(self, field_data, all_data):
             from django.core import validators
diff --git a/docs/overview.txt b/docs/overview.txt
index 6917ef8a1d..90e3db9302 100644
--- a/docs/overview.txt
+++ b/docs/overview.txt
@@ -21,20 +21,16 @@ offers many rich ways of representing your models -- so far, it's been
 solving two years' worth of database-schema problems. Here's a quick example::
 
     class Reporter(meta.Model):
-        fields = (
-            meta.CharField('full_name', maxlength=70),
-        )
+        full_name = meta.CharField(maxlength=70)
 
         def __repr__(self):
             return self.full_name
 
     class Article(meta.Model):
-        fields = (
-            meta.DateTimeField('pub_date'),
-            meta.CharField('headline', maxlength=200),
-            meta.TextField('article'),
-            meta.ForeignKey(Reporter),
-        )
+        pub_date = meta.DateTimeField('pub_date')
+        headline = meta.CharField('headline', maxlength=200)
+        article = meta.TextField('article')
+        reporter = meta.ForeignKey(Reporter)
 
         def __repr__(self):
             return self.headline
@@ -134,17 +130,16 @@ A dynamic admin interface: It's not just scaffolding -- it's the whole house
 
 Once your models are defined, Django can automatically create an administrative
 interface -- a Web site that lets authenticated users add, change and
-delete objects. It's as easy as adding an extra admin attribute to your model
-classes::
+delete objects. It's as easy as adding an extra ``admin`` attribute to your
+model classes::
 
     class Article(meta.Model):
-        fields = (
-            meta.DateTimeField('pub_date'),
-            meta.CharField('headline', maxlength=200),
-            meta.TextField('article'),
-            meta.ForeignKey(Reporter),
-        )
-        admin = meta.Admin()
+        pub_date = meta.DateTimeField('pub_date')
+        headline = meta.CharField('headline', maxlength=200)
+        article = meta.TextField('article')
+        reporter = meta.ForeignKey(Reporter)
+        class META:
+            admin = meta.Admin()
 
 The philosophy here is that your site is edited by a staff, or a client, or
 maybe just you -- and you don't want to have to deal with creating backend
diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt
index 112f5ca05c..6b4380290f 100644
--- a/docs/tutorial01.txt
+++ b/docs/tutorial01.txt
@@ -167,41 +167,36 @@ These concepts are represented by simple Python classes. Edit the
     from django.core import meta
 
     class Poll(meta.Model):
-        fields = (
-            meta.CharField('question', maxlength=200),
-            meta.DateTimeField('pub_date', 'date published'),
-        )
+        question = meta.CharField(maxlength=200)
+        pub_date = meta.DateTimeField('date published')
 
     class Choice(meta.Model):
-        fields = (
-            meta.ForeignKey(Poll),
-            meta.CharField('choice', maxlength=200),
-            meta.IntegerField('votes'),
-        )
+        poll = meta.ForeignKey(Poll)
+        choice = meta.CharField(maxlength=200)
+        votes = meta.IntegerField()
 
 The code is straightforward. Each model is represented by a class that
-subclasses ``django.core.meta.Model``. Each model has a single class variable,
-``fields``, which is a tuple of database fields in the model.
+subclasses ``django.core.meta.Model``. Each model a number of class variables,
+each of which represents a database field in the model.
 
 Each field is represented by an instance of a ``meta.*Field`` class -- e.g.,
 ``meta.CharField`` for character fields and ``meta.DateTimeField`` for
 datetimes. This tells Django what type of data each field holds.
 
-The first argument to each ``Field`` call is the field's name, in
-machine-friendly format. You'll use this value in your Python code, and your
-database will use it as the column name.
+The name of each ``meta.*Field`` instance (e.g. ``question`` or ``pub_date`` )
+is the field's name, in machine-friendly format. You'll use this value in your
+Python code, and your database will use it as the column name.
 
-The second, optional, argument is the field's human-readable name. That's used
-in a couple of introspective parts of Django, and it doubles as documentation.
-If this field isn't provided, Django will use the machine-readable name. In
-this example, we've only defined a human-readable name for ``Poll.pub_date``.
-For all other fields in this model, the field's machine-readable name will
-suffice as its human-readable name.
+You can use an optional first positional argument to a ``Field`` to designate a
+human-readable name. That's used in a couple of introspective parts of Django,
+and it doubles as documentation. If this field isn't provided, Django will use
+the machine-readable name. In this example, we've only defined a human-readable
+name for ``Poll.pub_date``. For all other fields in this model, the field's
+machine-readable name will suffice as its human-readable name.
 
-Some ``meta.*Field`` classes have additional required elements.
-``meta.CharField``, for example, requires that you give it a ``maxlength``.
-That's used not only in the database schema, but in validation, as we'll soon
-see.
+Some ``meta.*Field`` classes have required elements. ``meta.CharField``, for
+example, requires that you give it a ``maxlength``. That's used not only in the
+database schema, but in validation, as we'll soon see.
 
 Finally, note a relationship is defined, using ``meta.ForeignKey``. That tells
 Django each Choice is related to a single Poll. Django supports all the common
@@ -266,6 +261,9 @@ Note the following:
 
     * Primary keys (IDs) are added automatically. (You can override this, too.)
 
+    * Django appends ``"_id"`` to the foreign key field name, by convention.
+      Yes, you can override this, as well.
+
     * The foreign key relationship is made explicit by a ``REFERENCES`` statement.
 
     * It's tailored to the database you're using, so database-specific field types
diff --git a/docs/tutorial02.txt b/docs/tutorial02.txt
index b42bafb374..e1d0401592 100644
--- a/docs/tutorial02.txt
+++ b/docs/tutorial02.txt
@@ -89,9 +89,7 @@ objects have an admin interface. Edit the ``myproject/apps/polls/models/polls.py
 file and make the following change to add an ``admin`` attribute::
 
     class Poll(meta.Model):
-        fields = (
-            # ...
-        )
+        # ...
         admin = meta.Admin()
 
 Now reload the Django admin page to see your changes. Note that you don't have
@@ -242,15 +240,15 @@ Poll object. Let's make that happen.
 Remove the ``admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)``
 field like so::
 
-    meta.ForeignKey(Poll, edit_inline=meta.STACKED, num_in_admin=3),
+    poll = meta.ForeignKey(Poll, edit_inline=meta.STACKED, num_in_admin=3)
 
 This tells Django: "Choice objects are edited on the Poll admin page. By
 default, provide enough fields for 3 Choices."
 
 Then change the other fields in ``Choice`` to give them ``core=True``::
 
-    meta.CharField('choice', 'choice', maxlength=200, core=True),
-    meta.IntegerField('votes', 'votes', core=True),
+    choice = meta.CharField(maxlength=200, core=True)
+    votes = meta.IntegerField(core=True)
 
 This tells Django: "When you edit a Choice on the Poll admin page, the 'choice'
 and 'votes' fields are required. The presence of at least one of them signifies
@@ -274,7 +272,7 @@ One small problem, though. It takes a lot of screen space to display all the
 fields for entering related Choice objects. For that reason, Django offers an
 alternate way of displaying inline related objects::
 
-    meta.ForeignKey(Poll, edit_inline=meta.TABULAR, num_in_admin=3),
+    poll = meta.ForeignKey(Poll, edit_inline=meta.TABULAR, num_in_admin=3)
 
 With that ``edit_inline=meta.TABULAR`` (instead of ``meta.STACKED``), the
 related objects are displayed in a more compact, table-based format:
diff --git a/tests/testapp/models/__init__.py b/tests/testapp/models/__init__.py
index d76059ec74..0fab083922 100644
--- a/tests/testapp/models/__init__.py
+++ b/tests/testapp/models/__init__.py
@@ -1,3 +1,4 @@
 __all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many',
            'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one',
-           'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk']
+           'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk',
+           'subclassing', 'many_to_one_null']
diff --git a/tests/testapp/models/basic.py b/tests/testapp/models/basic.py
index c51ceccb9d..ebd784137a 100644
--- a/tests/testapp/models/basic.py
+++ b/tests/testapp/models/basic.py
@@ -7,10 +7,8 @@ This is a basic model with only two non-primary-key fields.
 from django.core import meta
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100, default='Default headline'),
-        meta.DateTimeField('pub_date'),
-    )
+    headline = meta.CharField(maxlength=100, default='Default headline')
+    pub_date = meta.DateTimeField()
 
 API_TESTS = """
 # No articles are in the system yet.
diff --git a/tests/testapp/models/custom_methods.py b/tests/testapp/models/custom_methods.py
index f674b9c286..4f175752b4 100644
--- a/tests/testapp/models/custom_methods.py
+++ b/tests/testapp/models/custom_methods.py
@@ -23,10 +23,8 @@ namespace as custom methods.
 from django.core import meta
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateField('pub_date'),
-    )
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateField()
 
     def __repr__(self):
         return self.headline
diff --git a/tests/testapp/models/custom_pk.py b/tests/testapp/models/custom_pk.py
index d7407404f8..b7ebd61d4b 100644
--- a/tests/testapp/models/custom_pk.py
+++ b/tests/testapp/models/custom_pk.py
@@ -11,12 +11,11 @@ fails.
 from django.core import meta
 
 class Employee(meta.Model):
-    fields = (
-        meta.CharField('employee_code', maxlength=10, primary_key=True),
-        meta.CharField('first_name', maxlength=20),
-        meta.CharField('last_name', maxlength=20),
-    )
-    ordering = ('last_name', 'first_name')
+    employee_code = meta.CharField(maxlength=10, primary_key=True)
+    first_name = meta.CharField(maxlength=20)
+    last_name = meta.CharField(maxlength=20)
+    class META:
+        ordering = ('last_name', 'first_name')
 
     def __repr__(self):
         return "%s %s" % (self.first_name, self.last_name)
diff --git a/tests/testapp/models/get_latest.py b/tests/testapp/models/get_latest.py
index 2bd5a6660b..86697e85a7 100644
--- a/tests/testapp/models/get_latest.py
+++ b/tests/testapp/models/get_latest.py
@@ -11,11 +11,10 @@ date farthest into the future."
 from django.core import meta
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateTimeField('pub_date'),
-    )
-    get_latest_by = 'pub_date'
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateTimeField()
+    class META:
+        get_latest_by = 'pub_date'
 
     def __repr__(self):
         return self.headline
diff --git a/tests/testapp/models/lookup.py b/tests/testapp/models/lookup.py
index 4567b1f106..bb8f4aaad1 100644
--- a/tests/testapp/models/lookup.py
+++ b/tests/testapp/models/lookup.py
@@ -7,11 +7,10 @@ This demonstrates features of the database API.
 from django.core import meta
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateTimeField('pub_date'),
-    )
-    ordering = ('-pub_date', 'headline')
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateTimeField()
+    class META:
+        ordering = ('-pub_date', 'headline')
 
     def __repr__(self):
         return self.headline
diff --git a/tests/testapp/models/m2m_intermediary.py b/tests/testapp/models/m2m_intermediary.py
index 30d0bb2ac2..2a20072e03 100644
--- a/tests/testapp/models/m2m_intermediary.py
+++ b/tests/testapp/models/m2m_intermediary.py
@@ -13,49 +13,43 @@ writer").
 from django.core import meta
 
 class Reporter(meta.Model):
-    fields = (
-        meta.CharField('first_name', maxlength=30),
-        meta.CharField('last_name', maxlength=30),
-    )
+    first_name = meta.CharField(maxlength=30)
+    last_name = meta.CharField(maxlength=30)
 
     def __repr__(self):
         return "%s %s" % (self.first_name, self.last_name)
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateField('pub_date'),
-    )
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateField()
 
     def __repr__(self):
         return self.headline
 
 class Writer(meta.Model):
-    fields = (
-        meta.ForeignKey(Reporter),
-        meta.ForeignKey(Article),
-        meta.CharField('position', maxlength=100),
-    )
+    reporter = meta.ForeignKey(Reporter)
+    article = meta.ForeignKey(Article)
+    position = meta.CharField(maxlength=100)
 
     def __repr__(self):
         return '%r (%s)' % (self.get_reporter(), self.position)
 
 API_TESTS = """
 # Create a few Reporters.
->>> r1 = reporters.Reporter(id=None, first_name='John', last_name='Smith')
+>>> r1 = reporters.Reporter(first_name='John', last_name='Smith')
 >>> r1.save()
->>> r2 = reporters.Reporter(id=None, first_name='Jane', last_name='Doe')
+>>> r2 = reporters.Reporter(first_name='Jane', last_name='Doe')
 >>> r2.save()
 
 # Create an Article.
 >>> from datetime import datetime
->>> a = articles.Article(id=None, headline='This is a test', pub_date=datetime(2005, 7, 27))
+>>> a = articles.Article(headline='This is a test', pub_date=datetime(2005, 7, 27))
 >>> a.save()
 
 # Create a few Writers.
->>> w1 = writers.Writer(id=None, reporter_id=r1.id, article_id=a.id, position='Main writer')
+>>> w1 = writers.Writer(reporter=r1, article=a, position='Main writer')
 >>> w1.save()
->>> w2 = writers.Writer(id=None, reporter_id=r2.id, article_id=a.id, position='Contributor')
+>>> w2 = writers.Writer(reporter=r2, article=a, position='Contributor')
 >>> w2.save()
 
 # Play around with the API.
diff --git a/tests/testapp/models/m2o_recursive.py b/tests/testapp/models/m2o_recursive.py
index a0d211fc2c..27d13b4e7e 100644
--- a/tests/testapp/models/m2o_recursive.py
+++ b/tests/testapp/models/m2o_recursive.py
@@ -7,29 +7,25 @@ To define a many-to-one relationship between a model and itself, use
 In this example, a ``Category`` is related to itself. That is, each
 ``Category`` has a parent ``Category``.
 
-Because of this recursive relationship, we need to tell Django what the
-relationships should be called. Set ``rel_name`` for this, and set
-``related_name`` to designate what the reverse relationship is called.
+Set ``related_name`` to designate what the reverse relationship is called.
 """
 
 from django.core import meta
 
 class Category(meta.Model):
-    module_name = 'categories'
-    fields = (
-        meta.CharField('name', maxlength=20),
-        meta.ForeignKey('self', null=True,
-            rel_name='parent', related_name='child'),
-    )
+    name = meta.CharField(maxlength=20)
+    parent = meta.ForeignKey('self', null=True, related_name='child')
+    class META:
+        module_name = 'categories'
 
     def __repr__(self):
         return self.name
 
 API_TESTS = """
 # Create a few Category objects.
->>> r = categories.Category(id=None, name='Root category', parent_id=None)
+>>> r = categories.Category(id=None, name='Root category', parent=None)
 >>> r.save()
->>> c = categories.Category(id=None, name='Child category', parent_id=r.id)
+>>> c = categories.Category(id=None, name='Child category', parent=r)
 >>> c.save()
 
 >>> r.get_child_list()
diff --git a/tests/testapp/models/m2o_recursive2.py b/tests/testapp/models/m2o_recursive2.py
index ae1407ad31..52aa0f8b69 100644
--- a/tests/testapp/models/m2o_recursive2.py
+++ b/tests/testapp/models/m2o_recursive2.py
@@ -4,36 +4,28 @@
 In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of
 which are other ``Person`` objects.
 
-Because a ``Person`` has multiple relationships to ``Person``, we need to
-distinguish the relationships. Set ``rel_name`` to tell Django what the
-relationship should be called, because ``Person`` has two relationships to the
-same model. Also, set ``related_name`` to designate what the reverse
-relationship is called.
+Set ``related_name`` to designate what the reverse relationship is called.
 """
 
 from django.core import meta
 
 class Person(meta.Model):
-    fields = (
-        meta.CharField('full_name', maxlength=20),
-        meta.ForeignKey('self', null=True, rel_name='mother',
-            related_name='mothers_child'),
-        meta.ForeignKey('self', null=True, rel_name='father',
-            related_name='fathers_child'),
-    )
+    full_name = meta.CharField(maxlength=20)
+    mother = meta.ForeignKey('self', null=True, related_name='mothers_child')
+    father = meta.ForeignKey('self', null=True, related_name='fathers_child')
 
     def __repr__(self):
         return self.full_name
 
 API_TESTS = """
 # Create two Person objects -- the mom and dad in our family.
->>> dad = persons.Person(id=None, full_name='John Smith Senior', mother_id=None, father_id=None)
+>>> dad = persons.Person(full_name='John Smith Senior', mother=None, father=None)
 >>> dad.save()
->>> mom = persons.Person(id=None, full_name='Jane Smith', mother_id=None, father_id=None)
+>>> mom = persons.Person(full_name='Jane Smith', mother=None, father=None)
 >>> mom.save()
 
 # Give mom and dad a kid.
->>> kid = persons.Person(id=None, full_name='John Smith Junior', mother_id=mom.id, father_id=dad.id)
+>>> kid = persons.Person(full_name='John Smith Junior', mother=mom, father=dad)
 >>> kid.save()
 
 >>> kid.get_mother()
diff --git a/tests/testapp/models/many_to_many.py b/tests/testapp/models/many_to_many.py
index 283a004ca4..91addafe9b 100644
--- a/tests/testapp/models/many_to_many.py
+++ b/tests/testapp/models/many_to_many.py
@@ -10,18 +10,14 @@ and a publication has multiple articles.
 from django.core import meta
 
 class Publication(meta.Model):
-    fields = (
-        meta.CharField('title', maxlength=30),
-    )
+    title = meta.CharField(maxlength=30)
 
     def __repr__(self):
         return self.title
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.ManyToManyField(Publication),
-    )
+    headline = meta.CharField(maxlength=100)
+    publications = meta.ManyToManyField(Publication)
 
     def __repr__(self):
         return self.headline
diff --git a/tests/testapp/models/many_to_one.py b/tests/testapp/models/many_to_one.py
index a5bb16cb06..91dae95614 100644
--- a/tests/testapp/models/many_to_one.py
+++ b/tests/testapp/models/many_to_one.py
@@ -1,42 +1,38 @@
 """
 4. Many-to-one relationships
 
-To define a many-to-one relationship, use ForeignKey().
+To define a many-to-one relationship, use ``ForeignKey()`` .
 """
 
 from django.core import meta
 
 class Reporter(meta.Model):
-    fields = (
-        meta.CharField('first_name', maxlength=30),
-        meta.CharField('last_name', maxlength=30),
-    )
+    first_name = meta.CharField(maxlength=30)
+    last_name = meta.CharField(maxlength=30)
 
     def __repr__(self):
         return "%s %s" % (self.first_name, self.last_name)
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateField('pub_date'),
-        meta.ForeignKey(Reporter),
-    )
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateField()
+    reporter = meta.ForeignKey(Reporter)
 
     def __repr__(self):
         return self.headline
 
 API_TESTS = """
 # Create a Reporter.
->>> r = reporters.Reporter(id=None, first_name='John', last_name='Smith')
+>>> r = reporters.Reporter(first_name='John', last_name='Smith')
 >>> r.save()
 
 # Create an Article.
 >>> from datetime import datetime
->>> a = articles.Article(id=None, headline='This is a test', pub_date=datetime(2005, 7, 27), reporter_id=r.id)
+>>> a = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
 >>> a.save()
 
 >>> a.reporter_id
-1L
+1
 
 >>> a.get_reporter()
 John Smith
@@ -47,7 +43,7 @@ John Smith
 ('John', 'Smith')
 
 # Create an Article via the Reporter object.
->>> new_article = r.add_article(headline="John's second story", pub_date=datetime(2005, 7, 28))
+>>> new_article = r.add_article(headline="John's second story", pub_date=datetime(2005, 7, 29))
 >>> new_article
 John's second story
 >>> new_article.reporter_id
@@ -61,7 +57,7 @@ John's second story
 This is a test
 
 >>> r.get_article_count()
-2L
+2
 
 # The API automatically follows relationships as far as you need.
 # Use double underscores to separate relationships.
@@ -70,4 +66,32 @@ This is a test
 >>> articles.get_list(reporter__first_name__exact='John', order_by=['pub_date'])
 [This is a test, John's second story]
 
+# Find all Articles for the Reporter whose ID is 1.
+>>> articles.get_list(reporter__id__exact=1, order_by=['pub_date'])
+[This is a test, John's second story]
+
+# Note you need two underscores between "reporter" and "id" -- not one.
+>>> articles.get_list(reporter_id__exact=1)
+Traceback (most recent call last):
+    ...
+TypeError: got unexpected keyword argument 'reporter_id__exact'
+
+# "pk" shortcut syntax works in a related context, too.
+>>> articles.get_list(reporter__pk=1, order_by=['pub_date'])
+[This is a test, John's second story]
+
+# You can also instantiate an Article by passing
+# the Reporter's ID instead of a Reporter object.
+>>> a3 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id)
+>>> a3.save()
+>>> a3.reporter_id
+1
+>>> a3.get_reporter()
+John Smith
+
+# Similarly, the reporter ID can be a string.
+>>> a4 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1")
+>>> a4.save()
+>>> a4.get_reporter()
+John Smith
 """
diff --git a/tests/testapp/models/many_to_one_null.py b/tests/testapp/models/many_to_one_null.py
new file mode 100644
index 0000000000..dcf6373e68
--- /dev/null
+++ b/tests/testapp/models/many_to_one_null.py
@@ -0,0 +1,77 @@
+"""
+16. Many-to-one relationships that can be null
+
+To define a many-to-one relationship, use ``ForeignKey()`` with ``null=True`` .
+"""
+
+from django.core import meta
+
+class Reporter(meta.Model):
+    name = meta.CharField(maxlength=30)
+
+    def __repr__(self):
+        return self.name
+
+class Article(meta.Model):
+    headline = meta.CharField(maxlength=100)
+    reporter = meta.ForeignKey(Reporter, null=True)
+
+    def __repr__(self):
+        return self.headline
+
+API_TESTS = """
+# Create a Reporter.
+>>> r = reporters.Reporter(name='John Smith')
+>>> r.save()
+
+# Create an Article.
+>>> a = articles.Article(headline="First", reporter=r)
+>>> a.save()
+
+>>> a.reporter_id
+1
+
+>>> a.get_reporter()
+John Smith
+
+# Article objects have access to their related Reporter objects.
+>>> r = a.get_reporter()
+
+# Create an Article via the Reporter object.
+>>> a2 = r.add_article(headline="Second")
+>>> a2
+Second
+>>> a2.reporter_id
+1
+
+# Reporter objects have access to their related Article objects.
+>>> r.get_article_list(order_by=['headline'])
+[First, Second]
+>>> r.get_article(headline__startswith='Fir')
+First
+>>> r.get_article_count()
+2
+
+# Create an Article with no Reporter by passing "reporter=None".
+>>> a3 = articles.Article(headline="Third", reporter=None)
+>>> a3.save()
+>>> a3.id
+3
+>>> a3.reporter_id
+>>> print a3.reporter_id
+None
+>>> a3 = articles.get_object(pk=3)
+>>> print a3.reporter_id
+None
+
+# An article's get_reporter() method throws ReporterDoesNotExist
+# if the reporter is set to None.
+>>> a3.get_reporter()
+Traceback (most recent call last):
+    ...
+ReporterDoesNotExist
+
+# To retrieve the articles with no reporters set, use "reporter__isnull=True".
+>>> articles.get_list(reporter__isnull=True)
+[Third]
+"""
diff --git a/tests/testapp/models/one_to_one.py b/tests/testapp/models/one_to_one.py
index d150daea8e..51b3efe733 100644
--- a/tests/testapp/models/one_to_one.py
+++ b/tests/testapp/models/one_to_one.py
@@ -9,33 +9,29 @@ In this example, a ``Place`` optionally can be a ``Restaurant``.
 from django.core import meta
 
 class Place(meta.Model):
-    fields = (
-        meta.CharField('name', maxlength=50),
-        meta.CharField('address', maxlength=80),
-    )
+    name = meta.CharField(maxlength=50)
+    address = meta.CharField(maxlength=80)
 
     def __repr__(self):
         return "%s the place" % self.name
 
 class Restaurant(meta.Model):
-    fields = (
-        meta.OneToOneField(Place),
-        meta.BooleanField('serves_hot_dogs'),
-        meta.BooleanField('serves_pizza'),
-    )
+    place = meta.OneToOneField(Place)
+    serves_hot_dogs = meta.BooleanField()
+    serves_pizza = meta.BooleanField()
 
     def __repr__(self):
         return "%s the restaurant" % self.get_place().name
 
 API_TESTS = """
 # Create a couple of Places.
->>> p1 = places.Place(id=None, name='Demon Dogs', address='944 W. Fullerton')
+>>> p1 = places.Place(name='Demon Dogs', address='944 W. Fullerton')
 >>> p1.save()
->>> p2 = places.Place(id=None, name='Ace Hardware', address='1013 N. Ashland')
+>>> p2 = places.Place(name='Ace Hardware', address='1013 N. Ashland')
 >>> p2.save()
 
 # Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
->>> r = restaurants.Restaurant(id=p1.id, serves_hot_dogs=True, serves_pizza=False)
+>>> r = restaurants.Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
 >>> r.save()
 
 # A Restaurant can access its place.
@@ -50,7 +46,7 @@ Demon Dogs the restaurant
 >>> p2.get_restaurant()
 Traceback (most recent call last):
     ...
-RestaurantDoesNotExist: Restaurant does not exist for {'id__exact': ...}
+RestaurantDoesNotExist: Restaurant does not exist for {'place__id__exact': ...}
 
 # restaurants.get_list() just returns the Restaurants, not the Places.
 >>> restaurants.get_list()
@@ -60,4 +56,9 @@ RestaurantDoesNotExist: Restaurant does not exist for {'id__exact': ...}
 # Restaurants.
 >>> places.get_list(order_by=['name'])
 [Ace Hardware the place, Demon Dogs the place]
+
+>>> restaurants.get_object(place__id__exact=1)
+Demon Dogs the restaurant
+>>> restaurants.get_object(pk=1)
+Demon Dogs the restaurant
 """
diff --git a/tests/testapp/models/ordering.py b/tests/testapp/models/ordering.py
index 0efa0638df..b7b68b3158 100644
--- a/tests/testapp/models/ordering.py
+++ b/tests/testapp/models/ordering.py
@@ -16,11 +16,10 @@ undefined -- not random, just undefined.
 from django.core import meta
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateTimeField('pub_date'),
-    )
-    ordering = ('-pub_date', 'headline')
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateTimeField()
+    class META:
+        ordering = ('-pub_date', 'headline')
 
     def __repr__(self):
         return self.headline
@@ -28,13 +27,13 @@ class Article(meta.Model):
 API_TESTS = """
 # Create a couple of Articles.
 >>> from datetime import datetime
->>> a1 = articles.Article(id=None, headline='Article 1', pub_date=datetime(2005, 7, 26))
+>>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
 >>> a1.save()
->>> a2 = articles.Article(id=None, headline='Article 2', pub_date=datetime(2005, 7, 27))
+>>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
 >>> a2.save()
->>> a3 = articles.Article(id=None, headline='Article 3', pub_date=datetime(2005, 7, 27))
+>>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
 >>> a3.save()
->>> a4 = articles.Article(id=None, headline='Article 4', pub_date=datetime(2005, 7, 28))
+>>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
 >>> a4.save()
 
 # By default, articles.get_list() orders by pub_date descending, then
diff --git a/tests/testapp/models/repr.py b/tests/testapp/models/repr.py
index 00f78f967f..3d4daf22c9 100644
--- a/tests/testapp/models/repr.py
+++ b/tests/testapp/models/repr.py
@@ -11,10 +11,8 @@ automatically-generated admin.
 from django.core import meta
 
 class Article(meta.Model):
-    fields = (
-        meta.CharField('headline', maxlength=100),
-        meta.DateTimeField('pub_date'),
-    )
+    headline = meta.CharField(maxlength=100)
+    pub_date = meta.DateTimeField()
 
     def __repr__(self):
         return self.headline
@@ -22,7 +20,7 @@ class Article(meta.Model):
 API_TESTS = """
 # Create an Article.
 >>> from datetime import datetime
->>> a = articles.Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
+>>> a = articles.Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
 >>> a.save()
 
 >>> repr(a)
diff --git a/tests/testapp/models/save_delete_hooks.py b/tests/testapp/models/save_delete_hooks.py
index 3c895b8bee..f0fa836f71 100644
--- a/tests/testapp/models/save_delete_hooks.py
+++ b/tests/testapp/models/save_delete_hooks.py
@@ -13,10 +13,8 @@ Django provides hooks for executing arbitrary code around ``save()`` and
 from django.core import meta
 
 class Person(meta.Model):
-    fields = (
-        meta.CharField('first_name', maxlength=20),
-        meta.CharField('last_name', maxlength=20),
-    )
+    first_name = meta.CharField(maxlength=20)
+    last_name = meta.CharField(maxlength=20)
 
     def __repr__(self):
         return "%s %s" % (self.first_name, self.last_name)
diff --git a/tests/testapp/models/subclassing.py b/tests/testapp/models/subclassing.py
new file mode 100644
index 0000000000..7ec8d37ff9
--- /dev/null
+++ b/tests/testapp/models/subclassing.py
@@ -0,0 +1,168 @@
+"""
+15. Subclassing models
+
+You can subclass another model to create a copy of it that behaves slightly
+differently.
+"""
+
+from django.core import meta
+
+# From the "Bare-bones model" example
+from django.models.basic import Article
+
+# From the "Adding __repr__()" example
+from django.models.repr import Article as ArticleWithRepr
+
+# From the "Specifying ordering" example
+from django.models.ordering import Article as ArticleWithOrdering
+
+# This uses all fields and metadata from Article and
+# adds a "section" field.
+class ArticleWithSection(Article):
+    section = meta.CharField(maxlength=30)
+    class META:
+       module_name = 'subarticles1'
+
+# This uses all fields and metadata from Article but
+# removes the "pub_date" field.
+class ArticleWithoutPubDate(Article):
+    class META:
+       module_name = 'subarticles2'
+       remove_fields = ('pub_date',)
+
+# This uses all fields and metadata from Article but
+# overrides the "pub_date" field.
+class ArticleWithFieldOverride(Article):
+    pub_date = meta.DateField() # overrides the old field, a DateTimeField
+    class META:
+        module_name = 'subarticles3'
+        # No need to add remove_fields = ('pub_date',)
+
+# This uses all fields and metadata from ArticleWithRepr and
+# makes a few additions/changes.
+class ArticleWithManyChanges(ArticleWithRepr):
+    section = meta.CharField(maxlength=30)
+    is_popular = meta.BooleanField()
+    pub_date = meta.DateField() # overrides the old field, a DateTimeField
+    class META:
+       module_name = 'subarticles4'
+
+# This uses all fields from ArticleWithOrdering but
+# changes the ordering parameter.
+class ArticleWithChangedMeta(ArticleWithOrdering):
+    class META:
+       module_name = 'subarticles5'
+       ordering = ('headline', 'pub_date')
+
+API_TESTS = """
+# No data is in the system yet.
+>>> subarticles1.get_list()
+[]
+>>> subarticles2.get_list()
+[]
+>>> subarticles3.get_list()
+[]
+
+# Create an ArticleWithSection.
+>>> from datetime import date, datetime
+>>> a1 = subarticles1.ArticleWithSection(headline='First', pub_date=datetime(2005, 8, 22), section='News')
+>>> a1.save()
+>>> a1
+<ArticleWithSection object>
+>>> a1.id
+1
+>>> a1.headline
+'First'
+>>> a1.pub_date
+datetime.datetime(2005, 8, 22, 0, 0)
+
+# Retrieve it again, to prove the fields have been saved.
+>>> a1 = subarticles1.get_object(pk=1)
+>>> a1.headline
+'First'
+>>> a1.pub_date
+datetime.datetime(2005, 8, 22, 0, 0)
+>>> a1.section
+'News'
+
+# Create an ArticleWithoutPubDate.
+>>> a2 = subarticles2.ArticleWithoutPubDate(headline='Second')
+>>> a2.save()
+>>> a2
+<ArticleWithoutPubDate object>
+>>> a2.id
+1
+>>> a2.pub_date
+Traceback (most recent call last):
+    ...
+AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
+
+# Retrieve it again, to prove the fields have been saved.
+>>> a2 = subarticles2.get_object(pk=1)
+>>> a2.headline
+'Second'
+>>> a2.pub_date
+Traceback (most recent call last):
+    ...
+AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
+
+# Create an ArticleWithFieldOverride.
+>>> a3 = subarticles3.ArticleWithFieldOverride(headline='Third', pub_date=date(2005, 8, 22))
+>>> a3.save()
+>>> a3
+<ArticleWithFieldOverride object>
+>>> a3.id
+1
+>>> a3.pub_date
+datetime.date(2005, 8, 22)
+
+# Retrieve it again, to prove the fields have been saved.
+>>> a3 = subarticles3.get_object(pk=1)
+>>> a3.headline
+'Third'
+>>> a3.pub_date
+datetime.date(2005, 8, 22)
+
+# Create an ArticleWithManyChanges.
+>>> a4 = subarticles4.ArticleWithManyChanges(headline='Fourth', section='Arts',
+...     is_popular=True, pub_date=date(2005, 8, 22))
+>>> a4.save()
+
+# a4 inherits __repr__() from its parent model (ArticleWithRepr).
+>>> a4
+Fourth
+
+# Retrieve it again, to prove the fields have been saved.
+>>> a4 = subarticles4.get_object(pk=1)
+>>> a4.headline
+'Fourth'
+>>> a4.section
+'Arts'
+>>> a4.is_popular == True
+True
+>>> a4.pub_date
+datetime.date(2005, 8, 22)
+
+# Test get_list().
+>>> subarticles1.get_list()
+[<ArticleWithSection object>]
+>>> subarticles2.get_list()
+[<ArticleWithoutPubDate object>]
+>>> subarticles3.get_list()
+[<ArticleWithFieldOverride object>]
+>>> subarticles4.get_list()
+[Fourth]
+
+# Create a couple of ArticleWithChangedMeta objects.
+>>> a5 = subarticles5.ArticleWithChangedMeta(headline='A', pub_date=datetime(2005, 3, 1))
+>>> a5.save()
+>>> a6 = subarticles5.ArticleWithChangedMeta(headline='B', pub_date=datetime(2005, 4, 1))
+>>> a6.save()
+>>> a7 = subarticles5.ArticleWithChangedMeta(headline='C', pub_date=datetime(2005, 5, 1))
+>>> a7.save()
+
+# Ordering has been overridden, so objects are ordered
+# by headline ASC instead of pub_date DESC.
+>>> subarticles5.get_list()
+[A, B, C]
+"""