Django

Code

Changeset 3646

Show
Ignore:
Timestamp:
08/22/06 11:16:38 (2 years ago)
Author:
danderson
Message:

implemented schema evolution

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/schema-evolution/django/core/management.py

    r3307 r3646  
    4646    style = dummy() 
    4747 
    48 # Disable terminal coloring on Windows or if somebody's piping the output. 
    49 if sys.platform == 'win32' or not sys.stdout.isatty(): 
     48# Disable terminal coloring on Windows, Pocket PC, or if somebody's piping the output. 
     49if sys.platform == 'win32' or sys.platform == 'Pocket PC' or not sys.stdout.isatty(): 
    5050    disable_termcolors() 
    5151 
     
    7979    v = '.'.join([str(i) for i in VERSION[:-1]]) 
    8080    if VERSION[-1]: 
    81         v += ' (%s)' % VERSION[-1] 
     81        v += '-' + VERSION[-1] 
    8282    return v 
    8383 
     
    9595        sys.exit(1) 
    9696 
    97     # Get installed models, so we generate REFERENCES right 
    98     installed_models = _get_installed_models(_get_table_list()) 
    99  
     97    # Get installed models, so we generate REFERENCES right. 
     98    # We trim models from the current app so that the sqlreset command does not 
     99    # generate invalid SQL (leaving models out of known_models is harmless, so 
     100    # we can be conservative). 
     101    app_models = models.get_models(app) 
    100102    final_output = [] 
    101     models_output = set(installed_models
     103    known_models = set([model for model in _get_installed_models(_get_table_list()) if model not in app_models]
    102104    pending_references = {} 
    103105 
    104     app_models = models.get_models(app) 
    105  
    106     for klass in app_models: 
    107         output, references = _get_sql_model_create(klass, models_output) 
     106 
     107    for model in app_models: 
     108        output, references = _get_sql_model_create(model, known_models) 
    108109        final_output.extend(output) 
    109110        for refto, refs in references.items(): 
    110             try: 
    111                 pending_references[refto].extend(refs) 
    112             except KeyError: 
    113                 pending_references[refto] = refs 
    114         final_output.extend(_get_sql_for_pending_references(klass, pending_references)) 
     111            pending_references.setdefault(refto,[]).extend(refs) 
     112        final_output.extend(_get_sql_for_pending_references(model, pending_references)) 
    115113        # Keep track of the fact that we've created the table for this model. 
    116         models_output.add(klass
     114        known_models.add(model
    117115 
    118116    # Create the many-to-many join tables. 
    119     for klass in app_models: 
    120         final_output.extend(_get_many_to_many_sql_for_model(klass)) 
     117    for model in app_models: 
     118        final_output.extend(_get_many_to_many_sql_for_model(model)) 
    121119 
    122120    # Handle references to tables that are from other apps 
     
    124122    not_installed_models = set(pending_references.keys()) 
    125123    if not_installed_models: 
    126         final_output.append('-- The following references should be added but depend on non-existant tables:') 
    127         for klass in not_installed_models: 
    128             final_output.extend(['-- ' + sql for sql in 
    129                 _get_sql_for_pending_references(klass, pending_references)]) 
     124        alter_sql = [] 
     125        for model in not_installed_models: 
     126            alter_sql.extend(['-- ' + sql for sql in 
     127                _get_sql_for_pending_references(model, pending_references)]) 
     128        if alter_sql: 
     129            final_output.append('-- The following references should be added but depend on non-existent tables:') 
     130            final_output.extend(alter_sql) 
    130131 
    131132    return final_output 
     
    133134get_sql_create.args = APP_ARGS 
    134135 
    135 def _get_sql_model_create(klass, models_already_seen=set()): 
     136def _get_sql_model_create(model, known_models=set()): 
    136137    """ 
    137138    Get the SQL required to create a single model. 
     
    142143    data_types = get_creation_module().DATA_TYPES 
    143144 
    144     opts = klass._meta 
     145    opts = model._meta 
    145146    final_output = [] 
    146147    table_output = [] 
     
    164165                field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) 
    165166            if f.rel: 
    166                 if f.rel.to in models_already_seen
     167                if f.rel.to in known_models
    167168                    field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ 
    168169                        style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \ 
     
    172173                    # We haven't yet created the table to which this field 
    173174                    # is related, so save it for later. 
    174                     pr = pending_references.setdefault(f.rel.to, []).append((klass, f)) 
     175                    pr = pending_references.setdefault(f.rel.to, []).append((model, f)) 
    175176            table_output.append(' '.join(field_output)) 
    176177    if opts.order_with_respect_to: 
     
    190191    return final_output, pending_references 
    191192 
    192 def _get_sql_for_pending_references(klass, pending_references): 
     193def _get_sql_for_pending_references(model, pending_references): 
    193194    """ 
    194195    Get any ALTER TABLE statements to add constraints after the fact. 
     
    199200    final_output = [] 
    200201    if backend.supports_constraints: 
    201         opts = klass._meta 
    202         if klass in pending_references: 
    203             for rel_class, f in pending_references[klass]: 
     202        opts = model._meta 
     203        if model in pending_references: 
     204            for rel_class, f in pending_references[model]: 
    204205                rel_opts = rel_class._meta 
    205206                r_table = rel_opts.db_table 
     
    207208                table = opts.db_table 
    208209                col = opts.get_field(f.rel.field_name).column 
     210                # For MySQL, r_name must be unique in the first 64 characters. 
     211                # So we are careful with character usage here. 
     212                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) 
    209213                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \ 
    210                     (backend.quote_name(r_table), 
    211                     backend.quote_name('%s_referencing_%s_%s' % (r_col, table, col)), 
     214                    (backend.quote_name(r_table), r_name, 
    212215                    backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col))) 
    213             del pending_references[klass
     216            del pending_references[model
    214217    return final_output 
    215218 
    216 def _get_many_to_many_sql_for_model(klass): 
     219def _get_many_to_many_sql_for_model(model): 
    217220    from django.db import backend, get_creation_module 
    218221    from django.db.models import GenericRel 
    219      
     222 
    220223    data_types = get_creation_module().DATA_TYPES 
    221224 
    222     opts = klass._meta 
     225    opts = model._meta 
    223226    final_output = [] 
    224227    for f in opts.many_to_many: 
     
    274277    references_to_delete = {} 
    275278    app_models = models.get_models(app) 
    276     for klass in app_models: 
    277         if cursor and klass._meta.db_table in table_names: 
     279    for model in app_models: 
     280        if cursor and model._meta.db_table in table_names: 
    278281            # The table exists, so it needs to be dropped 
    279             opts = klass._meta 
     282            opts = model._meta 
    280283            for f in opts.fields: 
    281284                if f.rel and f.rel.to not in to_delete: 
    282                     references_to_delete.setdefault(f.rel.to, []).append( (klass, f) ) 
    283  
    284             to_delete.add(klass
    285  
    286     for klass in app_models: 
    287         if cursor and klass._meta.db_table in table_names: 
     285                    references_to_delete.setdefault(f.rel.to, []).append( (model, f) ) 
     286 
     287            to_delete.add(model
     288 
     289    for model in app_models: 
     290        if cursor and model._meta.db_table in table_names: 
    288291            # Drop the table now 
    289292            output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 
    290                 style.SQL_TABLE(backend.quote_name(klass._meta.db_table)))) 
    291             if backend.supports_constraints and references_to_delete.has_key(klass): 
    292                 for rel_class, f in references_to_delete[klass]: 
     293                style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) 
     294            if backend.supports_constraints and references_to_delete.has_key(model): 
     295                for rel_class, f in references_to_delete[model]: 
    293296                    table = rel_class._meta.db_table 
    294297                    col = f.column 
    295                     r_table = klass._meta.db_table 
    296                     r_col = klass._meta.get_field(f.rel.field_name).column 
     298                    r_table = model._meta.db_table 
     299                    r_col = model._meta.get_field(f.rel.field_name).column 
    297300                    output.append('%s %s %s %s;' % \ 
    298301                        (style.SQL_KEYWORD('ALTER TABLE'), 
    299302                        style.SQL_TABLE(backend.quote_name(table)), 
    300303                        style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()), 
    301                         style.SQL_FIELD(backend.quote_name("%s_referencing_%s_%s" % (col, r_table, r_col))))) 
    302                 del references_to_delete[klass
     304                        style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))))))) 
     305                del references_to_delete[model
    303306 
    304307    # Output DROP TABLE statements for many-to-many tables. 
    305     for klass in app_models: 
    306         opts = klass._meta 
     308    for model in app_models: 
     309        opts = model._meta 
    307310        for f in opts.many_to_many: 
    308311            if cursor and f.m2m_db_table() in table_names: 
     
    361364    app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql')) 
    362365 
    363     for klass in app_models: 
    364         output.extend(get_sql_initial_data_for_model(klass)) 
     366    for model in app_models: 
     367        output.extend(get_sql_initial_data_for_model(model)) 
    365368 
    366369    return output 
     
    372375    from django.db import backend, models 
    373376    output = [] 
    374     for klass in models.get_models(app): 
    375         for f in klass._meta.fields: 
     377    for model in models.get_models(app): 
     378        for f in model._meta.fields: 
    376379            if isinstance(f, models.AutoField): 
    377380                output.append("%s setval('%s', (%s max(%s) %s %s));" % \ 
    378381                    (style.SQL_KEYWORD('SELECT'), 
    379                     style.SQL_FIELD('%s_%s_seq' % (klass._meta.db_table, f.column)), 
     382                    style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), 
    380383                    style.SQL_KEYWORD('SELECT'), 
    381384                    style.SQL_FIELD(backend.quote_name(f.column)), 
    382385                    style.SQL_KEYWORD('FROM'), 
    383                     style.SQL_TABLE(backend.quote_name(klass._meta.db_table)))) 
     386                    style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) 
    384387                break # Only one AutoField is allowed per model, so don't bother continuing. 
    385         for f in klass._meta.many_to_many: 
     388        for f in model._meta.many_to_many: 
    386389            output.append("%s setval('%s', (%s max(%s) %s %s));" % \ 
    387390                (style.SQL_KEYWORD('SELECT'), 
     
    400403    output = [] 
    401404 
    402     for klass in models.get_models(app): 
    403         for f in klass._meta.fields: 
     405    for model in models.get_models(app): 
     406        for f in model._meta.fields: 
    404407            if f.db_index: 
    405408                unique = f.unique and 'UNIQUE ' or '' 
    406409                output.append( 
    407410                    style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ 
    408                     style.SQL_TABLE('%s_%s' % (klass._meta.db_table, f.column)) + ' ' + \ 
     411                    style.SQL_TABLE('%s_%s' % (model._meta.db_table, f.column)) + ' ' + \ 
    409412                    style.SQL_KEYWORD('ON') + ' ' + \ 
    410                     style.SQL_TABLE(backend.quote_name(klass._meta.db_table)) + ' ' + \ 
     413                    style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \ 
    411414                    "(%s);" % style.SQL_FIELD(backend.quote_name(f.column)) 
    412415                ) 
     
    414417get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)." 
    415418get_sql_indexes.args = APP_ARGS 
     419 
     420def get_sql_evolution(app): 
     421    "Returns SQL to update an existing schema to match the existing models." 
     422    from django.db import get_creation_module, models, backend, get_introspection_module, connection 
     423    data_types = get_creation_module().DATA_TYPES 
     424 
     425    if not data_types: 
     426        # This must be the "dummy" database backend, which means the user 
     427        # hasn't set DATABASE_ENGINE. 
     428        sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" + 
     429            "because you haven't specified the DATABASE_ENGINE setting.\n" + 
     430            "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n")) 
     431        sys.exit(1) 
     432 
     433    # First, try validating the models. 
     434    _check_for_validation_errors() 
     435 
     436    final_output = [] 
     437 
     438    # stolen and trimmed from syncdb so that we know which models are about  
     439    # to be created (so we don't check them for updates) 
     440    table_list = _get_table_list() 
     441    seen_models = _get_installed_models(table_list) 
     442    created_models = set() 
     443    pending_references = {} 
     444 
     445    model_list = models.get_models(app) 
     446    for model in model_list: 
     447        # Create the model's database table, if it doesn't already exist. 
     448        if model._meta.db_table in table_list or model._meta.aka in table_list or len(set(model._meta.aka) & set(table_list))>0: 
     449            continue 
     450        sql, references = _get_sql_model_create(model, seen_models) 
     451        seen_models.add(model) 
     452        created_models.add(model) 
     453        table_list.append(model._meta.db_table) 
     454 
     455    introspection = get_introspection_module() 
     456    # This should work even if a connecton isn't available 
     457    try: 
     458        cursor = connection.cursor() 
     459    except: 
     460        cursor = None 
     461 
     462    # get the existing models, minus the models we've just created 
     463    app_models = models.get_models(app) 
     464    for model in created_models: 
     465        if model in app_models: 
     466            app_models.remove(model) 
     467 
     468    for klass in app_models: 
     469         
     470        output, new_table_name = get_sql_evolution_check_for_changed_model_name(klass) 
     471        final_output.extend(output) 
     472         
     473        output = get_sql_evolution_check_for_changed_field_flags(klass, new_table_name) 
     474        final_output.extend(output) 
     475     
     476        output = get_sql_evolution_check_for_changed_field_name(klass, new_table_name) 
     477        final_output.extend(output) 
     478         
     479        output = get_sql_evolution_check_for_new_fields(klass, new_table_name) 
     480        final_output.extend(output) 
     481         
     482        output = get_sql_evolution_check_for_dead_fields(klass, new_table_name) 
     483        final_output.extend(output) 
     484         
     485    return final_output 
     486get_sql_evolution.help_doc = "Returns SQL to update an existing schema to match the existing models." 
     487get_sql_evolution.args = APP_ARGS 
     488 
     489def get_sql_evolution_check_for_new_fields(klass, new_table_name): 
     490    "checks for model fields that are not in the existing data structure" 
     491    from django.db import backend, get_creation_module, models, get_introspection_module, connection 
     492    data_types = get_creation_module().DATA_TYPES 
     493    cursor = connection.cursor() 
     494    introspection = get_introspection_module() 
     495    opts = klass._meta 
     496    output = [] 
     497    db_table = klass._meta.db_table 
     498    if new_table_name:  
     499        db_table = new_table_name 
     500    for f in opts.fields: 
     501        existing_fields = introspection.get_columns(cursor,db_table) 
     502        if f.column not in existing_fields and f.aka not in existing_fields and len(set(f.aka) & set(existing_fields))==0: 
     503            rel_field = f 
     504            data_type = f.get_internal_type() 
     505            col_type = data_types[data_type] 
     506            if col_type is not None: 
     507#                field_output = [] 
     508#                field_output.append('ALTER TABLE') 
     509#                field_output.append(db_table) 
     510#                field_output.append('ADD COLUMN') 
     511#                field_output.append(backend.quote_name(f.column)) 
     512#                field_output.append(style.SQL_COLTYPE(col_type % rel_field.__dict__)) 
     513#                field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or ''))) 
     514#                if f.unique: 
     515#                    field_output.append(style.SQL_KEYWORD('UNIQUE')) 
     516#                if f.primary_key: 
     517#                    field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) 
     518#                output.append(' '.join(field_output) + ';') 
     519                output.append( backend.get_add_column_sql( db_table, f.column, style.SQL_COLTYPE(col_type % rel_field.__dict__), f.null, f.unique, f.primary_key ) ) 
     520    return output 
     521 
     522def get_sql_evolution_check_for_changed_model_name(klass): 
     523    from django.db import backend, get_creation_module, models, get_introspection_module, connection 
     524    cursor = connection.cursor() 
     525    introspection = get_introspection_module() 
     526    table_list = introspection.get_table_list(cursor) 
     527    if klass._meta.db_table in table_list: 
     528        return [], None 
     529    if klass._meta.aka in table_list: 
     530        return [ 'ALTER TABLE '+ backend.quote_name(klass._meta.aka) +' RENAME TO '+ backend.quote_name(klass._meta.db_table) + ';' ], klass._meta.aka 
     531    elif len(set(klass._meta.aka) & set(table_list))==1: 
     532        return [ 'ALTER TABLE '+ backend.quote_name(klass._meta.aka[0]) +' RENAME TO '+ backend.quote_name(klass._meta.db_table) + ';' ], klass._meta.aka[0] 
     533    else: 
     534        return [], None 
     535     
     536def get_sql_evolution_check_for_changed_field_name(klass, new_table_name): 
     537    from django.db import backend, get_creation_module, models, get_introspection_module, connection 
     538    data_types = get_creation_module().DATA_TYPES 
     539    cursor = connection.cursor() 
     540    introspection = get_introspection_module() 
     541    opts = klass._meta 
     542    output = [] 
     543    db_table = klass._meta.db_table 
     544    if new_table_name:  
     545        db_table = new_table_name 
     546    for f in opts.fields: 
     547        existing_fields = introspection.get_columns(cursor,db_table) 
     548        if f.column not in existing_fields and (f.aka in existing_fields or len(set(f.aka) & set(existing_fields)))==1: 
     549            old_col = None 
     550            if isinstance( f.aka, str ): 
     551                old_col = f.aka 
     552            else: 
     553                old_col = f.aka[0] 
     554            rel_field = f 
     555            data_type = f.get_internal_type() 
     556            col_type = data_types[data_type] 
     557            if col_type is not None: 
     558                field_output = [] 
     559                col_def = style.SQL_COLTYPE(col_type % rel_field.__dict__) +' '+ style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')) 
     560                if f.unique: 
     561                    col_def += style.SQL_KEYWORD(' UNIQUE') 
     562                if f.primary_key: 
     563                    col_def += style.SQL_KEYWORD(' PRIMARY KEY') 
     564                field_output.append( backend.get_change_column_name_sql( klass._meta.db_table, introspection.get_indexes(cursor,db_table), backend.quote_name(old_col), backend.quote_name(f.column), col_def ) ) 
     565                output.append(' '.join(field_output)) 
     566    return output 
     567     
     568def get_sql_evolution_check_for_changed_field_flags(klass, new_table_name): 
     569    from django.db import backend, get_creation_module, models, get_introspection_module, connection 
     570    from django.db.models.fields import CharField, SlugField 
     571    from django.db.models.fields.related import RelatedField, ForeignKey 
     572    data_types = get_creation_module().DATA_TYPES 
     573    cursor = connection.cursor() 
     574    introspection = get_introspection_module() 
     575    opts = klass._meta 
     576    output = [] 
     577    db_table = klass._meta.db_table 
     578    if new_table_name:  
     579        db_table = new_table_name 
     580    for f in opts.fields: 
     581        existing_fields = introspection.get_columns(cursor,db_table) 
     582        cf = None # current field, ie what it is before any renames 
     583        if f.column in existing_fields: 
     584            cf = f.column 
     585        elif f.aka in existing_fields: 
     586            cf = f.aka 
     587        elif len(set(f.aka) & set(existing_fields))==1: 
     588            cf = f.aka[0] 
     589        else: 
     590            continue # no idea what column you're talking about - should be handled by get_sql_evolution_check_for_new_fields()) 
     591        data_type = f.get_internal_type() 
     592        if data_types.has_key(data_type): 
     593            column_flags = introspection.get_known_column_flags(cursor, db_table, cf) 
     594            if column_flags['allow_null']!=f.null or \ 
     595                    ( not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) ) or \ 
     596                    ( not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) ) or \ 
     597                    column_flags['unique']!=f.unique or \ 
     598                    column_flags['primary_key']!=f.primary_key: 
     599                    #column_flags['foreign_key']!=f.foreign_key: 
     600#                print  
     601#                print db_table, f.column, column_flags 
     602#                print "column_flags['allow_null']!=f.null", column_flags['allow_null']!=f.null 
     603#                print "not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength)", not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) 
     604#                print "not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength)", not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) 
     605#                print "column_flags['unique']!=f.unique", column_flags['unique']!=f.unique 
     606#                print "column_flags['primary_key']!=f.primary_key", column_flags['primary_key']!=f.primary_key 
     607                col_type = data_types[data_type] 
     608                col_type_def = style.SQL_COLTYPE(col_type % f.__dict__) 
     609#                col_def = style.SQL_COLTYPE(col_type % f.__dict__) +' '+ style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')) 
     610#                if f.unique: 
     611#                    col_def += ' '+ style.SQL_KEYWORD('UNIQUE') 
     612#                if f.primary_key: 
     613#                    col_def += ' '+ style.SQL_KEYWORD('PRIMARY KEY') 
     614                output.append( backend.get_change_column_def_sql( db_table, cf, col_type_def, f.null, f.unique, f.primary_key ) ) 
     615                    #print db_table, cf, f.maxlength, introspection.get_known_column_flags(cursor, db_table, cf) 
     616    return output 
     617 
     618def get_sql_evolution_check_for_dead_fields(klass, new_table_name): 
     619    from django.db import backend, get_creation_module, models, get_introspection_module, connection 
     620    from django.db.models.fields import CharField, SlugField 
     621    from django.db.models.fields.related import RelatedField, ForeignKey 
     622    data_types = get_creation_module().DATA_TYPES 
     623    cursor = connection.cursor() 
     624    introspection = get_introspection_module() 
     625    opts = klass._meta 
     626    output = [] 
     627    db_table = klass._meta.db_table 
     628    if new_table_name:  
     629        db_table = new_table_name 
     630    suspect_fields = set(introspection.get_columns(cursor,db_table)) 
     631    for f in opts.fields: 
     632        suspect_fields.discard(f.column) 
     633        suspect_fields.discard(f.aka) 
     634        suspect_fields.difference_update(f.aka) 
     635    if len(suspect_fields)>0: 
     636        output.append( '-- warning: as the following may cause data loss, it/they must be run manually' ) 
     637    for suspect_field in suspect_fields: 
     638        output.append( backend.get_drop_column_sql( db_table, suspect_field ) ) 
     639        output.append( '-- end warning' ) 
     640    return output 
    416641 
    417642def get_sql_all(app): 
     
    458683        for model in model_list: 
    459684            # Create the model's database table, if it doesn't already exist. 
    460             if model._meta.db_table in table_list
     685            if model._meta.db_table in table_list or model._meta.aka in table_list or len(set(model._meta.aka) & set(table_list))>0
    461686                continue 
    462687            sql, references = _get_sql_model_create(model, seen_models) 
     
    482707                        cursor.execute(statement) 
    483708 
     709        for sql in get_sql_evolution(app): 
     710            print sql 
     711#            cursor.execute(sql) 
     712 
    484713        transaction.commit_unless_managed() 
    485  
     714      
    486715    # Send the post_syncdb signal, so individual apps can do whatever they need 
    487716    # to do at this point. 
     
    518747    output.append('{%% if perms.%s %%}' % app_label) 
    519748    output.append('<div class="module"><h2>%s</h2><table>' % app_label.title()) 
    520     for klass in app_models: 
    521         if klass._meta.admin: 
     749    for model in app_models: 
     750        if model._meta.admin: 
    522751            output.append(MODULE_TEMPLATE % { 
    523752                'app': app_label, 
    524                 'mod': klass._meta.module_name, 
    525                 'name': capfirst(klass._meta.verbose_name_plural), 
    526                 'addperm': klass._meta.get_add_permission(), 
    527                 'changeperm': klass._meta.get_change_permission(), 
     753                'mod': model._meta.module_name, 
     754                'name': capfirst(model._meta.verbose_name_plural), 
     755                'addperm': model._meta.get_add_permission(), 
     756                'changeperm': model._meta.get_change_permission(), 
    528757            }) 
    529758    output.append('</table></div>') 
     
    593822    "Executes the equivalent of 'get_sql_reset' in the current database." 
    594823    from django.db import connection, transaction 
    595     from cStringIO import StringIO 
    596824    app_name = app.__name__.split('.')[-2] 
    597825 
     
    693921    "Generator that introspects the tables in the given database name and returns a Django model, one line at a time." 
    694922    from django.db import connection, get_introspection_module 
    695     from django.conf import settings 
    696923    import keyword 
    697924 
    698925    introspection_module = get_introspection_module() 
    699926 
    700     def table2model(table_name): 
    701         object_name = table_name.title().replace('_', '') 
    702         return object_name.endswith('s') and object_name[:-1] or object_name 
     927    table2model = lambda table_name: table_name.title().replace('_', '') 
    703928 
    704929    cursor = connection.cursor() 
     
    729954            extra_params = {}  # Holds Field parameters such as 'db_column'. 
    730955 
     956            if ' ' in att_name: 
     957                extra_params['db_column'] = att_name 
     958                att_name = att_name.replace(' ', '') 
     959                comment_notes.append('Field renamed to remove spaces.') 
    731960            if keyword.iskeyword(att_name): 
    732961                extra_params['db_column'] = att_name 
     
    8191048 
    8201049    e = ModelErrorCollection(outfile) 
    821      
     1050 
    8221051    for (app_name, error) in get_app_errors().items(): 
    8231052        e.add(app_name, error) 
    824          
     1053 
    8251054    for cls in models.get_models(app): 
    8261055        opts = cls._meta 
     
    8871116                            e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) 
    8881117 
    889                  
     1118 
    8901119        for i, f in enumerate(opts.many_to_many): 
    8911120            # Check to see if the related m2m field will clash with any 
     
    9591188                        except models.FieldDoesNotExist: 
    9601189                            e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) 
     1190                # date_hierarchy 
     1191                if opts.admin.date_hierarchy: 
     1192                    try: 
     1193                        f = opts.get_field(opts.admin.date_hierarchy) 
     1194                    except models.FieldDoesNotExist: 
     1195                        e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy) 
    9611196 
    9621197        # Check ordering attribute. 
     
    10251260        sys.exit(1) 
    10261261 
    1027 def runserver(addr, port): 
     1262def runserver(addr, port, use_reloader=True): 
    10281263    "Starts a lightweight Web server for development." 
    10291264    from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException 
     
    10591294        except KeyboardInterrupt: 
    10601295            sys.exit(0) 
    1061     from django.utils import autoreload 
    1062     autoreload.main(inner_run) 
    1063 runserver.args = '[optional port number, or ipaddr:port]' 
     1296    if use_reloader: 
     1297        from django.utils import autoreload 
     1298        autoreload.main(inner_run) 
     1299    else: 
     1300        inner_run() 
     1301runserver.args = '[--noreload] [optional port number, or ipaddr:port]' 
    10641302 
    10651303def createcachetable(tablename): 
     
    11311369 
    11321370def runfcgi(args): 
    1133     """Run this project as a FastCGI application. requires flup.""" 
     1371    "Runs this project as a FastCGI application. Requires flup." 
     1372    from django.conf import settings 
     1373    from django.utils import translation 
     1374    # Activate the current language, because it won't get activated later. 
     1375    try: 
     1376        translation.activate(settings.LANGUAGE_CODE) 
     1377    except AttributeError: 
     1378        pass 
    11341379    from django.core.servers.fastcgi import runfastcgi 
    11351380    runfastcgi(args) 
     
    11561401    'sqlreset': get_sql_reset, 
    11571402    'sqlsequencereset': get_sql_sequence_reset, 
     1403    'sqlevolve': get_sql_evolution, 
    11581404    'startapp': startapp, 
    11591405    'startproject': startproject, 
     
    12101456    parser.add_option('--plain', action='store_true', dest='plain', 
    12111457        help='Tells Django to use plain Python, not IPython, for "shell" command.') 
     1458    parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True, 
     1459        help='Tells Django to NOT use the auto-reloader when running the development server.') 
    12121460    options, args = parser.parse_args(argv[1:]) 
    12131461 
     
    12651513            except ValueError: 
    12661514                addr, port = '', args[1] 
    1267         action_mapping[action](addr, port
     1515        action_mapping[action](addr, port, options.use_reloader
    12681516    elif action == 'runfcgi': 
    12691517        action_mapping[action](args[1:]) 
     
    12861534            print style.SQL_KEYWORD("COMMIT;") 
    12871535 
    1288 def execute_manager(settings_mod, argv=None): 
     1536def setup_environ(settings_mod): 
     1537    """ 
     1538    Configure the runtime environment. This can also be used by external 
     1539    scripts wanting to set up a similar environment to manage.py. 
     1540    """ 
    12891541    # Add this project to sys.path so that it's importable in the conventional 
    12901542    # way. For example, if this file (manage.py) lives in a directory 
     
    12981550    # Set DJANGO_SETTINGS_MODULE appropriately. 
    12991551    os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name 
    1300  
     1552    return project_directory 
     1553 
     1554def execute_manager(settings_mod, argv=None): 
     1555    project_directory = setup_environ(settings_mod) 
    13011556    action_mapping = DEFAULT_ACTION_MAPPING.copy() 
    13021557 
  • django/branches/schema-evolution/django/db/backends/mysql/base.py

    r3115 r3646  
    4141        try: 
    4242            return self.cursor.executemany(sql, param_list) 
    43         except Database.Warning
     43        except Database.Warning, w
    4444            self.cursor.execute("SHOW WARNINGS") 
    4545            raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall()) 
     
    162162    return "DEFAULT" 
    163163 
     164def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ): 
     165    # mysql doesn't support column renames (AFAIK), so we fake it 
     166    # TODO: only supports a single primary key so far 
     167    pk_name = None 
     168    for key in indexes.keys(): 
     169        if indexes[key]['primary_key']: pk_name = key 
     170    output = [] 
     171    output.append( 'ALTER TABLE '+ quote_name(table_name) +' CHANGE COLUMN '+ quote_name(old_col_name) +' '+ quote_name(new_col_name) +' '+ col_def + ';' ) 
     172    return '\n'.join(output) 
     173 
     174def get_change_column_def_sql( table_name, col_name, col_type, null, unique, primary_key ): 
     175    output = [] 
     176    col_def = col_type +' '+ ('%sNULL' % (not null and 'NOT ' or '')) 
     177    if unique: 
     178        col_def += ' '+ 'UNIQUE' 
     179    if primary_key: 
     180        col_def += ' '+ 'PRIMARY KEY' 
     181    output.append( 'ALTER TABLE '+ quote_name(table_name) +' MODIFY COLUMN '+ quote_name(col_name) +' '+ col_def + ';' ) 
     182    return '\n'.join(output) 
     183 
     184def get_add_column_sql( table_name, col_name, col_type, null, unique, primary_key  ): 
     185    output = [] 
     186    field_output = [] 
     187    field_output.append('ALTER TABLE') 
     188    field_output.append(quote_name(table_name)) 
     189    field_output.append('ADD COLUMN') 
     190    field_output.append(quote_name(col_name)) 
     191    field_output.append(col_type) 
     192    field_output.append(('%sNULL' % (not null and 'NOT ' or ''))) 
     193    if unique: 
     194        field_output.append(('UNIQUE')) 
     195    if primary_key: 
     196        field_output.append(('PRIMARY KEY')) 
     197    output.append(' '.join(field_output) + ';') 
     198    return '\n'.join(output) 
     199 
     200def get_drop_column_sql( table_name, col_name ): 
     201    output = [] 
     202    output.append( '-- ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) + ';' ) 
     203    return '\n'.join(output) 
     204     
     205     
    164206OPERATOR_MAPPING = { 
    165207    'exact': '= %s', 
  • django/branches/schema-evolution/django/db/backends/mysql/introspection.py

    r2922 r3646  
    1 from django.db import transaction 
    21from django.db.backends.mysql.base import quote_name 
    32from MySQLdb import ProgrammingError, OperationalError 
     
    7473    return indexes 
    7574 
     75def get_columns(cursor, table_name): 
     76    try: 
     77        cursor.execute("describe %s" % quote_name(table_name)) 
     78        return [row[0] for row in cursor.fetchall()] 
     79    except: 
     80        return [] 
     81     
     82def get_known_column_flags( cursor, table_name, column_name ): 
     83    cursor.execute("describe %s" % quote_name(table_name)) 
     84    dict = {} 
     85    for row in cursor.fetchall(): 
     86        if row[0] == column_name: 
     87 
     88            # maxlength check goes here 
     89            if row[1][0:7]=='varchar': 
     90                dict['maxlength'] = row[1][8:len(row[1])-1] 
     91             
     92            # default flag check goes here 
     93            if row[2]=='YES': dict['allow_null'] = True 
     94            else: dict['allow_null'] = False 
     95             
     96            # primary/foreign/unique key flag check goes here 
     97            if row[3]=='PRI': dict['primary_key'] = True 
     98            else: dict['primary_key'] = False 
     99            if row[3]=='FOR': dict['foreign_key'] = True 
     100            else: dict['foreign_key'] = False 
     101            if row[3]=='UNI': dict['unique'] = True 
     102            else: dict['unique'] = False 
     103             
     104            # default value check goes here 
     105            # if row[4]=='NULL': dict['default'] = None 
     106            # else: dict['default'] = row[4] 
     107            dict['default'] = row[4] 
     108             
     109    # print table_name, column_name, dict 
     110    return dict 
     111     
    76112DATA_TYPES_REVERSE = { 
    77113    FIELD_TYPE.BLOB: 'TextField', 
  • django/branches/schema-evolution/django/db/backends/postgresql/base.py

    r3115 r3646  
    112112    return "DEFAULT" 
    113113 
     114def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ): 
     115    # TODO: only supports a single primary key so far 
     116    pk_name = None 
     117    for key in indexes.keys(): 
     118        if indexes[key]['primary_key']: pk_name = key 
     119    output = [] 
     120    output.append( 'ALTER TABLE '+ quote_name(table_name) +' RENAME COLUMN '+ quote_name(old_col_name) +' TO '+ quote_name(new_col_name) +';' ) 
     121    return '\n'.join(output) 
     122 
     123def get_change_column_def_sql( table_name, col_name, col_type, null, unique, primary_key ): 
     124    output = [] 
     125    output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD COLUMN '+ quote_name(col_name+'_tmp') +' '+ col_type + ';' ) 
     126    output.append( 'UPDATE '+ quote_name(table_name) +' SET '+ quote_name(col_name+'_tmp') +' = '+ quote_name(col_name) + ';' ) 
     127    output.append( 'ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) +';' ) 
     128    output.append( 'ALTER TABLE '+ quote_name(table_name) +' RENAME COLUMN '+ quote_name(col_name+'_tmp') +' TO '+ quote_name(col_name) + ';' ) 
     129    if not null: 
     130        output.append( 'ALTER TABLE '+ quote_name(table_name) +' ALTER COLUMN '+ quote_name(col_name) +' SET NOT NULL;' ) 
     131    if unique: 
     132        output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD CONSTRAINT '+ table_name +'_'+ col_name +'_unique_constraint UNIQUE('+ col_name +');' ) 
     133     
     134    return '\n'.join(output) 
     135 
     136def get_add_column_sql( table_name, col_name, col_type, null, unique, primary_key  ): 
     137    output = [] 
     138    output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD COLUMN '+ quote_name(col_name) +' '+ col_type + ';' ) 
     139    if not null: 
     140        output.append( 'ALTER TABLE '+ quote_name(table_name) +' ALTER COLUMN '+ quote_name(col_name) +' SET NOT NULL;' ) 
     141    if unique: 
     142        output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD CONSTRAINT '+ table_name +'_'+ col_name +'_unique_constraint UNIQUE('+ col_name +');' ) 
     143    return '\n'.join(output) 
     144     
     145def get_drop_column_sql( table_name, col_name ): 
     146    output = [] 
     147    output.append( '-- ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) + ';' ) 
     148    return '\n'.join(output) 
     149 
    114150# Register these custom typecasts, because Django expects dates/times to be 
    115151# in Python's native (standard-library) datetime/time format, whereas psycopg 
  • django/branches/schema-evolution/django/db/backends/postgresql/introspection.py

    r3047 r3646  
    1 from django.db import transaction 
    21from django.db.backends.postgresql.base import quote_name 
    32 
     
    6867    return indexes 
    6968 
     69def get_columns(cursor, table_name): 
     70    try: 
     71        cursor.execute("SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, a.attnum, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = (SELECT c.oid from pg_catalog.pg_class c where c.relname ~ '^%s$') AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum" % table_name) 
     72        return [row[0] for row in cursor.fetchall()] 
     73    except: 
     74        return [] 
     75     
     76def get_known_column_flags( cursor, table_name, column_name ): 
     7