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"}), )
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 "/"), ), )
def header(): editbutton = breadlayout.button.Button( _("Edit"), buttontype="ghost", icon="edit", notext=True, ).as_href(ModelHref.from_object(hg.C("object"), "edit")) readbutton = breadlayout.button.Button( _("Read"), buttontype="ghost", icon="view", notext=True, ).as_href(ModelHref.from_object(hg.C("object"), "read")) deletebutton = breadlayout.button.Button( _("Delete"), buttontype="tertiary", icon="trash-can", notext=True, style="border-color: red; background-color: inherit", ).as_href(ModelHref.from_object(hg.C("object"), "delete")) deletebutton[1].attributes["style"] = "fill: red; color: red;" copybutton = breadlayout.button.Button( _("Copy"), buttontype="ghost", icon="copy", notext=True, ).as_href(ModelHref.from_object(hg.C("object"), "copy")) return hg.DIV( hg.H3( hg.If( hg.C("object"), hg.BaseElement( hg.SPAN(hg.C("object")), hg.SPAN( hg.If( hg.C("request").resolver_match.url_name.endswith(".read"), editbutton, readbutton, ), copybutton, breadlayout.button.PrintPageButton(buttontype="ghost"), deletebutton, _class="no-print", style="margin-bottom: 1rem; margin-left: 1rem", width=3, ), ), hg.SPAN(hg.format(_("Add {}"), hg.C("view").model._meta.verbose_name)), ), ), style="padding-top: 1rem", )
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, )
def asbutton(self): return hg.DIV( hg.SPAN(self.email, style="margin-right: 0.25rem"), hg.SPAN(style="flex-grow: 1"), button.Button( icon="email", onclick=f"window.location = 'mailto:{self.email}';", buttontype="ghost", _class="bx--overflow-menu", ), style="display: flex; flex-wrap: nowrap; align-items: center", )
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 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 __init__( self, *children, buttontype="primary", disabled_func=lambda context: False, icon=None, notext=False, small=False, **attributes, ): self.disabled_func = disabled_func attributes["type"] = attributes.get("type", "button") attributes["tabindex"] = attributes.get("tabindex", "0") attributes["_class"] = ( attributes.get("_class", "") + f" bx--btn bx--btn--{buttontype}" ) if small: attributes["_class"] += " bx--btn--sm " if notext: attributes[ "_class" ] += " bx--btn--icon-only bx--tooltip__trigger bx--tooltip--a11y bx--tooltip--bottom bx--tooltip--align-center" children = (htmlgenerator.SPAN(*children, _class="bx--assistive-text"),) if icon: icon.attributes["_class"] = ( icon.attributes.get("_class", "") + " bx--btn__icon" ) children += (icon,) super().__init__(*children, **attributes)
def full(title, datatable, primary_button, helper_text=None): header = [hg.H4(title)] if helper_text: header.append( hg.P(helper_text, _class="bx--data-table-header__description")) return hg.DIV( hg.DIV(*header, _class="bx--data-table-header"), hg.SECTION( hg.DIV( hg.DIV(_class="bx--action-list"), hg.DIV( hg.P( hg.SPAN(0, data_items_selected=True), _(" items selected"), _class="bx--batch-summary__para", ), _class="bx--batch-summary", ), _class="bx--batch-actions", aria_label=_("Table Action Bar"), ), hg.DIV( hg.DIV(Search(), _class="bx--toolbar-search-container-expandable"), primary_button, _class="bx--toolbar-content", ), _class="bx--table-toolbar", ), datatable, _class="bx--data-table-container", data_table=True, )
def __init__(self, *labels, selected=0, **wrapperkwargs): """ labels: tuples in the form (label, button-attributes) button-attributes will be applied to the generated button for a context label data_target can be a CSS-selector of a panel for the according context """ wrapperkwargs["_class"] = (wrapperkwargs.get("_class", "") + " bx--content-switcher") super().__init__( *[ hg.BUTTON( hg.SPAN(label, _class="bx--content-switcher__label"), _class="bx--content-switcher-btn" + (" bx--content-switcher--selected" if i == selected else ""), role="tab", type="button", **kwargs, ) for i, (label, kwargs) in enumerate(labels) ], data_content_switcher=True, role="tablist", **wrapperkwargs, )
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"))
def __init__( self, label, offlabel=_("Off"), onlabel=_("On"), help_text=None, errors=None, disabled=None, required=None, widgetattributes={}, **attributes, ): attributes["_class"] = attributes.get("_class", "") + " bx--form-item" widgetattributes["_class"] = (widgetattributes.get("_class", "") + " bx--toggle-input") widgetattributes["type"] = "checkbox" widgetattributes["id"] = widgetattributes.get("id", None) or hg.html_id(self) self.input = hg.INPUT(**widgetattributes) self.label = hg.LABEL( label, hg.If(required, REQUIRED_LABEL), hg.SPAN( hg.SPAN(offlabel, _class="bx--toggle__text--off", aria_hidden="true"), hg.SPAN(onlabel, _class="bx--toggle__text--on", aria_hidden="true"), _class="bx--toggle__switch", ), _class=hg.BaseElement( "bx--label bx--toggle-input__label", hg.If(disabled, " bx--label--disabled"), ), _for=widgetattributes["id"], ) super().__init__( self.input, self.label, HelpText(help_text), ErrorList(errors), **attributes, )
def edit_postal_button(modal): return hg.SPAN( layout.button.Button( "", buttontype="ghost", icon="edit", **modal.openerattributes, ), modal, )
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"))
def __init__(self, label, status, optional=False, tooltip=None, disabled=False, **kwargs): assert (status in ProgressStep.STATUS ), f"{status} must be one of {ProgressStep.STATUS}" kwargs["class"] = (kwargs.get("class", "") + f" bx--progress-step bx--progress-step--{status}") if disabled: kwargs["aria_disabled"] = "true" kwargs["class"] += " bx--progress-step--disabled" elements = [ Icon(ProgressStep.STATUS[status], size=16), hg.P(label, tabindex=0, _class="bx--progress-label"), hg.SPAN(_class="bx--progress-line"), ] if optional: elements.insert( 2, hg.P(_("Optional"), _class="bx--progress-optional")) if tooltip is not None: tooltipid = hg.html_id(tooltip, "tooltip-label") elements[1]["aria-describedby"] = tooltipid elements.insert( 2, hg.DIV( hg.SPAN(_class="bx--tooltip__caret"), hg.P(tooltip, _class="bx--tooltip__text"), id=tooltipid, role="tooltip", data_floating_menu_direction="bottom", _class="bx--tooltip", data_avoid_focus_on_open=True, ), ) super().__init__(*elements, **kwargs)
def profile_field(fieldname): return R( C( hg.SPAN( layout.ObjectFieldLabel(fieldname), style="font-weight: 700", ), width=4, ), C(layout.ObjectFieldValue(fieldname)), style="margin-bottom: 2rem", )
def person_in_relationship( header: str, field_name: str, get_person: Callable[[Relationship], Person], ) -> DataTableColumn: return DataTableColumn( header, hg.SPAN( person_name(field_name), person_number_in_brackets(field_name), **attributes_for_link_to_person(get_person), ), )
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("');"), ), ), )
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, )
def __init__(self, header: Any, content: Any, **attributes): """ Parameters ---------- header : Any the header of the tile that is not hidden under the fold content : Any the hidden content that only visible when user click to unfold the tile **attributes : optional keyword arguments representing the specific HTML attributes for the tile """ super().__init__( hg.BUTTON(Icon("chevron--down", size="16"), _class="bx--tile__chevron"), hg.DIV( hg.SPAN( header, data_tile_atf=True, _class="bx--tile-content__above-the-fold", ), hg.SPAN( content, _class="bx--tile-content__below-the-fold", onclick="event.stopPropagation();", style="cursor: initial", ), _class="bx--tile-content", ), **hg.merge_html_attrs( attributes, { "_class": "bx--tile bx--tile--expandable", "data_tile": "expandable", "tabindex": "0", }, ))
def __init__(self, *label, can_delete=False, tag_color=None, **kwargs): kwargs.setdefault( "type", "button" ) # prevents this from trying to submit a form when inside a FORM element kwargs["_class"] = (kwargs.get("_class", "") + " bx--tag" + (" bx--tag--filter" if can_delete else "") + (f" bx--tag--{tag_color}" if tag_color else "")) if can_delete: kwargs.setdefault("title", _("Remove")) super().__init__( hg.SPAN(*label, _class="bx--tag__label"), *([Icon("close", size=16)] if can_delete else []), **kwargs, )
def editperson_head(request): return hg.BaseElement( R( C( hg.H3( hg.SPAN( hg.C("object"), style=hg.If(hg.C("object.deleted"), "text-decoration: line-through"), ), editperson_toolbar(request), ), width=12, ), style="padding-top: 1rem", ), )
def __init__( self, description: Any, icon: Union[Icon, str] = "information", align: str = "center", position: str = "bottom", **attributes, ): """ Parameters ---------- description : Any One line of string describing an icon. icon : Icon, str, optional Specify an icon for the tooltip. The default value is 'information'. align : str, optional Specify where the arrow pointing the icon should align to the text. It can be either start, center, or end. The default value is 'center'. position : str Where should the tooltip appear besides the tooltip icon. It can be either top, left, right, or bottom. The default value is 'bottom'. """ tooltip_attributes = { "_class": ( "bx--tooltip__trigger " "bx--tooltip--a11y " "bx--tooltip--%s " "bx--tooltip--align-%s" ) % (position, align), "data_tooltip_icon": True, } tooltip_attributes = hg.merge_html_attrs(tooltip_attributes, attributes) icon = _get_icon(icon) super().__init__( hg.DIV( hg.SPAN(description, _class="bx--assistive-text"), icon, **tooltip_attributes, ) )
def as_download(value, label=None): if not value: return CONSTANTS[None] if not value.storage.exists(value.name): return hg.SMALL(hg.EM(_("File not found"))) if label is None: label = hg.SPAN(os.path.basename(value.name)) return hg.A( layout.icon.Icon( "launch", size=16, style="vertical-align: middle; margin-right: 0.25rem;", ), label, newtab=True, href=value.url, style="margin-right: 0.5rem; margin-left: 0.5rem", onclick="event.stopPropagation();", )
def __init__( self, columns, row_iterator, valueproviderclass=hg.ValueProvider, spacing="default", zebra=False, ): """columns: tuple(header_expression, row_expression) if the header_expression/row_expression has an attribute td_attributes it will be used as attributes for the TH/TD elements (necessary because sometimes the content requires additional classes on the parent element) spacing: one of "default", "compact", "short", "tall" valueproviderclass: A class which implements ValueProvider which will be passed to the Iterator """ assert spacing in ["default", "compact", "short", "tall"] classes = ["bx--data-table"] if spacing != "default": classes.append(f"bx--data-table--{spacing}") if zebra: classes.append("bx--data-table--zebra") super().__init__( hg.TABLE( hg.THEAD( hg.TR(*[ hg.TH( hg.SPAN( column[0], _class="bx--table-header-label", ), **getattr(column[1], "td_attributes", {}), ) for column in columns ])), hg.TBODY( hg.Iterator( row_iterator, hg.TR(*[ hg.TD(column[1], **getattr(column[1], "td_attributes", {})) for column in columns ]), valueproviderclass, )), _class=" ".join(classes), ))
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, )
def __init__( self, *children, buttontype="primary", icon=None, notext=False, small=False, **attributes, ): attributes["type"] = attributes.get("type", "button") attributes["tabindex"] = attributes.get("tabindex", "0") attributes["_class"] = hg.BaseElement( attributes.get("_class", ""), f" bx--btn bx--btn--{buttontype}", hg.If( hg.F(lambda c: hg.resolve_lazy( self.attributes.get("disabled", False), c)), " bx--btn--disabled", ), ) if small: attributes["_class"] += " bx--btn--sm " if notext or not children: attributes["_class"] += " bx--btn--icon-only" if children: attributes["_class"] += ( " bx--btn--icon-only bx--tooltip__trigger bx--tooltip--a11y " "bx--tooltip--bottom bx--tooltip--align-center") children = (hg.SPAN(*children, _class="bx--assistive-text"), ) if icon is not None: if isinstance(icon, str): icon = Icon(icon) if isinstance(icon, Icon): icon.attributes["_class"] = ( icon.attributes.get("_class", "") + " bx--btn__icon") children += (icon, ) super().__init__(*children, **attributes)
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)", )
def with_toolbar( self, title: Any, helper_text: Any = None, primary_button: Optional[Button] = None, bulkactions: Iterable[Link] = (), pagination_config: Optional[PaginationConfig] = None, checkbox_for_bulkaction_name: str = "_selected", search_urlparameter: Optional[str] = None, settingspanel: Any = None, ): """ wrap this datatable with title and toolbar title: table title helper_text: sub title primary_button: bread.layout.button.Button instance bulkactions: List of bread.utils.links.Link instances. Will send a post or a get (depending on its "method" attribute) to the target url the sent data will be a form with the selected checkboxes as fields if the head-checkbox has been selected only that field will be selected. """ checkboxallid = f"datatable-check-{hg.html_id(self)}" header: List[hg.BaseElement] = [ hg.H4(title, _class="bx--data-table-header__title") ] if helper_text is not None: header.append( hg.P(helper_text, _class="bx--data-table-header__description")) bulkactionlist = [] for link in bulkactions: bulkactionlist.append( Button( link.label, icon=link.iconname, onclick=hg.BaseElement( "submitbulkaction(this.closest('[data-table]'), '", link.href, "', method='GET')", ), ), ) if bulkactions: self.head.insert( 0, hg.TH( hg.INPUT( data_event="select-all", id=checkboxallid, _class="bx--checkbox", type="checkbox", name=checkbox_for_bulkaction_name, value="all", ), hg.LABEL( _for=checkboxallid, _class="bx--checkbox-label", ), _class="bx--table-column-checkbox", ), ) self.iterator[0].insert( 0, hg.TD( hg.INPUT( data_event="select", id=hg.BaseElement( checkboxallid, "-", hg.C(self.iterator.loopvariable + "_index"), ), _class="bx--checkbox", type="checkbox", name=checkbox_for_bulkaction_name, value=hg.If( hg.F(lambda c: hasattr( c[self.iterator.loopvariable], "pk")), hg.C(f"{self.iterator.loopvariable}.pk"), hg.C(f"{self.iterator.loopvariable}_index"), ), ), hg.LABEL( _for=hg.BaseElement( checkboxallid, "-", hg.C(self.iterator.loopvariable + "_index"), ), _class="bx--checkbox-label", aria_label="Label name", ), _class="bx--table-column-checkbox", ), ) return hg.DIV( hg.DIV(*header, _class="bx--data-table-header"), hg.SECTION( hg.DIV( hg.DIV( *(bulkactionlist + [ Button( _("Cancel"), data_event="action-bar-cancel", _class="bx--batch-summary__cancel", ) ]), _class="bx--action-list", ), hg.DIV( hg.P( hg.SPAN(0, data_items_selected=True), _(" items selected"), _class="bx--batch-summary__para", ), _class="bx--batch-summary", ), _class="bx--batch-actions", aria_label=_("Table Action Bar"), ), hg.DIV( searchbar(search_urlparameter) if search_urlparameter else None, Button( icon="settings--adjust", buttontype="ghost", onclick=""" let settings = this.parentElement.parentElement.parentElement.querySelector('.settingscontainer'); settings.style.display = settings.style.display == 'block' ? 'none' : 'block'; event.stopPropagation()""", ) if settingspanel else None, primary_button or None, _class="bx--toolbar-content", ), _class="bx--table-toolbar", ), hg.DIV( hg.DIV( hg.DIV( settingspanel, _class="bx--tile raised", style="margin: 0; padding: 0", ), _class="settingscontainer", style= "position: absolute; z-index: 999; right: 0; display: none", onload= "document.addEventListener('click', (e) => {this.style.display = 'none'})", ), style="position: relative", onclick="event.stopPropagation()", ), self, Pagination.from_config(pagination_config) if pagination_config else None, _class="bx--data-table-container", data_table=True, )
def get_layout(self): return hg.BaseElement( hg.H4(_("Manage Profile")), hg.DIV( layout.grid.Grid( R( C( hg.H4( layout.icon.Icon("user--profile"), style="text-align: center", ), width=1, ), C( hg.H4(_("Personal Information"), style="margin-bottom: 3rem"), profile_field("first_name"), profile_field("last_name"), R( C( hg.SPAN( _("Preferred Language"), style="font-weight: 700", ), width=4, ), C( hg.F(lambda c: get_language_info( c["request"].user.preferences.get( "general__preferred_language") or get_language())["name_translated"])), style="margin-bottom: 2rem", ), R( C( hg.SPAN( _("Timezone"), style="font-weight: 700", ), width=4, ), C( hg.F(lambda c: c["request"].user. preferences.get("general__timezone") or get_current_timezone())), style="margin-bottom: 2rem", ), layout.modal.modal_with_trigger( layout.modal.Modal.with_ajax_content( _("Personal Information"), reverse("userprofile.personal", query={"asajax": True}), submitlabel=_("Save"), ), layout.button.Button, _("Edit"), buttontype="tertiary", icon="edit", style="margin-top: 1rem", ), width=7, ), C( hg.H4( layout.icon.Icon("password"), style="text-align: center", ), width=1, ), C( hg.H4(_("Login"), style="margin-bottom: 3rem"), profile_field("username"), profile_field("email"), profile_field_password("password"), layout.modal.modal_with_trigger( layout.modal.Modal.with_ajax_content( _("Login"), reverse("userprofile.login", query={"asajax": True}), submitlabel=_("Save"), ), layout.button.Button, _("Edit"), buttontype="tertiary", icon="edit", style="margin-top: 1rem", ), width=7, ), style="margin-bottom: 2rem; margin-top: 2rem", ), R( C( hg.H4( layout.icon.Icon("virtual-column--key"), style="text-align: center", ), width=1, ), C( hg.H4(_("Permissions")), profile_field_checkbox("is_active"), profile_field_checkbox("is_superuser"), profile_field_checkbox("is_staff"), layout.modal.modal_with_trigger( layout.modal.Modal.with_ajax_content( _("Permissions"), reverse( "userprofile.permissions", query={"asajax": True}, ), submitlabel=_("Save"), ), layout.button.Button, _("Edit"), buttontype="tertiary", icon="edit", style="margin-top: 1rem", disabled=not self.request.user.is_superuser, ), width=7, ), C(width=1), C( hg.DIV( layout.toggle.Toggle( _("Developer Mode"), _("Disabled"), _("Enabled"), help_text=hg.SPAN( _( "Warning: This is a dangerous option!", ), hg.BR(), _( "Enable it only if you know what you are doing!", ), style="color: red", ), widgetattributes={ "checked": hg. C(f"request.session.{layout.DEVMODE_KEY}" ), }, onclick= f"fetch('{reverse('devmode', kwargs={'enable': not self.request.session.get(layout.DEVMODE_KEY, False)})}').then((resp) => {{}}).then(() => location.reload(true)); return false;", style="margin-bottom: 0", ), style="align-self: flex-end", ), width=7, style="display: flex; justify-content: flex-end", ), style="margin-bottom: 2rem; margin-top: 2rem", ), ), _class="bx--tile", ), )