feat(logs): add workspace activity log api

This commit is contained in:
2026-04-28 16:42:37 +03:30
parent c8a118788b
commit 71924ce6fb
32 changed files with 1118 additions and 122 deletions

View File

@@ -1,30 +1,30 @@
# Generated by Django 5.2.12 on 2026-04-26 05:53
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workspaces', '0002_workspaceuserrate'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveIndex(
model_name='workspaceuserrate',
name='workspaceuserrate_id_idx',
),
migrations.AlterField(
model_name='workspaceuserrate',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='workspaceuserrate',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
]
# Generated by Django 5.2.12 on 2026-04-26 05:53
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workspaces', '0002_workspaceuserrate'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveIndex(
model_name='workspaceuserrate',
name='workspaceuserrate_id_idx',
),
migrations.AlterField(
model_name='workspaceuserrate',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='workspaceuserrate',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -1,30 +1,30 @@
# Generated by Django 5.2.12 on 2026-04-26 06:25
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workspaces', '0004_priceunit'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveIndex(
model_name='priceunit',
name='priceunit_id_idx',
),
migrations.AlterField(
model_name='priceunit',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='priceunit',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
]
# Generated by Django 5.2.12 on 2026-04-26 06:25
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workspaces', '0004_priceunit'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveIndex(
model_name='priceunit',
name='priceunit_id_idx',
),
migrations.AlterField(
model_name='priceunit',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='priceunit',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(app_label)s_%(class)s_set', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -1,6 +1,12 @@
from django.contrib.auth import get_user_model
from django.db import models
from apps.logs.services import build_workspace_log_metadata
from apps.logs.services.constants import (
SECTION_RATES,
SECTION_WORKSPACE,
SECTION_WORKSPACE_MEMBERS,
)
from core.models.base import BaseModel
User = get_user_model()
@@ -26,6 +32,15 @@ class Workspace(BaseModel):
def __str__(self):
return self.name
def get_additional_data(self):
return build_workspace_log_metadata(
section=SECTION_WORKSPACE,
workspace_id=self.id,
target_id=self.id,
target_label=self.name,
extra={"owner_id": str(self.owner_id)},
)
@property
def members(self):
return User.objects.filter(
@@ -77,6 +92,21 @@ class WorkspaceMembership(BaseModel):
def __str__(self):
return f"{self.user} @ {self.workspace}"
def get_additional_data(self):
return build_workspace_log_metadata(
section=SECTION_WORKSPACE_MEMBERS,
workspace_id=self.workspace_id,
target_id=self.id,
target_label=self.user.full_name or self.user.mobile,
extra={
"member_user_id": str(self.user_id),
"role": self.role,
"canonical_owner_membership": (
self.role == self.Role.OWNER and self.user_id == self.workspace.owner_id
),
},
)
class PriceUnit(BaseModel):
code = models.CharField(max_length=8, unique=True)
@@ -130,3 +160,15 @@ class WorkspaceUserRate(BaseModel):
models.Index(fields=["workspace"], name="wur_workspace_idx"),
models.Index(fields=["user"], name="wur_user_idx"),
]
def get_additional_data(self):
return build_workspace_log_metadata(
section=SECTION_RATES,
workspace_id=self.workspace_id,
target_id=self.id,
target_label=self.user.full_name or self.user.mobile,
extra={
"rate_user_id": str(self.user_id),
"currency": self.currency,
},
)

View File

@@ -20,6 +20,7 @@ from apps.workspaces.services.permissions import (
TIME_ENTRIES_VIEW_OWN,
WORKSPACE_DELETE,
WORKSPACE_EDIT,
WORKSPACE_LOGS_VIEW,
WORKSPACE_MEMBERS_ADD,
WORKSPACE_MEMBERS_CHANGE_ROLE,
WORKSPACE_MEMBERS_REMOVE,
@@ -43,6 +44,7 @@ __all__ = [
"WORKSPACE_VIEW",
"WORKSPACE_EDIT",
"WORKSPACE_DELETE",
"WORKSPACE_LOGS_VIEW",
"WORKSPACE_MEMBERS_VIEW",
"WORKSPACE_MEMBERS_ADD",
"WORKSPACE_MEMBERS_REMOVE",

View File

@@ -7,6 +7,7 @@ from apps.workspaces.models import Workspace, WorkspaceMembership
WORKSPACE_VIEW = "workspace.view"
WORKSPACE_EDIT = "workspace.edit"
WORKSPACE_DELETE = "workspace.delete"
WORKSPACE_LOGS_VIEW = "workspace.logs.view"
WORKSPACE_MEMBERS_VIEW = "workspace.members.view"
WORKSPACE_MEMBERS_ADD = "workspace.members.add"
WORKSPACE_MEMBERS_REMOVE = "workspace.members.remove"
@@ -45,6 +46,7 @@ WORKSPACE_ROLE_CAPABILITIES = {
WORKSPACE_VIEW,
WORKSPACE_EDIT,
WORKSPACE_DELETE,
WORKSPACE_LOGS_VIEW,
WORKSPACE_MEMBERS_VIEW,
WORKSPACE_MEMBERS_ADD,
WORKSPACE_MEMBERS_REMOVE,
@@ -72,6 +74,7 @@ WORKSPACE_ROLE_CAPABILITIES = {
WorkspaceMembership.Role.ADMIN: {
WORKSPACE_VIEW,
WORKSPACE_EDIT,
WORKSPACE_LOGS_VIEW,
WORKSPACE_MEMBERS_VIEW,
WORKSPACE_MEMBERS_ADD,
WORKSPACE_MEMBERS_REMOVE,