def relationshipstab(request):
    person = get_object_or_404(Person, pk=request.resolver_match.kwargs["pk"])
    modal_from = modal_add_relationship_from(person)
    modal_to = modal_add_relationship_to(person)
    return layout.tabs.Tab(
        _("Relationships"),
        utils.grid_inside_tab(
            R(
                utils.tiling_col(
                    relationships_datatable(
                        request,
                        title=_("Relationships to person"),
                        queryset=hg.F(
                            lambda c: c["object"].relationships_from.all()),
                        primary_button=button_add_relationship_to(modal_to),
                    ),
                    modal_to,
                    hg.DIV(style="margin-top: 4rem;"),
                    relationships_datatable(
                        request,
                        title=_("Relationships from person"),
                        queryset=hg.F(
                            lambda c: c["object"].relationships_to.all()),
                        primary_button=button_add_relationship_from(
                            modal_from),
                    ),
                    modal_from,
                )), ),
    )
예제 #2
0
 def get_layout(self):
     form_fields = [layout.forms.FormField(field) for field in [*self.fields]] + [
         hg.If(
             hg.F(
                 lambda c: c["object"].person.primary_email_address
                 and c["object"].person.primary_email_address.pk != c["object"].pk
             ),
             layout.forms.FormField("is_primary"),
             "",
         ),
         hg.If(
             hg.F(
                 lambda c: apps.is_installed("basxconnect.mailer_integration")
                 and hasattr(c["object"], "subscription")
             ),
             layout.forms.FormField("propagate_change_to_mailer"),
             "",
         ),
     ]
     return layout.grid.Grid(
         hg.H3(_("Edit Email")),
         layout.grid.Row(
             layout.grid.Col(
                 layout.forms.Form(
                     hg.C("form"),
                     hg.DIV(*form_fields),
                     layout.forms.helpers.Submit(),
                 ),
                 width=4,
             )
         ),
         gutter=False,
     )
예제 #3
0
def variable_size_header_part(platform, company, searchbar, searchbar_position):
    return hg.BaseElement(
        hg.A(
            logo(),
            platform,
            _class="bx--header__name",
            style="font-weight: 400",  # override carbon design
            href=hg.F(lambda c: c["request"].META["SCRIPT_NAME"] or "/"),
        ),
        hg.If(
            searchbar,
            hg.SPAN(
                searchbar,
                style=f"position: absolute; left: {searchbar_position}",
                _class="theme-gray-100",
            ),
            "",
        ),
        hg.A(
            hg.SPAN(
                company,
                style=hg.format(
                    "position: absolute; left: {}",
                    hg.If(searchbar, "50%", searchbar_position),
                ),
            ),
            _class="bx--header__name",
            style="font-weight: 400",  # override carbon design
            href=hg.F(lambda c: c["request"].META["SCRIPT_NAME"] or "/"),
        ),
    )
예제 #4
0
def editperson_toolbar(request):
    deletebutton = layout.button.Button(
        _("Delete"),
        buttontype="ghost",
        icon="trash-can",
        notext=True,
        **layout.aslink_attributes(
            hg.F(lambda c: layout.objectaction(c["object"], "delete"))),
    )
    restorebutton = layout.button.Button(
        _("Restore"),
        buttontype="ghost",
        icon="undo",
        notext=True,
        **layout.aslink_attributes(
            hg.F(lambda c: layout.objectaction(
                c["object"], "delete", query={"restore": True}))),
    )
    copybutton = layout.button.Button(
        _("Copy"),
        buttontype="ghost",
        icon="copy",
        notext=True,
        **layout.aslink_attributes(
            hg.F(lambda c: layout.objectaction(c["object"], "copy"))),
    )

    return hg.SPAN(
        hg.If(hg.C("object.deleted"), restorebutton, deletebutton),
        copybutton,
        layout.button.PrintPageButton(buttontype="ghost"),
        _class="no-print",
        style="margin-bottom: 1rem; margin-left: 1rem",
        width=3,
    )
    def __init__(
        self,
        links,
        menuiconname="overflow-menu--vertical",
        menuname=None,
        direction="bottom",
        flip=False,
        item_attributes={},
        **attributes,
    ):
        attributes["data-overflow-menu"] = True
        attributes["_class"] = attributes.get("_class",
                                              "") + " bx--overflow-menu"
        item_attributes["_class"] = (item_attributes.get("_class", "") +
                                     " bx--overflow-menu-options__option")

        menuid = hg.F(lambda c: OverflowMenu.MENUID_TEMPLATE % hg.html_id(
            c.get("row", self)))
        triggerid = hg.F(lambda c: (OverflowMenu.MENUID_TEMPLATE % hg.html_id(
            c.get("row", self))) + "-trigger")

        super().__init__(
            hg.BUTTON(
                Icon(menuiconname, size=16),
                _class="bx--overflow-menu__trigger" +
                (" bx--tooltip__trigger bx--tooltip--a11y bx--tooltip--right bx--tooltip--align-start"
                 if menuname is not None else ""),
                aria_haspopup="true",
                aria_expanded="false",
                aria_controls=menuid,
                type="button",
                id=triggerid,
            ),
            hg.DIV(
                hg.UL(
                    hg.Iterator(
                        links,
                        "link",
                        hg.LI(
                            hg.F(asoverflowbutton),
                            **item_attributes,
                        ),
                    ),
                    _class="bx--overflow-menu-options__content",
                ),
                _class="bx--overflow-menu-options" +
                (" bx--overflow-menu--flip" if flip else ""),
                tabindex="-1",
                role="menu",
                aria_labelledby=triggerid,
                data_floating_menu_direction=direction,
                id=menuid,
            ),
            **attributes,
        )
        if menuname is not None:
            self[0].insert(0, hg.SPAN(menuname, _class="bx--assistive-text"))
예제 #6
0
def active_toggle():
    toggle = layout.toggle.Toggle(None,
                                  _("Inactive"),
                                  _("Active"),
                                  style="margin-top:-1rem; margin-bottom:0;")
    toggle.input.attributes["id"] = "person_active_toggle2"
    toggle.input.attributes["hx_trigger"] = "change"
    toggle.input.attributes["hx_post"] = hg.F(lambda c: reverse_lazy(
        "core.person.togglestatus", args=[c["object"].pk]))
    toggle.input.attributes["checked"] = hg.F(lambda c: c["object"].active)
    toggle.label.attributes["_for"] = toggle.input.attributes["id"]
    return hg.DIV(toggle)
예제 #7
0
 def __init__(self, platform, company, searchbar, actions=(), *args, **kwargs):
     super().__init__(
         hg.If(
             HasBreadCookieValue("sidenav-hidden", "true"),
             variable_size_header_part(hg.BaseElement(), company, searchbar, "5rem"),
             variable_size_header_part(
                 hg.SPAN(platform, _class="bx--header__name--prefix"),
                 company,
                 searchbar,
                 "18rem",
             ),
         ),
         hg.DIV(
             hg.If(
                 hg.F(lambda c: c["request"].user.is_authenticated),
                 hg.A(
                     hg.SPAN(
                         hg.C("request.user.get_username"),
                         _class="bx--header__name--prefix",
                     ),
                     _class="bx--header__name",
                     href=reverse("userprofile"),
                     title=hg.C("request.user.get_username"),
                     style="padding: 0; margin-right: 1rem",
                 ),
             ),
             hg.If(
                 hg.F(lambda c: c["request"].user.is_authenticated),
                 hg.BUTTON(
                     Icon(
                         "logout",
                         size=20,
                         _class="bx--navigation-menu-panel-expand-icon",
                         aria_hidden="true",
                     ),
                     Icon(
                         "logout",
                         size=20,
                         _class="bx--navigation-menu-panel-collapse-icon",
                         aria_hidden="true",
                     ),
                     _class="bx--header__menu-trigger bx--header__action",
                     title=_("Logout"),
                     data_navigation_menu_panel_label_expand=_("Logout"),
                     data_navigation_menu_panel_label_collapse=_("Close"),
                     onclick=f"document.location = '{reverse('logout')}'",
                 ),
             ),
             _class="bx--header__global",
         ),
         _class="bx--header",
         data_header=True,
     )
예제 #8
0
 def __init__(
     self,
     label=None,
     help_text=None,
     errors=None,
     inputelement_attrs=None,
     boundfield=None,
     **attributes,
 ):
     inputelement_attrs = inputelement_attrs or {}
     attrs = {}
     if boundfield:
         attrs["checked"] = hg.F(
             lambda c: hg.resolve_lazy(boundfield, c).field.widget.
             check_test(hg.resolve_lazy(boundfield, c).value()))
     inputelement_attrs = _combine_lazy_dict(inputelement_attrs, attrs)
     label = None if label is None else label.label
     super().__init__(
         self.get_input_element(inputelement_attrs, errors),
         hg.LABEL(
             hg.SPAN(_class="bx--radio-button__appearance"),
             hg.SPAN(label, _class="bx--radio-button__label-text"),
             _class="bx--radio-button__label",
             _for=inputelement_attrs.get("id"),
         ),
         help_text,
         errors,
         **hg.merge_html_attrs(attributes,
                               {"_class": "bx--radio-button-wrapper"}),
     )
예제 #9
0
def _optgroups_from_choices(optchoices, name, value):
    groups = []

    for index, (option_value, option_label) in enumerate(optchoices):
        if option_value is None:
            option_value = ""

        subgroup = []
        if isinstance(option_label, (list, tuple)):
            group_name = option_value
            subindex = 0
            choices = option_label
        else:
            group_name = None
            subindex = None
            choices = [(option_value, option_label)]
        groups.append((group_name, subgroup, index))

        for subvalue, sublabel in choices:
            selected = hg.F(lambda c, v=subvalue: hg.resolve_lazy(v, c) == hg.
                            resolve_lazy(value, c))
            subgroup.append({
                "name": name,
                "value": subvalue,
                "label": sublabel,
                "selected": selected,
                "attrs": {
                    "selected": selected,
                },
            })
            if subindex is not None:
                subindex += 1
    return groups
예제 #10
0
def urls(request):
    return tile_with_datatable(
        models.Web,
        hg.F(lambda c: c["object"].core_web_list.all()),
        ["type", "url"],
        request,
    )
예제 #11
0
def _guess_widget(fieldname, form, suggested_widgetclass) -> hg.Lazy:
    widget_map: dict = {}
    for cls in _all_subclasses(BaseWidget):
        if cls.django_widget not in widget_map:
            widget_map[cls.django_widget] = []
        widget_map[cls.django_widget].append(cls)

    def wrapper(context):
        realform = hg.resolve_lazy(form, context)
        widgetclass = type(realform[fieldname].field.widget)
        fieldclass = type(realform[fieldname].field)

        # Hidden widgets have highest priority
        if issubclass(widgetclass, forms.HiddenInput):
            return HiddenInput
        # Manually passed widgets have second priority
        if suggested_widgetclass is not None:
            return suggested_widgetclass

        # Automated detection via django-bread-widget-mapp have lowest priority
        if fieldclass in widget_map:
            return widget_map[fieldclass][0]
        if widgetclass in widget_map:
            return widget_map[widgetclass][0]

        # Fallback for unknown widgets
        warnings.warn(
            f"Form field {type(realform).__name__}.{fieldname} ({fieldclass}) uses widget {widgetclass} but "
            "bread has no implementation, default to TextInput")
        return TextInput

    return hg.F(wrapper)
예제 #12
0
def error_layout(
    request,
    status_code: int,
    status_title: str,
    description: Union[str, hg.BaseElement],
    exception_detail: str = None,
):
    return hg.BaseElement(
        hg.H1(f"{status_code}: {status_title}", style="margin-bottom: 1rem;"),
        hg.P(
            description,
            style="margin-bottom: 1rem;",
        ),
        hg.If(
            bool(exception_detail),
            hg.BaseElement(
                hg.H4("Detail", style="margin-bottom: 1rem;"),
                hg.DIV(
                    exception_detail,
                    style=(
                        "border: 1px solid grey;"
                        "padding: 1rem;"
                        "font-family: monospace;"
                        "margin-bottom: 1rem;"
                    ),
                ),
            ),
        ),
        Button.from_link(
            Link(
                label=_("Back to homepage"),
                href=hg.F(lambda c: c["request"].META["SCRIPT_NAME"] or "/"),
            )
        ),
    )
예제 #13
0
def numbers(request):
    return tile_with_datatable(
        models.Phone,
        hg.F(lambda c: c["object"].core_phone_list.all()),
        ["type", "number"],
        request,
    )
def attributes_for_link_to_person(get_person: Callable[[Relationship],
                                                       Person]):
    return layout.aslink_attributes(
        hg.F(lambda c: reverse_model(
            get_person(c["row"]),
            "read",
            kwargs={"pk": get_person(c["row"]).pk},
        )))
예제 #15
0
 def __init__(
     self,
     label=None,
     help_text=None,
     errors=None,
     inputelement_attrs=None,
     boundfield=None,
     **attributes,
 ):
     inputelement_attrs = inputelement_attrs or {}
     attrs = {}
     if boundfield:
         attrs["checked"] = hg.F(
             lambda c: hg.resolve_lazy(boundfield, c).field.widget.
             check_test(hg.resolve_lazy(boundfield, c).value()))
         attrs["value"] = None
     inputelement_attrs = _combine_lazy_dict(inputelement_attrs, attrs)
     # labels for checkboxes are treated a bit different, need to use plain value
     label = hg.F(lambda c, label=label: getattr(hg.resolve_lazy(label, c),
                                                 "label", label))
     required = hg.F(
         lambda c, label=label: hg.resolve_lazy(label, c) is not None)
     super().__init__(
         hg.LABEL(
             self.get_input_element(inputelement_attrs, errors),
             label,
             hg.If(inputelement_attrs.get("required"),
                   hg.If(required, REQUIRED_LABEL)),
             _class=hg.BaseElement(
                 "bx--checkbox-label",
                 hg.If(inputelement_attrs.get("disabled"),
                       " bx--label--disabled"),
             ),
             data_contained_checkbox_state=hg.If(
                 inputelement_attrs.get("checked"),
                 "true",
                 "false",
             ),
             data_invalid=hg.If(getattr(errors, "condition", False), True),
         ),
         help_text,
         errors,
         **hg.merge_html_attrs(attributes,
                               {"_class": "bx--checkbox-wrapper"}),
     )
예제 #16
0
def _gen_optgroup(boundfield):
    def wrapper(context):
        bfield = hg.resolve_lazy(boundfield, context)
        return bfield.field.widget.optgroups(
            bfield.name,
            bfield.field.widget.format_value(bfield.value()),
        )

    return hg.F(wrapper)
예제 #17
0
def searchbar(search_urlparameter: str):
    """
    Creates a searchbar element for datatables to submit an entered search
    term via a GET url parameter
    """
    searchinput = Search(
        widgetattributes={
            "autofocus":
            True,
            "name":
            search_urlparameter,
            "value":
            hg.F(lambda c: c["request"].GET.get(search_urlparameter, "")),
            "onfocus":
            "this.setSelectionRange(this.value.length, this.value.length);",
        })
    searchinput.close_button.attributes[
        "onclick"] = "this.closest('form').querySelector('input').value = ''; this.closest('form').submit()"

    return hg.DIV(
        hg.FORM(
            searchinput,
            hg.Iterator(
                hg.C("request").GET.lists(),
                "urlparameter",
                hg.If(
                    hg.F(
                        lambda c: c["urlparameter"][0] != search_urlparameter),
                    hg.Iterator(
                        hg.C("urlparameter")[1],
                        "urlvalue",
                        hg.INPUT(
                            type="hidden",
                            name=hg.C("urlparameter")[0],
                            value=hg.C("urlvalue"),
                        ),
                    ),
                ),
            ),
            method="GET",
        ),
        _class="bx--toolbar-search-container-persistent",
    )
예제 #18
0
def _append_classes(lazy_attrs, *_classes):
    def wrapper_func(context):
        _classlist = []
        for _class in _classes:
            _classlist.append(_class)
            _classlist.append(" ")
        ret = hg.resolve_lazy(lazy_attrs, context) or {}
        ret["_class"] = hg.BaseElement(ret.get("_class", ""), " ", *_classlist)
        return ret

    return hg.F(wrapper_func)
def tags():
    return tile_with_icon(
        Icon("tag--group"),
        hg.H4(_("Tags")),
        hg.Iterator(hg.F(lambda c: c["object"].tags.all()), "i",
                    Tag(hg.C("i"))),
        open_modal_popup_button(
            _("Edit Tags"),
            hg.C("object").person_ptr,
            "ajax_edit_tags",
        ),
    )
예제 #20
0
def sortingclass_for_column(orderingurlparameter, columnname):
    def extracturlparameter(context):
        value = context["request"].GET.get(orderingurlparameter, "")
        if not value:
            return ""
        if value == columnname:
            return "bx--table-sort--active"
        if value == "-" + columnname:
            return "bx--table-sort--active bx--table-sort--ascending"
        return ""

    return hg.F(extracturlparameter)
예제 #21
0
 def get_layout(self):
     form_fields = [layout.forms.FormField(field) for field in [*self.fields]] + [
         hg.If(
             hg.F(
                 lambda c: c["object"].person.primary_postal_address
                 and c["object"].person.primary_postal_address.pk != c["object"].pk
             ),
             layout.forms.FormField("is_primary"),
             "",
         )
     ]
     return hg.DIV(layout.components.forms.Form(hg.C("form"), *form_fields))
예제 #22
0
def email(request):
    return tile_with_datatable(
        models.Email,
        hg.F(lambda c: c["object"].core_email_list.all()),
        [
            DataTableColumn(
                layout.ObjectFieldLabel("type", models.Email),
                hg.SPAN(
                    hg.C("row.type"),
                    hg.If(
                        hg.F(lambda c: c["row"] == c["row"].person.
                             primary_email_address),
                        hg.BaseElement(" (", _("primary"), ")"),
                        "",
                    ),
                ),
            ),
            "email",
        ],
        request,
    )
예제 #23
0
def display_sync_persons(sync_status):
    return hg.Iterator(
        hg.F(lambda c: c["row"].persons.filter(sync_status=sync_status)),
        "person",
        hg.DIV(
            hg.format(
                "{} {} <{}>",
                hg.C("person.first_name"),
                hg.C("person.last_name"),
                hg.C("person.email"),
            )),
    )
예제 #24
0
def create_modal(heading, model: Union[type, Lazy], action: str):
    modal = layout.modal.Modal.with_ajax_content(
        heading=heading,
        url=ModelHref(
            model,
            action,
            kwargs={"pk": hg.F(lambda c: c["object"].pk)},
            query={"asajax": True},
        ),
        submitlabel=_("Save"),
    )
    return modal
예제 #25
0
 def management_form(self):
     # the management form is required for Django formsets
     return hg.BaseElement(
         # management forms, for housekeeping of inline forms
         hg.F(lambda c: Form(
             c[self.formname][self.fieldname].formset.management_form,
             *[
                 FormField(
                     f, no_wrapper=True, no_label=True, no_helptext=True)
                 for f in c[self.formname][self.fieldname].formset.
                 management_form.fields
             ],
             standalone=False,
         )),
         # Empty form as template for new entries. The script tag works very well
         # for this since we need a single, raw, unescaped HTML string
         hg.SCRIPT(
             Form(
                 hg.C(f"{self.formname}.{self.fieldname}.formset.empty_form"
                      ),
                 hg.WithContext(
                     self.content,
                     **{
                         DEFAULT_FORMSET_CONTEXTNAME:
                         hg.
                         C(f"{self.formname}.{self.fieldname}.formset.empty_form"
                           )
                     },
                 ),
                 standalone=False,
             ),
             id=hg.BaseElement(
                 "empty_",
                 hg.C(f"{self.formname}.{self.fieldname}.formset.prefix"),
                 "_form",
             ),
             type="text/plain",
         ),
         hg.SCRIPT(
             mark_safe(
                 "document.addEventListener('DOMContentLoaded', e => init_formset('"
             ),
             hg.C(f"{self.formname}.{self.fieldname}.formset.prefix"),
             mark_safe("'));"),
         ),
         hg.SPAN(onload=hg.BaseElement(
             mark_safe("init_formset('"),
             hg.C(f"{self.formname}.{self.fieldname}.formset.prefix"),
             mark_safe("');"),
         ), ),
     )
예제 #26
0
def modal_add_postal():
    return layout.modal.Modal.with_ajax_content(
        heading=_("Add Address"),
        url=hg.F(
            lambda c: reverse_model(
                models.Postal,
                "ajax_add",
                query={
                    "asajax": True,
                    "person": c["object"].pk
                },
            ), ),
        submitlabel=_("Save"),
    )
예제 #27
0
 def from_link(link, **kwargs):
     buttonargs = {
         "icon": link.iconname,
         "notext": not link.label,
         "disabled": hg.F(lambda c: not link.has_permission(c["request"])),
     }
     return Button(
         *([link.label] if link.label else []),
         **{
             **buttonargs,
             **link.attributes,
             **kwargs
         },
     ).as_href(link.href)
예제 #28
0
    def __init__(self,
                 label=None,
                 help_text=None,
                 errors=None,
                 inputelement_attrs=None,
                 boundfield=None,
                 **attributes):
        super().__init__(label=label,
                         help_text=help_text,
                         errors=errors,
                         inputelement_attrs=inputelement_attrs,
                         boundfield=boundfield,
                         **attributes)

        def introspections(context):
            return json.dumps(DjangoQLSchemaSerializer().serialize(
                DjangoQLSchema(
                    hg.resolve_lazy(boundfield,
                                    context).value().queryset.model)))

        if boundfield:
            self.append(
                hg.SCRIPT(
                    hg.format(
                        """
                        document.addEventListener("DOMContentLoaded", () => DjangoQL.DOMReady(function () {{
                            new DjangoQL({{
                            introspections: {},
                            selector: 'textarea[name={}]',
                            syntaxHelp: '{}',
                            autoResize: false
                            }});
                        }}));
                    """,
                        hg.F(introspections),
                        inputelement_attrs.get("name"),
                        reverse("reporthelp"),
                        autoescape=False,
                    ), ))
            self.append(
                hg.LINK(
                    rel="stylesheet",
                    type="text/css",
                    href=staticfiles_storage.url(
                        "djangoql/css/completion.css"),
                ))
            self.append(
                hg.SCRIPT(
                    src=staticfiles_storage.url("djangoql/js/completion.js")))
예제 #29
0
    def __init__(
        self,
        label=None,
        help_text=None,
        errors=None,
        inputelement_attrs=None,
        boundfield=None,
        backend=None,
        **attributes,
    ):
        """
        :param SearchBackendConfig backend: Where and how to get search results
        """
        inputelement_attrs = inputelement_attrs or {}

        # This works inside a formset. Might need to be changed for other usages.

        widget_id = inputelement_attrs.get("id")
        resultcontainerid = hg.format("search-result-{}", widget_id)
        tag_id = hg.format("{}-tag", widget_id)
        super().__init__(
            label,
            Tag(
                hg.F(lambda c: hg.resolve_lazy(boundfield, c).field.to_python(
                    hg.resolve_lazy(boundfield, c).value()))
                if boundfield else "",
                id=tag_id,
                style=hg.If(
                    inputelement_attrs.get("value"),
                    hg.BaseElement(""),
                    hg.BaseElement("display: none;"),
                ),
                onclick="return false;",
            ),
            self.get_input_element(inputelement_attrs, errors, type="hidden"),
            Search(
                backend=backend,
                resultcontainerid=resultcontainerid,
                resultcontainer_onload_js=_resultcontainer_onload_js(
                    backend, resultcontainerid, tag_id, widget_id),
                size="lg",
                disabled=inputelement_attrs.get("disabled", False),
                widgetattributes={"id": hg.format("search__{}", widget_id)},
            ),
            help_text,
            errors,
            **hg.merge_html_attrs(attributes,
                                  {"_class": "bx--text-input-wrapper"}),
        )
예제 #30
0
def tile_with_datatable(model, queryset, columns, request):
    modal = layout.modal.Modal.with_ajax_content(
        _("Add"),
        ModelHref(
            model,
            "add",
            query=hg.F(lambda c: {
                "person": c["object"].pk,
                "asajax": True
            }),
        ),
        submitlabel=_("Save"),
    )
    return tiling_col(
        layout.datatable.DataTable.from_queryset(
            queryset,
            model=model,
            prevent_automatic_sortingnames=True,
            columns=columns,
            rowactions=[
                Link(
                    href=ModelHref(
                        model,
                        "edit",
                        kwargs={"pk": hg.C("row.pk")},
                        query={"next": request.get_full_path()},
                    ),
                    iconname="edit",
                    label=_("Edit"),
                ),
                Link(
                    href=ModelHref(
                        model,
                        "delete",
                        kwargs={"pk": hg.C("row.pk")},
                        query={"next": request.get_full_path()},
                    ),
                    iconname="trash-can",
                    label=_("Delete"),
                ),
            ],
            primary_button=layout.button.Button(_("Add"),
                                                buttontype="primary",
                                                **modal.openerattributes),
            style="border-top: none;",
        ),
        modal,
    )