aminalaee / sqladmin

SQLAlchemy Admin for FastAPI and Starlette
https://aminalaee.dev/sqladmin/
BSD 3-Clause "New" or "Revised" License
1.8k stars 181 forks source link

Tag field is pre-populated with an unrelated value #622

Closed Toshakins closed 11 months ago

Toshakins commented 11 months ago

Checklist

Describe the bug

The select tag field gets prepopulated with the unrelated value. The issue happens both on creation and edit actions. The list of available options is updated with form_args ModelView field.

Steps to reproduce the bug

  1. Start app.
  2. Open the admin panel(http://localhost:8000/admin/).
  3. Open role list.
  4. Click the "New Role" button.

Expected behavior

A form with an empty input is shown up.

Actual behavior

Input is populated with "firmware_schedule:edit" value. The browser debug console has an error trace:

Uncaught TypeError: o is null
    <anonymous> bootstrap.min.js:381
    jQuery 8
    <anonymous> bootstrap.min.js:368
    <anonymous> bootstrap.min.js:11
    <anonymous> bootstrap.min.js:17
image

Debugging material

Please replace postgres engine link while reproducing the error with the provided code sample.

App sample ```python from enum import Enum from typing import List import sqlalchemy as sa import uvicorn from fastapi import FastAPI from sqladmin import Admin, ModelView from sqlalchemy import create_engine from sqlalchemy.dialects import postgresql from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped class Base(DeclarativeBase): pass engine = create_engine('postgresql://postgres:example@localhost:5432/iqtools') class PermissionEnum(Enum): firmware_edit = 'firmware_schedule:edit' video_edit = 'video:edit' lab_edit = 'lab:edit' roboarm_edit = 'roboarm:edit' class Role(Base): __tablename__ = "roles" id: Mapped[int] = mapped_column(primary_key=True) permissions: Mapped[List[PermissionEnum]] = mapped_column( postgresql.ARRAY(sa.Text()), nullable=False) Base.metadata.create_all(engine) app = FastAPI() admin = Admin(app, engine) class RoleAdmin(ModelView, model=Role): form_args = {'permissions': dict(choices=[it.value for it in PermissionEnum])} admin.add_view(RoleAdmin) if __name__ == "__main__": uvicorn.run("main:app", host="127.0.0.1", port=8000) ```

Environment

Browser: Version 116.0.5845.187 OS: Mac OS Ventura 13.5.2 Postgres: 12.14 Python: 3.11 Requirements.txt:

SQLAlchemy==2.0.21
sqladmin[full]==0.15.0
fastapi==0.103.1
psycopg2-binary==2.9.7
uvicorn

Additional context

No response

aminalaee commented 11 months ago

Hey, I haven't yet checked your code (BTW you have a nice formatted example code, that's great), but I think choices needs to be a tuple of (value, label) in WTForms: https://wtforms.readthedocs.io/en/2.3.x/fields/#wtforms.fields.SelectField

Toshakins commented 11 months ago

@aminalaee Thank you very much for looking into the issue. Open source is a tough job, and I'm all in to make it a bit easier.

As the documentation suggests, the SelectField could also accept the list of values that will be used as labels. The issue is still reproducible for me while sending (value, label) to the class.

For now, I can work around the bug by deliberately using SelectMultipleField but it gives an inferior user experience, therefore I've created this report.

aminalaee commented 11 months ago

You're right. But first I need to say that console error is not relevant. I need to check that separately.

About this issue, it is normal that the browser selects the first option, so you need to add an empty option: https://stackoverflow.com/questions/40905579/flask-wtf-dynamic-select-field-with-an-empty-option

But if you try that the UI gets a bit weird, because this is not normal select, it is select2 option, so in addition to that empty option, we also need to add a placeholder which makes it nice: https://groups.google.com/g/select2/c/QCP1srwg48s?pli=1

Screenshot from 2023-09-21 14-18-57

PRs welcome, otherwise I can check it later.

Toshakins commented 11 months ago

Thanks for investigating it more deeply, your findings led me to PR, please take a look 🙏