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