"""Management command to reset the database and migrations."""from__future__importannotationsimportosfrompathlibimportPathfromtypingimportTYPE_CHECKINGimportpsycopgfromdjango.confimportsettingsfromdjango.contrib.auth.modelsimportUserfromdjango.core.managementimportBaseCommand,call_commandifTYPE_CHECKING:fromdjango.core.management.baseimportCommandParser
[docs]classCommand(BaseCommand):"""Management command to reset the database and migrations."""
[docs]help=('Resets the database by deleting current version migrations, dropping ''the database, then running makemigrations and migrate. ''Creates a superuser unless --no-user is provided.')
[docs]defadd_arguments(self,parser:CommandParser)->None:"""Adds command arguments/options."""parser.add_argument('--add',action='store_true',help='Add demo domains and devices after reset.')parser.add_argument('--force',action='store_true',help='Force database reset without prompt.')parser.add_argument('--no-user',action='store_true',help='Skip superuser creation.')parser.add_argument('--initial-migrations',action='store_true',help='DO NOT USE! Breaks the DB of existing installations! Remove all migrations and create initial.')
[docs]defhandle(self,*_args:tuple[str],**options:dict[str,str])->None:"""Executes the command."""# Confirm database resetifnotoptions.get('force'):self.stdout.write('WARNING: This will delete the database and all migration files.')answer=input('Are you sure you want to continue? (y/N): ').strip().lower()ifanswernotin('y','yes'):self.stdout.write('Aborted.')return# Remove migration filesself.stdout.write('Removing migration files...')keep_established=notoptions.get('initial_migrations',False)base_path=Path(__file__).resolve().parent.parent.parent.parentself._remove_migration_files(base_path,keep_established=keep_established)# Reset database depending on engineengine=settings.DATABASES['default']['ENGINE']ifengine=='django.db.backends.sqlite3':self._reset_sqlite(base_path)elifengine=='django.db.backends.postgresql':self._reset_postgresql()else:self.stderr.write(f'Database engine {engine} is not supported by this command.')return# Run migrationsmigration_name='tp_v'+settings.APP_VERSION.replace('.','_')ifoptions.get('initial_migrations'):migration_name='initial'self.stdout.write('Running makemigrations...')call_command('makemigrations',name=migration_name)self.stdout.write('Running migrate...')call_command('migrate')# Create superuser if neededifnotoptions.get('no_user'):self.stdout.write('Creating superuser...')call_command('createsuperuser',interactive=False,username='admin',email='')user=User.objects.get(username='admin')user.set_password('testing321')user.save()self.stdout.write('Superuser created:')self.stdout.write(' Username: admin')self.stdout.write(' Password: testing321')self.stdout.write('Database reset complete.')# Add demo domains and devicesifoptions.get('add'):call_command('add_domains_and_devices')
[docs]def_remove_migration_files(self,base_path:Path,*_args:tuple,keep_established:bool)->None:"""Removes all Django migration files."""current_version_py_id=settings.APP_VERSION.replace('.','_')forroot,_dirs,filesinos.walk(base_path):if'migrations'inroot:forfileinfiles:if(file.endswith('.py')andfile!='__init__.py')orfile.endswith('.pyc'):if(keep_establishedand(('_tp_v'infileandcurrent_version_py_idnotinfile)or'0001_initial'infile)):continuetry:Path(Path(root)/file).unlink()exceptExceptionase:# noqa: BLE001self.stderr.write(f'Error removing {file}: {e}')
[docs]def_reset_sqlite(self,base_path:Path)->None:"""Deletes the SQLite database file."""db_path=base_path/'db.sqlite3'ifdb_path.exists():try:Path(db_path).unlink()self.stdout.write('SQLite database file deleted.')exceptExceptionase:self.stderr.write(f'Error deleting SQLite database file: {e}')self.stderr.write('Is it still open in another program?')raiseelse:self.stdout.write('No SQLite database file found.')
[docs]def_reset_postgresql(self)->None:"""Drops and recreates the PostgreSQL database."""db_config=settings.DATABASES['default']db_name=db_config['NAME']db_user=db_config['USER']db_password=db_config['PASSWORD']db_host=db_config['HOST']db_port=db_config['PORT']self.stdout.write(f"Resetting PostgreSQL database '{db_name}'...")try:# Connect to the default 'postgres' database to issue drop/create commandsconn=psycopg.connect(dbname='postgres',user=db_user,password=db_password,host=db_host,port=db_port,autocommit=True,)cur=conn.cursor()# Terminate existing connections to the target databaseself.stdout.write('Terminating existing connections...')cur.execute(""" SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = %s AND pid <> pg_backend_pid(); """,(db_name,),)# Drop the database if it existsself.stdout.write('Dropping database...')cur.execute(f'DROP DATABASE IF EXISTS {db_name};')# Recreate the database owned by the specified userself.stdout.write('Creating database...')cur.execute(f'CREATE DATABASE {db_name} OWNER {db_user};')cur.close()conn.close()self.stdout.write('PostgreSQL database reset successfully.')exceptExceptionase:# noqa: BLE001self.stderr.write(f'Error resetting PostgreSQL database: {e}')