2014-05-06 01:50:51 +08:00
from __future__ import unicode_literals
2013-12-29 03:13:08 +08:00
from django . apps import AppConfig
2014-05-01 03:25:12 +08:00
from django . apps . registry import Apps , apps as global_apps
2013-05-11 00:07:13 +08:00
from django . db import models
2014-03-02 03:06:15 +08:00
from django . db . models . options import DEFAULT_NAMES , normalize_together
2014-05-01 03:25:12 +08:00
from django . db . models . fields . related import do_pending_lookups
2014-06-16 05:55:44 +08:00
from django . db . models . fields . proxy import OrderWrt
2014-05-08 04:04:29 +08:00
from django . conf import settings
2013-09-07 01:14:09 +08:00
from django . utils import six
2014-08-07 20:33:42 +08:00
from django . utils . encoding import force_text , smart_text
2014-01-21 04:15:14 +08:00
from django . utils . module_loading import import_string
2013-05-11 00:07:13 +08:00
2013-09-07 01:14:09 +08:00
class InvalidBasesError ( ValueError ) :
pass
2013-05-11 00:07:13 +08:00
class ProjectState ( object ) :
"""
Represents the entire project ' s overall state.
This is the item that is passed around - we do it here rather than at the
app level so that cross - app FKs / etc . resolve properly .
"""
2014-05-01 03:25:12 +08:00
def __init__ ( self , models = None , real_apps = None ) :
2013-05-11 00:07:13 +08:00
self . models = models or { }
2013-12-24 19:25:17 +08:00
self . apps = None
2014-05-01 03:25:12 +08:00
# Apps to include from main registry, usually unmigrated ones
self . real_apps = real_apps or [ ]
2013-05-11 00:07:13 +08:00
2013-05-19 00:30:34 +08:00
def add_model_state ( self , model_state ) :
self . models [ ( model_state . app_label , model_state . name . lower ( ) ) ] = model_state
2013-05-11 00:07:13 +08:00
def clone ( self ) :
" Returns an exact copy of this ProjectState "
2013-05-18 19:49:56 +08:00
return ProjectState (
2014-05-01 03:25:12 +08:00
models = dict ( ( k , v . clone ( ) ) for k , v in self . models . items ( ) ) ,
real_apps = self . real_apps ,
2013-05-11 00:07:13 +08:00
)
2014-06-08 09:17:31 +08:00
def render ( self , include_real = None , ignore_swappable = False , skip_cache = False ) :
2013-12-24 19:25:17 +08:00
" Turns the project state into actual models in a new Apps "
2014-06-08 09:17:31 +08:00
if self . apps is None or skip_cache :
2014-05-01 03:25:12 +08:00
# Any apps in self.real_apps should have all their models included
# in the render. We don't use the original model instances as there
# are some variables that refer to the Apps object.
2014-06-13 01:21:26 +08:00
# FKs/M2Ms from real apps are also not included as they just
# mess things up with partial states (due to lack of dependencies)
2014-05-01 03:25:12 +08:00
real_models = [ ]
for app_label in self . real_apps :
app = global_apps . get_app_config ( app_label )
for model in app . get_models ( ) :
2014-06-13 01:21:26 +08:00
real_models . append ( ModelState . from_model ( model , exclude_rels = True ) )
2013-12-29 03:13:08 +08:00
# Populate the app registry with a stub for each application.
app_labels = set ( model_state . app_label for model_state in self . models . values ( ) )
2014-05-01 03:25:12 +08:00
self . apps = Apps ( [ AppConfigStub ( label ) for label in sorted ( self . real_apps + list ( app_labels ) ) ] )
2013-09-07 01:14:09 +08:00
# We keep trying to render the models in a loop, ignoring invalid
# base errors, until the size of the unrendered models doesn't
# decrease by at least one, meaning there's a base dependency loop/
# missing base.
2014-05-01 03:25:12 +08:00
unrendered_models = list ( self . models . values ( ) ) + real_models
2013-09-07 01:14:09 +08:00
while unrendered_models :
new_unrendered_models = [ ]
for model in unrendered_models :
try :
2013-12-24 19:25:17 +08:00
model . render ( self . apps )
2013-09-07 01:14:09 +08:00
except InvalidBasesError :
new_unrendered_models . append ( model )
if len ( new_unrendered_models ) == len ( unrendered_models ) :
2014-07-10 14:57:35 +08:00
raise InvalidBasesError ( " Cannot resolve bases for %r \n This can happen if you are inheriting models from an app with migrations (e.g. contrib.auth) \n in an app with no migrations; see https://docs.djangoproject.com/en/1.7/topics/migrations/#dependencies for more " % new_unrendered_models )
2013-09-07 01:14:09 +08:00
unrendered_models = new_unrendered_models
2014-04-08 13:30:25 +08:00
# make sure apps has no dangling references
if self . apps . _pending_lookups :
2014-05-01 03:25:12 +08:00
# There's some lookups left. See if we can first resolve them
# ourselves - sometimes fields are added after class_prepared is sent
for lookup_model , operations in self . apps . _pending_lookups . items ( ) :
try :
model = self . apps . get_model ( lookup_model [ 0 ] , lookup_model [ 1 ] )
except LookupError :
2014-05-09 12:33:24 +08:00
if " %s . %s " % ( lookup_model [ 0 ] , lookup_model [ 1 ] ) == settings . AUTH_USER_MODEL and ignore_swappable :
continue
2014-05-01 03:25:12 +08:00
# Raise an error with a best-effort helpful message
# (only for the first issue). Error message should look like:
# "ValueError: Lookup failed for model referenced by
# field migrations.Book.author: migrations.Author"
2014-05-09 12:33:24 +08:00
raise ValueError ( " Lookup failed for model referenced by field {field} : {model[0]} . {model[1]} " . format (
2014-05-09 03:49:54 +08:00
field = operations [ 0 ] [ 1 ] ,
model = lookup_model ,
2014-05-01 03:25:12 +08:00
) )
else :
do_pending_lookups ( model )
2014-06-08 09:17:31 +08:00
try :
return self . apps
finally :
if skip_cache :
self . apps = None
2013-05-11 00:07:13 +08:00
2013-05-18 17:48:46 +08:00
@classmethod
2013-12-24 19:25:17 +08:00
def from_apps ( cls , apps ) :
" Takes in an Apps and returns a ProjectState matching it "
2013-10-10 23:07:48 +08:00
app_models = { }
2014-06-23 04:00:49 +08:00
for model in apps . get_models ( include_swapped = True ) :
2014-03-26 06:30:29 +08:00
model_state = ModelState . from_model ( model )
app_models [ ( model_state . app_label , model_state . name . lower ( ) ) ] = model_state
2013-10-10 23:07:48 +08:00
return cls ( app_models )
2013-05-18 17:48:46 +08:00
2013-09-25 20:47:46 +08:00
def __eq__ ( self , other ) :
if set ( self . models . keys ( ) ) != set ( other . models . keys ( ) ) :
return False
2014-05-01 03:25:12 +08:00
if set ( self . real_apps ) != set ( other . real_apps ) :
return False
2013-09-25 20:47:46 +08:00
return all ( model == other . models [ key ] for key , model in self . models . items ( ) )
def __ne__ ( self , other ) :
return not ( self == other )
2013-05-11 00:07:13 +08:00
2013-12-29 03:13:08 +08:00
class AppConfigStub ( AppConfig ) :
"""
2014-01-28 04:28:53 +08:00
Stubs a Django AppConfig . Only provides a label , and a dict of models .
2013-12-29 03:13:08 +08:00
"""
2014-01-28 04:28:53 +08:00
# Not used, but required by AppConfig.__init__
path = ' '
2013-12-29 03:13:08 +08:00
def __init__ ( self , label ) :
2013-12-31 23:23:42 +08:00
super ( AppConfigStub , self ) . __init__ ( label , None )
2013-12-29 03:13:08 +08:00
def import_models ( self , all_models ) :
self . models = all_models
2013-05-11 00:07:13 +08:00
class ModelState ( object ) :
"""
Represents a Django Model . We don ' t use the actual Model class
as it ' s not designed to have its options changed - instead, we
mutate this one and then render it into a Model as required .
2013-05-31 01:21:32 +08:00
Note that while you are allowed to mutate . fields , you are not allowed
to mutate the Field instances inside there themselves - you must instead
assign new ones , as these are not detached during a clone .
2013-05-11 00:07:13 +08:00
"""
2013-05-30 00:47:10 +08:00
def __init__ ( self , app_label , name , fields , options = None , bases = None ) :
2013-05-11 00:07:13 +08:00
self . app_label = app_label
2014-05-06 02:39:26 +08:00
self . name = force_text ( name )
2013-05-30 00:47:10 +08:00
self . fields = fields
2013-05-11 00:07:13 +08:00
self . options = options or { }
2013-05-18 19:49:56 +08:00
self . bases = bases or ( models . Model , )
2013-05-30 00:47:10 +08:00
# Sanity-check that fields is NOT a dict. It must be ordered.
if isinstance ( self . fields , dict ) :
raise ValueError ( " ModelState.fields cannot be a dict - it must be a list of 2-tuples. " )
2014-05-23 06:51:11 +08:00
# Sanity-check that fields are NOT already bound to a model.
for name , field in fields :
if hasattr ( field , ' model ' ) :
raise ValueError (
' ModelState.fields cannot be bound to a model - " %s " is. ' % name
)
2013-05-18 19:49:56 +08:00
@classmethod
2014-06-13 01:21:26 +08:00
def from_model ( cls , model , exclude_rels = False ) :
2013-05-18 19:49:56 +08:00
"""
Feed me a model , get a ModelState representing it out .
"""
# Deconstruct the fields
fields = [ ]
2013-09-01 05:18:44 +08:00
for field in model . _meta . local_fields :
2014-06-13 01:21:26 +08:00
if getattr ( field , " rel " , None ) and exclude_rels :
continue
2014-06-16 05:55:44 +08:00
if isinstance ( field , OrderWrt ) :
continue
2013-05-18 19:49:56 +08:00
name , path , args , kwargs = field . deconstruct ( )
2014-01-21 04:15:14 +08:00
field_class = import_string ( path )
2013-12-05 22:19:46 +08:00
try :
fields . append ( ( name , field_class ( * args , * * kwargs ) ) )
except TypeError as e :
2013-12-06 21:59:08 +08:00
raise TypeError ( " Couldn ' t reconstruct field %s on %s . %s : %s " % (
2013-12-05 22:19:46 +08:00
name ,
2013-12-06 21:59:08 +08:00
model . _meta . app_label ,
2013-12-05 22:19:46 +08:00
model . _meta . object_name ,
e ,
) )
2014-06-13 01:21:26 +08:00
if not exclude_rels :
for field in model . _meta . local_many_to_many :
name , path , args , kwargs = field . deconstruct ( )
field_class = import_string ( path )
try :
fields . append ( ( name , field_class ( * args , * * kwargs ) ) )
except TypeError as e :
raise TypeError ( " Couldn ' t reconstruct m2m field %s on %s : %s " % (
name ,
model . _meta . object_name ,
e ,
) )
2013-05-19 18:35:17 +08:00
# Extract the options
options = { }
for name in DEFAULT_NAMES :
# Ignore some special options
2013-12-24 19:25:17 +08:00
if name in [ " apps " , " app_label " ] :
2013-05-19 18:35:17 +08:00
continue
2013-07-02 18:19:02 +08:00
elif name in model . _meta . original_attrs :
if name == " unique_together " :
2013-12-06 22:05:12 +08:00
ut = model . _meta . original_attrs [ " unique_together " ]
2014-03-02 03:06:15 +08:00
options [ name ] = set ( normalize_together ( ut ) )
elif name == " index_together " :
it = model . _meta . original_attrs [ " index_together " ]
options [ name ] = set ( normalize_together ( it ) )
2013-07-02 18:19:02 +08:00
else :
options [ name ] = model . _meta . original_attrs [ name ]
2014-08-07 10:13:37 +08:00
# Force-convert all options to text_type (#23226)
options = cls . force_text_recursive ( options )
2014-06-13 02:12:07 +08:00
# If we're ignoring relationships, remove all field-listing model
# options (that option basically just means "make a stub model")
if exclude_rels :
for key in [ " unique_together " , " index_together " , " order_with_respect_to " ] :
if key in options :
del options [ key ]
2014-03-05 04:17:07 +08:00
def flatten_bases ( model ) :
bases = [ ]
for base in model . __bases__ :
if hasattr ( base , " _meta " ) and base . _meta . abstract :
bases . extend ( flatten_bases ( base ) )
else :
bases . append ( base )
return bases
# We can't rely on __mro__ directly because we only want to flatten
# abstract models and not the whole tree. However by recursing on
# __bases__ we may end up with duplicates and ordering issues, we
# therefore discard any duplicates and reorder the bases according
# to their index in the MRO.
2014-03-05 07:13:15 +08:00
flattened_bases = sorted ( set ( flatten_bases ( model ) ) , key = lambda x : model . __mro__ . index ( x ) )
2014-03-05 04:17:07 +08:00
2013-05-18 19:49:56 +08:00
# Make our record
2013-09-07 01:14:09 +08:00
bases = tuple (
2014-01-20 02:09:52 +08:00
(
" %s . %s " % ( base . _meta . app_label , base . _meta . model_name )
if hasattr ( base , " _meta " ) else
base
)
2014-03-05 04:17:07 +08:00
for base in flattened_bases
2013-09-07 01:14:09 +08:00
)
2014-01-20 02:09:52 +08:00
# Ensure at least one base inherits from models.Model
if not any ( ( isinstance ( base , six . string_types ) or issubclass ( base , models . Model ) ) for base in bases ) :
2014-03-05 04:17:07 +08:00
bases = ( models . Model , )
2013-05-18 19:49:56 +08:00
return cls (
model . _meta . app_label ,
model . _meta . object_name ,
fields ,
2013-05-19 18:35:17 +08:00
options ,
2013-06-23 00:15:51 +08:00
bases ,
2013-05-18 19:49:56 +08:00
)
2013-05-11 00:07:13 +08:00
2014-08-07 10:13:37 +08:00
@classmethod
def force_text_recursive ( cls , value ) :
if isinstance ( value , six . string_types ) :
2014-08-07 20:33:42 +08:00
return smart_text ( value )
2014-08-07 10:13:37 +08:00
elif isinstance ( value , list ) :
return [ cls . force_text_recursive ( x ) for x in value ]
elif isinstance ( value , tuple ) :
return tuple ( cls . force_text_recursive ( x ) for x in value )
elif isinstance ( value , set ) :
return set ( cls . force_text_recursive ( x ) for x in value )
elif isinstance ( value , dict ) :
return dict (
( cls . force_text_recursive ( k ) , cls . force_text_recursive ( v ) )
for k , v in value . items ( )
)
return value
2014-05-23 06:51:11 +08:00
def construct_fields ( self ) :
" Deep-clone the fields using deconstruction "
2013-06-20 22:12:59 +08:00
for name , field in self . fields :
_ , path , args , kwargs = field . deconstruct ( )
2014-01-21 04:15:14 +08:00
field_class = import_string ( path )
2014-05-23 06:51:11 +08:00
yield name , field_class ( * args , * * kwargs )
def clone ( self ) :
" Returns an exact copy of this ModelState "
2013-05-11 00:07:13 +08:00
return self . __class__ (
2013-11-03 17:22:11 +08:00
app_label = self . app_label ,
name = self . name ,
2014-05-23 06:51:11 +08:00
fields = list ( self . construct_fields ( ) ) ,
2013-11-03 17:22:11 +08:00
options = dict ( self . options ) ,
bases = self . bases ,
2013-05-11 00:07:13 +08:00
)
2013-12-24 19:25:17 +08:00
def render ( self , apps ) :
" Creates a Model object from our current state into the given apps "
2013-05-11 00:07:13 +08:00
# First, make a Meta object
2013-12-24 19:25:17 +08:00
meta_contents = { ' app_label ' : self . app_label , " apps " : apps }
2013-05-11 00:07:13 +08:00
meta_contents . update ( self . options )
2014-05-06 01:50:51 +08:00
meta = type ( str ( " Meta " ) , tuple ( ) , meta_contents )
2013-05-11 00:07:13 +08:00
# Then, work out our bases
2013-12-28 21:55:54 +08:00
try :
bases = tuple (
2014-01-26 19:57:08 +08:00
( apps . get_model ( base ) if isinstance ( base , six . string_types ) else base )
2013-12-28 21:55:54 +08:00
for base in self . bases
)
except LookupError :
2013-12-06 07:55:31 +08:00
raise InvalidBasesError ( " Cannot resolve one or more bases from %r " % ( self . bases , ) )
2013-05-11 00:07:13 +08:00
# Turn fields into a dict for the body, add other bits
2014-05-23 06:51:11 +08:00
body = dict ( self . construct_fields ( ) )
2013-05-11 00:07:13 +08:00
body [ ' Meta ' ] = meta
body [ ' __module__ ' ] = " __fake__ "
# Then, make a Model object
return type (
2014-04-01 03:25:08 +08:00
str ( self . name ) ,
2013-09-07 01:14:09 +08:00
bases ,
2013-05-11 00:07:13 +08:00
body ,
)
2013-06-20 22:19:30 +08:00
def get_field_by_name ( self , name ) :
for fname , field in self . fields :
if fname == name :
return field
raise ValueError ( " No field called %s on model %s " % ( name , self . name ) )
2013-09-25 20:47:46 +08:00
2014-07-06 14:57:23 +08:00
def __repr__ ( self ) :
return " <ModelState: ' %s . %s ' > " % ( self . app_label , self . name )
2013-09-25 20:47:46 +08:00
def __eq__ ( self , other ) :
return (
( self . app_label == other . app_label ) and
( self . name == other . name ) and
( len ( self . fields ) == len ( other . fields ) ) and
all ( ( k1 == k2 and ( f1 . deconstruct ( ) [ 1 : ] == f2 . deconstruct ( ) [ 1 : ] ) ) for ( k1 , f1 ) , ( k2 , f2 ) in zip ( self . fields , other . fields ) ) and
( self . options == other . options ) and
( self . bases == other . bases )
)
def __ne__ ( self , other ) :
return not ( self == other )