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")), )), )
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, errors): super().__init__( errors, hg.DIV( hg.UL(hg.Iterator(errors or (), "error", hg.LI(hg.C("error")))), _class="bx--form-requirement", ), )
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", )
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"), )), )
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", ), )
def __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, **attributes, ): inputelement_attrs = inputelement_attrs or {} super().__init__( hg.FIELDSET( label, hg.DIV( hg.Iterator( boundfield.subwidgets, "radiobutton", RadioButton( label=Label(hg.C("radiobutton").data["label"]), inputelement_attrs=_combine_lazy_dict( _combine_lazy_dict( inputelement_attrs, { "name": hg.C("radiobutton").data["name"], "value": hg.C("radiobutton").data["value"], "checked": hg.C("radiobutton").data["selected"], }, ), hg.C("radiobutton").data["attrs"], ), ), ), _class= "bx--radio-button-group bx--radio-button-group--vertical", ), data_invalid=hg.If(getattr(errors, "condition", False), True), ), help_text, errors, **attributes, )
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 __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, **attributes, ): inputelement_attrs = inputelement_attrs or {} super().__init__( hg.FIELDSET( label, hg.Iterator( boundfield.subwidgets, "checkbox", Checkbox( label=Label(hg.C("checkbox").data["label"]), inputelement_attrs=_combine_lazy_dict( _combine_lazy_dict( inputelement_attrs, { "name": hg.C("checkbox").data["name"], "value": hg.C("checkbox").data["value"], "checked": hg.C("checkbox").data["selected"], }, ), hg.C("checkbox").data["attrs"], ), ), ), data_invalid=hg.If(getattr(errors, "condition", False), True), ), help_text, errors, **attributes, )
def __init__( self, columns: List["DataTableColumn"], row_iterator: Union[hg.Lazy, Iterable, hg.Iterator], orderingurlparameter: str = "ordering", rowvariable: str = "row", spacing: str = "default", zebra: bool = False, sticky: bool = False, **kwargs: Any, ): """A carbon DataTable element :param columns: Column definitions :param row_iterator: Iterator which yields row objects. If this is a hg.Iterator instance it will be used for the table body, otherwise a default iterator will be used to render the column cells. This can also be htmlgenerator.Lazy object which returns a Python iterator when beeing evaluated. :param rowvariable: Name of the current object passed to childrens context :param orderingurlparameter: The name of the GET query parameter which is used to set the table ordering. :param spacing: One of "default", "compact", "short", "tall", according to the carbon styles :param zebra: If True alternate row colors :param kwargs: HTML element attributes """ self.head = DataTable.headrow(columns, orderingurlparameter) if isinstance(row_iterator, hg.Iterator): self.iterator = row_iterator else: self.iterator = hg.Iterator(row_iterator, rowvariable, DataTable.row(columns)) kwargs["_class"] = kwargs.get("_class", "") + " ".join( DataTable.tableclasses(spacing, zebra, sticky)) super().__init__(hg.THEAD(self.head), hg.TBODY(self.iterator), **kwargs)
def from_queryset( queryset, # column behaviour columns: Iterable[Union[str, "DataTableColumn"]] = (), prevent_automatic_sortingnames=False, # row behaviour rowvariable="row", rowactions: Iterable[Link] = (), rowactions_dropdown=False, rowclickaction=None, # bulkaction behaviour bulkactions: Iterable[Link] = (), checkbox_for_bulkaction_name="_selected", # toolbar configuration title=None, primary_button: Optional[Button] = None, settingspanel: Any = None, pagination_config: Optional[PaginationConfig] = None, search_urlparameter: Optional[str] = None, model=None, # required if queryset is Lazy **kwargs, ): """TODO: Write Docs!!!! Yeah yeah, on it already... :param settingspanel: A panel which will be opened when clicking on the "Settings" button of the datatable, usefull e.g. for showing filter options. Currently only one button and one panel are supported. More buttons and panels could be interesting but may to over- engineered because it is a rare case and it is not difficutl to add another button by modifying the datatable after creation. """ if not isinstance(queryset, hg.Lazy): model = queryset.model if model is None: raise ValueError( "Argument for 'model' must be given if 'queryset' is of type hg.Lazy" ) columns = columns or filter_fieldlist(model, ["__all__"]) title = title or pretty_modelname(model, plural=True) if primary_button is None: primary_button = Button.from_link( Link( href=ModelHref(model, "add"), label=_("Add %s") % pretty_modelname(model), permissions=[ f"{model._meta.app_label}.add_{model._meta.model_name}" ], ), icon=Icon("add", size=20), ) if rowactions_dropdown: objectactions_menu: hg.HTMLElement = OverflowMenu( rowactions, flip=True, item_attributes={"_class": "bx--table-row--menu-option"}, ) else: objectactions_menu = hg.DIV( hg.Iterator( rowactions, "link", hg.F(lambda c: Button.from_link( c["link"], notext=True, small=True, buttontype="ghost", _class="bx--overflow-menu", ) if isinstance(c["link"], Link) else c["link"]), ), style="display: flex; justify-content: flex-end;", ) column_definitions: List[DataTableColumn] = [] for col in columns: if not (isinstance(col, DataTableColumn) or isinstance(col, str)): raise ValueError( f"Argument 'columns' needs to be of a List[str] or a List[DataTableColumn], but found {col}" ) td_attributes: Optional[dict] = None if rowclickaction and getattr(col, "enable_row_click", True): assert isinstance(rowclickaction, Link), "rowclickaction must be of type Link" td_attributes = { **aslink_attributes(rowclickaction.href), **(rowclickaction.attributes or {}), } # convert simple string (modelfield) to column definition if isinstance(col, str): col = DataTableColumn.from_modelfield( col, model, prevent_automatic_sortingnames, rowvariable, td_attributes=td_attributes, ) else: if td_attributes: col = col._replace( td_attributes=td_attributes) # type: ignore column_definitions.append(col) return DataTable( column_definitions + ([ DataTableColumn( "", objectactions_menu, td_attributes=hg.F( lambda c: { "_class": "bx--table-column-menu" if rowactions_dropdown else "" }), th_attributes=hg.F( lambda c: {"_class": "bx--table-column-menu"}), ) ] if rowactions else []), # querysets are cached, the call to all will make sure a new query is used in every request hg.F(lambda c: queryset), **kwargs, ).with_toolbar( title, helper_text=hg.format( "{} {}", hg.F(lambda c: len(hg.resolve_lazy(queryset, c)) if pagination_config is None else pagination_config. paginator.count), model._meta.verbose_name_plural, ), primary_button=primary_button, bulkactions=bulkactions, pagination_config=pagination_config, checkbox_for_bulkaction_name=checkbox_for_bulkaction_name, search_urlparameter=search_urlparameter, settingspanel=settingspanel, )
def as_list(iterable): return hg.UL(hg.Iterator(iterable, "item", hg.LI(hg.C("item"))))
def postals(): return tile_with_icon( Icon("map"), hg.H4(_("Address(es)")), R( C( hg.Iterator( hg.F(lambda c: getattr(c["object"], "core_postal_list"). order_by( django.db.models.F("valid_until").desc( nulls_first=True)).all() if hasattr(c["object"], "core_postal_list") else []), "i", display_postal(), ))), R( C( layout.button.Button( _("Hide inactive postals"), onclick="hideInactivePostals();", id="hideInactivePostalsButton", style="display: none;", icon="view--off", buttontype="ghost", ), layout.button.Button( _("Show inactive postals"), onclick="showInactivePostals();", id="showInactivePostalsButton", icon="view", buttontype="ghost", ), ), style="margin-top: 1.5rem;", ), R( C( modal_with_trigger( modal_add_postal(), layout.button.Button, _("Add"), buttontype="ghost", icon="add", ), ), ), hg.SCRIPT( mark_safe(""" function hideInactivePostals() { for(i of $$('.inactive_postal')) { $(i)._.style({display: "none"}); } $$('#hideInactivePostalsButton')._.style({display: "none"}) $$('#showInactivePostalsButton')._.style({display: "block"}) } function showInactivePostals() { for(i of $$('.inactive_postal')) { $(i)._.style({display: "block"}); } $$('#hideInactivePostalsButton')._.style({display: "block"}) $$('#showInactivePostalsButton')._.style({display: "none"}) } """)), )
def __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, # for django-form select elements use this choices=None, # for non-django-form select elements use this **attributes, # for non-django-form select elements use this ): inputelement_attrs = inputelement_attrs or {} optgroups = (_optgroups_from_choices( choices, name=inputelement_attrs.get("name"), value=inputelement_attrs.get("value"), ) if choices else _gen_optgroup(boundfield)) def countselected(context): options = [ o for og in hg.resolve_lazy(optgroups, context) for o in og[1] ] return len([o for o in options if o and o["selected"]]) searchfieldid = hg.html_id(self) super().__init__( label, hg.If( inputelement_attrs.get("disabled"), hg.DIV( hg.Iterator( optgroups, "optiongroup", hg.Iterator( hg.C("optiongroup.1"), "option", hg.If(hg.C("option.selected"), Tag(hg.C("option.label"))), ), )), hg.DIV( hg.DIV( hg.DIV( hg.F(countselected), Icon( "close", focusable="false", size=15, role="img", onclick= "clearMultiselect(this.parentElement.parentElement.parentElement)", ), role="button", _class= "bx--list-box__selection bx--list-box__selection--multi bx--tag--filter", tabindex="0", title="Clear all selected items", ), hg.INPUT( id=searchfieldid, _class="bx--text-input", placeholder="Filter...", onclick= "this.parentElement.nextElementSibling.style.display = 'block'", onkeyup= "filterOptions(this.parentElement.parentElement)", ), hg.DIV( Icon("chevron--down", size=16, role="img", focusable="false"), _class="bx--list-box__menu-icon", onclick= "this.parentElement.nextElementSibling.style.display = this.parentElement.nextElementSibling.style.display == 'none' ? 'block' : 'none';", ), role="button", _class="bx--list-box__field", tabindex="0", onload= "window.addEventListener('click', (e) => {this.nextElementSibling.style.display = 'none'})", ), hg.FIELDSET( hg.Iterator( optgroups, "optgroup", hg.Iterator( hg.C("optgroup.1"), "option", hg.DIV( hg.DIV( hg.DIV( hg.LABEL( hg.INPUT( type="checkbox", readonly=True, _class="bx--checkbox", value=hg.C("option.value"), lazy_attributes=hg.C( "option.attrs"), onchange= "updateMultiselect(this.closest('.bx--multi-select'))", checked=hg.C( "option.selected"), name=hg.C("option.name"), ), hg.SPAN( _class= "bx--checkbox-appearance"), hg.SPAN( hg.C("option.label"), _class= "bx--checkbox-label-text", ), title=hg.C("option.label"), _class="bx--checkbox-label", ), _class= "bx--form-item bx--checkbox-wrapper", ), _class= "bx--list-box__menu-item__option", ), _class="bx--list-box__menu-item", ), ), ), _class="bx--list-box__menu", role="listbox", style="display: none", ), _class=hg.BaseElement( "bx--multi-select bx--list-box bx--multi-select--selected bx--combo-box bx--multi-select--filterable", hg.If( inputelement_attrs.get("disabled"), " bx--list-box--disabled", ), ), data_invalid=hg.If(getattr(errors, "condition", None), True), ), ), help_text, errors, **hg.merge_html_attrs( attributes, { "onclick": "event.stopPropagation()", "_class": "bx--list-box__wrapper", }, ), )
def __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, inline=False, choices=None, # for non-django-form select elements use this **attributes, ): inputelement_attrs = inputelement_attrs or {} select_wrapper = hg.DIV( hg.SELECT( hg.Iterator( _optgroups_from_choices( choices, name=inputelement_attrs.get("name"), value=inputelement_attrs.get("value"), ) if choices else _gen_optgroup(boundfield), "optgroup", hg.If( hg.C("optgroup.0"), hg.OPTGROUP( hg.Iterator( hg.C("optgroup.1"), "option", hg.OPTION( hg.C("option.label"), _class="bx--select-option", value=hg.C("option.value"), lazy_attributes=hg.C("option.attrs"), ), ), _class="bx--select-optgroup", label=hg.C("optgroup.0"), ), hg.Iterator( hg.C("optgroup.1"), "option", hg.OPTION( hg.C("option.label"), _class="bx--select-option", value=hg.C("option.value"), lazy_attributes=hg.C("option.attrs"), ), ), ), ), lazy_attributes=_append_classes( inputelement_attrs, self.carbon_input_class, hg.If( getattr(errors, "condition", None), self.carbon_input_error_class, ), ), ), Icon( "chevron--down", size=16, _class="bx--select__arrow", aria_hidden="true", ), hg.If( getattr(errors, "condition", None), Icon( "warning--filled", size=16, _class="bx--select__invalid-icon", ), ), _class="bx--select-input__wrapper", data_invalid=hg.If(getattr(errors, "condition", None), True), ) super().__init__( label, hg.If( inline, hg.DIV( select_wrapper, errors, _class="bx--select-input--inline__wrapper", ), select_wrapper, ), help_text, hg.If(inline, None, errors), # not displayed if this is inline **hg.merge_html_attrs( attributes, { "_class": hg.BaseElement( "bx--select", hg.If(inline, " bx--select--inline"), hg.If(getattr(errors, "condition", None), " bx--select--invalid"), hg.If( inputelement_attrs.get("disabled"), " bx--select--disabled", ), ), }, ), )
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, )
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 default_page_layout(menu, *content): return hg.HTML( hg.HEAD( hg.TITLE( hg.F(lambda c: strip_tags( hg.render( hg.BaseElement( c.get("pagetitle", c.get("PLATFORMNAME"))), c)) + " | " + strip_tags(c.get("PLATFORMNAME")))), hg.LINK(rel="shortcut icon", href=_static("logo.png")), hg.LINK( rel="stylesheet", type="text/css", href=hg.If( settings.DEBUG, _static("css/bread-main.css"), # generate with "make css" _static( "css/bread-main.min.css"), # generate with "make css" ), media="all", ), ), hg.BODY( ShellHeader( hg.C("PLATFORMNAME"), hg.C("COMPANYNAME"), searchbar=hg.If( hg.C("request.user.is_authenticated"), hg.C("SEARCHBAR"), "", ), ), hg.If( hg.C("request.user.is_authenticated"), SideNav(menu), ), hg.DIV( hg.Iterator( hg.C("messages"), "message", ToastNotification( message=hg.F( lambda c: _(c["message"].tags.capitalize())), details=hg.C("message.message"), kind=hg.C("message.level_tag"), hidetimestamp=True, autoremove=5.0, ), ), style="position: fixed; right: 0; z-index: 999", ), hg.DIV(*content, _class="bx--content"), hg.If( settings.DEBUG, hg.BaseElement( hg.SCRIPT(src=_static("js/bliss.js")), hg.SCRIPT(src=_static("js/htmx.js")), hg.SCRIPT(src=_static("js/main.js")), hg.SCRIPT(src=_static( "design/carbon_design/js/carbon-components.js")), ), hg.SCRIPT( src=_static("js/bread.min.js")), # generate with "make js" ), hg.SCRIPT("CarbonComponents.watch(document);"), ), doctype=True, _class="no-js", lang=get_language(), )
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", )
def __init__(self, form, *children, use_csrf=True, standalone=True, **kwargs): """ form: lazy evaluated value which should resolve to the form object children: any child elements, can be formfields or other use_csrf: add a CSRF input, but only for POST submission and standalone forms standalone: if true, will add a CSRF token and will render enclosing FORM-element """ self.standalone = standalone attributes = {"method": "POST", "autocomplete": "off"} attributes.update(kwargs) if (attributes["method"].upper() == "POST" and use_csrf is not False and standalone is True): children = (CsrfToken(), ) + children if self.standalone and "enctype" not in attributes: # note: We will always use "multipart/form-data" instead of the # default "application/x-www-form-urlencoded" inside bread. We do # this because forms with file uploads require multipart/form-data. # Not distinguishing between two encoding types can save us some issues, # especially when handling files. # The only draw back with this is a slightly larger payload because # multipart-encoding takes a little bit more space attributes["enctype"] = "multipart/form-data" super().__init__( hg.WithContext( # generic errors hg.If( form.non_field_errors(), hg.Iterator( form.non_field_errors(), "formerror", InlineNotification(_("Form error"), hg.C("formerror"), kind="error"), ), ), # errors from hidden fields hg.If( form.hidden_fields(), hg.Iterator( form.hidden_fields(), "hiddenfield", hg.Iterator( hg.C("hiddenfield").errors, "hiddenfield_error", InlineNotification( _("Hidden field error: "), hg.format( "{}: {}", hg.C("hiddenfield").name, hg.C("hiddenfield_error"), ), kind="error", ), ), ), ), *children, **{DEFAULT_FORM_CONTEXTNAME: form}, ), **attributes, )