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, title, subtitle, kind="info", lowcontrast=False, hideclosebutton=False, hidetimestamp=False, **attributes, ): """ kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt" """ assert ( kind in KIND_ICON_MAPPING ), f"kind '{kind}' does not exists, must be one of {KIND_ICON_MAPPING.keys()}" self.hidetimestamp = hidetimestamp attributes["data-notification"] = True attributes["_class"] = ( attributes.get("_class", "") + f" bx--toast-notification bx--toast-notification--{kind}" ) if lowcontrast: attributes["_class"] += " bx--toast-notification--low-contrast" attributes["role"] = "alert" timestampelem = ( [ htmlgenerator.P( _("Time stamp "), _class="bx--toast-notification__caption" ) ] if not hidetimestamp else [] ) children = [ Icon( KIND_ICON_MAPPING[kind], size=20, _class="bx--toast-notification__icon", ), htmlgenerator.DIV( htmlgenerator.H3(title, _class="bx--toast-notification__title"), htmlgenerator.P(subtitle, _class="bx--toast-notification__subtitle"), *timestampelem, _class="bx--toast-notification__details", ), ] if not hideclosebutton: children.append( htmlgenerator.BUTTON( Icon("close", size=20, _class="bx--toast-notification__close-icon"), data_notification_btn=True, _class="bx--toast-notification__close-button", aria_label="close", ) ) super().__init__(*children, **attributes)
def error_layout( request, status_code: int, status_title: str, description: Union[str, hg.BaseElement], exception_detail: str = None, ): return hg.BaseElement( hg.H1(f"{status_code}: {status_title}", style="margin-bottom: 1rem;"), hg.P( description, style="margin-bottom: 1rem;", ), hg.If( bool(exception_detail), hg.BaseElement( hg.H4("Detail", style="margin-bottom: 1rem;"), hg.DIV( exception_detail, style=( "border: 1px solid grey;" "padding: 1rem;" "font-family: monospace;" "margin-bottom: 1rem;" ), ), ), ), Button.from_link( Link( label=_("Back to homepage"), href=hg.F(lambda c: c["request"].META["SCRIPT_NAME"] or "/"), ) ), )
def maintenance_search_reindex(request): class ReindexForm(forms.Form): confirmed = forms.BooleanField( widget=forms.HiddenInput, initial=True, ) form: ReindexForm received_post = False logmsg: str = None if request.method == "POST": form = ReindexForm(request.POST) if form.is_valid() and "confirmed" in form.cleaned_data: received_post = True out = StringIO() management.call_command("rebuild_index", interactive=False, stdout=out) logmsg = out.getvalue().replace("\n", "<br>") # try adding some message here. messages.info(request, _("Rebuilt search index")) if not received_post: form = ReindexForm() reindex_btn = Form( form, FormField("confirmed"), Button( _("Rebuild"), type="submit", style="margin-bottom: 1rem;", ), ) return hg.BaseElement( hg.P( _(("After certain kind of database updates the search index may become outdated. " "You can reindex the search index by clicking the button below. " "This should fix most problems releated to search fields.")), style="margin-bottom: 1rem;", ), reindex_btn, hg.If( logmsg, hg.BaseElement( hg.H6(_("Log from the server"), style="margin-bottom: 0.75rem;"), hg.SAMP(hg.mark_safe(logmsg), style="font-family: monospace;"), ), ), )
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 __init__( self, title, subtitle, action=None, kind="info", lowcontrast=False, hideclosebutton=False, **attributes, ): """ action: typle with (action_name, javascript_onclick), e.g. ("Open Google", "windows.location='https://google.com'") kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt" """ assert ( kind in KIND_ICON_MAPPING ), f"kind '{kind}' does not exists, must be one of {KIND_ICON_MAPPING.keys()}" assert action is None or ( len(action) == 2 ), "action must be a tuple with: (action_name, javascript_onclick)" attributes["data-notification"] = True attributes["_class"] = ( attributes.get("_class", "") + f" bx--inline-notification bx--inline-notification--{kind}" ) if lowcontrast: attributes["_class"] += " bx--inline-notification--low-contrast" attributes["role"] = "alert" children = [ htmlgenerator.DIV( Icon( KIND_ICON_MAPPING[kind], size=20, _class="bx--inline-notification__icon", ), htmlgenerator.DIV( htmlgenerator.P(title, _class="bx--inline-notification__title"), htmlgenerator.P( subtitle, _class="bx--inline-notification__subtitle" ), _class="bx--inline-notification__text-wrapper", ), _class="bx--inline-notification__details", ), ] if action is not None: children.append( Button( action[0], onclick=action[1], type="ghost", small=True, _class="bx--inline-notification__action-button", ) ) if not hideclosebutton: children.append( htmlgenerator.BUTTON( Icon( "close", size=20, _class="bx--inline-notification__close-icon" ), data_notification_btn=True, _class="bx--inline-notification__close-button", aria_label="close", ) ) super().__init__(*children, **attributes)
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 __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, **attributes, ): inputelement_attrs = inputelement_attrs or {} uploadbutton = hg.LABEL( hg.SPAN(_("Select file"), role="button"), tabindex=0, _class=hg.BaseElement( "bx--btn bx--btn--tertiary", hg.If(inputelement_attrs.get("disabled"), " bx--btn--disabled"), ), data_file_drop_container=True, disabled=inputelement_attrs.get("disabled"), data_invalid=hg.If(getattr(errors, "condition", False), True), _for=inputelement_attrs.get("id"), ) input = self.get_input_element( inputelement_attrs, errors, onload=""" that = this; document.addEventListener('change', (e) => { that.parentElement.querySelector('[data-file-container]').innerHTML = ''; var widget = new CarbonComponents.FileUploader(that.parentElement); widget._displayFilenames(); widget.setState('edit'); }); """, ) # we can only clear the field if it originates form a django field # otherwise it has no use clearbox = None if boundfield: checkbox_name = hg.F( lambda c: hg.resolve_lazy(boundfield, c).field.widget. clear_checkbox_name(hg.resolve_lazy(boundfield, c).html_name)) checkbox_id = hg.F( lambda c: hg.resolve_lazy(boundfield, c).field.widget. clear_checkbox_id(hg.resolve_lazy(checkbox_name, c))) clearbox = hg.If( self.clearable, hg.INPUT( type="checkbox", name=checkbox_name, id=checkbox_id, style="display: none", ), ) # clearbutton is always used, to allow clearing a just selected field in the browser clearbutton = hg.If( self.clearable, hg.SPAN( hg.BUTTON( Icon("close", size=16), _class="bx--file-close", type="button", aria_label="close", onclick=hg.If( clearbox, hg.format("$('#{}').checked = 'checked';", checkbox_id), ), ), data_for=inputelement_attrs.get("id"), _class="bx--file__state-container", ), ) super().__init__( label, hg.DIV( uploadbutton, input, clearbox, hg.DIV( hg.If( inputelement_attrs.get("value"), hg.SPAN( hg.P( hg.If( hg.F(lambda c: hasattr( hg.resolve_lazy(inputelement_attrs, c). get("value").file, "name", )), hg.A( hg.F(lambda c: os.path.basename( hg.resolve_lazy( inputelement_attrs, c).get( "value").name)), href=hg.F(lambda c: settings.MEDIA_URL + hg.resolve_lazy( inputelement_attrs, c ).get("value").name), ), hg.F(lambda c: os.path.basename( hg.resolve_lazy(inputelement_attrs, c). get("value").name)), ), _class="bx--file-filename", ), clearbutton, _class="bx--file__selected-file", ), ), data_file_container=True, _class="bx--file-container", ), help_text, errors, _class="bx--file", data_file=True, ), **attributes, )
def __init__( self, label: Any, body: Any, heading: Optional[Any] = None, link: Optional[Link] = None, button: Optional[Button] = None, icon: Union[Icon, str] = "information", menudirection: str = "bottom", **attributes, ): """ Parameters ---------- label : Any Label for a tooltip body : Any The content inside a tooltip heading : Any, optional A heading for a tooltip. link : Link, optional Bread's Link NamedTuple in case you want to bring users to a specific webpage. button : Button, optional Insert the Bread's Button onto the tooltip. icon : Icon, str, optional Specify an icon for the tooltip The default value is "information". menudirection : str Where should the tooltip appear besides the tooltip icon. It can be either top, left, right, or bottom. The default value is "bottom". """ base_class = "bx--tooltip" footer_elements: typing.List[hg.BaseElement] = [] if link: footer_elements.append(hg.A(link.label, href=link.href, _class="bx--link")) if button: footer_elements.append(button) base_id = hg.html_id(self, base_class + "-id") label_id = f"{base_id}-label" trigger_attributes = { "_class": base_class + "__trigger", "aria_controls": base_id, "aria_expanded": "false", "aria_haspopup": "true", "aria_labelledby": label_id, "data_tooltip_target": "#" + base_id, "data_tooltip_trigger": True, } tooltip_attributes = { "_class": base_class, "aria_hidden": "true", "data_floating_menu_direction": menudirection, "id": base_id, } tooltip_content_attributes = { "_class": base_class + "__content", "aria_describedby": base_id + "-body", "aria_labelledby": label_id, "role": "dialog", "tabindex": "-1", } tooltip_attributes = hg.merge_html_attrs(tooltip_attributes, attributes) icon = _get_icon(icon) super().__init__( # tooltip label hg.DIV( label, # cannot use bread's Button class because # only the class bx--tooltip__trigger can be used hg.DIV( icon, **trigger_attributes, ), id=label_id, _class=base_class + "__label", ), # the real tooltip goes here hg.DIV( hg.SPAN(_class=base_class + "__caret"), hg.DIV( hg.If( bool(heading), hg.H4( heading, id=base_id + "-heading", _class=base_class + "__heading", ), ), hg.P(body, id=base_id + "-body"), hg.If( len(footer_elements) > 0, hg.DIV( *footer_elements, _class=base_class + "__footer", ), ), **tooltip_content_attributes, ), hg.SPAN(tabindex="0"), **tooltip_attributes, ), )
def __init__( self, message, details, kind="info", lowcontrast=False, hideclosebutton=False, hidetimestamp=False, autoremove=4.0, **attributes, ): """ kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt" autoremove: remove notification after ``autoremove`` seconds """ self.hidetimestamp = hidetimestamp attributes["data-notification"] = True attributes["_class"] = hg.BaseElement( attributes.get("_class", ""), " bx--toast-notification bx--toast-notification--", kind, hg.If(lowcontrast, " bx--toast-notification--low-contrast"), ) attributes["role"] = "alert" attributes["style"] = hg.BaseElement( attributes.get("style", ""), ";opacity: 0; animation: ", hg.F(lambda c: autoremove * (c["message_index"] + 1)), "s ease-in-out notification", ) attributes["onload"] = hg.BaseElement( attributes.get("onload", ""), ";setTimeout(() => this.style.display = 'None', ", hg.F(lambda c: (autoremove * 1000 * (c["message_index"] + 1))), ")", ) timestampelem = ([ hg.P( _("Time stamp"), " ", _class="bx--toast-notification__caption") ] if not hidetimestamp else []) children = [ Icon( hg.F(lambda c: KIND_ICON_MAPPING[hg.resolve_lazy(kind, c)]), size=20, _class="bx--toast-notification__icon", ), hg.DIV( hg.DIV(message, _class="bx--toast-notification__title"), hg.DIV(details, _class="bx--toast-notification__subtitle"), *timestampelem, _class="bx--toast-notification__details", ), ] children.append( hg.If( hideclosebutton, None, hg.BUTTON( Icon("close", size=20, _class="bx--toast-notification__close-icon"), data_notification_btn=True, _class="bx--toast-notification__close-button", aria_label="close", ), )) super().__init__(*children, **attributes)
def __init__( self, message, details, action=None, kind="info", lowcontrast=False, hideclosebutton=False, **attributes, ): """ action: typle with (action_name, javascript_onclick), e.g. ("Open Google", "windows.location='https://google.com'") kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt" """ if action is not None and (len(action) != 2): raise ValueError( "action must be a tuple with: (action_name, javascript_onclick)" ) attributes["data-notification"] = True attributes["_class"] = hg.BaseElement( attributes.get("_class", ""), " bx--inline-notification bx--inline-notification--", kind, hg.If(lowcontrast, " bx--inline-notification--low-contrast"), ) attributes["role"] = "alert" children = [ hg.DIV( Icon( hg.F( lambda c: KIND_ICON_MAPPING[hg.resolve_lazy(kind, c)]), size=20, _class="bx--inline-notification__icon", ), hg.DIV( hg.P(message, _class="bx--inline-notification__title"), hg.P(details, _class="bx--inline-notification__subtitle"), _class="bx--inline-notification__text-wrapper", ), _class="bx--inline-notification__details", ), ] if action is not None: children.append( Button( action[0], onclick=action[1], buttontype="ghost", small=True, _class="bx--inline-notification__action-button", )) children.append( hg.If( hideclosebutton, None, hg.BUTTON( Icon("close", size=20, _class="bx--inline-notification__close-icon"), data_notification_btn=True, _class="bx--inline-notification__close-button", aria_label="close", ), )) super().__init__(*children, **attributes)
def __init__( self, heading, *content, label="", size="sm", buttons=(), id=None, with_form=False, **attributes, ): """ heading: Modal title content: content elements of the modal, can be empty label: small informative label above heading size: one of ["xs", "sm", "md", "lg"] buttons: buttons displayed on bottom of modal, last button has default focus the attribute "data_modal_close" can be set on an button in order to make it a cancel button In order to open the modal just pass self.openerattributes as kwargs to another html element, e.g. a button modal = modal.Modal("My Modal", "Hello world") button.Button("Model", **modal.openerattributes) """ if size not in Modal.SIZES: raise ValueError( f"argument 'size' has value {size} but needs to be one of {Modal.SIZES}" ) if buttons and "data_modal_primary_focus" not in buttons[-1].attributes: buttons[-1].attributes["data_modal_primary_focus"] = True attributes["_class"] = attributes.get("_class", "") + " bx--modal" self.id = hg.html_id(self, prefix="modal-") if id is None else id self.openerattributes = { "data_modal_target": hg.format("#{}", self.id) } self.contentcontainer = hg.DIV( *content, _class="bx--modal-content" + (" bx--modal-content--with-form" if with_form else ""), tabindex=0, ) super().__init__( hg.DIV( hg.DIV( hg.P( label, _class="bx--modal-header__label", ), hg.P( heading, _class="bx--modal-header__heading", ), hg.BUTTON( Icon( "close", size=16, _class="bx--modal-close__icon", aria_hidden="true", ), _class="bx--modal-close", type="button", data_modal_close=True, ), _class="bx--modal-header", ), self.contentcontainer, hg.DIV(_class="bx--modal-content--overflow-indicator"), hg.DIV( *buttons, _class="bx--modal-footer", ) if buttons else "", _class=f"bx--modal-container bx--modal-container--{size}", ), data_modal=True, id=self.id, role="dialog", aria_modal="true", tabindex="-1", **attributes, )