starlette-admin icon indicating copy to clipboard operation
starlette-admin copied to clipboard

Enhancement: doc could illustrate how to select an entity instead of just typing text in batch action modals

Open sglebs opened this issue 2 years ago • 3 comments

The batch action example has something along the lines of:

        <form>
            <div class="mt-3">
                <input type="text" class="form-control" name="new-owner-input" placeholder="Enter owner id">
            </div>
        </form>

This was an ok first cut, but instead of typing the id for an entity, I would like to be able to have a "choices" widget where I could pick one. How can I put such a widget in this batch action modal? It could be a list of users, it could be a list of parts, etc. Not a string representing a primary key. Is there such an advanced example or documentation?

In my particular case I want to be able to assign N tasks to 1 user. The batch action stuff is almost perfect, I just need a way to select which user I want. Right now I enter its primary key in the INPUT of the FORM above.

Thanks for listening!

sglebs avatar Aug 30 '23 01:08 sglebs

In other words: I want to put a drop-down for a foreign key, just like in the CREATE or EDIT for an entity (relationship entities appear as a drop-down)

sglebs avatar Aug 30 '23 10:08 sglebs

I also want to make sure only a user from the same tenancy can be selected. So, I guess I could manually query the database for a list of available users from the same tenancy - something I already have done for the Users ModelView:

    def get_list_query_for_request(self, request: Request):
        if request.session["user_role"] in [UserRole.SUPERADMIN.name]:
            return self.get_list_query()
        else:
            return self.get_list_query().where(User.company_id == request.session["user_company_id"])

Is there a way for me to leverage this stuff in the User's ModelView from the @action in my Task's ModelView? Can I somehow call <the logged user's ModelView>.get_list_query_for_request(request) from form= in my Task ModelView, perhaps leveraging some sort of template injection?

I mean, I am just trying to figure out how to generate this HTML markup just like starlet-admin already generates at its core when I say a field is of type "User".

Thanks.

sglebs avatar Sep 05 '23 01:09 sglebs

@sglebs I have similar requirement, and made a workaround with this, hope this helps

  1. load your custom statics with add custom statics_dir, should be the same as original
from starlette_admin.contrib.sqlmodel import Admin
admin_app = Admin(engine,
                  statics_dir=app_path('resources/views/admin/static'),
                  ])
  1. add your custom js with select2 css, the .is-select2 is used for rendering action modal with select2, the point here is observing rendered modal with your select
# resources/views/admin/static/js/custom-select2.js

const targetElement = document.querySelector('#modal-form');

const observer = new MutationObserver(function (mutations) {
  for (const mutation of mutations) {
    // Check if the target element has been added to the DOM
    if (mutation.addedNodes && mutation.addedNodes.length > 0) {
      for (const node of mutation.addedNodes) {
        if (node.hasChildNodes() && node.childNodes[1].classList.contains('is-select2')) {
          renderAction();
        }
      }
    }
  }
});

observer.observe(targetElement, {childList: true, characterData: true});

function renderAction() {
  $('.is-select2').select2({
    width: '100%',
    dropdownParent: $('#modal-form')
  });
}

  1. render in your admin_view
class YourView(ModelView):
    actions = ['select2_dropdown']

    # load select2.min.css here
    def _additional_css_links(
            self, request: Request, action: RequestAction
    ) -> Sequence[str]:
        if action.is_form():
            return super()._additional_css_links(request, action)
        return [
            str(
                request.url_for(
                    f"{request.app.state.ROUTE_NAME}:statics",
                    path="css/select2.min.css",
                )
            ),
        ]
    # load your custom js here
    def _additional_js_links(
            self, request: Request, action: RequestAction
    ) -> Sequence[str]:
        if action.is_form():
            return super()._additional_js_links(request, action)

        return [
            str(
                request.url_for(
                    f"{request.app.state.ROUTE_NAME}:statics",
                    path="js/vendor/select2.min.js",
                )
            ),
            str(
                request.url_for(
                    f"{request.app.state.ROUTE_NAME}:statics",
                    path="js/custom-select2.js",
                )
            ),
        ]


    @action(name='select2_dropdown_actions',
            text='a modal with dropwodn',
            confirmation='Proceed ?',
            submit_btn_text='Yes',
            submit_btn_class='btn-success',
            form="""
    <form>
        <select  id="anyid" class="form-control is-select2" multiple="multiple">
            <option></option>
            ...
        </select>
    </form>

then you can init your select2 with ajax to your API endpoints and get what you want

whchi avatar Sep 06 '23 07:09 whchi