Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: custom relation field #521

Open
Ilya-Green opened this issue Mar 6, 2024 · 6 comments
Open

Enhancement: custom relation field #521

Ilya-Green opened this issue Mar 6, 2024 · 6 comments
Labels
enhancement New feature or request

Comments

@Ilya-Green
Copy link

Ilya-Green commented Mar 6, 2024

Is your feature request related to a problem? Please describe.
I want to make custom relation field so i can set custom render_function_key and custom display template.

Describe the solution you'd like
A clear and concise description of what you want to happen.
Something like this in list:
image
and something like this in detail:
image

Describe alternatives you've considered
If i trying to use StringField as base class:

@dataclass
class NotesField(StringField):
    rows: int = 6
    render_function_key: str = "notes"
    class_: str = "field-textarea form-control"
    form_template: str = "forms/textarea.html"
    display_template: str = "displays/note.html"
    exclude_from_create: Optional[bool] = True
    exclude_from_edit: Optional[bool] = True
    exclude_from_list: Optional[bool] = False

class ClientView(MyModelView):
    fields = [
        NotesField("notes"),
        Client.notes,
    ]

class DesktView(MyModelView):
    fields = [
        Desk.clients,
    ]

It leads to this api response:
GET: http://127.0.0.1:8000/admin/api/desk?skip=0&limit=20&order_by=id%20asc:
Reponse:

   {
    "items": [
        {
            "id": 1,
            "client": [
                {
                    "id": 15,
                    "notes": "[Note(content='test', client_id=15, id=4,), Note(content='teaa', client_id=15, id=6)]",
                    "_repr": "15",
                    "_detail_url": "http://127.0.0.1:8000/admin/client/detail/15",
                    "_edit_url": "http://127.0.0.1:8000/admin/client/edit/15"
                }
            ],

As you can see it resolving "notes" field (relation of a relation) and showing it like text (serializing func thinks it string field).
This is problem because it overloads backend by resolving a lot of related records.
And it might be not a problem if there is no a lot of relations, but if there a long chain of relations it starting resolving literally all databse which leads to crash of worker and even cycling of resolving records which leads to endless fetching to the database (and blocking it) until worker crashing and restarts by itself.

I found out that i can change "serialize" function to exclude this field:

    @dataclass
    class CustomRelationField(StringField):
        rows: int = 6

    async def serialize(
        self,
        obj: Any,
        request: Request,
        action: RequestAction,
        include_relationships: bool = True,
        include_relationships2: bool = True,
        include_relationships3: bool = True,
        include_select2: bool = False,
    ) -> Dict[str, Any]:
    ...
                elif not isinstance(field, RelationField):
+                    if isinstance(field, CustomRelationField):
+                           continue
                     ...

but this method is not allowing me to use relations information in detail view.

This method leads to overloading database in detail view:

    async def serialize(
        self,
        obj: Any,
        request: Request,
        action: RequestAction,
        include_relationships: bool = True,
        include_relationships2: bool = True,
        include_relationships3: bool = True,
        include_select2: bool = False,
    ) -> Dict[str, Any]:
    ...
                elif not isinstance(field, RelationField):
+                    if isinstance(field, CustomRelationField)  and action == RequestAction.LIST:
+                           continue
                     ...

How i can change serialize def so it stop to resolving in some moment? Or it will be even better if there is another more correct way to customize relation field display?

@Ilya-Green Ilya-Green added the enhancement New feature or request label Mar 6, 2024
@Ilya-Green
Copy link
Author

Ilya-Green commented Mar 7, 2024

Somehow this saves the optimization and at the same moment returns all required data:

@dataclass
class CustomRelationField(StringField):
    async def serialize_value(
        self, request: Request, value: Any, action: RequestAction
    ) -> Any:
        if action == RequestAction.DETAIL:
            return '123'

I don't understand how it all works.
In some cases it actually didt resolve some needed fields, in some it resolves exactly as many dates as needed.

@Ilya-Green
Copy link
Author

Ilya-Green commented Mar 7, 2024

I found thys syntax in admin demo source code:

class Post(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True)
    async def __admin_select2_repr__(self, request: Request) -> str:
        template_str = (
            "<span><strong>Title: </strong>{{obj.title}}, <strong>Publish by:"
            " </strong>{{obj.publisher.full_name}}</span>"
        )
        return Template(template_str, autoescape=True).render(obj=self)

I belive this can help to change list view.
Finally, it remains to understand how to change relation field template in the detail view.

@Ilya-Green
Copy link
Author

Is there any way to set representation html template for detail view also?

@Ilya-Green
Copy link
Author

I made _detail_repr
If anyone need this I can provide modified code

@jowilf
Copy link
Owner

jowilf commented Mar 13, 2024

You can simply override the detail template for this specific field. You can find more information on https://jowilf.github.io/starlette-admin/advanced/custom-field/?h=custom#detail-page

Also, I noticed that you are currently using an outdated version of starlette-admin. I recommend upgrading to the latest version.

@jowilf
Copy link
Owner

jowilf commented Mar 13, 2024

class ClientView(MyModelView):
    fields = [
        NotesField("notes"),
        Client.notes,
    ]

The field names must be unique per view

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants