I recently increased the default permissions for all my Django Views. In order for the users to have some default permissions I add new users to a default Group that has mainly viewing permissions. This is done via a post_save
signal.
MODELS_VIEW = [
"time entry",
"time entry version",
"project",
"client",
"user",
]
MODELS_CHANGE = ["time entry version", "user"]
MODELS_CREATE = ["time entry version"]
MODELS_DELETE = ["time entry version"]
def _add_permissions_to_group(group, perm_type: str, models: List[str]) -> None:
"""Add the permission of the `perm_type` to the given models."""
from django.contrib.auth.models import Permission
for model in models:
perm_name = f"Can {perm_type} {model}"
permission = Permission.objects.get(name=perm_name)
group.permissions.add(permission)
def _add_view_permissions_to_group(group) -> None:
_add_permissions_to_group(group, perm_type="view", models=MODELS_VIEW)
def _add_create_permissions_to_group(group) -> None:
_add_permissions_to_group(group, perm_type="add", models=MODELS_CREATE)
def _add_change_permissions_to_group(group) -> None:
_add_permissions_to_group(group, perm_type="change", models=MODELS_CHANGE)
def _add_delete_permissions_to_group(group) -> None:
_add_permissions_to_group(group, perm_type="delete", models=MODELS_DELETE)
def _get_default_user_group_with_permissions():
"""Get or create the default UserGroup and add all standard permissions."""
from django.contrib.auth.models import Group
logger.debug("Getting default group for user")
group, _ = Group.objects.get_or_create(name="default")
# Add the standard permissions for users
_add_view_permissions_to_group(group)
_add_create_permissions_to_group(group)
_add_change_permissions_to_group(group)
_add_delete_permissions_to_group(group)
return group
class UsersConfig(AppConfig):
"""Config for Users"""
default_auto_field = "django.db.models.BigAutoField"
name = "users"
def ready(self):
"""Add the user to the default UserGroup."""
logger.debug("User-config: App ready")
def _add_to_default_group(sender, **kwargs):
"""Add the user to the default group on creation."""
if kwargs["created"]:
group = _get_default_user_group_with_permissions()
user = kwargs["instance"]
logger.debug(f"Adding user {user} to group {group}")
user.groups.add(group)
post_save.connect(_add_to_default_group, sender=settings.AUTH_USER_MODEL)
I have a fixture in my tests that creates a user and the following test using that fixture:
@pytest.fixture()
def user_max(django_user_model):
"""Create a normal user called max."""
logger.debug("Creating user max")
user = django_user_model.objects.create_user(
username="max",
email="[email protected]",
password="password",
)
logger.debug(f"Permissions after creating: {user.get_all_permissions()}")
return user
def test_default_permissions(django_user_model):
"""Test default permissions."""
print("Group default permissions: ", Group.objects.get(name="default").permissions.all())
print(
"Permissions of user_max: ",
django_user_model.objects.get(id=django_user_model.objects.get(username="max").id).get_all_permissions(),
)
assert django_user_model.objects.get(username="max").id).get_all_permissions().count() > 5
When running the test I receive the debug messages from ready()
-function, _get_default_user_group_with_permissions()
-function as well as the test:
2023-04-03 11:13:05,685 - conftest - DEBUG - Creating user max
2023-04-03 11:13:05,900 - users.apps - DEBUG - Getting default group for user
2023-04-03 11:13:05,978 - users.apps - DEBUG - Adding user max to group default
2023-04-03 11:13:05,995 - conftest - DEBUG - Permissions after creating: {'projects.view_task', 'projects.view_project', 'bookings.delete_timeentryversion', 'projects.view_projecttype', 'bookings.view_timeentry', 'bookings.view_timeentryversion', 'clients.view_client', 'users.view_resource', 'projects.view_taskgroup', 'users.change_user', 'users.view_team', 'bookings.change_timeentryversion', 'bookings.add_timeentryversion', 'users.view_user'}
Now to my issue: When running the same code on Gitlab CI/CD this test errors because no default
-Group is created and there are neither debug messages from the ready()
-function nor the _get_default_user_group_with_permissions()
-function:
2023-04-03 10:31:07,963 - conftest - DEBUG - Creating user max
2023-04-03 10:31:08,293 - conftest - DEBUG - Permissions after creating: set()
How come that the code from ready()
is not executed in the CI/CD Pipeline?
I finally found the issue. According to the docs:
Note also that Django stores signal handlers as weak references by default, so if your handler is a local function, it may be garbage collected. To prevent this, pass
weak=False
when you call the signal’sconnect()
.
Apparently garbage collection on the CI/CD pipeline was removing the reference with the default value weak=True
. It worked when setting weak=False
in the post_save:
post_save.connect(_add_to_default_group, sender=settings.AUTH_USER_MODEL, weak=False)