5971: added organizations to invitation model#6008
Conversation
|
👋 Hi @ArthurMousatov, thanks for contributing! For the review process to begin, please verify that the following is satisfied:
Also check that issue requirements are satisfied & you ran Pull requests that don't follow the guidelines will be closed. Reviewer assignment can take up to 2 weeks. |
|
📢✨ Before we assign a reviewer, we'll turn on |
rtibblesbot
left a comment
There was a problem hiding this comment.
Three blocking issues must be resolved before merge: broken method dispatch in accept(), a two-write invalid-state pattern in _accept_organization_invitation, and missing acceptance-criteria tests. One migration safety concern is flagged as a suggestion. CI was pending at review time.
- blocking
accept()calls non-existent method names — every invitation acceptance raisesAttributeError(see inline models.py:3754) - blocking
_accept_organization_invitationwrites an empty-stringroleon firstcreate(), then overwrites it; unknownshare_modesilently persists invalid data; re-acceptance raisesIntegrityError(see inline models.py:3767) - blocking Acceptance criteria require unit tests for the invitation–organization relationship, the mutual-exclusivity constraint violation case, and the
share_mode→ org-role mapping — none are present in this PR - suggestion
AddConstraintin the migration may fail at deployment if any existing rows havechannel=NULL(see inline migration:19) - praise Splitting
accept()into named per-type helpers is a clean refactor — the dispatch logic is now symmetrical and the original comment about the nullable channel field is correctly removed
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran a phased review pipeline over the pull request diff:
- Classified the diff to select review passes (core, frontend, backend) and whether manual QA was required
- Core review pass checked correctness, design, architecture, testing, completeness, and DRY/SRP/Rule-of-Three principles
- Specialized frontend/backend review passes applied framework-specific lenses where those files changed
- For UI changes: manual QA and an accessibility audit against a live dev server, when available
- Checked CI status and linked issue acceptance criteria
- Synthesized one review from those passes and chose the verdict from the findings, CI status, and QA evidence
…exception for invalid share_mode
…thurMousatov/studio into add-organizations-to-invitation-model
rtibblesbot
left a comment
There was a problem hiding this comment.
1 of 4 prior findings resolved; 3 still open (see below).
- blocking
_accept_organization_invitationunchanged from last round — still writes an emptyrole, silently drops unknownshare_mode, raisesIntegrityErroron re-accept. See inline comment. - blocking The mutual-exclusivity
CheckConstraintonInvitationwas deleted with no replacement (noMeta.constraintsremains; not visible in the diff since it was added and removed within this branch's own history). The maintainer thread called forNOT VALID+ separate validate, not removal. - blocking This leaves the XOR invariant ("Organizations XOR channels", #5971) enforced nowhere, so the required "invitations with channel & organization set" unit test has nothing to validate against — and no such test was added anyway.
- nitpick Duplicate imports in the new migration (see inline comment).
Prior-finding status
RESOLVED — models.py:3746 — accept() dispatch to nonexistent method names
UNADDRESSED — models.py:3761 — _accept_organization_invitation two-write invalid-state pattern, silent corruption, IntegrityError on re-acceptance
UNADDRESSED — missing acceptance-criteria unit tests
UNADDRESSED — mutual-exclusivity constraint: previously a deployment-safety suggestion on the AddConstraint, now the constraint has been removed entirely rather than fixed, regressing the acceptance criterion
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
rtibblesbot
left a comment
There was a problem hiding this comment.
2 of 5 prior findings resolved; 3 still open (see below). CI passing.
- [blocking] Mutual-exclusivity constraint between
channelandorganizationstill not restored — see inline comment. - [blocking] No unit tests added for the invitation–organization relationship, mutual-exclusivity violation, or
share_mode→role mapping, despite these being acceptance criteria for #5971. - [nitpick] Duplicate imports in
migrations/0168_invitation_organization.py(lines 3, 5, 6) — still unresolved from the open thread on this PR. - New [suggestion] — see inline comment on the bare
ValueErrorin_accept_organization_invitation.
Prior-finding status
RESOLVED — models.py:3746 — accept() dispatch to nonexistent method names
RESOLVED — models.py:3761 — _accept_organization_invitation two-write invalid-state pattern, silent corruption, IntegrityError on re-acceptance
UNADDRESSED — missing acceptance-criteria unit tests
UNADDRESSED — models.py:3742 — mutual-exclusivity constraint removed entirely, XOR invariant enforced nowhere
UNADDRESSED — migrations/0168_invitation_organization.py:3-6 — duplicate imports
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
rtibblesbot
left a comment
There was a problem hiding this comment.
2 of 6 prior findings resolved.
4 still open, listed below.
- [blocking] Mutual-exclusivity constraint between
channelandorganizationstill missing. - [blocking] Missing acceptance-criteria unit tests.
- [blocking] Invalid-
share_modepath still raises an exception DRF won't convert to a 400. - [nitpick] Migration still has duplicate imports.
Prior-finding status
RESOLVED — models.py:3746 — accept() dispatch to nonexistent method names
RESOLVED — models.py:3761 — two-write invalid-state pattern / IntegrityError on re-acceptance
UNADDRESSED — models.py:3742 — mutual-exclusivity constraint removed entirely, XOR invariant enforced nowhere
UNADDRESSED — missing acceptance-criteria unit tests
UNADDRESSED — migrations/0168_invitation_organization.py:3-6 — duplicate imports
UNADDRESSED — models.py:3770 — invalid-share_mode error path still won't surface as a clean 400
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
rtibblesbot
left a comment
There was a problem hiding this comment.
3 of 6 prior findings resolved; 3 still open (see below).
CI passing.
- blocking —
Invitation.Metastill lacks a mutual-exclusivity guard forchannel/organization. See inline comment. - blocking — No test for the invitation–organization relationship (
organizationFK usage inaccept()). - blocking — No test for the mutual-exclusivity violation case (channel + organization both set).
- blocking — No test for the
share_mode→role mapping in_accept_organization_invitation. - blocking — Invalid
share_modestill raises a non-DRFValidationError. See inline comment.
Prior-finding status
RESOLVED — models.py:3746 — accept() dispatch fix
RESOLVED — models.py:3761 — two-write pattern fix
UNADDRESSED — models.py:3742 — mutual-exclusivity constraint missing
UNADDRESSED — missing acceptance-criteria tests
UNADDRESSED — models.py:3770 — non-DRF ValidationError
RESOLVED — migrations/0168_invitation_organization.py:3-6 — duplicate imports
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
🔵 Review postedLast updated: 2026-07-06 20:02 UTC |
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #6008 delta re-review: CI is failing, and 2 of the prior 3 blocking findings are still unresolved.
Blocking (see inline comments):
- Migration leaf conflict breaks CI — new this round.
- Mutual-exclusivity constraint between
channel/organizationstill missing. - Invalid-
share_modepath still raises a non-DRF exception.
Blocking (no changed line to anchor to):
- Missing acceptance-criteria unit tests in
tests/viewsets/test_invitation.py(untouched by this branch): a test for the invitation-organization relationship, a test for the mutual-exclusivity violation, and a test for theshare_mode→role mapping.
Prior-finding status
RESOLVED — models.py:3746 — accept() dispatch to nonexistent method names
RESOLVED — models.py:3761 — two-write invalid-state pattern / IntegrityError on re-acceptance
RESOLVED — migrations/0168_invitation_organization.py — duplicate imports
RESOLVED — migrations/0168_auto_20260627_2219.py — AddConstraint deployment-lock suggestion (constraint operation no longer exists)
UNADDRESSED — models.py:3742 — mutual-exclusivity constraint between channel/organization still missing
UNADDRESSED — missing acceptance-criteria unit tests
UNADDRESSED — models.py:3770 — invalid-share_mode path still raises an exception DRF won't convert to a 400
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
| @@ -0,0 +1,24 @@ | |||
| # Generated by Django 3.2.24 on 2026-07-02 04:15 | |||
There was a problem hiding this comment.
blocking: This migration and unstable's new 0168_alter_assessmentitem_type.py both depend on 0167_add_organization and both claim number 0168, creating two leaf nodes. CI fails with:
CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0168_alter_assessmentitem_type, 0168_invitation_organization in contentcuration).
Rebase onto current unstable, renumber this to 0169_invitation_organization.py, and update dependencies to point at 0168_alter_assessmentitem_type (or run manage.py makemigrations --merge).
| first_name = models.CharField(max_length=100, blank=True) | ||
| last_name = models.CharField(max_length=100, blank=True, null=True) | ||
| organization = models.ForeignKey( | ||
| "Organization", null=True, blank=True, on_delete=models.CASCADE |
There was a problem hiding this comment.
blocking: channel (line 3734) and organization are both nullable FKs with no constraint preventing both from being set on one invitation — accept() would then run both _accept_channel_invitation and _accept_organization_invitation off a single share_mode. This is the "Organizations XOR channels" acceptance criterion from #5971. Options:
- A DB
CheckConstraint, added asNOT VALIDand validated separately (per the earlier maintainer discussion on this PR, to avoid a migration-timeout risk on large tables). - A
clean()/save()guard enforcing exactly one of the two.
Either way, add a test for the violation.
| elif self.share_mode == ADMIN_ACCESS: | ||
| role = ORGANIZATION_ADMIN | ||
| else: | ||
| raise ValidationError(f"Invalid share_mode: {self.share_mode}") |
There was a problem hiding this comment.
blocking: ValidationError here resolves to django.core.exceptions.ValidationError (imported at models.py:21), not rest_framework.serializers.ValidationError. Invitation.accept() is called from viewsets/invitation.py:59 and :157, both DRF view/serializer code paths. DRF's exception_handler doesn't translate Django's ValidationError, so an invalid share_mode still surfaces as an unhandled 500 instead of a 400. Raise rest_framework.serializers.ValidationError instead.
Summary
As detailed in Issue #5971, this PR adds organizations to the invitation model. The saving behavior for organizations is slightly different to channels, as there is a separate model for org roles.
I have also omitted tests in this PR, as this is just a model change and does not affect current code (organizations are fully optional in the invitation). I suggest moving testing to the next implementation step (Organization/Invitation viewsets).
References
Direct PR for Issue #5971.
Reviewer guidance
As part of these changes, migrations were run successfully. I tried running
make test, but it ran into 905 failures, mostly related to S3Storage errors. No linting was performed, since no frontend work was done.AI usage
Only basic VS code autocomplete was used.