Skip to content

Closes #181: Introduce a configuration parameter to define the default schema name #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ exempt_models = (

---

## `main_schema`

Default: `"public"`

The name of the main (primary) PostgreSQL schema. (Use the `\dn` command in the PostgreSQL CLI to list all schemas.)

---

## `max_working_branches`

Default: None
Expand All @@ -46,7 +54,7 @@ The maximum total number of branches that can exist simultaneously, including me

## `schema_prefix`

Default: `branch_`
Default: `"branch_"`

The string to prefix to the unique branch ID when provisioning the PostgreSQL schema for a branch. Per [the PostgreSQL documentation](https://www.postgresql.org/docs/16/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS), this string must begin with a letter or underscore.

Expand Down
3 changes: 3 additions & 0 deletions netbox_branching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class AppConfig(PluginConfig):
# Models from other plugins which should be excluded from branching support
'exempt_models': [],

# The name of the main schema
'main_schema': 'public',

# This string is prefixed to the name of each new branch schema during provisioning
'schema_prefix': 'branch_',

Expand Down
3 changes: 0 additions & 3 deletions netbox_branching/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# Name of the main (non-branch) PostgreSQL schema
MAIN_SCHEMA = 'public'

# HTTP cookie
COOKIE_NAME = 'active_branch'

Expand Down
2 changes: 1 addition & 1 deletion netbox_branching/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ def db_for_write(self, model, **hints):
return self._get_db(model, **hints)

def allow_relation(self, obj1, obj2, **hints):
# Permit relations from the branch schema to the main (public) schema
# Permit relations from the branch schema to the main schema
return True
7 changes: 4 additions & 3 deletions netbox_branching/models/branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ def provision(self, user):
"""
logger = logging.getLogger('netbox_branching.branch.provision')
logger.info(f'Provisioning branch {self} ({self.schema_name})')
main_schema = get_plugin_config('netbox_branching', 'main_schema')

# Emit pre-provision signal
pre_provision.send(sender=self.__class__, branch=self, user=user)
Expand Down Expand Up @@ -572,21 +573,21 @@ def provision(self, user):
# Create an empty copy of the global change log. Share the ID sequence from the main table to avoid
# reusing change record IDs.
table = ObjectChange_._meta.db_table
main_table = f'public.{table}'
main_table = f'{main_schema}.{table}'
schema_table = f'{schema}.{table}'
logger.debug(f'Creating table {schema_table}')
cursor.execute(
f"CREATE TABLE {schema_table} ( LIKE {main_table} INCLUDING INDEXES )"
)
# Set the default value for the ID column to the sequence associated with the source table
sequence_name = f'public.{table}_id_seq'
sequence_name = f'{main_schema}.{table}_id_seq'
cursor.execute(
f"ALTER TABLE {schema_table} ALTER COLUMN id SET DEFAULT nextval(%s)", [sequence_name]
)

# Replicate relevant tables from the main schema
for table in get_tables_to_replicate():
main_table = f'public.{table}'
main_table = f'{main_schema}.{table}'
schema_table = f'{schema}.{table}'
logger.debug(f'Creating table {schema_table}')
# Create the table in the new schema
Expand Down
5 changes: 3 additions & 2 deletions netbox_branching/tests/test_branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from django.test import TransactionTestCase, override_settings
from django.utils import timezone

from netbox.plugins import get_plugin_config
from netbox_branching.choices import BranchStatusChoices
from netbox_branching.constants import MAIN_SCHEMA
from netbox_branching.models import Branch
from netbox_branching.utilities import get_tables_to_replicate
from .utils import fetchall, fetchone
Expand All @@ -21,6 +21,7 @@ def test_create_branch(self):
branch.save(provision=False)
branch.provision(user=None)

main_schema = get_plugin_config('netbox_branching', 'main_schema')
tables_to_replicate = get_tables_to_replicate()

with connection.cursor() as cursor:
Expand All @@ -44,7 +45,7 @@ def test_create_branch(self):

# Check that object counts match the main schema for each table
for table_name in tables_to_replicate:
cursor.execute(f"SELECT COUNT(id) FROM {MAIN_SCHEMA}.{table_name}")
cursor.execute(f"SELECT COUNT(id) FROM {main_schema}.{table_name}")
main_count = fetchone(cursor).count
cursor.execute(f"SELECT COUNT(id) FROM {branch.schema_name}.{table_name}")
branch_count = fetchone(cursor).count
Expand Down
7 changes: 6 additions & 1 deletion netbox_branching/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import defaultdict
from contextlib import contextmanager, nullcontext
from dataclasses import dataclass
from functools import cached_property

from django.contrib import messages
from django.db.models import ForeignKey, ManyToManyField
Expand Down Expand Up @@ -39,14 +40,18 @@ class DynamicSchemaDict(dict):
Behaves like a normal dictionary, except for keys beginning with "schema_". Any lookup for
"schema_*" will return the default configuration extended to include the search_path option.
"""
@cached_property
def main_schema(self):
return get_plugin_config('netbox_branching', 'main_schema')

def __getitem__(self, item):
if type(item) is str and item.startswith('schema_'):
if schema := item.removeprefix('schema_'):
default_config = super().__getitem__('default')
return {
**default_config,
'OPTIONS': {
'options': f'-c search_path={schema},public'
'options': f'-c search_path={schema},{self.main_schema}'
}
}
return super().__getitem__(item)
Expand Down