def relationshipstab(request): person = get_object_or_404(Person, pk=request.resolver_match.kwargs["pk"]) modal_from = modal_add_relationship_from(person) modal_to = modal_add_relationship_to(person) return layout.tabs.Tab( _("Relationships"), utils.grid_inside_tab( R( utils.tiling_col( relationships_datatable( request, title=_("Relationships to person"), queryset=hg.F( lambda c: c["object"].relationships_from.all()), primary_button=button_add_relationship_to(modal_to), ), modal_to, hg.DIV(style="margin-top: 4rem;"), relationships_datatable( request, title=_("Relationships from person"), queryset=hg.F( lambda c: c["object"].relationships_to.all()), primary_button=button_add_relationship_from( modal_from), ), modal_from, )), ), )
def get_layout(self): form_fields = [layout.forms.FormField(field) for field in [*self.fields]] + [ hg.If( hg.F( lambda c: c["object"].person.primary_email_address and c["object"].person.primary_email_address.pk != c["object"].pk ), layout.forms.FormField("is_primary"), "", ), hg.If( hg.F( lambda c: apps.is_installed("basxconnect.mailer_integration") and hasattr(c["object"], "subscription") ), layout.forms.FormField("propagate_change_to_mailer"), "", ), ] return layout.grid.Grid( hg.H3(_("Edit Email")), layout.grid.Row( layout.grid.Col( layout.forms.Form( hg.C("form"), hg.DIV(*form_fields), layout.forms.helpers.Submit(), ), width=4, ) ), gutter=False, )
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 editperson_toolbar(request): deletebutton = layout.button.Button( _("Delete"), buttontype="ghost", icon="trash-can", notext=True, **layout.aslink_attributes( hg.F(lambda c: layout.objectaction(c["object"], "delete"))), ) restorebutton = layout.button.Button( _("Restore"), buttontype="ghost", icon="undo", notext=True, **layout.aslink_attributes( hg.F(lambda c: layout.objectaction( c["object"], "delete", query={"restore": True}))), ) copybutton = layout.button.Button( _("Copy"), buttontype="ghost", icon="copy", notext=True, **layout.aslink_attributes( hg.F(lambda c: layout.objectaction(c["object"], "copy"))), ) return hg.SPAN( hg.If(hg.C("object.deleted"), restorebutton, deletebutton), copybutton, layout.button.PrintPageButton(buttontype="ghost"), _class="no-print", style="margin-bottom: 1rem; margin-left: 1rem", width=3, )
def __init__( self, links, menuiconname="overflow-menu--vertical", menuname=None, direction="bottom", flip=False, item_attributes={}, **attributes, ): attributes["data-overflow-menu"] = True attributes["_class"] = attributes.get("_class", "") + " bx--overflow-menu" item_attributes["_class"] = (item_attributes.get("_class", "") + " bx--overflow-menu-options__option") menuid = hg.F(lambda c: OverflowMenu.MENUID_TEMPLATE % hg.html_id( c.get("row", self))) triggerid = hg.F(lambda c: (OverflowMenu.MENUID_TEMPLATE % hg.html_id( c.get("row", self))) + "-trigger") super().__init__( hg.BUTTON( Icon(menuiconname, size=16), _class="bx--overflow-menu__trigger" + (" bx--tooltip__trigger bx--tooltip--a11y bx--tooltip--right bx--tooltip--align-start" if menuname is not None else ""), aria_haspopup="true", aria_expanded="false", aria_controls=menuid, type="button", id=triggerid, ), hg.DIV( hg.UL( hg.Iterator( links, "link", hg.LI( hg.F(asoverflowbutton), **item_attributes, ), ), _class="bx--overflow-menu-options__content", ), _class="bx--overflow-menu-options" + (" bx--overflow-menu--flip" if flip else ""), tabindex="-1", role="menu", aria_labelledby=triggerid, data_floating_menu_direction=direction, id=menuid, ), **attributes, ) if menuname is not None: self[0].insert(0, hg.SPAN(menuname, _class="bx--assistive-text"))
def active_toggle(): toggle = layout.toggle.Toggle(None, _("Inactive"), _("Active"), style="margin-top:-1rem; margin-bottom:0;") toggle.input.attributes["id"] = "person_active_toggle2" toggle.input.attributes["hx_trigger"] = "change" toggle.input.attributes["hx_post"] = hg.F(lambda c: reverse_lazy( "core.person.togglestatus", args=[c["object"].pk])) toggle.input.attributes["checked"] = hg.F(lambda c: c["object"].active) toggle.label.attributes["_for"] = toggle.input.attributes["id"] return hg.DIV(toggle)
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 __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 _optgroups_from_choices(optchoices, name, value): groups = [] for index, (option_value, option_label) in enumerate(optchoices): if option_value is None: option_value = "" subgroup = [] if isinstance(option_label, (list, tuple)): group_name = option_value subindex = 0 choices = option_label else: group_name = None subindex = None choices = [(option_value, option_label)] groups.append((group_name, subgroup, index)) for subvalue, sublabel in choices: selected = hg.F(lambda c, v=subvalue: hg.resolve_lazy(v, c) == hg. resolve_lazy(value, c)) subgroup.append({ "name": name, "value": subvalue, "label": sublabel, "selected": selected, "attrs": { "selected": selected, }, }) if subindex is not None: subindex += 1 return groups
def urls(request): return tile_with_datatable( models.Web, hg.F(lambda c: c["object"].core_web_list.all()), ["type", "url"], request, )
def _guess_widget(fieldname, form, suggested_widgetclass) -> hg.Lazy: widget_map: dict = {} for cls in _all_subclasses(BaseWidget): if cls.django_widget not in widget_map: widget_map[cls.django_widget] = [] widget_map[cls.django_widget].append(cls) def wrapper(context): realform = hg.resolve_lazy(form, context) widgetclass = type(realform[fieldname].field.widget) fieldclass = type(realform[fieldname].field) # Hidden widgets have highest priority if issubclass(widgetclass, forms.HiddenInput): return HiddenInput # Manually passed widgets have second priority if suggested_widgetclass is not None: return suggested_widgetclass # Automated detection via django-bread-widget-mapp have lowest priority if fieldclass in widget_map: return widget_map[fieldclass][0] if widgetclass in widget_map: return widget_map[widgetclass][0] # Fallback for unknown widgets warnings.warn( f"Form field {type(realform).__name__}.{fieldname} ({fieldclass}) uses widget {widgetclass} but " "bread has no implementation, default to TextInput") return TextInput return hg.F(wrapper)
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 numbers(request): return tile_with_datatable( models.Phone, hg.F(lambda c: c["object"].core_phone_list.all()), ["type", "number"], request, )
def attributes_for_link_to_person(get_person: Callable[[Relationship], Person]): return layout.aslink_attributes( hg.F(lambda c: reverse_model( get_person(c["row"]), "read", kwargs={"pk": get_person(c["row"]).pk}, )))
def __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, **attributes, ): inputelement_attrs = inputelement_attrs or {} attrs = {} if boundfield: attrs["checked"] = hg.F( lambda c: hg.resolve_lazy(boundfield, c).field.widget. check_test(hg.resolve_lazy(boundfield, c).value())) attrs["value"] = None inputelement_attrs = _combine_lazy_dict(inputelement_attrs, attrs) # labels for checkboxes are treated a bit different, need to use plain value label = hg.F(lambda c, label=label: getattr(hg.resolve_lazy(label, c), "label", label)) required = hg.F( lambda c, label=label: hg.resolve_lazy(label, c) is not None) super().__init__( hg.LABEL( self.get_input_element(inputelement_attrs, errors), label, hg.If(inputelement_attrs.get("required"), hg.If(required, REQUIRED_LABEL)), _class=hg.BaseElement( "bx--checkbox-label", hg.If(inputelement_attrs.get("disabled"), " bx--label--disabled"), ), data_contained_checkbox_state=hg.If( inputelement_attrs.get("checked"), "true", "false", ), data_invalid=hg.If(getattr(errors, "condition", False), True), ), help_text, errors, **hg.merge_html_attrs(attributes, {"_class": "bx--checkbox-wrapper"}), )
def _gen_optgroup(boundfield): def wrapper(context): bfield = hg.resolve_lazy(boundfield, context) return bfield.field.widget.optgroups( bfield.name, bfield.field.widget.format_value(bfield.value()), ) return hg.F(wrapper)
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 _append_classes(lazy_attrs, *_classes): def wrapper_func(context): _classlist = [] for _class in _classes: _classlist.append(_class) _classlist.append(" ") ret = hg.resolve_lazy(lazy_attrs, context) or {} ret["_class"] = hg.BaseElement(ret.get("_class", ""), " ", *_classlist) return ret return hg.F(wrapper_func)
def tags(): return tile_with_icon( Icon("tag--group"), hg.H4(_("Tags")), hg.Iterator(hg.F(lambda c: c["object"].tags.all()), "i", Tag(hg.C("i"))), open_modal_popup_button( _("Edit Tags"), hg.C("object").person_ptr, "ajax_edit_tags", ), )
def sortingclass_for_column(orderingurlparameter, columnname): def extracturlparameter(context): value = context["request"].GET.get(orderingurlparameter, "") if not value: return "" if value == columnname: return "bx--table-sort--active" if value == "-" + columnname: return "bx--table-sort--active bx--table-sort--ascending" return "" return hg.F(extracturlparameter)
def get_layout(self): form_fields = [layout.forms.FormField(field) for field in [*self.fields]] + [ hg.If( hg.F( lambda c: c["object"].person.primary_postal_address and c["object"].person.primary_postal_address.pk != c["object"].pk ), layout.forms.FormField("is_primary"), "", ) ] return hg.DIV(layout.components.forms.Form(hg.C("form"), *form_fields))
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 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 create_modal(heading, model: Union[type, Lazy], action: str): modal = layout.modal.Modal.with_ajax_content( heading=heading, url=ModelHref( model, action, kwargs={"pk": hg.F(lambda c: c["object"].pk)}, query={"asajax": True}, ), submitlabel=_("Save"), ) return modal
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 modal_add_postal(): return layout.modal.Modal.with_ajax_content( heading=_("Add Address"), url=hg.F( lambda c: reverse_model( models.Postal, "ajax_add", query={ "asajax": True, "person": c["object"].pk }, ), ), submitlabel=_("Save"), )
def from_link(link, **kwargs): buttonargs = { "icon": link.iconname, "notext": not link.label, "disabled": hg.F(lambda c: not link.has_permission(c["request"])), } return Button( *([link.label] if link.label else []), **{ **buttonargs, **link.attributes, **kwargs }, ).as_href(link.href)
def __init__(self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, **attributes): super().__init__(label=label, help_text=help_text, errors=errors, inputelement_attrs=inputelement_attrs, boundfield=boundfield, **attributes) def introspections(context): return json.dumps(DjangoQLSchemaSerializer().serialize( DjangoQLSchema( hg.resolve_lazy(boundfield, context).value().queryset.model))) if boundfield: self.append( hg.SCRIPT( hg.format( """ document.addEventListener("DOMContentLoaded", () => DjangoQL.DOMReady(function () {{ new DjangoQL({{ introspections: {}, selector: 'textarea[name={}]', syntaxHelp: '{}', autoResize: false }}); }})); """, hg.F(introspections), inputelement_attrs.get("name"), reverse("reporthelp"), autoescape=False, ), )) self.append( hg.LINK( rel="stylesheet", type="text/css", href=staticfiles_storage.url( "djangoql/css/completion.css"), )) self.append( hg.SCRIPT( src=staticfiles_storage.url("djangoql/js/completion.js")))
def __init__( self, label=None, help_text=None, errors=None, inputelement_attrs=None, boundfield=None, backend=None, **attributes, ): """ :param SearchBackendConfig backend: Where and how to get search results """ inputelement_attrs = inputelement_attrs or {} # This works inside a formset. Might need to be changed for other usages. widget_id = inputelement_attrs.get("id") resultcontainerid = hg.format("search-result-{}", widget_id) tag_id = hg.format("{}-tag", widget_id) super().__init__( label, Tag( hg.F(lambda c: hg.resolve_lazy(boundfield, c).field.to_python( hg.resolve_lazy(boundfield, c).value())) if boundfield else "", id=tag_id, style=hg.If( inputelement_attrs.get("value"), hg.BaseElement(""), hg.BaseElement("display: none;"), ), onclick="return false;", ), self.get_input_element(inputelement_attrs, errors, type="hidden"), Search( backend=backend, resultcontainerid=resultcontainerid, resultcontainer_onload_js=_resultcontainer_onload_js( backend, resultcontainerid, tag_id, widget_id), size="lg", disabled=inputelement_attrs.get("disabled", False), widgetattributes={"id": hg.format("search__{}", widget_id)}, ), help_text, errors, **hg.merge_html_attrs(attributes, {"_class": "bx--text-input-wrapper"}), )
def tile_with_datatable(model, queryset, columns, request): modal = layout.modal.Modal.with_ajax_content( _("Add"), ModelHref( model, "add", query=hg.F(lambda c: { "person": c["object"].pk, "asajax": True }), ), submitlabel=_("Save"), ) return tiling_col( layout.datatable.DataTable.from_queryset( queryset, model=model, prevent_automatic_sortingnames=True, columns=columns, rowactions=[ Link( href=ModelHref( model, "edit", kwargs={"pk": hg.C("row.pk")}, query={"next": request.get_full_path()}, ), iconname="edit", label=_("Edit"), ), Link( href=ModelHref( model, "delete", kwargs={"pk": hg.C("row.pk")}, query={"next": request.get_full_path()}, ), iconname="trash-can", label=_("Delete"), ), ], primary_button=layout.button.Button(_("Add"), buttontype="primary", **modal.openerattributes), style="border-top: none;", ), modal, )