コード例 #1
0
def systeminformation(request):
    git_status = ""
    try:
        git_status = (
            subprocess.
            run(  # nosec because we have no user input to subprocess
                ["git", "log", "-n", "5", "--oneline"],
                capture_output=True,
                check=True).stdout.decode())
    except subprocess.SubprocessError as e:
        git_status = hg.BaseElement(
            "ERROR",
            hg.BR(),
            str(e),
            hg.BR(),
            getattr(e, "stdout", b"").decode(),
            hg.BR(),
            getattr(e, "stderr", b"").decode(),
        )

    return hg.BaseElement(
        hg.H3(_("System information")),
        hg.H4("Git log"),
        hg.PRE(hg.CODE(git_status)),
        hg.H4("PIP packages", style="margin-top: 2rem"),
        hg.UL(
            hg.Iterator(
                sorted([
                    "%s==%s" % (i.key, i.version)
                    for i in pkg_resources.working_set
                ]),
                "package",
                hg.LI(hg.C("package")),
            )),
    )
コード例 #2
0
def sync_help_modal():
    return bread.layout.modal.Modal(
        _("Help"),
        _("The button below is currently the only way of getting new subcribers from the mailer into our system. Is it also the only way of getting updates for subscribers that we already have in our system. This is what happens when the button is pressed:"
          ),
        hg.DIV(
            hg.UL(
                hg.LI(
                    _("For all the Subscriptions that are in the relevant segment in the Mailer, we check whether the email address is already in BasxConnect."
                      ),
                    _class="bx--list__item",
                ),
                hg.LI(
                    _("If an email address is already in BasxConnect, the downloaded subscription will be attached to the email address and override the current values in case there are any."
                      ),
                    _class="bx--list__item",
                ),
                hg.LI(
                    _("If an email address is not yet in BasxConnect, a new person will be created with that email address."
                      ),
                    _class="bx--list__item",
                ),
                _class="bx--list--unordered",
            ),
            style="padding-left: 1rem;",
        ),
        width=8,
    )
コード例 #3
0
    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"))
コード例 #4
0
 def __init__(self, errors):
     super().__init__(
         errors,
         hg.DIV(
             hg.UL(hg.Iterator(errors or (), "error",
                               hg.LI(hg.C("error")))),
             _class="bx--form-requirement",
         ),
     )
コード例 #5
0
    def __init__(
        self,
        item_iterator,
        iteratorclass=htmlgenerator.Iterator,
        menuname=None,
        direction="bottom",
        flip=False,
        item_attributes={},
        **attributes,
    ):
        # making the class inline seems better, I think we can enforce scoping the type to this instance of OverflowMenu
        class MenuItemValueProvider(htmlgenerator.ValueProvider):
            attributename = "item"

        """item_iterator: an iterable which contains bread.menu.Action objects where the onclick value is what will be passed to the onclick attribute of the menu-item (and therefore should be javascript, e.g. "window.location.href='/home'"). All three item_iterator in the tuple can be lazy objects
        iteratorclass: If the Iterator needs additional values in order to generate item_iterator it can be customized and passed here"""
        attributes["data-overflow-menu"] = True
        attributes["_class"] = attributes.get("_class",
                                              "") + " bx--overflow-menu"
        menuid = f"overflow-menu-{hash(id(self))}"
        super().__init__(
            htmlgenerator.BUTTON(
                Icon("overflow-menu--vertical", 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,
                _id=f"{menuid}-trigger",
            ),
            htmlgenerator.DIV(
                htmlgenerator.UL(
                    iteratorclass(
                        item_iterator,
                        MenuItemValueProvider.Binding(OverflowMenuItem)(
                            MenuItemValueProvider, **item_attributes),
                        MenuItemValueProvider,
                    ),
                    _class="bx--overflow-menu-options__content",
                ),
                _class="bx--overflow-menu-options" +
                (" bx--overflow-menu--flip" if flip else ""),
                tabindex="-1",
                role="menu",
                aria_labelledby=f"{menuid}-trigger",
                data_floating_menu_direction=direction,
                id=menuid,
            ),
            **attributes,
        )
        if menuname is not None:
            self[0].insert(
                0, htmlgenerator.SPAN(menuname, _class="bx--assistive-text"))
コード例 #6
0
def _display_results(query_set, highlight, onclick):
    if query_set.count() == 0:
        return _("No results")

    def _display_as_list_item(person):
        if person is None:
            # this happens if we have entries in the search-backend which have been deleted
            return hg.BaseElement()
        return hg.LI(
            hg.SPAN(
                mark_safe(highlight.highlight(person.personnumber)),
                style="width: 48px; display: inline-block",
                _class=ITEM_VALUE_CLASS,
            ),
            hg.SPAN(
                person.name,
                _class=ITEM_LABEL_CLASS,
                style="dispay:none;",
            ),
            " ",
            mark_safe(highlight.highlight(person.search_index_snippet())),
            style="cursor: pointer; padding: 8px 0;",
            onclick=onclick(person),
            onmouseenter="this.style.backgroundColor = 'lightgray'",
            onmouseleave="this.style.backgroundColor = 'initial'",
            _class=ITEM_CLASS,
        )

    result_list = [
        _display_as_list_item(search_result.object)
        for search_result in query_set[:25]
        if search_result and search_result.object
    ]

    return hg.UL(
        hg.LI(_("%s items found") % len(query_set),
              style="margin-bottom: 20px"),
        *result_list,
    )
コード例 #7
0
ファイル: tabs.py プロジェクト: basxsoftwareassociation/bread
    def __init__(
        self,
        *tabs,
        container=False,
        tabpanel_attributes=None,
        labelcontainer_attributes=None,
        **attributes,
    ):
        tabpanel_attributes = collections.defaultdict(
            str, tabpanel_attributes or {})
        labelcontainer_attributes = collections.defaultdict(
            str, labelcontainer_attributes or {})

        self.tablabels = hg.UL(_class="bx--tabs__nav bx--tabs__nav--hidden",
                               style="flex-wrap: wrap;")
        labelcontainer_attributes["_class"] += " bx--tabs" + (
            " bx--tabs--container" if container else "")
        self.labelcontainer = hg.DIV(
            hg.DIV(
                hg.A(
                    href="javascript:void(0)",
                    _class="bx--tabs-trigger-text",
                    tabindex=-1,
                ),
                _class="bx--tabs-trigger",
                style="display: none",
                tabindex=0,
            ),
            self.tablabels,
            data_tabs=True,
            **hg.merge_html_attrs(
                {
                    "onload":
                    "if( $('.bx--tabs__nav-item--selected', this) == null ) "
                    "(new CarbonComponents.Tab(this)).setActive($('.bx--tabs__nav-item', this))",
                },
                labelcontainer_attributes,
            ),
        )
        tabpanel_attributes["_class"] += " bx--tab-content"
        self.tabpanels = hg.DIV(**tabpanel_attributes)

        firsttab = None
        for i, (label, content) in enumerate(tabs):
            tabid = f"tab-{slugify(label)}-{i}"
            panelid = f"panel-{slugify(label)}-{i}"
            if firsttab is None:
                firsttab = tabid
            self.tablabels.append(
                TabLabel(
                    label,
                    tabid,
                    panelid,
                    HasBreadCookieValue("selected-tab", tabid, firsttab),
                ))
            self.tabpanels.append(
                TabPanel(
                    content,
                    panelid,
                    tabid,
                    HasBreadCookieValue("selected-tab", tabid, firsttab),
                ))
        super().__init__(
            self.labelcontainer,
            self.tabpanels,
        )
コード例 #8
0
class PersonBrowseView(BrowseView):
    columns = [
        DataTableColumn(
            layout.ObjectFieldLabel("personnumber", models.Person),
            hg.DIV(
                hg.C("row.personnumber"),
                style=hg.If(hg.C("row.deleted"),
                            "text-decoration:line-through"),
            ),
            "personnumber__int",
        ),
        DataTableColumn(
            hg.DIV(
                layout.tooltip.IconTooltip(
                    hg.UL(
                        hg.LI(hg.SPAN("●", style="color: green"), " ",
                              _("Active")),
                        hg.LI(hg.SPAN("●", style="color: red"), " ",
                              _("Inactive")),
                    ),
                    position="top",
                )),
            hg.DIV(
                "●",
                style=hg.format("color: {}",
                                hg.If(hg.C("row.active"), "green", "red")),
            ),
            "active",
        ),
        DataTableColumn(layout.ObjectFieldLabel("_type", models.Person),
                        hg.C("row._type"), "_type"),
        DataTableColumn(
            _("Name"),
            hg.DIV(
                hg.If(
                    hg.F(lambda context: type(context["row"]) == models.
                         NaturalPerson),
                    hg.C("row.last_name"),
                    hg.C("row.name"),
                ),
                style=hg.If(hg.C("row.deleted"),
                            "text-decoration:line-through"),
            ),
            "default_sorting_name",
        ),
        DataTableColumn(
            layout.ObjectFieldLabel("first_name", models.NaturalPerson),
            hg.DIV(
                hg.If(
                    hg.F(lambda context: type(context["row"]) == models.
                         NaturalPerson),
                    hg.C("row.first_name"),
                    "",
                ),
                style=hg.If(hg.C("row.deleted"),
                            "text-decoration:line-through"),
            ),
            "naturalperson__first_name",
        ),
        "primary_postal_address.address",
        "primary_postal_address.postcode",
        "primary_postal_address.city",
        "primary_postal_address.country",
        DataTableColumn(
            _("Email"),
            hg.C("row.primary_email_address.asbutton", ),
            "primary_email_address__email",
            False,
        ),
        DataTableColumn(
            layout.ObjectFieldLabel("tags", models.Person),
            hg.UL(
                hg.Iterator(hg.C("row.tags.all"), "tag",
                            layout.tag.Tag(hg.C("tag")))),
        ),
    ]
    bulkactions = (
        BulkAction(
            "add-tag",
            label=_("Add tag"),
            iconname="add",
            action=bulkaddtag,
        ),
        BulkAction(
            "remove-tag",
            label=_("Remove tag"),
            iconname="subtract",
            action=bulkremovetag,
        ),
        BulkAction(
            "delete",
            label=_("Delete"),
            iconname="trash-can",
            action=bulkdelete,
        ),
        BulkAction(
            "restore",
            label=_("Restore"),
            iconname="restart",
            action=bulkrestore,
        ),
        BulkAction(
            "excel",
            label=_("Excel"),
            iconname="download",
            action=export,
        ),
    )
    search_backend = layout.search.SearchBackendConfig(url=reverse(
        "basxconnect.core.views.person.search_person_view.searchperson"))
    rowclickaction = BrowseView.gen_rowclickaction("read")
    viewstate_sessionkey = "personbrowseview"

    class FilterForm(forms.Form):
        naturalperson = forms.BooleanField(required=False,
                                           label=_("Natural Person"))
        legalperson = forms.BooleanField(required=False,
                                         label=_("Legal Person"))
        personassociation = forms.BooleanField(required=False,
                                               label=_("Person Association"))
        naturalperson_subtypes = forms.ModelMultipleChoiceField(
            queryset=models.Term.objects.filter(
                vocabulary__slug="naturaltype"),
            widget=forms.CheckboxSelectMultiple,
            required=False,
            label="",
        )
        legalperson_subtypes = forms.ModelMultipleChoiceField(
            queryset=models.Term.objects.filter(vocabulary__slug="legaltype"),
            widget=forms.CheckboxSelectMultiple,
            required=False,
            label="",
        )
        personassociation_subtypes = forms.ModelMultipleChoiceField(
            queryset=models.Term.objects.filter(
                vocabulary__slug="associationtype"),
            widget=forms.CheckboxSelectMultiple,
            required=False,
            label="",
        )
        tags = forms.ModelMultipleChoiceField(
            queryset=models.Term.objects.filter(vocabulary__slug="tag"),
            widget=forms.CheckboxSelectMultiple,
            required=False,
        )
        preferred_language = forms.MultipleChoiceField(
            choices=settings.PREFERRED_LANGUAGES,
            widget=forms.CheckboxSelectMultiple,
            required=False,
        )
        status = forms.MultipleChoiceField(
            choices=[("active", _("Active")), ("inactive", _("Inactive"))],
            widget=forms.CheckboxSelectMultiple,
            required=False,
        )
        trash = forms.BooleanField(required=False, label=_("Trash"))

    def get_layout(self):
        self.checkboxcounterid = hg.html_id(self, "checkbox-counter")
        ret = super().get_layout()
        toolbar = list(
            ret.filter(lambda e, a: getattr(e, "attributes", {}).get(
                "_class", "") == "bx--toolbar-content"))[0]
        nfilters = self._checkbox_count()
        toolbar.insert(
            -2,
            hg.DIV(
                hg.SPAN(nfilters, id=self.checkboxcounterid),
                layout.icon.Icon(
                    "close",
                    focusable="false",
                    size=15,
                    role="img",
                    onclick=
                    f"document.location = '{self.request.path}?reset=1'",
                ),
                role="button",
                _class=
                "bx--list-box__selection bx--list-box__selection--multi bx--tag--filter",
                style="margin: auto 0.5rem;" +
                (" display: none;" if nfilters == 0 else ""),
                tabindex="0",
                title=("Reset"),
            ),
        )
        return ret

    def _filterform(self):
        return self.FilterForm({"status": ["active"], **self.request.GET})

    def _checkbox_count(self):
        counter = 0
        form = self._filterform()
        if form.is_valid():
            counter += 1 if form.cleaned_data["naturalperson"] else 0
            counter += 1 if form.cleaned_data["legalperson"] else 0
            counter += 1 if form.cleaned_data["personassociation"] else 0
            counter += form.cleaned_data["naturalperson_subtypes"].count()
            counter += form.cleaned_data["legalperson_subtypes"].count()
            counter += form.cleaned_data["personassociation_subtypes"].count()
            counter += form.cleaned_data["tags"].count()
            counter += len(form.cleaned_data["preferred_language"])
            counter += (
                1 if "inactive" in form.cleaned_data["status"] else 0
            )  # don't count the "active" checkbox, it is a permanent default
            counter += 1 if form.cleaned_data["trash"] else 0
        return counter

    def get_queryset(self):
        form = self._filterform()
        qs = super().get_queryset()
        if form.is_valid():
            ret = ((qs.filter(
                deleted=form.cleaned_data.get("trash", False))).select_related(
                    "primary_email_address", "primary_postal_address",
                    "_type").prefetch_related("tags"))
            if any([
                    form.cleaned_data[i] for i in (
                        "naturalperson",
                        "legalperson",
                        "personassociation",
                        "naturalperson_subtypes",
                        "legalperson_subtypes",
                        "personassociation_subtypes",
                    )
            ]):
                q = Q()
                for i in ("naturalperson", "legalperson", "personassociation"):
                    # setup some logic descriptors
                    maintype_selected = bool(form.cleaned_data[i])
                    subtype_selected = bool(form.cleaned_data[f"{i}_subtypes"])
                    all_subtypes_selected = bool(
                        form.cleaned_data[f"{i}_subtypes"].count() ==
                        form.fields[f"{i}_subtypes"].queryset.count())

                    # the semantics for this filter are not 100% clear
                    # there are also cases where a subtype has the wrong maintype
                    # This code tries to make the selection consistent to what a user
                    # would expect, but these expectations can still vary...
                    if maintype_selected:
                        typeq = Q(_maintype=i)
                        if subtype_selected:
                            if not all_subtypes_selected:
                                typeq &= Q(_type__in=form.
                                           cleaned_data[f"{i}_subtypes"])
                        else:
                            typeq &= ~Q(_type__in=form.fields[f"{i}_subtypes"].
                                        queryset)
                        q |= typeq
                    else:
                        q |= Q(_type__in=form.cleaned_data[f"{i}_subtypes"])
                ret = ret.filter(q)
            if form.cleaned_data.get("tags"):
                ret = ret.filter(tags__in=form.cleaned_data["tags"])
            if form.cleaned_data.get("preferred_language"):
                ret = ret.filter(preferred_language__in=form.
                                 cleaned_data["preferred_language"])
            if len(form.cleaned_data.get("status")
                   ) == 1 and not form.cleaned_data.get("trash", False):
                ret = ret.filter(
                    active=form.cleaned_data.get("status")[0] == "active")

            return ret
        return qs

    def get_settingspanel(self):
        return hg.DIV(
            layout.forms.Form(
                self._filterform(),
                hg.DIV(
                    hg.DIV(
                        hg.DIV(layout.helpers.Label(_("Person Type"))),
                        hg.DIV(
                            hg.DIV(
                                hg.DIV(
                                    layout.forms.FormField(
                                        "naturalperson",
                                        onclick=
                                        "updateCheckboxGroupItems(this.parentElement.parentElement)",
                                    ),
                                    hg.DIV(
                                        layout.forms.FormField(
                                            "naturalperson_subtypes",
                                            style="padding-left: 1rem",
                                        ),
                                        style="margin-top: -2rem",
                                    ),
                                ),
                                layout.forms.FormField(
                                    "personassociation",
                                    onclick=
                                    "updateCheckboxGroupItems(this.parentElement.parentElement)",
                                ),
                                hg.DIV(
                                    layout.forms.FormField(
                                        "personassociation_subtypes",
                                        style="padding-left: 1rem",
                                    ),
                                    style="margin-top: -2rem",
                                ),
                                style="margin-right: 16px",
                            ),
                            hg.DIV(
                                layout.forms.FormField(
                                    "legalperson",
                                    onclick=
                                    "updateCheckboxGroupItems(this.parentElement.parentElement)",
                                ),
                                hg.DIV(
                                    layout.forms.FormField(
                                        "legalperson_subtypes",
                                        style="padding-left: 1rem",
                                    ),
                                    style="margin-top: -2rem",
                                ),
                                style="margin-right: 16px",
                            ),
                            style="display: flex",
                        ),
                        style=
                        "border-right: #ccc solid 1px; margin: 0 16px 0 0",
                    ),
                    hg.DIV(
                        hg.DIV(
                            layout.forms.FormField("tags"),
                            style="margin-right: 16px",
                        ),
                        style=
                        "border-right: #ccc solid 1px; margin: 0 16px 0 0; overflow-y: scroll",
                    ),
                    hg.DIV(
                        hg.DIV(
                            layout.forms.FormField("preferred_language"),
                            style="margin-right: 16px",
                        ),
                        style=
                        "border-right: #ccc solid 1px; margin: 0 16px 0 0",
                    ),
                    hg.DIV(
                        hg.DIV(layout.forms.FormField("status"),
                               style="flex-grow: 0"),
                        hg.DIV(style="flex-grow: 1"),
                        hg.DIV(
                            layout.forms.FormField("trash"),
                            style="max-height: 2rem",
                        ),
                        style="display: flex; flex-direction: column",
                    ),
                    style=
                    "display: flex; max-height: 50vh; padding: 24px 32px 0 32px",
                ),
                hg.DIV(
                    layout.button.Button(
                        _("Cancel"),
                        buttontype="ghost",
                        onclick=
                        "this.parentElement.parentElement.parentElement.parentElement.parentElement.style.display = 'none'",
                    ),
                    layout.button.Button.from_link(
                        Link(
                            label=_("Reset"),
                            href=self.request.path + "?reset=1",
                            iconname=None,
                        ),
                        buttontype="secondary",
                    ),
                    layout.button.Button(
                        pgettext_lazy("apply filter", "Filter"),
                        type="submit",
                    ),
                    style=
                    "display: flex; justify-content: flex-end; margin-top: 24px",
                    _class="bx--modal-footer",
                ),
                method="GET",
            ),
            hg.SCRIPT(
                mark_safe("""
                    function updateCheckboxGroupItems(group) {
                        var items = $$('input[type=checkbox]', group);
                        var value = items[0].getAttribute('aria-checked');
                        value = value == 'true' ? 'true' : 'false';
                        for(var i = 1; i < items.length; ++i) {
                            new CarbonComponents.Checkbox(items[i]).setState(value);
                        }
                    }
                    function updateCheckboxCounter(group) {
                        var items = $$('input[type=checkbox]', group);
                        var count = 0;
                        for(item of items) {
                            if(!(item.name === 'status' && item.value === 'active'))
                                count += item.getAttribute('aria-checked') == 'true' ? 1 : 0
                        }
                        $('#%s').innerHTML = count;
                        $('#%s').closest('div[role=button]').style.display = count === 0 ? "none" : "flex";
                    }
                    """ % (self.checkboxcounterid, self.checkboxcounterid))),
            style="background-color: #fff",
            onclick="updateCheckboxCounter(this)",
        )
コード例 #9
0
def as_list(iterable):
    return hg.UL(hg.Iterator(iterable, "item", hg.LI(hg.C("item"))))
コード例 #10
0
def get_attribute_description_modal(obj):
    from . import datatable, modal

    columns = []
    fields = {f.name: f for f in obj._meta.get_fields()}
    for i in set(dir(obj) + list(vars(obj))):
        try:
            desc = _get_attribute_description(obj, i, fields)
            if desc is not None and desc[3]:
                f = desc[3]._meta.get_fields()
                additional_attrs = list(
                    filter(
                        None,
                        (
                            _get_attribute_description(desc[3], a, f)
                            for a in set(dir(desc[3]) + list(vars(desc[3])))
                        ),
                    )
                )
                desc = (
                    desc[0],
                    desc[1],
                    desc[2],
                    hg.BaseElement(
                        hg.UL(
                            hg.Iterator(
                                additional_attrs,
                                "attr",
                                hg.LI(
                                    hg.format("{}.{}", i, hg.C("attr.0")),
                                    style="font-weight: 700",
                                ),
                            )
                        ),
                    ),
                )
            if desc is not None:
                columns.append(desc)
        except Exception as e:
            columns.append((i, _("Unknown"), e))
    return modal.Modal(
        _("Available columns"),
        hg.DIV(
            hg.DIV(_("Bold text marks valid column names")),
            datatable.DataTable(
                columns=[
                    datatable.DataTableColumn(
                        _("Column name"),
                        hg.SPAN(hg.C("row.0"), style="font-weight: 700"),
                    ),
                    datatable.DataTableColumn(
                        _("Description"), hg.F(lambda c: c["row"][2])
                    ),
                    datatable.DataTableColumn(_("Type"), hg.F(lambda c: c["row"][1])),
                    datatable.DataTableColumn(_("Extended columns"), hg.C("row.3")),
                ],
                row_iterator=sorted(columns),
            ),
        ),
        size="lg",
    )
コード例 #11
0
 def __init__(self, errors):
     super().__init__(
         htmlgenerator.UL(*[htmlgenerator.LI(e) for e in errors]),
         _class="bx--form-requirement",
     )
コード例 #12
0
 def __init__(self, menu: "bread.menu.Menu", **kwargs):
     kwargs["_class"] = hg.BaseElement(
         kwargs.get("_class", ""),
         " bx--side-nav bx--side-nav--rail",
         hg.If(
             HasBreadCookieValue("sidenav-hidden", "true"),
             "",
             " bx--side-nav--expanded",
         ),
     )
     kwargs["data_side_nav"] = True
     super().__init__(
         hg.NAV(
             hg.UL(
                 hg.Iterator(
                     hg.F(lambda c: (i for i in sorted(
                         hg.resolve_lazy(menu, c)._registry.values())
                                     if i.has_permission(c["request"]))),
                     "menugroup",
                     hg.LI(
                         hg.If(
                             hg.F(lambda c: len(c["menugroup"].items) > 1 or
                                  c["menugroup"].force_show),
                             hg.BaseElement(
                                 hg.BUTTON(
                                     hg.DIV(
                                         Icon(hg.C("menugroup.iconname"),
                                              size=16),
                                         _class="bx--side-nav__icon",
                                     ),
                                     hg.SPAN(
                                         hg.C("menugroup.label"),
                                         _class=
                                         "bx--side-nav__submenu-title",
                                     ),
                                     hg.DIV(
                                         Icon("chevron--down", size=16),
                                         _class=
                                         "bx--side-nav__icon bx--side-nav__submenu-chevron",
                                     ),
                                     _class="bx--side-nav__submenu",
                                     type="button",
                                     aria_haspopup="true",
                                     aria_expanded=hg.If(
                                         isactive("menugroup"), "true"),
                                 ),
                                 hg.UL(
                                     hg.Iterator(
                                         hg.F(lambda c: (i for i in sorted(
                                             c["menugroup"].items) if i.
                                                         has_permission(c[
                                                             "request"]))),
                                         "menuitem",
                                         hg.LI(
                                             hg.A(
                                                 hg.SPAN(
                                                     hg.
                                                     C("menuitem.link.label"
                                                       ),
                                                     _class=
                                                     "bx--side-nav__link-text",
                                                 ),
                                                 _class=hg.BaseElement(
                                                     "bx--side-nav__link",
                                                     hg.If(
                                                         isactive(
                                                             "menuitem"),
                                                         " bx--side-nav__link--current",
                                                     ),
                                                 ),
                                                 href=hg.C(
                                                     "menuitem.link.href"),
                                             ),
                                             _class=hg.BaseElement(
                                                 "bx--side-nav__menu-item",
                                                 hg.If(
                                                     isactive("menuitem"),
                                                     " bx--side-nav__menu-item--current",
                                                 ),
                                             ),
                                         ),
                                     ),
                                     _class="bx--side-nav__menu",
                                 ),
                             ),
                             hg.A(
                                 hg.DIV(
                                     Icon(
                                         hg.
                                         C("menugroup.items.0.link.iconname"
                                           ),
                                         size=16,
                                     ),
                                     _class="bx--side-nav__icon",
                                 ),
                                 hg.SPAN(
                                     hg.C("menugroup.items.0.link.label"),
                                     _class="bx--side-nav__link-text",
                                 ),
                                 _class=hg.BaseElement(
                                     "bx--side-nav__link",
                                     hg.If(
                                         isactive("menugroup"),
                                         " bx--side-nav__link--current",
                                     ),
                                 ),
                                 href=hg.C("menugroup.items.0.link.href"),
                             ),
                         ),
                         _class=hg.BaseElement(
                             "bx--side-nav__item",
                             hg.If(isactive("menugroup"),
                                   " bx--side-nav__item--active"),
                         ),
                     ),
                 ),
                 _class="bx--side-nav__items",
             ),
             hg.FOOTER(
                 hg.BUTTON(
                     hg.DIV(
                         Icon(
                             "close",
                             size=20,
                             _class=
                             "bx--side-nav__icon--collapse bx--side-nav-collapse-icon",
                             aria_hidden="true",
                         ),
                         Icon(
                             "chevron--right",
                             size=20,
                             _class=
                             "bx--side-nav__icon--expand bx--side-nav-expand-icon",
                             aria_hidden="true",
                         ),
                         _class="bx--side-nav__icon",
                     ),
                     hg.SPAN(
                         "Toggle the expansion state of the navigation",
                         _class="bx--assistive-text",
                     ),
                     _class="bx--side-nav__toggle",
                     onclick=
                     "setBreadCookie('sidenav-hidden', getBreadCookie('sidenav-hidden', 'false') != 'true');",
                 ),
                 _class="bx--side-nav__footer",
             ),
             _class="bx--side-nav__navigation",
         ),
         **kwargs,
     )