Skip to content

feat(admin): add organization update API and schemas#1338

Closed
sachin9919 wants to merge 11 commits into
DalgoT4D:mainfrom
sachin9919:feat/admin-org-update-api
Closed

feat(admin): add organization update API and schemas#1338
sachin9919 wants to merge 11 commits into
DalgoT4D:mainfrom
sachin9919:feat/admin-org-update-api

Conversation

@sachin9919

@sachin9919 sachin9919 commented May 9, 2026

Copy link
Copy Markdown

Overview

This PR extends the Admin Organization APIs by adding support for updating organizations and introducing reusable admin schemas for request validation.

Features

  • Added PUT /api/admin/v1/organizations/{id} endpoint
  • Added reusable admin organization schemas
  • Added structured validation for update requests
  • Added permission-protected admin routes
  • Added consistent success response structure

Technical Details

  • Implemented using Django Ninja
  • Integrated with existing Org model
  • Added schema-based validation for admin APIs
  • Added admin router integration
  • Tested locally using Thunder Client

API Endpoint

Update Organization

PUT /api/admin/v1/organizations/{id}

Example request:

{
  "name": "Updated Org",
  "slug": "updated-org"
}

Impact

This improves the Admin Portal backend foundation by enabling organization update workflows and reusable schema-driven validation for future admin APIs.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added admin-only organization management endpoints: administrators can now list all organizations with optional name filtering, view individual organization details, and update organization information.
    • Organization updates are automatically tracked and logged for audit purposes with full change history.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@sachin9919 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 7 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1286bc5a-cdc3-4f94-aa7b-a9e5d1369598

📥 Commits

Reviewing files that changed from the base of the PR and between f904045 and c65aa05.

📒 Files selected for processing (1)
  • ddpui/migrations/0160_alter_syncstats_sync_type_adminauditlog.py

Walkthrough

This PR adds admin organization management functionality with three protected endpoints for listing, fetching, and updating organizations. It includes a request schema with validation, API endpoint implementations with slug uniqueness enforcement and audit logging, and router registration under the admin path.

Changes

Admin Organization Management API

Layer / File(s) Summary
Organization Update Schema
ddpui/schemas/admin_org_schema.py
UpdateOrganizationSchema defines required name and slug fields with non-empty constraints and a validator that strips whitespace and rejects blank values.
Model Updates
ddpui/models/airbyte.py, ddpui/models/__init__.py
SyncStats.sync_type adds explicit max_length=20. AdminAuditLog is exported from ddpui.models package namespace.
Admin API Implementation
ddpui/api/admin_portal/organizations_api.py
New module with admin_org_router provides three admin endpoints: list organizations with optional name filtering returning {count, data} of {id, name, slug}; fetch single organization by org_id returning {id, name, slug} or 404; update organization by org_id with slug uniqueness validation (400 on conflict), organization persistence, and AdminAuditLog record creation with old_data and new_data.
Router Registration
ddpui/routes.py
Imports admin_org_router, assigns documentation tag, and mounts router at /api/admin/ on main src_api.

Sequence Diagram

sequenceDiagram
  participant Admin as Admin Client
  participant API as Admin API
  participant DB as Database
  participant Log as AuditLog
  Admin->>API: GET /api/admin/organizations?name=...
  API->>DB: Query organizations (filtered)
  DB-->>API: Organization list
  API-->>Admin: {count, data}
  Admin->>API: GET /api/admin/organizations/{id}
  API->>DB: Fetch organization
  DB-->>API: Organization or empty
  API-->>Admin: {id, name, slug} or 404
  Admin->>API: PUT /api/admin/organizations/{id} (payload)
  API->>DB: Load organization by id
  DB-->>API: Organization or empty
  API->>DB: Check for existing org with slug
  DB-->>API: Other org with slug or empty
  API->>DB: Update name and slug
  DB-->>API: Saved organization
  API->>Log: Create AdminAuditLog record
  Log-->>API: Audit record created
  API-->>Admin: {message, id, name, slug}
Loading

🎯 2 (Simple) | ⏱️ ~8 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding organization update API endpoints and new admin organization schemas for validation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ddpui/api/admin_portal/organizations_api.py`:
- Around line 64-75: The slug uniqueness check is race-prone because it lacks a
DB constraint and the exists() pre-check is separate from the save; add a
uniqueness constraint to the Org model (Org.slug -> unique=True or a
UniqueConstraint in ddpui/models/org.py and generate a migration), then in
organizations_api.py wrap the write in a transaction.atomic block around
org.save() and catch django.db.IntegrityError (or DatabaseError) to translate it
into HttpError(409, "Slug already exists"); you can keep the existing_slug
pre-check for early feedback but must still handle IntegrityError on org.save()
to ensure atomicity and return 409 on conflict.

In `@ddpui/schemas/admin_org_schema.py`:
- Around line 7-8: The schema currently allows empty or whitespace-only strings
for the name and slug fields; update the AdminOrgSchema (or the schema class
that defines name and slug) to enforce non-blank values by either changing the
field types to Pydantic constrained strings (e.g., use
constr(strip_whitespace=True, min_length=1) for name and slug) or adding a
`@validator` for ("name","slug") that strips the value and raises ValueError if
the result is empty, ensuring both fields cannot be blank/whitespace-only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4471907f-6ea2-41f6-81fb-39b87ed0abc3

📥 Commits

Reviewing files that changed from the base of the PR and between 43b75f8 and b9e31f2.

📒 Files selected for processing (4)
  • ddpui/api/admin_portal/organizations_api.py
  • ddpui/models/airbyte.py
  • ddpui/routes.py
  • ddpui/schemas/admin_org_schema.py

Comment on lines +64 to +75
existing_slug = (
Org.objects.filter(slug=payload.slug)
.exclude(id=org_id)
.exists()
)

if existing_slug:
raise HttpError(400, "Slug already exists")

org.name = payload.name
org.slug = payload.slug
org.save()

@coderabbitai coderabbitai Bot May 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Org model field definitions (name/slug) =="
fd -i 'org.py' | xargs -r rg -n -C2 'class\s+Org|name\s*=|slug\s*='

echo
echo "== Uniqueness constraints involving slug =="
rg -n -C2 'slug|UniqueConstraint|unique_together|unique\s*=\s*True' --type py

Repository: DalgoT4D/DDP_backend

Length of output: 50377


Slug uniqueness check is non-atomic and race-prone.

The Org.slug field has no database-level uniqueness constraint (unique=True or UniqueConstraint). The pre-check with .exists() and the write with .save() are separate operations, allowing concurrent requests to both pass validation and create duplicate slugs.

Add a database constraint and wrap the write in atomic transaction handling with IntegrityError catching. Also update the HTTP status code to 409 (Conflict) to properly reflect the error semantics.

💡 Suggested hardening
+from django.db import IntegrityError, transaction
 from ninja import Router
 from ninja.errors import HttpError
@@
     existing_slug = (
         Org.objects.filter(slug=payload.slug)
         .exclude(id=org_id)
         .exists()
     )
 
     if existing_slug:
-        raise HttpError(400, "Slug already exists")
+        raise HttpError(409, "Slug already exists")
 
     org.name = payload.name
     org.slug = payload.slug
-    org.save()
+    try:
+        with transaction.atomic():
+            org.save(update_fields=["name", "slug"])
+    except IntegrityError:
+        raise HttpError(409, "Slug already exists")

Also add to the Org model (ddpui/models/org.py):

 class Org(models.Model):
     name = models.CharField(max_length=50)
-    slug = models.CharField(max_length=20, null=True)
+    slug = models.CharField(max_length=20, null=True, unique=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ddpui/api/admin_portal/organizations_api.py` around lines 64 - 75, The slug
uniqueness check is race-prone because it lacks a DB constraint and the exists()
pre-check is separate from the save; add a uniqueness constraint to the Org
model (Org.slug -> unique=True or a UniqueConstraint in ddpui/models/org.py and
generate a migration), then in organizations_api.py wrap the write in a
transaction.atomic block around org.save() and catch django.db.IntegrityError
(or DatabaseError) to translate it into HttpError(409, "Slug already exists");
you can keep the existing_slug pre-check for early feedback but must still
handle IntegrityError on org.save() to ensure atomicity and return 409 on
conflict.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. I’ve noted the race-condition concern around slug uniqueness validation. Since this PR focuses on introducing the initial admin update APIs, I’d prefer to keep database-level uniqueness constraints and transactional handling as a follow-up improvement to avoid expanding the migration scope significantly in this change set.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sachin9919, understood — thanks for the context. Deferring database-level constraints and transactional handling to a follow-up is a reasonable call to keep this PR focused.

Would you like me to open a GitHub issue to track the follow-up work? It would cover:

  1. Adding unique=True (or a UniqueConstraint) to Org.slug along with the corresponding migration.
  2. Wrapping org.save() in transaction.atomic() and catching IntegrityError to return HTTP 409 on slug conflicts in the update endpoint.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the schema validation to reject whitespace-only values for name and slug using field_validator, ensuring inputs are properly stripped and validated before update operations.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -i 'admin_org_schema.py' | xargs -r cat -n

Length of output: 683


@sachin9919, the implementation looks solid. A few notes from reviewing the updated file:

  1. Correct layeringField(..., min_length=1) rejects empty strings at Pydantic's field level, and must_not_be_blank then catches whitespace-only inputs (e.g. " "). The two guards complement each other well.

  2. Silent normalisation — because the validator returns value.strip(), inputs like " admin " will be silently saved as "admin". This is usually desirable, but worth being aware of (and potentially documenting) so callers know the API normalises values rather than rejecting them.

  3. Classmethod pattern@field_validator + @classmethod is the correct Pydantic v2 pattern; no issues there.


The race-condition / atomicity concern from the earlier review thread is still tracked as a follow-up. Would you still like me to open a GitHub issue to track:

  • Adding unique=True to Org.slug with a migration.
  • Wrapping org.save() in transaction.atomic() and catching IntegrityErrorHTTP 409?

Comment thread ddpui/schemas/admin_org_schema.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ddpui/models/admin_audit_log.py`:
- Around line 14-17: Change the org ForeignKey on the AdminAuditLog model from
on_delete=models.CASCADE to on_delete=models.SET_NULL and add null=True,
blank=True so audit records persist when an Org is deleted; update the
AdminAuditLog.__str__ method to guard against self.org being None (e.g., check
if self.org is None and use a fallback string or "deleted-org" instead of
accessing self.org.slug directly) to avoid AttributeError after an Org is
removed.
- Around line 27-30: The created_at DateTimeField in the model (created_at =
models.DateTimeField(...)) is using the undocumented auto_created=True; change
it to use auto_now_add=True so the timestamp is set once on creation, remove the
explicit default=timezone.now (not needed with auto_now_add), and add
editable=False to prevent edits; update the created_at field definition
accordingly in the admin_audit_log model.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 675ce16e-1b97-4a57-93ea-2c048a888184

📥 Commits

Reviewing files that changed from the base of the PR and between 3d4c486 and a954f11.

📒 Files selected for processing (5)
  • ddpui/api/admin_portal/organizations_api.py
  • ddpui/migrations/0160_alter_syncstats_sync_type_adminauditlog.py
  • ddpui/models/__init__.py
  • ddpui/models/admin_audit_log.py
  • ddpui/schemas/admin_org_schema.py
✅ Files skipped from review due to trivial changes (1)
  • ddpui/models/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • ddpui/api/admin_portal/organizations_api.py

Comment thread ddpui/models/admin_audit_log.py Outdated
Comment on lines +14 to +17
org = models.ForeignKey(
Org,
on_delete=models.CASCADE,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

cat -n ddpui/models/admin_audit_log.py

Repository: DalgoT4D/DDP_backend

Length of output: 1104


🏁 Script executed:

# Search for AdminAuditLog usage
rg "AdminAuditLog" --type py -B 2 -A 2

# Look for migrations related to this model
find . -path "*/migrations/*" -name "*.py" -exec grep -l "AdminAuditLog" {} \;

Repository: DalgoT4D/DDP_backend

Length of output: 2128


🏁 Script executed:

# Check the migration
cat -n ddpui/migrations/0160_alter_syncstats_sync_type_adminauditlog.py | head -80

# Check the usage in organizations_api.py
rg "AdminAuditLog" ddpui/api/admin_portal/organizations_api.py -B 5 -A 10

Repository: DalgoT4D/DDP_backend

Length of output: 2815


🏁 Script executed:

# Search for org deletion logic
rg "\.delete\(\)" --type py -B 3 -A 3 | grep -A 5 -B 5 "org" | head -50

# Search for audit-related comments or requirements
rg "audit|compliance|retention" --type py -i | head -20

# Check if Org model has CASCADE dependencies elsewhere
rg "on_delete=models\.CASCADE" --type py | grep -i org

Repository: DalgoT4D/DDP_backend

Length of output: 8626


Use SET_NULL instead of CASCADE to preserve audit logs when organizations are deleted

Audit logs should survive organization deletion to maintain auditability and compliance. Currently, deleting an org cascades and removes all its audit records. Change on_delete=models.CASCADE to on_delete=models.SET_NULL with null=True, blank=True.

Additionally, update the __str__ method to handle self.org is None, as it currently accesses self.org.slug unconditionally and will fail with AttributeError if the org is deleted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ddpui/models/admin_audit_log.py` around lines 14 - 17, Change the org
ForeignKey on the AdminAuditLog model from on_delete=models.CASCADE to
on_delete=models.SET_NULL and add null=True, blank=True so audit records persist
when an Org is deleted; update the AdminAuditLog.__str__ method to guard against
self.org being None (e.g., check if self.org is None and use a fallback string
or "deleted-org" instead of accessing self.org.slug directly) to avoid
AttributeError after an Org is removed.

Comment thread ddpui/models/admin_audit_log.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants