Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added model Meta option for swappable models, and made auth.User a sw…

…appable model
  • Loading branch information...
commit 7cc0baf89d490c92ef3f1dc909b8090191a1294b 1 parent f292341
Russell Keith-Magee authored June 04, 2012
2  django/conf/global_settings.py
@@ -488,6 +488,8 @@
488 488
 # AUTHENTICATION #
489 489
 ##################
490 490
 
  491
+AUTH_USER_MODEL = 'auth.User'
  492
+
491 493
 AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
492 494
 
493 495
 LOGIN_URL = '/accounts/login/'
6  django/contrib/admin/models.py
... ...
@@ -1,6 +1,6 @@
1 1
 from django.db import models
  2
+from django.conf import settings
2 3
 from django.contrib.contenttypes.models import ContentType
3  
-from django.contrib.auth.models import User
4 4
 from django.contrib.admin.util import quote
5 5
 from django.utils.translation import ugettext_lazy as _
6 6
 from django.utils.encoding import smart_unicode
@@ -10,14 +10,16 @@
10 10
 CHANGE = 2
11 11
 DELETION = 3
12 12
 
  13
+
13 14
 class LogEntryManager(models.Manager):
14 15
     def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
15 16
         e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message)
16 17
         e.save()
17 18
 
  19
+
18 20
 class LogEntry(models.Model):
19 21
     action_time = models.DateTimeField(_('action time'), auto_now=True)
20  
-    user = models.ForeignKey(User)
  22
+    user = models.ForeignKey(settings.AUTH_USER_MODEL)
21 23
     content_type = models.ForeignKey(ContentType, blank=True, null=True)
22 24
     object_id = models.TextField(_('object id'), blank=True, null=True)
23 25
     object_repr = models.CharField(_('object repr'), max_length=200)
92  django/contrib/auth/models.py
@@ -93,6 +93,7 @@ class GroupManager(models.Manager):
93 93
     def get_by_natural_key(self, name):
94 94
         return self.get(name=name)
95 95
 
  96
+
96 97
 class Group(models.Model):
97 98
     """
98 99
     Groups are a generic way of categorizing users to apply permissions, or
@@ -197,8 +198,6 @@ def _user_get_all_permissions(user, obj):
197 198
 
198 199
 
199 200
 def _user_has_perm(user, perm, obj):
200  
-    anon = user.is_anonymous()
201  
-    active = user.is_active
202 201
     for backend in auth.get_backends():
203 202
         if hasattr(backend, "has_perm"):
204 203
             if obj is not None:
@@ -211,8 +210,6 @@ def _user_has_perm(user, perm, obj):
211 210
 
212 211
 
213 212
 def _user_has_module_perms(user, app_label):
214  
-    anon = user.is_anonymous()
215  
-    active = user.is_active
216 213
     for backend in auth.get_backends():
217 214
         if hasattr(backend, "has_module_perms"):
218 215
             if backend.has_module_perms(user, app_label):
@@ -220,7 +217,54 @@ def _user_has_module_perms(user, app_label):
220 217
     return False
221 218
 
222 219
 
223  
-class User(models.Model):
  220
+class AbstractBaseUser(models.Model):
  221
+    password = models.CharField(_('password'), max_length=128)
  222
+
  223
+    class Meta:
  224
+        abstract = True
  225
+
  226
+    def is_anonymous(self):
  227
+        """
  228
+        Always returns False. This is a way of comparing User objects to
  229
+        anonymous users.
  230
+        """
  231
+        return False
  232
+
  233
+    def is_authenticated(self):
  234
+        """
  235
+        Always return True. This is a way to tell if the user has been
  236
+        authenticated in templates.
  237
+        """
  238
+        return True
  239
+
  240
+    def set_password(self, raw_password):
  241
+        self.password = make_password(raw_password)
  242
+
  243
+    def check_password(self, raw_password):
  244
+        """
  245
+        Returns a boolean of whether the raw_password was correct. Handles
  246
+        hashing formats behind the scenes.
  247
+        """
  248
+        def setter(raw_password):
  249
+            self.set_password(raw_password)
  250
+            self.save()
  251
+        return check_password(raw_password, self.password, setter)
  252
+
  253
+    def set_unusable_password(self):
  254
+        # Sets a value that will never be a valid hash
  255
+        self.password = make_password(None)
  256
+
  257
+    def has_usable_password(self):
  258
+        return is_password_usable(self.password)
  259
+
  260
+    def get_full_name(self):
  261
+        raise NotImplementedError()
  262
+
  263
+    def get_short_name(self):
  264
+        raise NotImplementedError()
  265
+
  266
+
  267
+class User(AbstractBaseUser):
224 268
     """
225 269
     Users within the Django authentication system are represented by this
226 270
     model.
@@ -233,7 +277,6 @@ class User(models.Model):
233 277
     first_name = models.CharField(_('first name'), max_length=30, blank=True)
234 278
     last_name = models.CharField(_('last name'), max_length=30, blank=True)
235 279
     email = models.EmailField(_('e-mail address'), blank=True)
236  
-    password = models.CharField(_('password'), max_length=128)
237 280
     is_staff = models.BooleanField(_('staff status'), default=False,
238 281
         help_text=_('Designates whether the user can log into this admin '
239 282
                     'site.'))
@@ -257,6 +300,7 @@ class User(models.Model):
257 300
     class Meta:
258 301
         verbose_name = _('user')
259 302
         verbose_name_plural = _('users')
  303
+        swappable = 'AUTH_USER_MODEL'
260 304
 
261 305
     def __unicode__(self):
262 306
         return self.username
@@ -267,20 +311,6 @@ def natural_key(self):
267 311
     def get_absolute_url(self):
268 312
         return "/users/%s/" % urllib.quote(smart_str(self.username))
269 313
 
270  
-    def is_anonymous(self):
271  
-        """
272  
-        Always returns False. This is a way of comparing User objects to
273  
-        anonymous users.
274  
-        """
275  
-        return False
276  
-
277  
-    def is_authenticated(self):
278  
-        """
279  
-        Always return True. This is a way to tell if the user has been
280  
-        authenticated in templates.
281  
-        """
282  
-        return True
283  
-
284 314
     def get_full_name(self):
285 315
         """
286 316
         Returns the first_name plus the last_name, with a space in between.
@@ -288,26 +318,6 @@ def get_full_name(self):
288 318
         full_name = u'%s %s' % (self.first_name, self.last_name)
289 319
         return full_name.strip()
290 320
 
291  
-    def set_password(self, raw_password):
292  
-        self.password = make_password(raw_password)
293  
-
294  
-    def check_password(self, raw_password):
295  
-        """
296  
-        Returns a boolean of whether the raw_password was correct. Handles
297  
-        hashing formats behind the scenes.
298  
-        """
299  
-        def setter(raw_password):
300  
-            self.set_password(raw_password)
301  
-            self.save()
302  
-        return check_password(raw_password, self.password, setter)
303  
-
304  
-    def set_unusable_password(self):
305  
-        # Sets a value that will never be a valid hash
306  
-        self.password = make_password(None)
307  
-
308  
-    def has_usable_password(self):
309  
-        return is_password_usable(self.password)
310  
-
311 321
     def get_group_permissions(self, obj=None):
312 322
         """
313 323
         Returns a list of permission strings that this user has through his/her
@@ -428,7 +438,7 @@ def __ne__(self, other):
428 438
         return not self.__eq__(other)
429 439
 
430 440
     def __hash__(self):
431  
-        return 1 # instances always return the same hash value
  441
+        return 1  # instances always return the same hash value
432 442
 
433 443
     def save(self):
434 444
         raise NotImplementedError
17  django/contrib/comments/models.py
... ...
@@ -1,15 +1,15 @@
1  
-from django.contrib.auth.models import User
  1
+from django.conf import settings
2 2
 from django.contrib.comments.managers import CommentManager
3 3
 from django.contrib.contenttypes import generic
4 4
 from django.contrib.contenttypes.models import ContentType
5 5
 from django.contrib.sites.models import Site
6  
-from django.db import models
7 6
 from django.core import urlresolvers
  7
+from django.db import models
8 8
 from django.utils.translation import ugettext_lazy as _
9 9
 from django.utils import timezone
10  
-from django.conf import settings
11 10
 
12  
-COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
  11
+COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
  12
+
13 13
 
14 14
 class BaseCommentAbstractModel(models.Model):
15 15
     """
@@ -39,6 +39,7 @@ def get_content_object_url(self):
39 39
             args=(self.content_type_id, self.object_pk)
40 40
         )
41 41
 
  42
+
42 43
 class Comment(BaseCommentAbstractModel):
43 44
     """
44 45
     A user comment about some object.
@@ -47,7 +48,7 @@ class Comment(BaseCommentAbstractModel):
47 48
     # Who posted this comment? If ``user`` is set then it was an authenticated
48 49
     # user; otherwise at least user_name should have been set and the comment
49 50
     # was posted by a non-authenticated user.
50  
-    user        = models.ForeignKey(User, verbose_name=_('user'),
  51
+    user        = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
51 52
                     blank=True, null=True, related_name="%(class)s_comments")
52 53
     user_name   = models.CharField(_("user's name"), max_length=50, blank=True)
53 54
     user_email  = models.EmailField(_("user's email address"), blank=True)
@@ -115,6 +116,7 @@ def _get_userinfo(self):
115 116
 
116 117
     def _get_name(self):
117 118
         return self.userinfo["name"]
  119
+
118 120
     def _set_name(self, val):
119 121
         if self.user_id:
120 122
             raise AttributeError(_("This comment was posted by an authenticated "\
@@ -124,6 +126,7 @@ def _set_name(self, val):
124 126
 
125 127
     def _get_email(self):
126 128
         return self.userinfo["email"]
  129
+
127 130
     def _set_email(self, val):
128 131
         if self.user_id:
129 132
             raise AttributeError(_("This comment was posted by an authenticated "\
@@ -133,6 +136,7 @@ def _set_email(self, val):
133 136
 
134 137
     def _get_url(self):
135 138
         return self.userinfo["url"]
  139
+
136 140
     def _set_url(self, val):
137 141
         self.user_url = val
138 142
     url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")
@@ -153,6 +157,7 @@ def get_as_text(self):
153 157
         }
154 158
         return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
155 159
 
  160
+
156 161
 class CommentFlag(models.Model):
157 162
     """
158 163
     Records a flag on a comment. This is intentionally flexible; right now, a
@@ -166,7 +171,7 @@ class CommentFlag(models.Model):
166 171
     design users are only allowed to flag a comment with a given flag once;
167 172
     if you want rating look elsewhere.
168 173
     """
169  
-    user      = models.ForeignKey(User, verbose_name=_('user'), related_name="comment_flags")
  174
+    user      = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name="comment_flags")
170 175
     comment   = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
171 176
     flag      = models.CharField(_('flag'), max_length=30, db_index=True)
172 177
     flag_date = models.DateTimeField(_('date'), default=None)
1  django/core/management/commands/sqlall.py
@@ -4,6 +4,7 @@
4 4
 from django.core.management.sql import sql_all
5 5
 from django.db import connections, DEFAULT_DB_ALIAS
6 6
 
  7
+
7 8
 class Command(AppCommand):
8 9
     help = "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s)."
9 10
 
2  django/core/management/commands/syncdb.py
@@ -68,6 +68,7 @@ def handle_noargs(self, **options):
68 68
                 if router.allow_syncdb(db, m)])
69 69
             for app in models.get_apps()
70 70
         ]
  71
+
71 72
         def model_installed(model):
72 73
             opts = model._meta
73 74
             converter = connection.introspection.table_name_converter
@@ -101,7 +102,6 @@ def model_installed(model):
101 102
                     cursor.execute(statement)
102 103
                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
103 104
 
104  
-
105 105
         transaction.commit_unless_managed(using=db)
106 106
 
107 107
         # Send the post_syncdb signal, so individual apps can do whatever they need
1  django/core/management/commands/validate.py
... ...
@@ -1,5 +1,6 @@
1 1
 from django.core.management.base import NoArgsCommand
2 2
 
  3
+
3 4
 class Command(NoArgsCommand):
4 5
     help = "Validates all installed models."
5 6
 
11  django/core/management/sql.py
@@ -6,6 +6,7 @@
6 6
 from django.db import models
7 7
 from django.db.models import get_models
8 8
 
  9
+
9 10
 def sql_create(app, style, connection):
10 11
     "Returns a list of the CREATE TABLE SQL statements for the given app."
11 12
 
@@ -52,6 +53,7 @@ def sql_create(app, style, connection):
52 53
 
53 54
     return final_output
54 55
 
  56
+
55 57
 def sql_delete(app, style, connection):
56 58
     "Returns a list of the DROP TABLE SQL statements for the given app."
57 59
 
@@ -80,7 +82,7 @@ def sql_delete(app, style, connection):
80 82
             opts = model._meta
81 83
             for f in opts.local_fields:
82 84
                 if f.rel and f.rel.to not in to_delete:
83  
-                    references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
  85
+                    references_to_delete.setdefault(f.rel.to, []).append((model, f))
84 86
 
85 87
             to_delete.add(model)
86 88
 
@@ -94,7 +96,8 @@ def sql_delete(app, style, connection):
94 96
         cursor.close()
95 97
         connection.close()
96 98
 
97  
-    return output[::-1] # Reverse it, to deal with table dependencies.
  99
+    return output[::-1]  # Reverse it, to deal with table dependencies.
  100
+
98 101
 
99 102
 def sql_flush(style, connection, only_django=False):
100 103
     """
@@ -112,6 +115,7 @@ def sql_flush(style, connection, only_django=False):
112 115
     )
113 116
     return statements
114 117
 
  118
+
115 119
 def sql_custom(app, style, connection):
116 120
     "Returns a list of the custom table modifying SQL statements for the given app."
117 121
     output = []
@@ -123,6 +127,7 @@ def sql_custom(app, style, connection):
123 127
 
124 128
     return output
125 129
 
  130
+
126 131
 def sql_indexes(app, style, connection):
127 132
     "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
128 133
     output = []
@@ -130,10 +135,12 @@ def sql_indexes(app, style, connection):
130 135
         output.extend(connection.creation.sql_indexes_for_model(model, style))
131 136
     return output
132 137
 
  138
+
133 139
 def sql_all(app, style, connection):
134 140
     "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
135 141
     return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection)
136 142
 
  143
+
137 144
 def custom_sql_for_model(model, style, connection):
138 145
     opts = model._meta
139 146
     app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
17  django/core/management/validation.py
@@ -3,6 +3,7 @@
3 3
 from django.core.management.color import color_style
4 4
 from django.utils.itercompat import is_iterable
5 5
 
  6
+
6 7
 class ModelErrorCollection:
7 8
     def __init__(self, outfile=sys.stdout):
8 9
         self.errors = []
@@ -13,6 +14,7 @@ def add(self, context, error):
13 14
         self.errors.append((context, error))
14 15
         self.outfile.write(self.style.ERROR("%s: %s\n" % (context, error)))
15 16
 
  17
+
16 18
 def get_validation_errors(outfile, app=None):
17 19
     """
18 20
     Validates all models that are part of the specified app. If no app name is provided,
@@ -54,7 +56,7 @@ def get_validation_errors(outfile, app=None):
54 56
                     e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
55 57
             if isinstance(f, models.DecimalField):
56 58
                 decimalp_ok, mdigits_ok = False, False
57  
-                decimalp_msg ='"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
  59
+                decimalp_msg = '"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
58 60
                 try:
59 61
                     decimal_places = int(f.decimal_places)
60 62
                     if decimal_places < 0:
@@ -121,6 +123,10 @@ def get_validation_errors(outfile, app=None):
121 123
                 if isinstance(f.rel.to, (str, unicode)):
122 124
                     continue
123 125
 
  126
+                # Make sure the model we're related hasn't been swapped out
  127
+                if f.rel.to._meta.swapped:
  128
+                    e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
  129
+
124 130
                 # Make sure the related field specified by a ForeignKey is unique
125 131
                 if not f.rel.to._meta.get_field(f.rel.field_name).unique:
126 132
                     e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.rel.field_name, f.rel.to.__name__))
@@ -163,6 +169,10 @@ def get_validation_errors(outfile, app=None):
163 169
                 if isinstance(f.rel.to, (str, unicode)):
164 170
                     continue
165 171
 
  172
+            # Make sure the model we're related hasn't been swapped out
  173
+            if f.rel.to._meta.swapped:
  174
+                e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
  175
+
166 176
             # Check that the field is not set to unique.  ManyToManyFields do not support unique.
167 177
             if f.unique:
168 178
                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
@@ -174,7 +184,7 @@ def get_validation_errors(outfile, app=None):
174 184
                 seen_from, seen_to, seen_self = False, False, 0
175 185
                 for inter_field in f.rel.through._meta.fields:
176 186
                     rel_to = getattr(inter_field.rel, 'to', None)
177  
-                    if from_model == to_model: # relation to self
  187
+                    if from_model == to_model:  # relation to self
178 188
                         if rel_to == from_model:
179 189
                             seen_self += 1
180 190
                         if seen_self > 2:
@@ -276,7 +286,8 @@ def get_validation_errors(outfile, app=None):
276 286
         # Check ordering attribute.
277 287
         if opts.ordering:
278 288
             for field_name in opts.ordering:
279  
-                if field_name == '?': continue
  289
+                if field_name == '?':
  290
+                    continue
280 291
                 if field_name.startswith('-'):
281 292
                     field_name = field_name[1:]
282 293
                 if opts.order_with_respect_to and field_name == '_order':
16  django/db/backends/creation.py
@@ -34,7 +34,7 @@ def sql_create_model(self, model, style, known_models=set()):
34 34
             (list_of_sql, pending_references_dict)
35 35
         """
36 36
         opts = model._meta
37  
-        if not opts.managed or opts.proxy:
  37
+        if not opts.managed or opts.proxy or opts.swapped:
38 38
             return [], {}
39 39
         final_output = []
40 40
         table_output = []
@@ -86,9 +86,9 @@ def sql_create_model(self, model, style, known_models=set()):
86 86
 
87 87
         full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' +
88 88
                           style.SQL_TABLE(qn(opts.db_table)) + ' (']
89  
-        for i, line in enumerate(table_output): # Combine and add commas.
  89
+        for i, line in enumerate(table_output):  # Combine and add commas.
90 90
             full_statement.append(
91  
-                '    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
  91
+                '    %s%s' % (line, i < len(table_output) - 1 and ',' or ''))
92 92
         full_statement.append(')')
93 93
         if opts.db_tablespace:
94 94
             tablespace_sql = self.connection.ops.tablespace_sql(
@@ -137,11 +137,11 @@ def sql_for_pending_references(self, model, style, pending_references):
137 137
         """
138 138
         from django.db.backends.util import truncate_name
139 139
 
140  
-        if not model._meta.managed or model._meta.proxy:
  140
+        opts = model._meta
  141
+        if not opts.managed or opts.proxy or opts.swapped:
141 142
             return []
142 143
         qn = self.connection.ops.quote_name
143 144
         final_output = []
144  
-        opts = model._meta
145 145
         if model in pending_references:
146 146
             for rel_class, f in pending_references[model]:
147 147
                 rel_opts = rel_class._meta
@@ -166,7 +166,7 @@ def sql_indexes_for_model(self, model, style):
166 166
         """
167 167
         Returns the CREATE INDEX SQL statements for a single model.
168 168
         """
169  
-        if not model._meta.managed or model._meta.proxy:
  169
+        if not model._meta.managed or model._meta.proxy or model._meta.swapped:
170 170
             return []
171 171
         output = []
172 172
         for f in model._meta.local_fields:
@@ -205,7 +205,7 @@ def sql_destroy_model(self, model, references_to_delete, style):
205 205
         Return the DROP TABLE and restraint dropping statements for a single
206 206
         model.
207 207
         """
208  
-        if not model._meta.managed or model._meta.proxy:
  208
+        if not model._meta.managed or model._meta.proxy or model._meta.swapped:
209 209
             return []
210 210
         # Drop the table now
211 211
         qn = self.connection.ops.quote_name
@@ -222,7 +222,7 @@ def sql_destroy_model(self, model, references_to_delete, style):
222 222
 
223 223
     def sql_remove_table_constraints(self, model, references_to_delete, style):
224 224
         from django.db.backends.util import truncate_name
225  
-        if not model._meta.managed or model._meta.proxy:
  225
+        if not model._meta.managed or model._meta.proxy or model._meta.swapped:
226 226
             return []
227 227
         output = []
228 228
         qn = self.connection.ops.quote_name
15  django/db/models/base.py
@@ -3,7 +3,7 @@
3 3
 from functools import update_wrapper
4 4
 from future_builtins import zip
5 5
 
6  
-import django.db.models.manager     # Imported to register signal handler.
  6
+import django.db.models.manager  # Imported to register signal handler.
7 7
 from django.conf import settings
8 8
 from django.core.exceptions import (ObjectDoesNotExist,
9 9
     MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS)
@@ -230,6 +230,7 @@ def _prepare(cls):
230 230
         if opts.order_with_respect_to:
231 231
             cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True)
232 232
             cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False)
  233
+
233 234
             # defer creating accessors on the foreign class until we are
234 235
             # certain it has been created
235 236
             def make_foreign_order_accessors(field, model, cls):
@@ -260,6 +261,7 @@ def make_foreign_order_accessors(field, model, cls):
260 261
 
261 262
         signals.class_prepared.send(sender=cls)
262 263
 
  264
+
263 265
 class ModelState(object):
264 266
     """
265 267
     A class for storing instance state
@@ -271,6 +273,7 @@ def __init__(self, db=None):
271 273
         # This impacts validation only; it has no effect on the actual save.
272 274
         self.adding = True
273 275
 
  276
+
274 277
 class Model(object):
275 278
     __metaclass__ = ModelBase
276 279
     _deferred = False
@@ -585,7 +588,6 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
585 588
             signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
586 589
                                    update_fields=update_fields, raw=raw, using=using)
587 590
 
588  
-
589 591
     save_base.alters_data = True
590 592
 
591 593
     def delete(self, using=None):
@@ -609,7 +611,7 @@ def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
609 611
         order = not is_next and '-' or ''
610 612
         param = smart_str(getattr(self, field.attname))
611 613
         q = Q(**{'%s__%s' % (field.name, op): param})
612  
-        q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
  614
+        q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk})
613 615
         qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
614 616
         try:
615 617
             return qs[0]
@@ -802,7 +804,7 @@ def unique_error_message(self, model_class, unique_check):
802 804
             field = opts.get_field(field_name)
803 805
             field_label = capfirst(field.verbose_name)
804 806
             # Insert the error into the error dict, very sneaky
805  
-            return field.error_messages['unique'] %  {
  807
+            return field.error_messages['unique'] % {
806 808
                 'model_name': unicode(model_name),
807 809
                 'field_label': unicode(field_label)
808 810
             }
@@ -810,7 +812,7 @@ def unique_error_message(self, model_class, unique_check):
810 812
         else:
811 813
             field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check)
812 814
             field_labels = get_text_list(field_labels, _('and'))
813  
-            return _(u"%(model_name)s with this %(field_label)s already exists.") %  {
  815
+            return _(u"%(model_name)s with this %(field_label)s already exists.") % {
814 816
                 'model_name': unicode(model_name),
815 817
                 'field_label': unicode(field_labels)
816 818
             }
@@ -915,6 +917,7 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
915 917
 class Empty(object):
916 918
     pass
917 919
 
  920
+
918 921
 def simple_class_factory(model, attrs):
919 922
     """Used to unpickle Models without deferred fields.
920 923
 
@@ -924,6 +927,7 @@ def simple_class_factory(model, attrs):
924 927
     """
925 928
     return model
926 929
 
  930
+
927 931
 def model_unpickle(model, attrs, factory):
928 932
     """
929 933
     Used to unpickle Model subclasses with deferred fields.
@@ -932,5 +936,6 @@ def model_unpickle(model, attrs, factory):
932 936
     return cls.__new__(cls)
933 937
 model_unpickle.__safe_for_unpickle__ = True
934 938
 
  939
+
935 940
 def subclass_exception(name, parents, module):
936 941
     return type(name, parents, {'__module__': module})
37  django/db/models/fields/related.py
@@ -20,6 +20,7 @@
@@ -76,6 +77,7 @@ class MyModel(Model):
@@ -86,6 +88,7 @@ def do_pending_lookups(sender, **kwargs):
@@ -219,6 +222,7 @@ def related_query_name(self):
@@ -305,6 +309,7 @@ def __set__(self, instance, value):
@@ -429,6 +434,7 @@ def __set__(self, instance, value):
@@ -659,7 +665,7 @@ def _add_items(self, source_field_name, target_field_name, *objs):
@@ -751,6 +757,7 @@ def _clear_items(self, source_field_name):
@@ -859,12 +866,13 @@ def __set__(self, instance, value):
@@ -890,6 +898,7 @@ def get_related_field(self):
@@ -899,6 +908,7 @@ def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
@@ -923,16 +933,18 @@ def get_related_field(self):
@@ -1049,6 +1061,7 @@ def db_type(self, connection):
@@ -1057,6 +1070,7 @@ class OneToOneField(ForeignKey):
@@ -1076,12 +1090,14 @@ def save_form_data(self, instance, data):
@@ -1118,12 +1134,14 @@ def set_managed(field, model, cls):
@@ -1134,7 +1152,7 @@ def __init__(self, to, **kwargs):
@@ -1165,7 +1183,7 @@ def _get_m2m_attr(self, related, attr):
@@ -1176,7 +1194,7 @@ def _get_m2m_reverse_attr(self, related, attr):
@@ -1221,7 +1239,8 @@ def contribute_to_class(self, cls, name):
1  django/db/models/loading.py
@@ -13,6 +13,7 @@
13 13
 __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
14 14
         'load_app', 'app_cache_ready')
15 15
 
  16
+
16 17
 class AppCache(object):
17 18
     """
18 19
     A cache that stores installed applications and their models. Used to
16  django/db/models/options.py
@@ -17,7 +17,8 @@
17 17
 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
18 18
                  'unique_together', 'permissions', 'get_latest_by',
19 19
                  'order_with_respect_to', 'app_label', 'db_tablespace',
20  
-                 'abstract', 'managed', 'proxy', 'auto_created')
  20
+                 'abstract', 'managed', 'proxy', 'swappable', 'auto_created')
  21
+
21 22
 
22 23
 class Options(object):
23 24
     def __init__(self, meta, app_label=None):
@@ -27,8 +28,8 @@ def __init__(self, meta, app_label=None):
27 28
         self.verbose_name_plural = None
28 29
         self.db_table = ''
29 30
         self.ordering = []
30  
-        self.unique_together =  []
31  
-        self.permissions =  []
  31
+        self.unique_together = []
  32
+        self.permissions = []
32 33
         self.object_name, self.app_label = None, app_label
33 34
         self.get_latest_by = None
34 35
         self.order_with_respect_to = None
@@ -50,6 +51,7 @@ def __init__(self, meta, app_label=None):
50 51
         # in the end of the proxy_for_model chain. In particular, for
51 52
         # concrete models, the concrete_model is always the class itself.
52 53
         self.concrete_model = None
  54
+        self.swappable = None
53 55
         self.parents = SortedDict()
54 56
         self.duplicate_targets = {}
55 57
         self.auto_created = False
@@ -213,6 +215,14 @@ def verbose_name_raw(self):
213 215
         return raw
214 216
     verbose_name_raw = property(verbose_name_raw)
215 217
 
  218
+    def _swapped(self):
  219
+        """
  220
+        Has this model been swapped out for another?
  221
+        """
  222
+        model_label = '%s.%s' % (self.app_label, self.object_name)
  223
+        return self.swappable and getattr(settings, self.swappable, None) not in (None, model_label)
  224
+    swapped = property(_swapped)
  225
+
216 226
     def _fields(self):
217 227
         """
218 228
         The getter for self.fields. This returns the list of field objects
7  django/test/utils.py
@@ -27,7 +27,7 @@ def __repr__(self):
27 27
     def __eq__(self, other):
28 28
         if self.val == other:
29 29
             return True
30  
-        return round(abs(self.val-other), self.places) == 0
  30
+        return round(abs(self.val - other), self.places) == 0
31 31
 
32 32
 
33 33
 class ContextList(list):
@@ -45,7 +45,7 @@ def __getitem__(self, key):
45 45
 
46 46
     def __contains__(self, key):
47 47
         try:
48  
-            value = self[key]
  48
+            self[key]
49 49
         except KeyError:
50 50
             return False
51 51
         return True
@@ -187,9 +187,11 @@ def __call__(self, test_func):
187 187
         if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase):
188 188
             original_pre_setup = test_func._pre_setup
189 189
             original_post_teardown = test_func._post_teardown
  190
+
190 191
             def _pre_setup(innerself):
191 192
                 self.enable()
192 193
                 original_pre_setup(innerself)
  194
+
193 195
             def _post_teardown(innerself):
194 196
                 original_post_teardown(innerself)
195 197
                 self.disable()
@@ -218,4 +220,3 @@ def disable(self):
218 220
             new_value = getattr(settings, key, None)
219 221
             setting_changed.send(sender=settings._wrapped.__class__,
220 222
                                  setting=key, value=new_value)
221  
-
9  docs/ref/settings.txt
@@ -120,6 +120,15 @@ Default: Not defined
120 120
 The site-specific user profile model used by this site. See
121 121
 :ref:`auth-profiles`.
122 122
 
  123
+.. setting:: AUTH_USER_MODEL
  124
+
  125
+AUTH_USER_MODEL
  126
+---------------
  127
+
  128
+Default: 'auth.User'
  129
+
  130
+The model to use to represent a User. See :ref:`auth-custom-user`.
  131
+
123 132
 .. setting:: CACHES
124 133
 
125 134
 CACHES
7  docs/topics/auth.txt
@@ -1723,6 +1723,13 @@ Fields
1723 1723
         group.permissions.remove(permission, permission, ...)
1724 1724
         group.permissions.clear()
1725 1725
 
  1726
+.. _auth-custom-user:
  1727
+
  1728
+Customizing the User model
  1729
+==========================
  1730
+
  1731
+TODO
  1732
+
1726 1733
 .. _authentication-backends:
1727 1734
 
1728 1735
 Other authentication sources
88  tests/modeltests/invalid_models/invalid_models/models.py
@@ -19,11 +19,12 @@ class FieldErrors(models.Model):
19 19
     decimalfield5 = models.DecimalField(max_digits=10, decimal_places=10)
20 20
     filefield = models.FileField()
21 21
     choices = models.CharField(max_length=10, choices='bad')
22  
-    choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)])
  22
+    choices2 = models.CharField(max_length=10, choices=[(1, 2, 3), (1, 2, 3)])
23 23
     index = models.CharField(max_length=10, db_index='bad')
24 24
     field_ = models.CharField(max_length=10)
25 25
     nullbool = models.BooleanField(null=True)
26 26
 
  27
+
27 28
 class Target(models.Model):
28 29
     tgt_safe = models.CharField(max_length=10)
29 30
     clash1 = models.CharField(max_length=10)
@@ -31,12 +32,14 @@ class Target(models.Model):
31 32
 
32 33
     clash1_set = models.CharField(max_length=10)
33 34
 
  35
+
34 36
 class Clash1(models.Model):
35 37
     src_safe = models.CharField(max_length=10)
36 38
 
37 39
     foreign = models.ForeignKey(Target)
38 40
     m2m = models.ManyToManyField(Target)
39 41
 
  42
+
40 43
 class Clash2(models.Model):
41 44
     src_safe = models.CharField(max_length=10)
42 45
 
@@ -46,6 +49,7 @@ class Clash2(models.Model):
46 49
     m2m_1 = models.ManyToManyField(Target, related_name='id')
47 50
     m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
48 51
 
  52
+
49 53
 class Target2(models.Model):
50 54
     clash3 = models.CharField(max_length=10)
51 55
     foreign_tgt = models.ForeignKey(Target)
@@ -54,6 +58,7 @@ class Target2(models.Model):
54 58
     m2m_tgt = models.ManyToManyField(Target)
55 59
     clashm2m_set = models.ManyToManyField(Target)
56 60
 
  61
+
57 62
 class Clash3(models.Model):
58 63
     src_safe = models.CharField(max_length=10)
59 64
 
@@ -63,12 +68,15 @@ class Clash3(models.Model):
63 68
     m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt')
64 69
     m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt')
65 70
 
  71
+
66 72
 class ClashForeign(models.Model):
67 73
     foreign = models.ForeignKey(Target2)
68 74
 
  75
+
69 76
 class ClashM2M(models.Model):
70 77
     m2m = models.ManyToManyField(Target2)
71 78
 
  79
+
72 80
 class SelfClashForeign(models.Model):
73 81
     src_safe = models.CharField(max_length=10)
74 82
     selfclashforeign = models.CharField(max_length=10)
@@ -77,6 +85,7 @@ class SelfClashForeign(models.Model):
77 85
     foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
78 86
     foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
79 87
 
  88
+
80 89
 class ValidM2M(models.Model):
81 90
     src_safe = models.CharField(max_length=10)
82 91
     validm2m = models.CharField(max_length=10)
@@ -92,6 +101,7 @@ class ValidM2M(models.Model):
92 101
     m2m_3 = models.ManyToManyField('self')
93 102
     m2m_4 = models.ManyToManyField('self')
94 103
 
  104
+
95 105
 class SelfClashM2M(models.Model):
96 106
     src_safe = models.CharField(max_length=10)
97 107
     selfclashm2m = models.CharField(max_length=10)
@@ -106,120 +116,148 @@ class SelfClashM2M(models.Model):
106 116
     m2m_3 = models.ManyToManyField('self', symmetrical=False)
107 117
     m2m_4 = models.ManyToManyField('self', symmetrical=False)
108 118
 
  119
+
109 120
 class Model(models.Model):
110 121
     "But it's valid to call a model Model."
111  
-    year = models.PositiveIntegerField() #1960
112  
-    make = models.CharField(max_length=10) #Aston Martin
113  
-    name = models.CharField(max_length=10) #DB 4 GT
  122
+    year = models.PositiveIntegerField()  # 1960
  123
+    make = models.CharField(max_length=10)  # Aston Martin
  124
+    name = models.CharField(max_length=10)  # DB 4 GT
  125
+
114 126
 
115 127
 class Car(models.Model):
116 128
     colour = models.CharField(max_length=5)
117 129
     model = models.ForeignKey(Model)
118 130
 
  131
+
119 132
 class MissingRelations(models.Model):
120 133
     rel1 = models.ForeignKey("Rel1")
121 134
     rel2 = models.ManyToManyField("Rel2")
122 135
 
  136
+
123 137
 class MissingManualM2MModel(models.Model):
124 138
     name = models.CharField(max_length=5)
125 139
     missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel")
126 140
 
  141
+
127 142
 class Person(models.Model):
128 143
     name = models.CharField(max_length=5)
129 144
 
  145
+
130 146
 class Group(models.Model):
131 147
     name = models.CharField(max_length=5)
132 148
     primary = models.ManyToManyField(Person, through="Membership", related_name="primary")
133 149
     secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary")
134 150
     tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary")
135 151
 
  152
+
136 153
 class GroupTwo(models.Model):
137 154
     name = models.CharField(max_length=5)
138 155
     primary = models.ManyToManyField(Person, through="Membership")
139 156
     secondary = models.ManyToManyField(Group, through="MembershipMissingFK")
140 157
 
  158
+
141 159
 class Membership(models.Model):
142 160
     person = models.ForeignKey(Person)
143 161
     group = models.ForeignKey(Group)
144 162
     not_default_or_null = models.CharField(max_length=5)
145 163
 
  164
+
146 165
 class MembershipMissingFK(models.Model):
147 166
     person = models.ForeignKey(Person)
148 167
 
  168
+
149 169
 class PersonSelfRefM2M(models.Model):
150 170
     name = models.CharField(max_length=5)
151 171
     friends = models.ManyToManyField('self', through="Relationship")
152 172
     too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK")
153 173
 
  174
+
154 175
 class PersonSelfRefM2MExplicit(models.Model):
155 176
     name = models.CharField(max_length=5)
156 177
     friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True)
157 178
 
  179
+
158 180
 class Relationship(models.Model):
159 181
     first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set")
160 182
     second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set")
161 183
     date_added = models.DateTimeField()
162 184
 
  185
+
163 186
 class ExplicitRelationship(models.Model):
164 187
     first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set")
165 188
     second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set")
166 189
     date_added = models.DateTimeField()
167 190
 
  191
+
168 192
 class RelationshipTripleFK(models.Model):
169 193
     first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2")
170 194
     second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2")
171 195
     third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far")
172 196
     date_added = models.DateTimeField()
173 197
 
  198
+
174 199
 class RelationshipDoubleFK(models.Model):
175 200
     first = models.ForeignKey(Person, related_name="first_related_name")
176 201
     second = models.ForeignKey(Person, related_name="second_related_name")
177 202
     third = models.ForeignKey(Group, related_name="rel_to_set")
178 203
     date_added = models.DateTimeField()
179 204
 
  205