diff --git a/backend/proteins/admin.py b/backend/proteins/admin.py index 28fbbe02..deeee449 100644 --- a/backend/proteins/admin.py +++ b/backend/proteins/admin.py @@ -225,7 +225,7 @@ class CameraAdmin(SpectrumOwner, VersionAdmin): class SpectrumAdminForm(forms.ModelForm): """Custom form to handle the Spectrum.data property in admin.""" - data = forms.CharField( + data = forms.CharField( # type: ignore[assignment] widget=forms.Textarea(attrs={"rows": 10, "cols": 80}), required=False, help_text="Spectrum data as [[wavelength, value], ...] pairs", diff --git a/backend/proteins/views/protein.py b/backend/proteins/views/protein.py index df055add..64080088 100644 --- a/backend/proteins/views/protein.py +++ b/backend/proteins/views/protein.py @@ -65,7 +65,7 @@ def check_switch_type(object, request): suggested = suggested_switch_type(object) if suggested and object.switch_type != suggested: - disp = dict(Protein.SwitchingChoices).get(suggested).lower() + disp = dict(Protein.SwitchingChoices.choices).get(suggested).lower() actual = object.get_switch_type_display().lower() msg = ( "Warning: " @@ -683,7 +683,7 @@ def problems_inconsistencies(request): for prot in p: suggestion = suggested_switch_type(prot) if (prot.switch_type or suggestion) and (prot.switch_type != suggestion): - bad_switch.append((prot, dict(Protein.SwitchingChoices).get(suggestion))) + bad_switch.append((prot, dict(Protein.SwitchingChoices.choices).get(suggestion))) return render( request, diff --git a/backend/tests/test_proteins/test_views.py b/backend/tests/test_proteins/test_views.py index 2138ff02..08d50829 100644 --- a/backend/tests/test_proteins/test_views.py +++ b/backend/tests/test_proteins/test_views.py @@ -465,3 +465,56 @@ def test_scope_report_json_full_workflow_integration(self, client): # Verify fluors dict contains our fluorophores assert state.slug in data["fluors"], f"State {state.slug} missing from fluors" assert dye_state.slug in data["fluors"], f"DyeState {dye_state.slug} missing from fluors" + + +class TestProteinSubmitMultiState(TestCase): + """Regression tests for FPBASE-6H7: protein submission with multiple states.""" + + def setUp(self) -> None: + self.admin_user = User.objects.create_superuser( + username="admin", email="admin@example.com", password="password" + ) + + def test_protein_submit_with_two_states(self): + """Test protein submission with multiple states doesn't raise ValueError. + + Regression test for FPBASE-6H7: When a protein has 2+ states but no transitions, + the check_switch_type function is called and previously crashed with ValueError + due to incorrect dict(Protein.SwitchingChoices) usage. + """ + self.client.login(username="admin", password="password") + initial_count = Protein.objects.count() + + # Submit protein with 2 states (no transitions) - triggers check_switch_type + # with suggested switch_type='o' (OTHER) + response = self.client.post( + reverse("proteins:submit"), + data={ + "name": "MultiStateProtein", + "reference_doi": "10.1038/nmeth.2413", + # Two states, no transitions = triggers 'OTHER' suggestion + "states-0-name": "on", + "states-0-ex_max": 488, + "states-0-em_max": 525, + "states-1-name": "off", + "states-1-ex_max": 400, + "states-1-em_max": 450, + "confirmation": True, + "lineage-TOTAL_FORMS": 1, + "lineage-INITIAL_FORMS": 0, + "lineage-MIN_NUM_FORMS": 0, + "lineage-MAX_NUM_FORMS": 1, + "states-TOTAL_FORMS": 2, + "states-INITIAL_FORMS": 0, + "states-MIN_NUM_FORMS": 0, + "states-MAX_NUM_FORMS": 1000, + }, + ) + + # Should redirect to protein detail (success), not error + assert response.status_code == 302 + assert Protein.objects.count() == initial_count + 1 + + new_prot = cast("Protein", Protein.objects.get(name="MultiStateProtein")) + assert response.url == new_prot.get_absolute_url() + assert new_prot.states.count() == 2