Example #1
0
def _resultcontainer_onload_js(backend, resultcontainerid, tag_id, widget_id):
    on_click = hg.format(
        """function(evt) {{
        let label = $('{}', this).innerHTML;
        let value = $('{}', this).innerHTML;
        $('#{}').value = value;
        $('#{}').innerHTML = label;
        $('#{}').style = 'display: inline-block;';
        }}""",
        backend.result_label_selector,
        backend.result_value_selector,
        widget_id,
        tag_id,
        tag_id,
        autoescape=False,
    )

    return hg.format(
        """
    document.addEventListener('click', (evt) => this.innerHTML='');
    htmx.onLoad(function(target) {{
    // remove existing onlick attribute in case it e.g. redirects to the selected person
    $$('#{} {}')._.setAttribute('onclick', null);
    $$('#{} {}')._
    .addEventListener('click', {});
    }});""",
        resultcontainerid,
        backend.result_selector,
        resultcontainerid,
        backend.result_selector,
        on_click,
    )
Example #2
0
    def __init__(
        self,
        size="xl",
        placeholder=None,
        widgetattributes=None,
        backend=None,
        resultcontainerid=None,
        show_result_container=True,
        resultcontainer_onload_js=None,
        disabled=False,
        **kwargs,
    ):
        """
        :param SearchBackendConfig backend: Where and how to get search results
        """
        kwargs["_class"] = kwargs.get("_class", "") + f" bx--search bx--search--{size}"
        kwargs["data_search"] = True
        kwargs["role"] = "search"
        width = kwargs.get("width", None)
        if width:
            kwargs["style"] = kwargs.get("style", "") + f"width:{width};"

        widgetattributes = {
            "id": "search__" + hg.html_id(self),
            "_class": "bx--search-input",
            "type": "text",
            "placeholder": placeholder or _("Search"),
            "autocomplete": "off",
            **(widgetattributes or {}),
        }
        if backend:
            if resultcontainerid is None:
                resultcontainerid = f"search-result-{hg.html_id((self, backend.url))}"
            widgetattributes["hx_get"] = backend.url
            widgetattributes["hx_trigger"] = "changed, click, keyup changed delay:500ms"
            widgetattributes["hx_target"] = hg.format("#{}", resultcontainerid)
            widgetattributes["hx_indicator"] = hg.format(
                "#{}-indicator", resultcontainerid
            )
            widgetattributes["name"] = backend.query_parameter

        self.close_button = _close_button(resultcontainerid, widgetattributes)

        super().__init__(
            hg.DIV(
                hg.LABEL(_("Search"), _class="bx--label", _for=widgetattributes["id"]),
                hg.INPUT(**widgetattributes),
                _search_icon(),
                self.close_button,
                hg.If(backend is not None, _loading_indicator(resultcontainerid)),
                **kwargs,
            ),
            hg.If(
                backend is not None and show_result_container,
                _result_container(resultcontainerid, resultcontainer_onload_js, width),
            ),
            style=hg.If(disabled, hg.BaseElement("display: none")),
        )
Example #3
0
    def with_ajax_content(cls,
                          heading,
                          url,
                          label="",
                          size="xs",
                          submitlabel=None,
                          id=None,
                          **attributes):
        """
        Same arguments as Modal() except ``url`` replaces ``content`` and ``submitlabel`` replaces ``buttons``

        url: string or htmlgenerator.Lazy
        submitlabel: string or an htmlgenerator element which will be displayed on the submit button.
                     A value of None means no submit button should be displayed.
        """
        buttons = (Button(_("Cancel"),
                          buttontype="ghost",
                          data_modal_close=True), )
        if submitlabel:
            buttons += (Button(submitlabel, type="submit"), )

        modal = cls(
            heading,
            hg.DIV(
                Loading(),
                style=
                "opacity: 0.5; background-color: #EEE; text-align: center;",
            ),
            label="",
            buttons=buttons,
            size=size,
            id=id,
            with_form=bool(submitlabel),
            **attributes,
        )
        if submitlabel:
            buttons[1].attributes["hx_post"] = url
            # note: we always use multipart forms, avoids some issues
            # see ./forms/__init__.py:Form.__init__
            buttons[1].attributes["hx_encoding"] = "multipart/form-data"
            buttons[1].attributes["hx_target"] = hg.format(
                "#{} .bx--modal-content", modal.id)
            buttons[1].attributes["hx_include"] = hg.format(
                "#{} .bx--modal-content", modal.id)

        modal.openerattributes["hx_get"] = url
        modal.openerattributes["hx_target"] = hg.format(
            "#{} .bx--modal-content", modal.id)
        return modal
Example #4
0
    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 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 "/"),
        ),
    )
Example #6
0
def _loading_indicator(resultcontainerid):
    return hg.DIV(
        Loading(small=True),
        id=hg.format("{}-indicator", resultcontainerid),
        _class="htmx-indicator",
        style="position: absolute; right: 2rem",
    )
Example #7
0
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 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 __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")))
Example #10
0
def _close_button(resultcontainerid, widgetattributes):
    kwargs = {
        "_class": hg.BaseElement(
            "bx--search-close",
            hg.If(widgetattributes.get("value"), None, " bx--search-close--hidden"),
        ),
        "title": _("Clear search input"),
        "aria_label": _("Clear search input"),
        "type": "button",
    }
    if resultcontainerid is not None:
        kwargs["onclick"] = hg.format(
            "document.getElementById('{}').innerHTML = '';", resultcontainerid
        )
    return hg.BUTTON(Icon("close", size=20, _class="bx--search-clear"), **kwargs)
Example #11
0
    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,
        )
Example #12
0
    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,
        )
Example #13
0
class PersonBrowseView(BrowseView):
    columns = [
        DataTableColumn(
            layout.ObjectFieldLabel("personnumber", models.Person),
            hg.DIV(
                hg.C("row.personnumber"),
                style=hg.If(hg.C("row.deleted"),
                            "text-decoration:line-through"),
            ),
            "personnumber__int",
        ),
        DataTableColumn(
            hg.DIV(
                layout.tooltip.IconTooltip(
                    hg.UL(
                        hg.LI(hg.SPAN("●", style="color: green"), " ",
                              _("Active")),
                        hg.LI(hg.SPAN("●", style="color: red"), " ",
                              _("Inactive")),
                    ),
                    position="top",
                )),
            hg.DIV(
                "●",
                style=hg.format("color: {}",
                                hg.If(hg.C("row.active"), "green", "red")),
            ),
            "active",
        ),
        DataTableColumn(layout.ObjectFieldLabel("_type", models.Person),
                        hg.C("row._type"), "_type"),
        DataTableColumn(
            _("Name"),
            hg.DIV(
                hg.If(
                    hg.F(lambda context: type(context["row"]) == models.
                         NaturalPerson),
                    hg.C("row.last_name"),
                    hg.C("row.name"),
                ),
                style=hg.If(hg.C("row.deleted"),
                            "text-decoration:line-through"),
            ),
            "default_sorting_name",
        ),
        DataTableColumn(
            layout.ObjectFieldLabel("first_name", models.NaturalPerson),
            hg.DIV(
                hg.If(
                    hg.F(lambda context: type(context["row"]) == models.
                         NaturalPerson),
                    hg.C("row.first_name"),
                    "",
                ),
                style=hg.If(hg.C("row.deleted"),
                            "text-decoration:line-through"),
            ),
            "naturalperson__first_name",
        ),
        "primary_postal_address.address",
        "primary_postal_address.postcode",
        "primary_postal_address.city",
        "primary_postal_address.country",
        DataTableColumn(
            _("Email"),
            hg.C("row.primary_email_address.asbutton", ),
            "primary_email_address__email",
            False,
        ),
        DataTableColumn(
            layout.ObjectFieldLabel("tags", models.Person),
            hg.UL(
                hg.Iterator(hg.C("row.tags.all"), "tag",
                            layout.tag.Tag(hg.C("tag")))),
        ),
    ]
    bulkactions = (
        BulkAction(
            "add-tag",
            label=_("Add tag"),
            iconname="add",
            action=bulkaddtag,
        ),
        BulkAction(
            "remove-tag",
            label=_("Remove tag"),
            iconname="subtract",
            action=bulkremovetag,
        ),
        BulkAction(
            "delete",
            label=_("Delete"),
            iconname="trash-can",
            action=bulkdelete,
        ),
        BulkAction(
            "restore",
            label=_("Restore"),
            iconname="restart",
            action=bulkrestore,
        ),
        BulkAction(
            "excel",
            label=_("Excel"),
            iconname="download",
            action=export,
        ),
    )
    search_backend = layout.search.SearchBackendConfig(url=reverse(
        "basxconnect.core.views.person.search_person_view.searchperson"))
    rowclickaction = BrowseView.gen_rowclickaction("read")
    viewstate_sessionkey = "personbrowseview"

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

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

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

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

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

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

            return ret
        return qs

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

    columns = []
    fields = {f.name: f for f in obj._meta.get_fields()}
    for i in set(dir(obj) + list(vars(obj))):
        try:
            desc = _get_attribute_description(obj, i, fields)
            if desc is not None and desc[3]:
                f = desc[3]._meta.get_fields()
                additional_attrs = list(
                    filter(
                        None,
                        (
                            _get_attribute_description(desc[3], a, f)
                            for a in set(dir(desc[3]) + list(vars(desc[3])))
                        ),
                    )
                )
                desc = (
                    desc[0],
                    desc[1],
                    desc[2],
                    hg.BaseElement(
                        hg.UL(
                            hg.Iterator(
                                additional_attrs,
                                "attr",
                                hg.LI(
                                    hg.format("{}.{}", i, hg.C("attr.0")),
                                    style="font-weight: 700",
                                ),
                            )
                        ),
                    ),
                )
            if desc is not None:
                columns.append(desc)
        except Exception as e:
            columns.append((i, _("Unknown"), e))
    return modal.Modal(
        _("Available columns"),
        hg.DIV(
            hg.DIV(_("Bold text marks valid column names")),
            datatable.DataTable(
                columns=[
                    datatable.DataTableColumn(
                        _("Column name"),
                        hg.SPAN(hg.C("row.0"), style="font-weight: 700"),
                    ),
                    datatable.DataTableColumn(
                        _("Description"), hg.F(lambda c: c["row"][2])
                    ),
                    datatable.DataTableColumn(_("Type"), hg.F(lambda c: c["row"][1])),
                    datatable.DataTableColumn(_("Extended columns"), hg.C("row.3")),
                ],
                row_iterator=sorted(columns),
            ),
        ),
        size="lg",
    )
Example #18
0
def display_postal():
    postal = hg.C("i")
    modal = layout.modal.Modal.with_ajax_content(
        heading=edit_heading(models.Postal),
        url=ModelHref(
            models.Postal,
            "ajax_edit",
            kwargs={"pk": postal.pk},
            query={"asajax": True},
        ),
        id=hg.format("postal-modal-{}",
                     hg.C("i").id),
        submitlabel=_("Save"),
    )
    is_inactive = hg.F(lambda c: c["i"].valid_until and c["i"].valid_until <
                       timezone.now().date())
    return R(
        C(
            hg.DIV(
                postal.type,
                hg.If(
                    hg.F(lambda c: c["i"].person.primary_postal_address and c[
                        "i"].person.primary_postal_address.pk == c["i"].pk),
                    hg.BaseElement(" (", _("primary"), ")"),
                ),
                style="font-weight: bold; margin-bottom: 1rem;",
            ),
            hg.DIV(
                ObjectFieldValue("address", object_contextname="i"),
                style="margin-bottom: 0.25rem;",
            ),
            hg.DIV(postal.postcode,
                   " ",
                   postal.city,
                   style="margin-bottom: 0.25rem;"),
            hg.DIV(postal.get_country_display()),
            hg.If(
                postal.valid_from,
                hg.DIV(
                    hg.SPAN(_("Valid from: "), style="font-weight: bold;"),
                    ObjectFieldValue("valid_from", object_contextname="i"),
                    " ",
                    style=
                    "display: inline-block; margin-top: 1rem; margin-right: 1rem;",
                ),
                hg.BaseElement(),
            ),
            hg.If(
                postal.valid_until,
                hg.DIV(
                    hg.SPAN(_("Valid until: "), style="font-weight: bold;"),
                    ObjectFieldValue("valid_until", object_contextname="i"),
                    style="display: inline-block; margin-top: 1rem;",
                ),
                hg.BaseElement(),
            ),
            hg.DIV(edit_postal_button(modal), ),
        ),
        _class=hg.If(is_inactive, "inactive_postal", ""),
        style=hg.If(
            is_inactive,
            "display: none; margin-top: 1.5rem; color: #a8a8a8;",
            "margin-top: 1.5rem;",
        ),
    )