def set(self, objs, **kwargs): # Force evaluation of `objs` in case it's a queryset whose value # could be affected by `manager.clear()`. Refs #19816. objs = tuple(objs) bulk = kwargs.pop('bulk', True) clear = kwargs.pop('clear', False) if self.field.null: db = router.db_for_write(self.model, instance=self.instance) with transaction.atomic(using=db, savepoint=False): if clear: self.clear() self.add(*objs, bulk=bulk) else: old_objs = set(self.using(db).all()) new_objs = [] for obj in objs: if obj in old_objs: old_objs.remove(obj) else: new_objs.append(obj) self.remove(*old_objs, bulk=bulk) self.add(*new_objs, bulk=bulk) else: self.add(*objs, bulk=bulk)
def clear(self): db = router.db_for_write(self.through, instance=self.instance) with transaction.atomic(using=db, savepoint=False): signals.m2m_changed.send( sender=self.through, action="pre_clear", instance=self.instance, reverse=self.reverse, model=self.model, pk_set=None, using=db, ) filters = self._build_remove_filters( super(ManyRelatedManager, self).get_queryset().using(db)) self.through._default_manager.using(db).filter( filters).delete() signals.m2m_changed.send( sender=self.through, action="post_clear", instance=self.instance, reverse=self.reverse, model=self.model, pk_set=None, using=db, )
def add(self, *objs, **kwargs): bulk = kwargs.pop('bulk', True) objs = list(objs) db = router.db_for_write(self.model, instance=self.instance) def check_and_update_obj(obj): if not isinstance(obj, self.model): raise TypeError("'%s' instance expected, got %r" % ( self.model._meta.object_name, obj, )) setattr(obj, self.field.name, self.instance) if bulk: pks = [] for obj in objs: check_and_update_obj(obj) if obj._state.adding or obj._state.db != db: raise ValueError( "%r instance isn't saved. Use bulk=False or save " "the object first." % obj) pks.append(obj.pk) self.model._base_manager.using(db).filter(pk__in=pks).update( **{ self.field.name: self.instance, }) else: with transaction.atomic(using=db, savepoint=False): for obj in objs: check_and_update_obj(obj) obj.save()
def handle(self, **options): database = options['database'] connection = connections[database] verbosity = options['verbosity'] interactive = options['interactive'] # The following are stealth options used by Django's internals. reset_sequences = options.get('reset_sequences', True) allow_cascade = options.get('allow_cascade', False) inhibit_post_migrate = options.get('inhibit_post_migrate', False) self.style = no_style() # Import the 'management' module within each installed app, to register # dispatcher events. for app_config in apps.get_app_configs(): try: import_module('.management', app_config.name) except ImportError: pass sql_list = sql_flush(self.style, connection, only_django=True, reset_sequences=reset_sequences, allow_cascade=allow_cascade) if interactive: confirm = input("""You have requested a flush of the database. This will IRREVERSIBLY DESTROY all data currently in the %r database, and return each table to an empty state. Are you sure you want to do this? Type 'yes' to continue, or 'no' to cancel: """ % connection.settings_dict['NAME']) else: confirm = 'yes' if confirm == 'yes': try: with transaction.atomic(using=database, savepoint=connection.features.can_rollback_ddl): with connection.cursor() as cursor: for sql in sql_list: cursor.execute(sql) except Exception as e: new_msg = ( "Database %s couldn't be flushed. Possible reasons:\n" " * The database isn't running or isn't configured correctly.\n" " * At least one of the expected database tables doesn't exist.\n" " * The SQL was invalid.\n" "Hint: Look at the output of 'django-admin sqlflush'. " "That's the SQL this command wasn't able to run.\n" "The full error: %s") % (connection.settings_dict['NAME'], e) six.reraise(CommandError, CommandError(new_msg), sys.exc_info()[2]) # Empty sql_list may signify an empty database and post_migrate would then crash if sql_list and not inhibit_post_migrate: # Emit the post migrate signal. This allows individual applications to # respond as if the database had been migrated from scratch. emit_post_migrate_signal(verbosity, interactive, database) else: self.stdout.write("Flush cancelled.\n")
def delete(self): # sort instance collections for model, instances in self.data.items(): self.data[model] = sorted(instances, key=attrgetter("pk")) # if possible, bring the models in an order suitable for databases that # don't support transactions or cannot defer constraint checks until the # end of a transaction. self.sort() # number of objects deleted for each model label deleted_counter = Counter() with transaction.atomic(using=self.using, savepoint=False): # send pre_delete signals for model, obj in self.instances_with_model(): if not model._meta.auto_created: signals.pre_delete.send( sender=model, instance=obj, using=self.using ) # fast deletes for qs in self.fast_deletes: count = qs._raw_delete(using=self.using) deleted_counter[qs.model._meta.label] += count # update fields for model, instances_for_fieldvalues in six.iteritems(self.field_updates): query = sql.UpdateQuery(model) for (field, value), instances in six.iteritems(instances_for_fieldvalues): query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) # reverse instance collections for instances in six.itervalues(self.data): instances.reverse() # delete instances for model, instances in six.iteritems(self.data): query = sql.DeleteQuery(model) pk_list = [obj.pk for obj in instances] count = query.delete_batch(pk_list, self.using) deleted_counter[model._meta.label] += count if not model._meta.auto_created: for obj in instances: signals.post_delete.send( sender=model, instance=obj, using=self.using ) # update collected instances for model, instances_for_fieldvalues in six.iteritems(self.field_updates): for (field, value), instances in six.iteritems(instances_for_fieldvalues): for obj in instances: setattr(obj, field.attname, value) for model, instances in six.iteritems(self.data): for instance in instances: setattr(instance, model._meta.pk.attname, None) return sum(deleted_counter.values()), dict(deleted_counter)
def _clear(self, queryset, bulk): db = router.db_for_write(self.model, instance=self.instance) queryset = queryset.using(db) if bulk: # `QuerySet.update()` is intrinsically atomic. queryset.update(**{self.field.name: None}) else: with transaction.atomic(using=db, savepoint=False): for obj in queryset: setattr(obj, self.field.name, None) obj.save(update_fields=[self.field.name])
def _remove_items(self, source_field_name, target_field_name, *objs): # source_field_name: the PK colname in join table for the source object # target_field_name: the PK colname in join table for the target object # *objs - objects to remove if not objs: return # Check that all the objects are of the right type old_ids = set() for obj in objs: if isinstance(obj, self.model): fk_val = self.target_field.get_foreign_related_value( obj)[0] old_ids.add(fk_val) else: old_ids.add(obj) db = router.db_for_write(self.through, instance=self.instance) with transaction.atomic(using=db, savepoint=False): # Send a signal to the other end if need be. signals.m2m_changed.send( sender=self.through, action="pre_remove", instance=self.instance, reverse=self.reverse, model=self.model, pk_set=old_ids, using=db, ) target_model_qs = super(ManyRelatedManager, self).get_queryset() if target_model_qs._has_filters(): old_vals = target_model_qs.using(db).filter(**{ '%s__in' % self.target_field.target_field.attname: old_ids }) else: old_vals = old_ids filters = self._build_remove_filters(old_vals) self.through._default_manager.using(db).filter( filters).delete() signals.m2m_changed.send( sender=self.through, action="post_remove", instance=self.instance, reverse=self.reverse, model=self.model, pk_set=old_ids, using=db, )
def unapply(self, project_state, schema_editor, collect_sql=False): """ Takes a project_state representing all migrations prior to this one and a schema_editor for a live database and applies the migration in a reverse order. The backwards migration process consists of two phases: 1. The intermediate states from right before the first until right after the last operation inside this migration are preserved. 2. The operations are applied in reverse order using the states recorded in step 1. """ # Construct all the intermediate states we need for a reverse migration to_run = [] new_state = project_state # Phase 1 for operation in self.operations: # If it's irreversible, error out if not operation.reversible: raise IrreversibleError("Operation %s in %s is not reversible" % (operation, self)) # Preserve new state from previous run to not tamper the same state # over all operations new_state = new_state.clone() old_state = new_state.clone() operation.state_forwards(self.app_label, new_state) to_run.insert(0, (operation, old_state, new_state)) # Phase 2 for operation, to_state, from_state in to_run: if collect_sql: schema_editor.collected_sql.append("--") if not operation.reduces_to_sql: schema_editor.collected_sql.append( "-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:" ) schema_editor.collected_sql.append("-- %s" % operation.describe()) schema_editor.collected_sql.append("--") if not operation.reduces_to_sql: continue if not schema_editor.connection.features.can_rollback_ddl and operation.atomic: # We're forcing a transaction on a non-transactional-DDL backend with atomic(schema_editor.connection.alias): operation.database_backwards(self.app_label, schema_editor, from_state, to_state) else: # Normal behaviour operation.database_backwards(self.app_label, schema_editor, from_state, to_state) return project_state
def handle(self, *fixture_labels, **options): self.ignore = options['ignore'] self.using = options['database'] self.app_label = options['app_label'] self.verbosity = options['verbosity'] with transaction.atomic(using=self.using): self.loaddata(fixture_labels) # Close the DB connection -- unless we're still in a transaction. This # is required as a workaround for an edge case in MySQL: if the same # connection is used to create tables, load data, and query, the query # can return incorrect results. See Django #7572, MySQL #37735. if transaction.get_autocommit(self.using): connections[self.using].close()
def add(self, *objs): if not rel.through._meta.auto_created: opts = self.through._meta raise AttributeError( "Cannot use add() on a ManyToManyField which specifies an " "intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)) db = router.db_for_write(self.through, instance=self.instance) with transaction.atomic(using=db, savepoint=False): self._add_items(self.source_field_name, self.target_field_name, *objs) # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table if self.symmetrical: self._add_items(self.target_field_name, self.source_field_name, *objs)
def apply(self, project_state, schema_editor, collect_sql=False): """ Takes a project_state representing all migrations prior to this one and a schema_editor for a live database and applies the migration in a forwards order. Returns the resulting project state for efficient re-use by following Migrations. """ for operation in self.operations: # If this operation cannot be represented as SQL, place a comment # there instead if collect_sql: schema_editor.collected_sql.append("--") if not operation.reduces_to_sql: schema_editor.collected_sql.append( "-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:" ) schema_editor.collected_sql.append("-- %s" % operation.describe()) schema_editor.collected_sql.append("--") if not operation.reduces_to_sql: continue # Save the state before the operation has run old_state = project_state.clone() operation.state_forwards(self.app_label, project_state) # Run the operation atomic_operation = operation.atomic or (self.atomic and operation.atomic is not False) if not schema_editor.atomic_migration and atomic_operation: # Force a transaction on a non-transactional-DDL backend or an # atomic operation inside a non-atomic migration. with atomic(schema_editor.connection.alias): operation.database_forwards(self.app_label, schema_editor, old_state, project_state) else: # Normal behaviour operation.database_forwards(self.app_label, schema_editor, old_state, project_state) return project_state
def set(self, objs, **kwargs): if not rel.through._meta.auto_created: opts = self.through._meta raise AttributeError( "Cannot set values on a ManyToManyField which specifies an " "intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)) # Force evaluation of `objs` in case it's a queryset whose value # could be affected by `manager.clear()`. Refs #19816. objs = tuple(objs) clear = kwargs.pop('clear', False) db = router.db_for_write(self.through, instance=self.instance) with transaction.atomic(using=db, savepoint=False): if clear: self.clear() self.add(*objs) else: old_ids = set( self.using(db).values_list( self.target_field.target_field.attname, flat=True)) new_objs = [] for obj in objs: fk_val = ( self.target_field.get_foreign_related_value(obj)[0] if isinstance(obj, self.model) else obj) if fk_val in old_ids: old_ids.remove(fk_val) else: new_objs.append(obj) self.remove(*old_ids) self.add(*new_objs)
def __enter__(self): self.deferred_sql = [] if self.atomic_migration: self.atomic = atomic(self.connection.alias) self.atomic.__enter__() return self
def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT): timeout = self.get_backend_timeout(timeout) db = router.db_for_write(self.cache_model_class) connection = connections[db] table = connection.ops.quote_name(self._table) with connection.cursor() as cursor: cursor.execute("SELECT COUNT(*) FROM %s" % table) num = cursor.fetchone()[0] now = timezone.now() now = now.replace(microsecond=0) if timeout is None: exp = datetime.max elif settings.USE_TZ: exp = datetime.utcfromtimestamp(timeout) else: exp = datetime.fromtimestamp(timeout) exp = exp.replace(microsecond=0) if num > self._max_entries: self._cull(db, cursor, now) pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) b64encoded = base64.b64encode(pickled) # The DB column is expecting a string, so make sure the value is a # string, not bytes. Refs #19274. if six.PY3: b64encoded = b64encoded.decode('latin1') try: # Note: typecasting for datetimes is needed by some 3rd party # database backends. All core backends work without typecasting, # so be careful about changes here - test suite will NOT pick # regressions. with transaction.atomic(using=db): cursor.execute( "SELECT cache_key, expires FROM %s " "WHERE cache_key = %%s" % table, [key]) result = cursor.fetchone() if result: current_expires = result[1] expression = models.Expression( output_field=models.DateTimeField()) for converter in ( connection.ops.get_db_converters(expression) + expression.get_db_converters(connection)): current_expires = converter( current_expires, expression, connection, {}) exp = connection.ops.adapt_datetimefield_value(exp) if result and (mode == 'set' or (mode == 'add' and current_expires < now)): cursor.execute( "UPDATE %s SET value = %%s, expires = %%s " "WHERE cache_key = %%s" % table, [b64encoded, exp, key]) else: cursor.execute( "INSERT INTO %s (cache_key, value, expires) " "VALUES (%%s, %%s, %%s)" % table, [key, b64encoded, exp]) except DatabaseError: # To be threadsafe, updates/inserts are allowed to fail silently return False else: return True
def create_table(self, database, tablename, dry_run): cache = BaseDatabaseCache(tablename, {}) if not router.allow_migrate_model(database, cache.cache_model_class): return connection = connections[database] if tablename in connection.introspection.table_names(): if self.verbosity > 0: self.stdout.write("Cache table '%s' already exists." % tablename) return fields = ( # "key" is a reserved word in MySQL, so use "cache_key" instead. models.CharField(name='cache_key', max_length=255, unique=True, primary_key=True), models.TextField(name='value'), models.DateTimeField(name='expires', db_index=True), ) table_output = [] index_output = [] qn = connection.ops.quote_name for f in fields: field_output = [ qn(f.name), f.db_type(connection=connection), '%sNULL' % ('NOT ' if not f.null else ''), ] if f.primary_key: field_output.append("PRIMARY KEY") elif f.unique: field_output.append("UNIQUE") if f.db_index: unique = "UNIQUE " if f.unique else "" index_output.append("CREATE %sINDEX %s ON %s (%s);" % (unique, qn('%s_%s' % (tablename, f.name)), qn(tablename), qn(f.name))) table_output.append(" ".join(field_output)) full_statement = ["CREATE TABLE %s (" % qn(tablename)] for i, line in enumerate(table_output): full_statement.append( ' %s%s' % (line, ',' if i < len(table_output) - 1 else '')) full_statement.append(');') full_statement = "\n".join(full_statement) if dry_run: self.stdout.write(full_statement) for statement in index_output: self.stdout.write(statement) return with transaction.atomic( using=database, savepoint=connection.features.can_rollback_ddl): with connection.cursor() as curs: try: curs.execute(full_statement) except DatabaseError as e: raise CommandError( "Cache table '%s' could not be created.\nThe error was: %s." % (tablename, force_text(e))) for statement in index_output: curs.execute(statement) if self.verbosity > 1: self.stdout.write("Cache table '%s' created." % tablename)
def sync_apps(self, connection, app_labels): "Runs the old syncdb-style operation on a list of app_labels." cursor = connection.cursor() try: # Get a list of already installed *models* so that references work right. tables = connection.introspection.table_names(cursor) created_models = set() # Build the manifest of apps and models that are to be synchronized all_models = [ (app_config.label, router.get_migratable_models(app_config, connection.alias, include_auto_created=False)) for app_config in apps.get_app_configs() if app_config.models_module is not None and app_config.label in app_labels ] def model_installed(model): opts = model._meta converter = connection.introspection.table_name_converter # Note that if a model is unmanaged we short-circuit and never try to install it return not ((converter(opts.db_table) in tables) or (opts.auto_created and converter( opts.auto_created._meta.db_table) in tables)) manifest = OrderedDict( (app_name, list(filter(model_installed, model_list))) for app_name, model_list in all_models) # Create the tables for each model if self.verbosity >= 1: self.stdout.write(" Creating tables...\n") with transaction.atomic( using=connection.alias, savepoint=connection.features.can_rollback_ddl): deferred_sql = [] for app_name, model_list in manifest.items(): for model in model_list: if not model._meta.can_migrate(connection): continue if self.verbosity >= 3: self.stdout.write( " Processing %s.%s model\n" % (app_name, model._meta.object_name)) with connection.schema_editor() as editor: if self.verbosity >= 1: self.stdout.write(" Creating table %s\n" % model._meta.db_table) editor.create_model(model) deferred_sql.extend(editor.deferred_sql) editor.deferred_sql = [] created_models.add(model) if self.verbosity >= 1: self.stdout.write(" Running deferred SQL...\n") for statement in deferred_sql: cursor.execute(statement) finally: cursor.close() return created_models
def _add_items(self, source_field_name, target_field_name, *objs): # source_field_name: the PK fieldname in join table for the source object # target_field_name: the PK fieldname in join table for the target object # *objs - objects to add. Either object instances, or primary keys of object instances. # If there aren't any objects, there is nothing to do. from arouse._dj.db.models import Model if objs: new_ids = set() 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"' % (obj, self.instance._state.db, obj._state.db)) fk_val = self.through._meta.get_field( target_field_name).get_foreign_related_value( obj)[0] if fk_val is None: raise ValueError( 'Cannot add "%r": the value for field "%s" is None' % (obj, target_field_name)) new_ids.add(fk_val) elif isinstance(obj, Model): raise TypeError("'%s' instance expected, got %r" % (self.model._meta.object_name, obj)) else: new_ids.add(obj) db = router.db_for_write(self.through, instance=self.instance) vals = (self.through._default_manager.using(db).values_list( target_field_name, flat=True).filter( **{ source_field_name: self.related_val[0], '%s__in' % target_field_name: new_ids, })) new_ids = new_ids - set(vals) with transaction.atomic(using=db, savepoint=False): if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are inserting the # duplicate data row for symmetrical reverse entries. signals.m2m_changed.send( sender=self.through, action='pre_add', instance=self.instance, reverse=self.reverse, model=self.model, pk_set=new_ids, using=db, ) # Add the ones that aren't there already self.through._default_manager.using(db).bulk_create([ self.through( **{ '%s_id' % source_field_name: self.related_val[0], '%s_id' % target_field_name: obj_id, }) for obj_id in new_ids ]) if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are inserting the # duplicate data row for symmetrical reverse entries. signals.m2m_changed.send( sender=self.through, action='post_add', instance=self.instance, reverse=self.reverse, model=self.model, pk_set=new_ids, using=db, )