Skip to content

Add Organization and OrganizationRole models to studio#5953

Open
yasinelmi wants to merge 1 commit into
learningequality:unstablefrom
yasinelmi:feature_organization_model
Open

Add Organization and OrganizationRole models to studio#5953
yasinelmi wants to merge 1 commit into
learningequality:unstablefrom
yasinelmi:feature_organization_model

Conversation

@yasinelmi
Copy link
Copy Markdown

Summary

Added two new models for organizational structure and role-based access management.

Organization Model

Represents organizations that manage channels

Fields: name, description, tagline, website, email, thumbnail, public, deleted
Foreign keys to Channel and OrganizationRole
Includes preferences and settings (JSON)

OrganizationRole Model (Through Model)

Manages User-Organization relationship with role-based access

Fields: role, description, permissions (JSON), status, joined_at, invitation_accepted_at, invited_by
Status choices: active, inactive, pending, suspended, declined
Helper methods: accept_invitation(), decline_invitation()
Unique constraint: one role per user per organization

AI usage

Used AI to generate models but they were carefully reviewed

@learning-equality-bot
Copy link
Copy Markdown

👋 Hi @yasinelmi, thanks for contributing!

For the review process to begin, please verify that the following is satisfied:

  • Contribution is aligned with our contributing guidelines

  • Pull request description has correctly filled AI usage section & follows our AI guidance:

    AI guidance

    State explicitly whether you didn't use or used AI & how.

    If you used it, ensure that the PR is aligned with Using AI as well as our DEEP framework. DEEP asks you:

    • Disclose — Be open about when you've used AI for support.
    • Engage critically — Question what is generated. Review code for correctness and unnecessary complexity.
    • Edit — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.
    • Process sharing — Explain how you used the AI so others can learn.

    Examples of good disclosures:

    "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."

    "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."

Also check that issue requirements are satisfied & you ran pre-commit locally.

Pull requests that don't follow the guidelines will be closed.

Reviewer assignment can take up to 2 weeks.

@yasinelmi yasinelmi changed the title Add Orgnization and OrganizationRole models to studio Add Organization and OrganizationRole models to studio Jun 1, 2026
Copy link
Copy Markdown
Member

@bjester bjester left a comment

Choose a reason for hiding this comment

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

Overall, the foundational pieces are present, but I have some questions on the use cases of some fields. My requested changes are mostly around indices, but I think the area most in need of attention is the invitation architecture. The implementation would have benefited some planning on what that looked like, for example a Github issue/spec written up first. It feels like a really good idea to reuse the existing Invitation structure as much as possible, and without significant details in the PR description, I'm not able to gauge on what the decisions and motivations were for the current implementation.

Comment on lines +1852 to +1854
tagline = models.CharField(
max_length=150, blank=True, null=True, help_text="Short description"
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this have a use case? It seems influenced by Channel.tagline which does have a clear purpose.

)

# Metadata
preferences = models.TextField(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Having this for parity with User makes sense to me and will likely be useful at the organization level.

Although, this uses the same field type as User.preferences, which is a TextField, but that field should really have been a JSONField to start with. Please make this a JSONField.

preferences = models.TextField(
default=DEFAULT_USER_PREFERENCES, help_text="Organization preferences as JSON"
)
settings = JSONField(default=dict, help_text="Additional organization settings")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the use case for this field? We've been trying to be stricter with new JSON fields, e.g. validating against a JSON schema. Although, ideally we leverage relational models as much as possible. For existing patterns like the preferences field, it's totally acceptable to have it be JSON. For this field, it would nice if we either had well-defined models or a JSON schema for whatever capabilities this supports.

verbose_name_plural = "Organizations"
ordering = ["name"]
indexes = [
models.Index(fields=["name"], name="organization_name_idx"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is likely redundant. The field already has db_index=True. For a CharField, I believe with db_index=True, Django will create both a b-tree and text pattern ops index on it, which seems fine.

Comment on lines +1900 to +1902
models.Index(
fields=["deleted", "public"], name="organization_deleted_public_idx"
),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we could make this a conditional index. Operationally, we're unlikely to query deleted=True often enough to warrant it. So this index could be conditional on deleted=False.

Although, I'd feel fine leaving out this index entirely until we see the need for it.

Comment on lines +1988 to +1990
models.Index(
fields=["user", "organization"], name="org_role_unique_idx"
),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a redundant index with what's already defined by unique_together, but also, just FYI, it isn't actually a unique index either.

models.Index(
fields=["user", "organization"], name="org_role_unique_idx"
),
models.Index(fields=["status"], name="org_role_status_idx"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unless there's a planned use for this, my guess is we'd end up utilizing the compound index with organization, so this may not be needed. But depending on whether we consolidate with the existing Invitation model, we may not need status?

Comment on lines +1949 to +1951
permissions = JSONField(
default=dict, help_text="JSON object defining user's permissions"
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's this field used for?

Comment on lines +1942 to +1945
role = models.CharField(
max_length=100,
help_text="Role name (e.g., Admin, Editor, Viewer, Content Creator)",
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it would be good to have explicit choices for this field.

Comment on lines +1875 to +1882
organization_role = models.ForeignKey(
"OrganizationRole",
null=True,
blank=True,
related_name="organizations",
on_delete=models.SET_NULL,
help_text="Tracking the creator or primary role associated with this organization",
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes sense, but I do see some possibly problematic aspects to this in the data architecture. For example, presumably this FK role has something like "superuser" privileges for the org, but the model's role value could be something less privileged. There isn't a constraint to ensure logical integrity.

An alternative might be adding a boolean flag on the role model which signifies this, then a unique constraint can be made to ensure there's only one for an org, and another constraint to enforce the role has admin privileges. Enforcing this strictly means we can better rely on the data as a source of truth, making assumptions without as much defensive code.

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.

3 participants