def _install_baseline(app, latest_version, using, verbosity):
    """Install baselines for an app.

    This goes through the entire evolution sequence for an app and records
    each evolution as being applied, creating a baseline for any apps that
    are newly-added whose models have just been created (or existed prior to
    using Django Evolution).

    Args:
        app (module):
            The app models module.

        latest_version (django_evolution.models.Version):
            The latest version, which the evolutions will be associated with.

        using (str):
            The database being updated.

        verbosity (int):
            The verbosity used to control output.
    """
    app_label = get_app_label(app)
    sequence = get_evolution_sequence(app)

    if sequence and verbosity > 0:
        print('Evolutions in %s baseline: %s' %
              (app_label, ', '.join(sequence)))

    for evo_label in sequence:
        evolution = Evolution(app_label=app_label,
                              label=evo_label,
                              version=latest_version)
        evolution.save(using=using)
Exemple #2
0
def get_unapplied_evolutions(app, database):
    "Obtain the list of unapplied evolutions for an application"
    sequence = get_evolution_sequence(app)
    app_label = get_app_label(app)

    evolutions = Evolution.objects.filter(app_label=app_label).using(database)
    applied = [evo.label for evo in evolutions]

    return [seq for seq in sequence if seq not in applied]
Exemple #3
0
def create_project_sig(database):
    """
    Create a dictionary representation of the apps in a given project.
    """
    proj_sig = {
        '__version__': 1,
    }

    for app in get_apps():
        proj_sig[get_app_label(app)] = create_app_sig(app, database)

    return proj_sig
    def handle(self, *app_labels, **options):
        if not app_labels:
            app_labels = [get_app_label(app) for app in get_apps()]

        for app_label in app_labels:
            evolutions = list(Evolution.objects.filter(app_label=app_label))

            if evolutions:
                print "Applied evolutions for '%s':" % app_label

                for evolution in evolutions:
                    print '    %s' % evolution.label

                print
Exemple #5
0
def install_baseline(app, latest_version, using_args, verbosity):
    app_label = get_app_label(app)
    sequence = get_evolution_sequence(app)

    if sequence:
        if verbosity > 0:
            print 'Evolutions in %s baseline:' % app_label, \
                  ', '.join(sequence)

    for evo_label in sequence:
        evolution = django_evolution.Evolution(app_label=app_label,
                                               label=evo_label,
                                               version=latest_version)
        evolution.save(**using_args)
Exemple #6
0
def evolution(app, created_models, verbosity=1, **kwargs):
    """
    A hook into syncdb's post_syncdb signal, that is used to notify the user
    if a model evolution is necessary.
    """
    default_db = DEFAULT_DB_ALIAS

    db = kwargs.get('db', default_db)
    proj_sig = create_project_sig(db)
    signature = pickle.dumps(proj_sig)

    using_args = {
        'using': db,
    }

    try:
        latest_version = \
            django_evolution.Version.objects.current_version(using=db)
    except django_evolution.Version.DoesNotExist:
        # We need to create a baseline version.
        if verbosity > 0:
            print "Installing baseline version"

        latest_version = django_evolution.Version(signature=signature)
        latest_version.save(**using_args)

        for a in get_apps():
            install_baseline(a, latest_version, using_args, verbosity)

    unapplied = get_unapplied_evolutions(app, db)

    if unapplied:
        print style.NOTICE('There are unapplied evolutions for %s.'
                           % get_app_label(app))

    # Evolutions are checked over the entire project, so we only need to check
    # once. We do this check when Django Evolutions itself is synchronized.
    if app == django_evolution:
        old_proj_sig = pickle.loads(str(latest_version.signature))

        # If any models or apps have been added, a baseline must be set
        # for those new models
        changed = False
        new_apps = []

        for app_name, new_app_sig in proj_sig.items():
            if app_name == '__version__':
                # Ignore the __version__ tag
                continue

            old_app_sig = old_proj_sig.get(app_name, None)

            if old_app_sig is None:
                # App has been added
                old_proj_sig[app_name] = proj_sig[app_name]
                new_apps.append(app_name)
                changed = True
            else:
                for model_name, new_model_sig in new_app_sig.items():
                    old_model_sig = old_app_sig.get(model_name, None)

                    if old_model_sig is None:
                        # Model has been added
                        old_proj_sig[app_name][model_name] = \
                            proj_sig[app_name][model_name]
                        changed = True

        if changed:
            if verbosity > 0:
                print "Adding baseline version for new models"

            latest_version = \
                django_evolution.Version(signature=pickle.dumps(old_proj_sig))
            latest_version.save(**using_args)

            for app_name in new_apps:
                app = get_app(app_name, True)

                if app:
                    install_baseline(app, latest_version, using_args,
                                     verbosity)

        # TODO: Model introspection step goes here.
        # # If the current database state doesn't match the last
        # # saved signature (as reported by latest_version),
        # # then we need to update the Evolution table.
        # actual_sig = introspect_project_sig()
        # acutal = pickle.dumps(actual_sig)
        # if actual != latest_version.signature:
        #     nudge = Version(signature=actual)
        #     nudge.save()
        #     latest_version = nudge

        diff = Diff(old_proj_sig, proj_sig)

        if not diff.is_empty():
            print style.NOTICE(
                'Project signature has changed - an evolution is required')

            if verbosity > 1:
                old_proj_sig = pickle.loads(str(latest_version.signature))
                print diff
def _on_app_models_updated(app, verbosity=1, using=DEFAULT_DB_ALIAS, **kwargs):
    """Handler for when an app's models were updated.

    This is called in response to a syncdb or migrate operation for an app.
    It will install baselines for any new models, record the changes in the
    evolution history, and notify the user if any of the changes require an
    evolution.

    Args:
        app (module):
            The app models module that was updated.

        verbosity (int, optional):
            The verbosity used to control output. This will have been provided
            by the syncdb or migrate command.

        using (str, optional):
            The database being updated.

        **kwargs (dict):
            Additional keyword arguments provided by the signal handler for
            the syncdb or migrate operation.
    """
    project_sig = ProjectSignature.from_database(using)

    try:
        latest_version = Version.objects.current_version(using=using)
    except Version.DoesNotExist:
        # We need to create a baseline version.
        if verbosity > 0:
            print("Installing baseline version")

        latest_version = Version(signature=project_sig)
        latest_version.save(using=using)

        for a in get_apps():
            _install_baseline(app=a,
                              latest_version=latest_version,
                              using=using,
                              verbosity=verbosity)

    unapplied = get_unapplied_evolutions(app, using)

    if unapplied:
        print(
            style.NOTICE('There are unapplied evolutions for %s.' %
                         get_app_label(app)))

    # Evolutions are checked over the entire project, so we only need to check
    # once. We do this check when Django Evolutions itself is synchronized.

    if app is get_app('django_evolution'):
        old_project_sig = latest_version.signature

        # If any models or apps have been added, a baseline must be set
        # for those new models
        changed = False
        new_apps = []

        for new_app_sig in project_sig.app_sigs:
            app_id = new_app_sig.app_id
            old_app_sig = old_project_sig.get_app_sig(app_id)

            if old_app_sig is None:
                # App has been added
                old_project_sig.add_app_sig(new_app_sig.clone())
                new_apps.append(app_id)
                changed = True
            else:
                for new_model_sig in new_app_sig.model_sigs:
                    model_name = new_model_sig.model_name

                    old_model_sig = old_app_sig.get_model_sig(model_name)

                    if old_model_sig is None:
                        # Model has been added
                        old_app_sig.add_model_sig(
                            project_sig.get_app_sig(app_id).get_model_sig(
                                model_name).clone())
                        changed = True

        if changed:
            if verbosity > 0:
                print("Adding baseline version for new models")

            latest_version = Version(signature=old_project_sig)
            latest_version.save(using=using)

            for app_name in new_apps:
                app = get_app(app_name, True)

                if app:
                    _install_baseline(app=app,
                                      latest_version=latest_version,
                                      using=using,
                                      verbosity=verbosity)

        # TODO: Model introspection step goes here.
        # # If the current database state doesn't match the last
        # # saved signature (as reported by latest_version),
        # # then we need to update the Evolution table.
        # actual_sig = introspect_project_sig()
        # acutal = pickle.dumps(actual_sig)
        # if actual != latest_version.signature:
        #     nudge = Version(signature=actual)
        #     nudge.save()
        #     latest_version = nudge

        diff = Diff(old_project_sig, project_sig)

        if not diff.is_empty():
            print(
                style.NOTICE(
                    'Project signature has changed - an evolution is required')
            )

            if verbosity > 1:
                print(diff)
Exemple #8
0
def get_mutations(app, evolution_labels, database):
    """
    Obtain the list of mutations described by the named evolutions.
    """
    # For each item in the evolution sequence. Check each item to see if it is
    # a python file or an sql file.
    try:
        app_name = get_app_name(app)

        if app_name in BUILTIN_SEQUENCES:
            module_name = 'django_evolution.builtin_evolutions'
        else:
            module_name = '%s.evolutions' % app_name

        evolution_module = __import__(module_name, {}, {}, [''])
    except ImportError:
        return []

    mutations = []

    for label in evolution_labels:
        directory_name = os.path.dirname(evolution_module.__file__)

        # The first element is used for compatibility purposes.
        filenames = [
            os.path.join(directory_name, label + '.sql'),
            os.path.join(directory_name, "%s_%s.sql" % (database, label)),
        ]

        found = False

        for filename in filenames:
            if os.path.exists(filename):
                sql_file = open(filename, 'r')
                sql = sql_file.readlines()
                sql_file.close()

                mutations.append(SQLMutation(label, sql))

                found = True
                break

        if not found:
            try:
                module_name = [evolution_module.__name__, label]
                module = __import__('.'.join(module_name), {}, {},
                                    [module_name])
                mutations.extend(module.MUTATIONS)
            except ImportError:
                raise EvolutionException(
                    'Error: Failed to find an SQL or Python evolution named %s'
                    % label)

    latest_version = Version.objects.current_version(using=database)

    app_label = get_app_label(app)
    old_proj_sig = pickle.loads(str(latest_version.signature))
    proj_sig = create_project_sig(database)

    if app_label in old_proj_sig and app_label in proj_sig:
        # We want to go through now and make sure we're only applying
        # evolutions for models where the signature is different between
        # what's stored and what's current.
        #
        # The reason for this is that we may have just installed a baseline,
        # which would have the up-to-date signature, and we might be trying
        # to apply evolutions on top of that (which would already be applied).
        # These would generate errors. So, try hard to prevent that.
        old_app_sig = old_proj_sig[app_label]
        app_sig = proj_sig[app_label]

        changed_models = set()

        # Find the list of models in the latest signature of this app
        # that aren't in the old signature.
        for model_name, model_sig in app_sig.iteritems():
            if (model_name not in old_app_sig
                    or old_app_sig[model_name] != model_sig
                    or has_unique_together_changed(old_app_sig[model_name],
                                                   model_sig)):
                changed_models.add(model_name)

        # Now do the same for models in the old signature, in case the
        # model has been deleted.
        for model_name, model_sig in old_app_sig.iteritems():
            if model_name not in app_sig:
                changed_models.add(model_name)

        # We should now have a full list of which models changed. Filter
        # the list of mutations appropriately.
        #
        # Changes affecting a model that was newly-introduced are removed,
        # unless the mutation is a RenameModel, in which case we'll need it
        # during the optimization step (and will remove it if necessary then).
        mutations = [
            mutation for mutation in mutations
            if (not hasattr(mutation, 'model_name') or mutation.model_name in
                changed_models or isinstance(mutation, RenameModel))
        ]

    return mutations