Fix missing release field on admin interface
- Force SQLAlcheny relationships init before rendering the form - Add tests covering this problem Fixes #279 Assisted-by: Claude Code
This commit is contained in:
parent
3fbc4de037
commit
30089575e2
2 changed files with 70 additions and 0 deletions
|
|
@ -25,6 +25,7 @@ import logging
|
|||
from flask import flash
|
||||
from flask_admin.babel import gettext
|
||||
import flask_admin
|
||||
from sqlalchemy.orm import configure_mappers
|
||||
from blockerbugs import app, db
|
||||
from blockerbugs.models.release import Release
|
||||
from blockerbugs.models.milestone import Milestone
|
||||
|
|
@ -97,5 +98,10 @@ class MilestoneView(FasAuthModelView):
|
|||
return True
|
||||
|
||||
|
||||
# Force SQLAlchemy to resolve all Mapped[] forward references and back_populates linkages
|
||||
# before Flask-Admin introspects models to scaffold forms. Without this, relationship
|
||||
# fields (e.g. Milestone.release) are silently omitted, causing AttributeErrors on submit.
|
||||
configure_mappers()
|
||||
|
||||
admin.add_view(MilestoneView(Milestone, db.session))
|
||||
admin.add_view(ReleaseView(Release, db.session))
|
||||
|
|
|
|||
64
testing/unit/test_admin.py
Normal file
64
testing/unit/test_admin.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""Tests for the admin interface"""
|
||||
|
||||
import pytest
|
||||
from wtforms.fields.core import UnboundField
|
||||
|
||||
from blockerbugs.controllers.admin import admin
|
||||
|
||||
|
||||
def _get_admin_view(model_name):
|
||||
"""Look up a Flask-Admin ModelView by its model class name."""
|
||||
for view in admin._views:
|
||||
if hasattr(view, "model") and view.model.__name__ == model_name:
|
||||
return view
|
||||
raise AssertionError(f"{model_name} view not found in admin views")
|
||||
|
||||
|
||||
def _get_form_field_names(model_name):
|
||||
"""Return the set of field names that Flask-Admin scaffolds for a model."""
|
||||
view = _get_admin_view(model_name)
|
||||
form_class = view.scaffold_form()
|
||||
return {name for name in dir(form_class) if isinstance(getattr(form_class, name), UnboundField)}
|
||||
|
||||
|
||||
class TestAdminForms:
|
||||
"""Test that Flask-Admin generates the correct form fields for each model.
|
||||
|
||||
Flask-Admin auto-scaffolds form fields by introspecting the SQLAlchemy model.
|
||||
With SQLAlchemy 2.0's deferred relationship initialization, relationship fields
|
||||
(like 'release') can be silently dropped if configure_mappers() hasn't been
|
||||
called before Flask-Admin scaffolds the form. These tests guard against that
|
||||
regression.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_name, required_fields",
|
||||
[
|
||||
(
|
||||
"Milestone",
|
||||
{
|
||||
"release",
|
||||
"version",
|
||||
"blocker_tracker",
|
||||
"fe_tracker",
|
||||
"name",
|
||||
"active",
|
||||
"current",
|
||||
"succeeded_by",
|
||||
},
|
||||
),
|
||||
("Release", {"number", "active"}),
|
||||
],
|
||||
)
|
||||
def test_form_has_all_required_fields(self, model_name, required_fields):
|
||||
"""The admin form must include all fields used by create_model().
|
||||
|
||||
Each ModelView.create_model() accesses specific form fields when creating
|
||||
a new instance through the admin interface. If any are missing, form
|
||||
submission will fail with an AttributeError.
|
||||
"""
|
||||
fields = _get_form_field_names(model_name)
|
||||
missing = required_fields - fields
|
||||
assert not missing, (
|
||||
f"{model_name} admin form is missing fields required by create_model(): {missing}"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue