Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

…appable model
  • Loading branch information...
commit 7cc0baf89d490c92ef3f1dc909b8090191a1294b 1 parent f292341
@freakboy3742 freakboy3742 authored
View
2  django/conf/global_settings.py
@@ -488,6 +488,8 @@
# AUTHENTICATION #
##################
+AUTH_USER_MODEL = 'auth.User'
+
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
LOGIN_URL = '/accounts/login/'
View
6 django/contrib/admin/models.py
@@ -1,6 +1,6 @@
from django.db import models
+from django.conf import settings
from django.contrib.contenttypes.models import ContentType
-from django.contrib.auth.models import User
from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
@@ -10,14 +10,16 @@
CHANGE = 2
DELETION = 3
+
class LogEntryManager(models.Manager):
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message)
e.save()
+
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
- user = models.ForeignKey(User)
+ user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
View
92 django/contrib/auth/models.py
@@ -93,6 +93,7 @@ class GroupManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
+
class Group(models.Model):
"""
Groups are a generic way of categorizing users to apply permissions, or
@@ -197,8 +198,6 @@ def _user_get_all_permissions(user, obj):
def _user_has_perm(user, perm, obj):
- anon = user.is_anonymous()
- active = user.is_active
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
if obj is not None:
@@ -211,8 +210,6 @@ def _user_has_perm(user, perm, obj):
def _user_has_module_perms(user, app_label):
- anon = user.is_anonymous()
- active = user.is_active
for backend in auth.get_backends():
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(user, app_label):
@@ -220,7 +217,54 @@ def _user_has_module_perms(user, app_label):
return False
-class User(models.Model):
+class AbstractBaseUser(models.Model):
+ password = models.CharField(_('password'), max_length=128)
+
+ class Meta:
+ abstract = True
+
+ def is_anonymous(self):
+ """
+ Always returns False. This is a way of comparing User objects to
+ anonymous users.
+ """
+ return False
+
+ def is_authenticated(self):
+ """
+ Always return True. This is a way to tell if the user has been
+ authenticated in templates.
+ """
+ return True
+
+ def set_password(self, raw_password):
+ self.password = make_password(raw_password)
+
+ def check_password(self, raw_password):
+ """
+ Returns a boolean of whether the raw_password was correct. Handles
+ hashing formats behind the scenes.
+ """
+ def setter(raw_password):
+ self.set_password(raw_password)
+ self.save()
+ return check_password(raw_password, self.password, setter)
+
+ def set_unusable_password(self):
+ # Sets a value that will never be a valid hash
+ self.password = make_password(None)
+
+ def has_usable_password(self):
+ return is_password_usable(self.password)
+
+ def get_full_name(self):
+ raise NotImplementedError()
+
+ def get_short_name(self):
+ raise NotImplementedError()
+
+
+class User(AbstractBaseUser):
"""
Users within the Django authentication system are represented by this
model.
@@ -233,7 +277,6 @@ class User(models.Model):
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('e-mail address'), blank=True)
- password = models.CharField(_('password'), max_length=128)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
@@ -257,6 +300,7 @@ class User(models.Model):
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
+ swappable = 'AUTH_USER_MODEL'
def __unicode__(self):
return self.username
@@ -267,20 +311,6 @@ def natural_key(self):
def get_absolute_url(self):
return "/users/%s/" % urllib.quote(smart_str(self.username))
- def is_anonymous(self):
- """
- Always returns False. This is a way of comparing User objects to
- anonymous users.
- """
- return False
-
- def is_authenticated(self):
- """
- Always return True. This is a way to tell if the user has been
- authenticated in templates.
- """
- return True
-
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
@@ -288,26 +318,6 @@ def get_full_name(self):
full_name = u'%s %s' % (self.first_name, self.last_name)
return full_name.strip()
- def set_password(self, raw_password):
- self.password = make_password(raw_password)
-
- def check_password(self, raw_password):
- """
- Returns a boolean of whether the raw_password was correct. Handles
- hashing formats behind the scenes.
- """
- def setter(raw_password):
- self.set_password(raw_password)
- self.save()
- return check_password(raw_password, self.password, setter)
-
- def set_unusable_password(self):
- # Sets a value that will never be a valid hash
- self.password = make_password(None)
-
- def has_usable_password(self):
- return is_password_usable(self.password)
-
def get_group_permissions(self, obj=None):
"""
Returns a list of permission strings that this user has through his/her
@@ -428,7 +438,7 @@ def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
- return 1 # instances always return the same hash value
+ return 1 # instances always return the same hash value
def save(self):
raise NotImplementedError
View
17 django/contrib/comments/models.py
@@ -1,15 +1,15 @@
-from django.contrib.auth.models import User
+from django.conf import settings
from django.contrib.comments.managers import CommentManager
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
-from django.db import models
from django.core import urlresolvers
+from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
-from django.conf import settings
-COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
+COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
+
class BaseCommentAbstractModel(models.Model):
"""
@@ -39,6 +39,7 @@ def get_content_object_url(self):
args=(self.content_type_id, self.object_pk)
)
+
class Comment(BaseCommentAbstractModel):
"""
A user comment about some object.
@@ -47,7 +48,7 @@ class Comment(BaseCommentAbstractModel):
# Who posted this comment? If ``user`` is set then it was an authenticated
# user; otherwise at least user_name should have been set and the comment
# was posted by a non-authenticated user.
- user = models.ForeignKey(User, verbose_name=_('user'),
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_comments")
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
user_email = models.EmailField(_("user's email address"), blank=True)
@@ -115,6 +116,7 @@ def _get_userinfo(self):
def _get_name(self):
return self.userinfo["name"]
+
def _set_name(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "\
@@ -124,6 +126,7 @@ def _set_name(self, val):
def _get_email(self):
return self.userinfo["email"]
+
def _set_email(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "\
@@ -133,6 +136,7 @@ def _set_email(self, val):
def _get_url(self):
return self.userinfo["url"]
+
def _set_url(self, val):
self.user_url = val
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):
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
+
class CommentFlag(models.Model):
"""
Records a flag on a comment. This is intentionally flexible; right now, a
@@ -166,7 +171,7 @@ class CommentFlag(models.Model):
design users are only allowed to flag a comment with a given flag once;
if you want rating look elsewhere.
"""
- user = models.ForeignKey(User, verbose_name=_('user'), related_name="comment_flags")
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name="comment_flags")
comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
flag = models.CharField(_('flag'), max_length=30, db_index=True)
flag_date = models.DateTimeField(_('date'), default=None)
View
1  django/core/management/commands/sqlall.py
@@ -4,6 +4,7 @@
from django.core.management.sql import sql_all
from django.db import connections, DEFAULT_DB_ALIAS
+
class Command(AppCommand):
help = "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s)."
View
2  django/core/management/commands/syncdb.py
@@ -68,6 +68,7 @@ def handle_noargs(self, **options):
if router.allow_syncdb(db, m)])
for app in models.get_apps()
]
+
def model_installed(model):
opts = model._meta
converter = connection.introspection.table_name_converter
@@ -101,7 +102,6 @@ def model_installed(model):
cursor.execute(statement)
tables.append(connection.introspection.table_name_converter(model._meta.db_table))
-
transaction.commit_unless_managed(using=db)
# Send the post_syncdb signal, so individual apps can do whatever they need
View
1  django/core/management/commands/validate.py
@@ -1,5 +1,6 @@
from django.core.management.base import NoArgsCommand
+
class Command(NoArgsCommand):
help = "Validates all installed models."
View
11 django/core/management/sql.py
@@ -6,6 +6,7 @@
from django.db import models
from django.db.models import get_models
+
def sql_create(app, style, connection):
"Returns a list of the CREATE TABLE SQL statements for the given app."
@@ -52,6 +53,7 @@ def sql_create(app, style, connection):
return final_output
+
def sql_delete(app, style, connection):
"Returns a list of the DROP TABLE SQL statements for the given app."
@@ -80,7 +82,7 @@ def sql_delete(app, style, connection):
opts = model._meta
for f in opts.local_fields:
if f.rel and f.rel.to not in to_delete:
- references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
+ references_to_delete.setdefault(f.rel.to, []).append((model, f))
to_delete.add(model)
@@ -94,7 +96,8 @@ def sql_delete(app, style, connection):
cursor.close()
connection.close()
- return output[::-1] # Reverse it, to deal with table dependencies.
+ return output[::-1] # Reverse it, to deal with table dependencies.
+
def sql_flush(style, connection, only_django=False):
"""
@@ -112,6 +115,7 @@ def sql_flush(style, connection, only_django=False):
)
return statements
+
def sql_custom(app, style, connection):
"Returns a list of the custom table modifying SQL statements for the given app."
output = []
@@ -123,6 +127,7 @@ def sql_custom(app, style, connection):
return output
+
def sql_indexes(app, style, connection):
"Returns a list of the CREATE INDEX SQL statements for all models in the given app."
output = []
@@ -130,10 +135,12 @@ def sql_indexes(app, style, connection):
output.extend(connection.creation.sql_indexes_for_model(model, style))
return output
+
def sql_all(app, style, connection):
"Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection)
+
def custom_sql_for_model(model, style, connection):
opts = model._meta
app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
View
17 django/core/management/validation.py
@@ -3,6 +3,7 @@
from django.core.management.color import color_style
from django.utils.itercompat import is_iterable
+
class ModelErrorCollection:
def __init__(self, outfile=sys.stdout):
self.errors = []
@@ -13,6 +14,7 @@ def add(self, context, error):
self.errors.append((context, error))
self.outfile.write(self.style.ERROR("%s: %s\n" % (context, error)))
+
def get_validation_errors(outfile, app=None):
"""
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):
e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
if isinstance(f, models.DecimalField):
decimalp_ok, mdigits_ok = False, False
- decimalp_msg ='"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
+ decimalp_msg = '"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
try:
decimal_places = int(f.decimal_places)
if decimal_places < 0:
@@ -121,6 +123,10 @@ def get_validation_errors(outfile, app=None):
if isinstance(f.rel.to, (str, unicode)):
continue
+ # Make sure the model we're related hasn't been swapped out
+ if f.rel.to._meta.swapped:
+ 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))
+
# Make sure the related field specified by a ForeignKey is unique
if not f.rel.to._meta.get_field(f.rel.field_name).unique:
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):
if isinstance(f.rel.to, (str, unicode)):
continue
+ # Make sure the model we're related hasn't been swapped out
+ if f.rel.to._meta.swapped:
+ 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))
+
# Check that the field is not set to unique. ManyToManyFields do not support unique.
if f.unique:
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):
seen_from, seen_to, seen_self = False, False, 0
for inter_field in f.rel.through._meta.fields:
rel_to = getattr(inter_field.rel, 'to', None)
- if from_model == to_model: # relation to self
+ if from_model == to_model: # relation to self
if rel_to == from_model:
seen_self += 1
if seen_self > 2:
@@ -276,7 +286,8 @@ def get_validation_errors(outfile, app=None):
# Check ordering attribute.
if opts.ordering:
for field_name in opts.ordering:
- if field_name == '?': continue
+ if field_name == '?':
+ continue
if field_name.startswith('-'):
field_name = field_name[1:]
if opts.order_with_respect_to and field_name == '_order':
View
16 django/db/backends/creation.py
@@ -34,7 +34,7 @@ def sql_create_model(self, model, style, known_models=set()):
(list_of_sql, pending_references_dict)
"""
opts = model._meta
- if not opts.managed or opts.proxy:
+ if not opts.managed or opts.proxy or opts.swapped:
return [], {}
final_output = []
table_output = []
@@ -86,9 +86,9 @@ def sql_create_model(self, model, style, known_models=set()):
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' +
style.SQL_TABLE(qn(opts.db_table)) + ' (']
- for i, line in enumerate(table_output): # Combine and add commas.
+ for i, line in enumerate(table_output): # Combine and add commas.
full_statement.append(
- ' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
+ ' %s%s' % (line, i < len(table_output) - 1 and ',' or ''))
full_statement.append(')')
if opts.db_tablespace:
tablespace_sql = self.connection.ops.tablespace_sql(
@@ -137,11 +137,11 @@ def sql_for_pending_references(self, model, style, pending_references):
"""
from django.db.backends.util import truncate_name
- if not model._meta.managed or model._meta.proxy:
+ opts = model._meta
+ if not opts.managed or opts.proxy or opts.swapped:
return []
qn = self.connection.ops.quote_name
final_output = []
- opts = model._meta
if model in pending_references:
for rel_class, f in pending_references[model]:
rel_opts = rel_class._meta
@@ -166,7 +166,7 @@ def sql_indexes_for_model(self, model, style):
"""
Returns the CREATE INDEX SQL statements for a single model.
"""
- if not model._meta.managed or model._meta.proxy:
+ if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
output = []
for f in model._meta.local_fields:
@@ -205,7 +205,7 @@ def sql_destroy_model(self, model, references_to_delete, style):
Return the DROP TABLE and restraint dropping statements for a single
model.
"""
- if not model._meta.managed or model._meta.proxy:
+ if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
# Drop the table now
qn = self.connection.ops.quote_name
@@ -222,7 +222,7 @@ def sql_destroy_model(self, model, references_to_delete, style):
def sql_remove_table_constraints(self, model, references_to_delete, style):
from django.db.backends.util import truncate_name
- if not model._meta.managed or model._meta.proxy:
+ if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
output = []
qn = self.connection.ops.quote_name
View
15 django/db/models/base.py
@@ -3,7 +3,7 @@
from functools import update_wrapper
from future_builtins import zip
-import django.db.models.manager # Imported to register signal handler.
+import django.db.models.manager # Imported to register signal handler.
from django.conf import settings
from django.core.exceptions import (ObjectDoesNotExist,
MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS)
@@ -230,6 +230,7 @@ def _prepare(cls):
if opts.order_with_respect_to:
cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True)
cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False)
+
# defer creating accessors on the foreign class until we are
# certain it has been created
def make_foreign_order_accessors(field, model, cls):
@@ -260,6 +261,7 @@ def make_foreign_order_accessors(field, model, cls):
signals.class_prepared.send(sender=cls)
+
class ModelState(object):
"""
A class for storing instance state
@@ -271,6 +273,7 @@ def __init__(self, db=None):
# This impacts validation only; it has no effect on the actual save.
self.adding = True
+
class Model(object):
__metaclass__ = ModelBase
_deferred = False
@@ -585,7 +588,6 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
update_fields=update_fields, raw=raw, using=using)
-
save_base.alters_data = True
def delete(self, using=None):
@@ -609,7 +611,7 @@ def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
order = not is_next and '-' or ''
param = smart_str(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
- q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
+ q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
@@ -802,7 +804,7 @@ def unique_error_message(self, model_class, unique_check):
field = opts.get_field(field_name)
field_label = capfirst(field.verbose_name)
# Insert the error into the error dict, very sneaky
- return field.error_messages['unique'] % {
+ return field.error_messages['unique'] % {
'model_name': unicode(model_name),
'field_label': unicode(field_label)
}
@@ -810,7 +812,7 @@ def unique_error_message(self, model_class, unique_check):
else:
field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check)
field_labels = get_text_list(field_labels, _('and'))
- return _(u"%(model_name)s with this %(field_label)s already exists.") % {
+ return _(u"%(model_name)s with this %(field_label)s already exists.") % {
'model_name': unicode(model_name),
'field_label': unicode(field_labels)
}
@@ -915,6 +917,7 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
class Empty(object):
pass
+
def simple_class_factory(model, attrs):
"""Used to unpickle Models without deferred fields.
@@ -924,6 +927,7 @@ def simple_class_factory(model, attrs):
"""
return model
+
def model_unpickle(model, attrs, factory):
"""
Used to unpickle Model subclasses with deferred fields.
@@ -932,5 +936,6 @@ def model_unpickle(model, attrs, factory):
return cls.__new__(cls)
model_unpickle.__safe_for_unpickle__ = True
+
def subclass_exception(name, parents, module):
return type(name, parents, {'__module__': module})
View
37 django/db/models/fields/related.py
@@ -20,6 +20,7 @@
pending_lookups = {}
+
def add_lazy_relation(cls, field, relation, operation):
"""
Adds a lookup on ``cls`` when a related field is defined using a string,
@@ -76,6 +77,7 @@ class MyModel(Model):
value = (cls, field, operation)
pending_lookups.setdefault(key, []).append(value)
+
def do_pending_lookups(sender, **kwargs):
"""
Handle any pending relations to the sending model. Sent from class_prepared.
@@ -86,6 +88,7 @@ def do_pending_lookups(sender, **kwargs):
signals.class_prepared.connect(do_pending_lookups)
+
#HACK
class RelatedField(object):
def contribute_to_class(self, cls, name):
@@ -219,6 +222,7 @@ def related_query_name(self):
# "related_name" option.
return self.rel.related_name or self.opts.object_name.lower()
+
class SingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@@ -305,6 +309,7 @@ def __set__(self, instance, value):
setattr(instance, self.cache_name, value)
setattr(value, self.related.field.get_cache_name(), instance)
+
class ReverseSingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@@ -429,6 +434,7 @@ def __set__(self, instance, value):
if value is not None and not self.field.rel.multiple:
setattr(value, self.field.related.get_cache_name(), instance)
+
class ForeignRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@@ -659,7 +665,7 @@ def _add_items(self, source_field_name, target_field_name, *objs):
for obj in objs:
if isinstance(obj, self.model):
if not router.allow_relation(obj, self.instance):
- raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' %
+ raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' %
(obj, self.instance._state.db, obj._state.db))
new_ids.add(obj.pk)
elif isinstance(obj, Model):
@@ -751,6 +757,7 @@ def _clear_items(self, source_field_name):
return ManyRelatedManager
+
class ManyRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@@ -859,12 +866,13 @@ def __set__(self, instance, value):
manager.clear()
manager.add(*value)
+
class ManyToOneRel(object):
def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None):
try:
to._meta
- except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
self.to, self.field_name = to, field_name
self.related_name = related_name
@@ -890,6 +898,7 @@ def get_related_field(self):
self.field_name)
return data[0]
+
class OneToOneRel(ManyToOneRel):
def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None):
@@ -899,6 +908,7 @@ def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
)
self.multiple = False
+
class ManyToManyRel(object):
def __init__(self, to, related_name=None, limit_choices_to=None,
symmetrical=True, through=None):
@@ -923,16 +933,18 @@ def get_related_field(self):
"""
return self.to._meta.pk
+
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
default_error_messages = {
'invalid': _('Model %(model)s with pk %(pk)r does not exist.')
}
description = _("Foreign Key (type determined by related field)")
+
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try:
- to_name = to._meta.object_name.lower()
- except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ to._meta.object_name.lower()
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
else:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
@@ -1049,6 +1061,7 @@ def db_type(self, connection):
return IntegerField().db_type(connection=connection)
return rel_field.db_type(connection=connection)
+
class OneToOneField(ForeignKey):
"""
A OneToOneField is essentially the same as a ForeignKey, with the exception
@@ -1057,6 +1070,7 @@ class OneToOneField(ForeignKey):
rather than returning a list.
"""
description = _("One-to-one relationship")
+
def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@@ -1076,12 +1090,14 @@ def save_form_data(self, instance, data):
else:
setattr(instance, self.attname, data)
+
def create_many_to_many_intermediary_model(field, klass):
from django.db import models
managed = True
if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
to_model = field.rel.to
to = to_model.split('.')[-1]
+
def set_managed(field, model, cls):
field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
add_lazy_relation(klass, field, to_model, set_managed)
@@ -1118,12 +1134,14 @@ def set_managed(field, model, cls):
to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace)
})
+
class ManyToManyField(RelatedField, Field):
description = _("Many-to-many relationship")
+
def __init__(self, to, **kwargs):
try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
- except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
# Python 2.6 and earlier require dictionary keys to be of str type,
# not unicode and class names must be ASCII (in Python 2.x), so we
@@ -1134,7 +1152,7 @@ def __init__(self, to, **kwargs):
kwargs['rel'] = ManyToManyRel(to,
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
- symmetrical=kwargs.pop('symmetrical', to==RECURSIVE_RELATIONSHIP_CONSTANT),
+ symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT),
through=kwargs.pop('through', None))
self.db_table = kwargs.pop('db_table', None)
@@ -1165,7 +1183,7 @@ def _get_m2m_attr(self, related, attr):
if hasattr(self, cache_attr):
return getattr(self, cache_attr)
for f in self.rel.through._meta.fields:
- if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
+ if hasattr(f, 'rel') and f.rel and f.rel.to == related.model:
setattr(self, cache_attr, getattr(f, attr))
return getattr(self, cache_attr)
@@ -1176,7 +1194,7 @@ def _get_m2m_reverse_attr(self, related, attr):
return getattr(self, cache_attr)
found = False
for f in self.rel.through._meta.fields:
- if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
+ if hasattr(f, 'rel') and f.rel and f.rel.to == related.parent_model:
if related.model == related.parent_model:
# If this is an m2m-intermediate to self,
# the first foreign key you find will be
@@ -1221,7 +1239,8 @@ def contribute_to_class(self, cls, name):
# The intermediate m2m model is not auto created if:
# 1) There is a manually specified intermediate, or
# 2) The class owning the m2m field is abstract.
- if not self.rel.through and not cls._meta.abstract:
+ # 3) The class owning the m2m field has been swapped out.
+ if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped:
self.rel.through = create_many_to_many_intermediary_model(self, cls)
# Add the descriptor for the m2m relation
View
1  django/db/models/loading.py
@@ -13,6 +13,7 @@
__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
'load_app', 'app_cache_ready')
+
class AppCache(object):
"""
A cache that stores installed applications and their models. Used to
View
16 django/db/models/options.py
@@ -17,7 +17,8 @@
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace',
- 'abstract', 'managed', 'proxy', 'auto_created')
+ 'abstract', 'managed', 'proxy', 'swappable', 'auto_created')
+
class Options(object):
def __init__(self, meta, app_label=None):
@@ -27,8 +28,8 @@ def __init__(self, meta, app_label=None):
self.verbose_name_plural = None
self.db_table = ''
self.ordering = []
- self.unique_together = []
- self.permissions = []
+ self.unique_together = []
+ self.permissions = []
self.object_name, self.app_label = None, app_label
self.get_latest_by = None
self.order_with_respect_to = None
@@ -50,6 +51,7 @@ def __init__(self, meta, app_label=None):
# in the end of the proxy_for_model chain. In particular, for
# concrete models, the concrete_model is always the class itself.
self.concrete_model = None
+ self.swappable = None
self.parents = SortedDict()
self.duplicate_targets = {}
self.auto_created = False
@@ -213,6 +215,14 @@ def verbose_name_raw(self):
return raw
verbose_name_raw = property(verbose_name_raw)
+ def _swapped(self):
+ """
+ Has this model been swapped out for another?
+ """
+ model_label = '%s.%s' % (self.app_label, self.object_name)
+ return self.swappable and getattr(settings, self.swappable, None) not in (None, model_label)
+ swapped = property(_swapped)
+
def _fields(self):
"""
The getter for self.fields. This returns the list of field objects
View
7 django/test/utils.py
@@ -27,7 +27,7 @@ def __repr__(self):
def __eq__(self, other):
if self.val == other:
return True
- return round(abs(self.val-other), self.places) == 0
+ return round(abs(self.val - other), self.places) == 0
class ContextList(list):
@@ -45,7 +45,7 @@ def __getitem__(self, key):
def __contains__(self, key):
try:
- value = self[key]
+ self[key]
except KeyError:
return False
return True
@@ -187,9 +187,11 @@ def __call__(self, test_func):
if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase):
original_pre_setup = test_func._pre_setup
original_post_teardown = test_func._post_teardown
+
def _pre_setup(innerself):
self.enable()
original_pre_setup(innerself)
+
def _post_teardown(innerself):
original_post_teardown(innerself)
self.disable()
@@ -218,4 +220,3 @@ def disable(self):
new_value = getattr(settings, key, None)
setting_changed.send(sender=settings._wrapped.__class__,
setting=key, value=new_value)
-
View
9 docs/ref/settings.txt
@@ -120,6 +120,15 @@ Default: Not defined
The site-specific user profile model used by this site. See
:ref:`auth-profiles`.
+.. setting:: AUTH_USER_MODEL
+
+AUTH_USER_MODEL
+---------------
+
+Default: 'auth.User'
+
+The model to use to represent a User. See :ref:`auth-custom-user`.
+
.. setting:: CACHES
CACHES
View
7 docs/topics/auth.txt
@@ -1723,6 +1723,13 @@ Fields
group.permissions.remove(permission, permission, ...)
group.permissions.clear()
+.. _auth-custom-user:
+
+Customizing the User model
+==========================
+
+TODO
+
.. _authentication-backends:
Other authentication sources
View
88 tests/modeltests/invalid_models/invalid_models/models.py
@@ -19,11 +19,12 @@ class FieldErrors(models.Model):
decimalfield5 = models.DecimalField(max_digits=10, decimal_places=10)
filefield = models.FileField()
choices = models.CharField(max_length=10, choices='bad')
- choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)])
+ choices2 = models.CharField(max_length=10, choices=[(1, 2, 3), (1, 2, 3)])
index = models.CharField(max_length=10, db_index='bad')
field_ = models.CharField(max_length=10)
nullbool = models.BooleanField(null=True)
+
class Target(models.Model):
tgt_safe = models.CharField(max_length=10)
clash1 = models.CharField(max_length=10)
@@ -31,12 +32,14 @@ class Target(models.Model):
clash1_set = models.CharField(max_length=10)
+
class Clash1(models.Model):
src_safe = models.CharField(max_length=10)
foreign = models.ForeignKey(Target)
m2m = models.ManyToManyField(Target)
+
class Clash2(models.Model):
src_safe = models.CharField(max_length=10)
@@ -46,6 +49,7 @@ class Clash2(models.Model):
m2m_1 = models.ManyToManyField(Target, related_name='id')
m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
+
class Target2(models.Model):
clash3 = models.CharField(max_length=10)
foreign_tgt = models.ForeignKey(Target)
@@ -54,6 +58,7 @@ class Target2(models.Model):
m2m_tgt = models.ManyToManyField(Target)
clashm2m_set = models.ManyToManyField(Target)
+
class Clash3(models.Model):
src_safe = models.CharField(max_length=10)
@@ -63,12 +68,15 @@ class Clash3(models.Model):
m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt')
m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt')
+
class ClashForeign(models.Model):
foreign = models.ForeignKey(Target2)
+
class ClashM2M(models.Model):
m2m = models.ManyToManyField(Target2)
+
class SelfClashForeign(models.Model):
src_safe = models.CharField(max_length=10)
selfclashforeign = models.CharField(max_length=10)
@@ -77,6 +85,7 @@ class SelfClashForeign(models.Model):
foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
+
class ValidM2M(models.Model):
src_safe = models.CharField(max_length=10)
validm2m = models.CharField(max_length=10)
@@ -92,6 +101,7 @@ class ValidM2M(models.Model):
m2m_3 = models.ManyToManyField('self')
m2m_4 = models.ManyToManyField('self')
+
class SelfClashM2M(models.Model):
src_safe = models.CharField(max_length=10)
selfclashm2m = models.CharField(max_length=10)
@@ -106,120 +116,148 @@ class SelfClashM2M(models.Model):
m2m_3 = models.ManyToManyField('self', symmetrical=False)
m2m_4 = models.ManyToManyField('self', symmetrical=False)
+
class Model(models.Model):
"But it's valid to call a model Model."
- year = models.PositiveIntegerField() #1960
- make = models.CharField(max_length=10) #Aston Martin
- name = models.CharField(max_length=10) #DB 4 GT
+ year = models.PositiveIntegerField() # 1960
+ make = models.CharField(max_length=10) # Aston Martin
+ name = models.CharField(max_length=10) # DB 4 GT
+
class Car(models.Model):
colour = models.CharField(max_length=5)
model = models.ForeignKey(Model)
+
class MissingRelations(models.Model):
rel1 = models.ForeignKey("Rel1")
rel2 = models.ManyToManyField("Rel2")
+
class MissingManualM2MModel(models.Model):
name = models.CharField(max_length=5)
missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel")
+
class Person(models.Model):
name = models.CharField(max_length=5)
+
class Group(models.Model):
name = models.CharField(max_length=5)
primary = models.ManyToManyField(Person, through="Membership", related_name="primary")
secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary")
tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary")
+
class GroupTwo(models.Model):
name = models.CharField(max_length=5)
primary = models.ManyToManyField(Person, through="Membership")
secondary = models.ManyToManyField(Group, through="MembershipMissingFK")
+
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
not_default_or_null = models.CharField(max_length=5)
+
class MembershipMissingFK(models.Model):
person = models.ForeignKey(Person)
+
class PersonSelfRefM2M(models.Model):
name = models.CharField(max_length=5)
friends = models.ManyToManyField('self', through="Relationship")
too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK")
+
class PersonSelfRefM2MExplicit(models.Model):
name = models.CharField(max_length=5)
friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True)
+
class Relationship(models.Model):
first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set")
second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set")
date_added = models.DateTimeField()
+
class ExplicitRelationship(models.Model):
first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set")
second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set")
date_added = models.DateTimeField()
+
class RelationshipTripleFK(models.Model):
first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2")
second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2")
third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far")
date_added = models.DateTimeField()
+
class RelationshipDoubleFK(models.Model):
first = models.ForeignKey(Person, related_name="first_related_name")
second = models.ForeignKey(Person, related_name="second_related_name")
third = models.ForeignKey(Group, related_name="rel_to_set")
date_added = models.DateTimeField()
+
class AbstractModel(models.Model):
name = models.CharField(max_length=10)
+
class Meta:
abstract = True
+
class AbstractRelationModel(models.Model):
fk1 = models.ForeignKey('AbstractModel')
fk2 = models.ManyToManyField('AbstractModel')
+
class UniqueM2M(models.Model):
""" Model to test for unique ManyToManyFields, which are invalid. """
unique_people = models.ManyToManyField(Person, unique=True)
+
class NonUniqueFKTarget1(models.Model):
""" Model to test for non-unique FK target in yet-to-be-defined model: expect an error """
tgt = models.ForeignKey('FKTarget', to_field='bad')
+
class UniqueFKTarget1(models.Model):
""" Model to test for unique FK target in yet-to-be-defined model: expect no error """
tgt = models.ForeignKey('FKTarget', to_field='good')
+
class FKTarget(models.Model):
bad = models.IntegerField()
good = models.IntegerField(unique=True)
+
class NonUniqueFKTarget2(models.Model):
""" Model to test for non-unique FK target in previously seen model: expect an error """
tgt = models.ForeignKey(FKTarget, to_field='bad')
+
class UniqueFKTarget2(models.Model):
""" Model to test for unique FK target in previously seen model: expect no error """
tgt = models.ForeignKey(FKTarget, to_field='good')
+
class NonExistingOrderingWithSingleUnderscore(models.Model):
class Meta:
ordering = ("does_not_exist",)
+
class InvalidSetNull(models.Model):
fk = models.ForeignKey('self', on_delete=models.SET_NULL)
+
class InvalidSetDefault(models.Model):
fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT)
+
class UnicodeForeignKeys(models.Model):
"""Foreign keys which can translate to ascii should be OK, but fail if
they're not."""
@@ -230,9 +268,11 @@ class UnicodeForeignKeys(models.Model):
# when adding the errors in core/management/validation.py
#bad = models.ForeignKey(u'★')
+
class PrimaryKeyNull(models.Model):
my_pk_field = models.IntegerField(primary_key=True, null=True)
+
class OrderByPKModel(models.Model):
"""
Model to test that ordering by pk passes validation.
@@ -243,6 +283,42 @@ class OrderByPKModel(models.Model):
class Meta:
ordering = ('pk',)
+
+class SwappableModel(models.Model):
+ """A model that can be, but isn't swapped out.
+
+ References to this model *shoudln't* raise any validation error.
+ """
+ name = models.CharField(max_length=100)
+
+ class Meta:
+ swappable = 'TEST_SWAPPABLE_MODEL'
+
+
+class SwappedModel(models.Model):
+ """A model that is swapped out.
+
+ References to this model *should* raise a validation error.
+ Requires TEST_SWAPPED_MODEL to be defined in the test environment;
+ this is guaranteed by the test runner using @override_settings.
+ """
+ name = models.CharField(max_length=100)
+
+ class Meta:
+ swappable = 'TEST_SWAPPED_MODEL'
+
+
+class HardReferenceModel(models.Model):
+ fk_1 = models.ForeignKey(SwappableModel, related_name='fk_hardref1')
+ fk_2 = models.ForeignKey('invalid_models.SwappableModel', related_name='fk_hardref2')
+ fk_3 = models.ForeignKey(SwappedModel, related_name='fk_hardref3')
+ fk_4 = models.ForeignKey('invalid_models.SwappedModel', related_name='fk_hardref4')
+ m2m_1 = models.ManyToManyField(SwappableModel, related_name='m2m_hardref1')
+ m2m_2 = models.ManyToManyField('invalid_models.SwappableModel', related_name='m2m_hardref2')
+ m2m_3 = models.ManyToManyField(SwappedModel, related_name='m2m_hardref3')
+ m2m_4 = models.ManyToManyField('invalid_models.SwappedModel', related_name='m2m_hardref4')
+
+
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer.
invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer.
invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer.
@@ -351,6 +427,10 @@ class Meta:
invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist.
invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null.
invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value.
+invalid_models.hardreferencemodel: 'fk_3' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
+invalid_models.hardreferencemodel: 'fk_4' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
+invalid_models.hardreferencemodel: 'm2m_3' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
+invalid_models.hardreferencemodel: 'm2m_4' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
"""
if not connection.features.interprets_empty_strings_as_nulls:
View
9 tests/modeltests/invalid_models/tests.py
@@ -6,6 +6,7 @@
from django.db.models.loading import cache, load_app
from django.utils import unittest
+from django.test.utils import override_settings
class InvalidModelTestCase(unittest.TestCase):
@@ -31,14 +32,18 @@ def tearDown(self):
cache._get_models_cache = {}
sys.stdout = self.old_stdout
+ # Technically, this isn't an override -- TEST_SWAPPED_MODEL must be
+ # set to *something* in order for the test to work. However, it's
+ # easier to set this up as an override than to require every developer
+ # to specify a value in their test settings.
+ @override_settings(TEST_SWAPPED_MODEL='invalid_models.Target')
def test_invalid_models(self):
-
try:
module = load_app("modeltests.invalid_models.invalid_models")
except Exception:
self.fail('Unable to load invalid model module')
- count = get_validation_errors(self.stdout, module)
+ get_validation_errors(self.stdout, module)
self.stdout.seek(0)
error_log = self.stdout.read()
actual = error_log.split('\n')
Please sign in to comment.
Something went wrong with that request. Please try again.