def auth_page(content, submitname, show_cancelbutton=False): return hg.DIV( hg.DIV( hg.H3(hg.C("pagetitle")), hg.FORM(id="cancelform", action=reverse("login")), content, hg.DIV( hg.If( show_cancelbutton, layout.button.Button( _("Cancel"), buttontype="ghost", form="cancelform", type="submit", style="width: 50%", ), hg.DIV(style="width: 50%"), ), layout.button.Button( submitname, type="submit", form="authform", style="width: 50%" ), style="margin: 1rem -1rem -1rem -1rem; display: flex; height: 64px", ), style="margin: auto; width: 25rem", _class="bx--tile", ), style="background-image: linear-gradient(#0F62FE, #0008C9); position: absolute; left: 0; top: 0; bottom: 0; right: 0; display: flex; flex-direction: column", )
def get_layout(self): if self.section_name: section_names = [self.section_name] else: section_names = self.form_class.registry.section_objects.keys() section_fields = {} for section in section_names: section_fields[section] = [] for field in self.form_class.registry[section]: section_fields[section].append(f"{section}__{field}") return breadlayout.forms.Form( hg.C("form"), hg.H3(_("Global preferences")), Tabs( *[ Tab( self.form_class.registry.section_objects[section]. verbose_name, hg.BaseElement(*[ breadlayout.forms.FormField(f) for f in section_fields.get(section) ]), ) for section in section_fields.keys() ], tabpanel_attributes={"style": "padding-left:0;"}, ), breadlayout.forms.helpers.Submit(), )
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 generalsettings(request): if request.method == "POST": form = global_preference_form_builder( preferences=["general__organizationname"] )(request.POST, request.FILES) if form.is_valid(): form.update_preferences() else: form = global_preference_form_builder( preferences=["general__organizationname"] )() return layout.grid.Grid( R(C(hg.H3(_("Settings")))), R(C(hg.H4(_("General")))), R(C(hg.H5(_("Information about our organization")))), R( C( layout.forms.Form( form, hg.BaseElement(F("general__organizationname")), layout.forms.helpers.Submit(), style="max-width: 480px", ) ) ), gutter=False, )
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, 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 personsettings(request): ret = layout.grid.Grid(R(C(hg.H3(_("Persons")))), gutter=False) for vocabulary in Vocabulary.objects.all(): ret.append( R( C(generate_term_datatable(vocabulary.name, vocabulary.slug)), style="margin-bottom: 2rem", )) return ret
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 get_context_data(self, *args, **kwargs): layout = hg.BaseElement( hg.H3(_("Add %s") % pretty_modelname(self.model), ), self._get_layout_cached(), ) return { **super().get_context_data(*args, **kwargs), "layout": layout, "pagetitle": _("Add %s") % pretty_modelname(self.model), }
def get_layout(self): return layout.grid.Grid( hg.H3(_("Edit Relationship")), layout.grid.Row( layout.grid.Col( layout.forms.Form( hg.C("form"), hg.DIV(*formfields), layout.forms.helpers.Submit() ), width=4, ) ), gutter=False, )
def get_layout(self): modelclass = self.object.model.model_class() if modelclass is None: return layout.notification.InlineNotification( _("Error"), f"Model '{self.object.model}' does no longer exist.", kind="error", ) column_helper = layout.get_attribute_description_modal(modelclass) only_template, only_definition = self.object.missing_variables() warnings = hg.BaseElement() if only_template: warnings.append( layout.notification.InlineNotification( _("Variables in document but not defined below: "), f"{', '.join(only_template)}", kind="warning", )) if only_definition: warnings.append( layout.notification.InlineNotification( _("Variables defined below but not used in document: "), f"{', '.join(only_definition)}", kind="warning", )) F = layout.forms.FormField ret = hg.BaseElement( hg.H3(self.object), warnings, layout.forms.Form( hg.C("form"), F("name"), F("file"), layout.forms.FormsetField.as_datatable( "variables", ["name", "value"], formsetfield_kwargs={"extra": 1}, ), column_helper, layout.button.Button( _("Help"), buttontype="ghost", style="margin-top: 1rem", **column_helper.openerattributes, ), layout.forms.helpers.Submit(), ), ) return ret
def editperson_head(request): return hg.BaseElement( R( C( hg.H3( hg.SPAN( hg.C("object"), style=hg.If(hg.C("object.deleted"), "text-decoration: line-through"), ), editperson_toolbar(request), ), width=12, ), style="padding-top: 1rem", ), )
def preview(self): columns = [] for column in self.columns.all(): columns.append( DataTableColumn(column.header, layout.FC(f"row.{column.column}"))) qs = self.queryset if qs is None: return hg.BaseElement("Model does no longer exists!") return hg.BaseElement( hg.HR(), hg.H3(_("Preview"), style="margin-top: 1rem"), layout.datatable.DataTable.from_queryset(qs[:25], columns=columns, primary_button=""), )
def get_layout(self): if self.ajax_urlparameter in self.request.GET: return layout.forms.Form(hg.C("form"), hg.BaseElement(*formfields)) else: return layout.grid.Grid( hg.H3(_("Add Relationship")), layout.grid.Row( layout.grid.Col( layout.forms.Form( hg.C("form"), hg.DIV(*formfields), ), width=4, ) ), gutter=False, )
def mailer_synchronization_view(request): if request.method == "POST": try: sync_result = synchronize(settings.MAILER) notification = bread.layout.components.notification.InlineNotification( _("Sychronization successful"), _("Synchronized with mailer segment containing %s contacts. %s new persons were added to BasxConnect." ) % ( sync_result.total_synchronized_persons, sync_result.persons.filter( sync_status=SynchronizationPerson.NEW).count(), ), kind="success", ) except Exception: notification = bread.layout.components.notification.InlineNotification( "Error", f"An error occured during synchronization. {traceback.format_exc()}", kind="error", ) else: notification = None help_modal = sync_help_modal() return hg.BaseElement( Form( forms.Form(), bread.layout.grid.Grid( hg.H3(_("Synchronization of Email Subcriptions")), notification, gutter=False, ), help_modal, layout.forms.helpers.Submit(_("Download subscriptions"), style="display: inline-block;"), layout.button.Button( _("Help"), buttontype="ghost", style="margin-left: 1rem", icon="help", **help_modal.openerattributes, ), ), display_previous_execution(request), )
def maintenancesettings(request): # Add the view's header ret = layout.grid.Grid(R(C(hg.H3(_("Maintenance")))), gutter=False) # Add the Package Information modal ret.append( R( C( hg.H4(_("Packages")), maintainance_package_layout(request), ), C( hg.H4(_("Optimize database")), maintenance_database_optimization(request), hg.H4(_("Rebuild search index"), _style="margin-top: 3rem;"), maintenance_search_reindex(request), ), )) return ret
def bulk_tag_operation_view(request): operation = request.GET["operation"] initial = request.GET.get("new-tag") if operation not in ["add", "remove"]: return HttpResponseBadRequest("invalid GET parameter 'operation'") class PersonList(forms.Form): persons = forms.ModelMultipleChoiceField( queryset=models.Person.objects.all()) personlist = PersonList(request.GET) if personlist.is_valid(): persons = [person.pk for person in personlist.cleaned_data["persons"]] else: return HttpResponseBadRequest("invalid GET parameter 'persons'") class BulkTagOperationForm(forms.Form): tag = forms.ModelChoiceField( queryset=models.Term.objects.filter(vocabulary__slug="tag"), required=True, initial=initial, ) if request.method == "POST": form = BulkTagOperationForm(request.POST) if form.is_valid(): tag = form.cleaned_data.get("tag") for person in models.Person.objects.filter(pk__in=persons): if operation == "add": person.tags.add(tag) else: person.tags.remove(tag) person.save() return HttpResponseRedirect(reverse_model(models.Person, "browse")) form = BulkTagOperationForm() count = len(persons) if operation == "add": header = ngettext_lazy( "Add tag to %(count)d person", "Add tag to %(count)d persons", count, ) % { "count": count } else: header = ngettext_lazy( "Remove tag from %(count)d person", "Remove tag from %(count)d persons", count, ) % { "count": count } tags_vocabulary_id = (getattr( Vocabulary.objects.filter(slug="tag").first(), "id", "") or "") return bread.layout.forms.Form( form, hg.H3(header), hg.DIV( hg.DIV(bread.layout.forms.FormField("tag")), hg.If( operation == "add", hg.DIV( modal_with_trigger( Modal.with_ajax_content( heading=_("Create new tag"), url=reverse_model( Term, "ajax_add", query={ "vocabulary": tags_vocabulary_id, "asajax": True, "next": mark_safe( reverse_model( models.Person, "bulk-tag-operation", query={ "operation": operation, "persons": persons, }, )), }, ), submitlabel=_("Save"), ), Button, _("Create new tag"), buttontype="ghost", style="margin-bottom: 2rem;", icon="add", ), ), ), style="display:flex;align-items:end;", ), layout.forms.helpers.Submit(_("Submit")), )
def generate_wizard_form(wizardview, wizardtitle, steptitle, formlayout): """ title: Title of the current page steps: list of 2-tuples with (step_title, status) where status must be one of ["incomplete", "complete", "current"] """ # needs to be rendered in view of type NamedUrlSessionWizardView in order to work correctly def go_back_url(context): url = reverse( context["request"].resolver_match.view_name, kwargs={"step": context["wizard"]["steps"].prev}, ) return f"document.location='{url}'" steps = [] for i, (step, formclass) in enumerate(wizardview.get_form_list().items()): status = "incomplete" if i < wizardview.steps.index: status = "complete" if step == wizardview.steps.current: status = "current" steps.append((formclass.title, status)) return hg.BaseElement( hg.H3(wizardtitle), hg.H4(steptitle), layout.progress_indicator.ProgressIndicator( steps, style="margin-bottom: 2rem", ), layout.forms.Form( hg.C("wizard.form"), layout.forms.Form( hg.C("wizard.management_form"), layout.forms.FormField("current_step", form="wizard.management_form"), standalone=False, ), formlayout, hg.DIV( hg.DIV( hg.If( hg.C("wizard.steps.prev"), layout.button.Button( _("Back"), buttontype="secondary", onclick=hg.F(go_back_url), ), ), hg.If( hg.F(lambda c: c["wizard"]["steps"].last == c["wizard"] ["steps"].current), layout.button.Button(_("Complete"), type="submit", style="margin-left: 1rem"), layout.button.Button(_("Continue"), type="submit", style="margin-left: 1rem"), ), ), style="align-items: flex-end", _class="bx--form-item", ), ), )
def relationshipssettings(request): return layout.grid.Grid( R(C(hg.H3(_("Relationships")))), R( C( layout.datatable.DataTable.from_queryset( RelationshipType.objects.all(), columns=["name"], primary_button=layout.button.Button.from_link( Link( href=ModelHref( RelationshipType, "add", query={ "next": reverse( "basxconnect.core.views.settings_views.relationshipssettings" ) }, ), label=_("Add %s") % pretty_modelname(RelationshipType), ), icon=layout.icon.Icon("add", size=20), ), rowactions=[ Link( label=_("Edit"), href=ModelHref( RelationshipType, "edit", kwargs={"pk": hg.C("row.pk")}, query={ "next": reverse( "basxconnect.core.views.settings_views.relationshipssettings" ) }, ), iconname="edit", ), Link( label=_("Delete"), href=ModelHref( RelationshipType, "delete", kwargs={"pk": hg.C("row.pk")}, query={ "next": reverse( "basxconnect.core.views.settings_views.relationshipssettings" ) }, ), iconname="trash-can", ), ], backurl=reverse( "basxconnect.core.views.settings_views.relationshipssettings" ), ), )), gutter=False, )
def confirm_delete_email(request, pk: int): email = models.Email.objects.get(id=pk) enable_delete_mailer_contact_checkbox = apps.is_installed( "basxconnect.mailer_integration") and hasattr(email, "subscription") fields = [] if enable_delete_mailer_contact_checkbox: from basxconnect.mailer_integration.settings import MAILER class DeleteMailerSubscriptionForm(forms.Form): delete_mailer_contact = django.forms.BooleanField( label=_("Delete linked %s subscription as well") % MAILER.name(), required=False, ) fields.append("delete_mailer_contact") if request.method == "POST": form = DeleteMailerSubscriptionForm(request.POST) if form.is_valid(): person = email.person if enable_delete_mailer_contact_checkbox and form.cleaned_data.get( "delete_mailer_contact"): try: from basxconnect.mailer_integration.settings import MAILER MAILER.delete_person(email.email) except Exception: logging.error( "tried to delete person from mailer but failed") email.delete() person.refresh_from_db() person.save() return HttpResponseRedirect( reverse_model(person, "read", kwargs={"pk": person.pk})) else: form = DeleteMailerSubscriptionForm() else: if request.method == "POST": form = forms.Form(request.POST) if form.is_valid(): email.delete() return HttpResponseRedirect( reverse_model(email.person, "read", kwargs={"pk": email.person.pk})) else: form = forms.Form() return layout.render( request, import_string(settings.DEFAULT_PAGE_LAYOUT)( menu.main, Form( form, hg.BaseElement( hg.H3(_("Delete email %s") % email.email), *(bread.layout.forms.FormField(field) for field in fields), ), hg.DIV( Button.from_link( Link( href=reverse_model(email.person, "read", kwargs={"pk": email.person.pk}), label=_("Cancel"), iconname=None, ), buttontype="tertiary", ), hg.DIV(style="width: 1rem"), *layout.forms.helpers.Submit(_("Confirm")), style="display: flex; ", ), ), ), )
def componentpreview(request): class ConfigForm(forms.Form): with_label = forms.BooleanField(required=False) with_helptext = forms.BooleanField(required=False) with_errors = forms.BooleanField(required=False) disabled = forms.BooleanField(required=False) CHOICES = ( ("choice1", "Choice 1"), ("choice2", "Choice 2"), ("choice3", "Choice 3"), ("choice4", "Choice 4"), ) widgets = { forms.TextInput: (forms.CharField, { "widget": forms.TextInput }), forms.NumberInput: (forms.DecimalField, { "widget": forms.NumberInput }), forms.EmailInput: (forms.EmailField, { "widget": forms.EmailInput }), forms.URLInput: (forms.URLField, { "widget": forms.URLInput }), forms.PasswordInput: (forms.CharField, { "widget": forms.PasswordInput }), forms.HiddenInput: (forms.CharField, { "widget": forms.HiddenInput }), forms.DateInput: (forms.DateField, { "widget": forms.DateInput }), forms.DateTimeInput: (forms.DateTimeField, { "widget": forms.DateTimeInput }), forms.TimeInput: (forms.TimeField, { "widget": forms.TimeInput }), forms.Textarea: (forms.CharField, { "widget": forms.Textarea }), forms.CheckboxInput: (forms.BooleanField, { "widget": forms.CheckboxInput }), forms.Select: (forms.ChoiceField, { "widget": forms.Select, "choices": CHOICES }), forms.NullBooleanSelect: ( forms.NullBooleanField, { "widget": forms.NullBooleanSelect }, ), forms.SelectMultiple: ( forms.MultipleChoiceField, { "widget": forms.SelectMultiple, "choices": CHOICES }, ), forms.RadioSelect: ( forms.ChoiceField, { "widget": forms.RadioSelect, "choices": CHOICES }, ), forms.CheckboxSelectMultiple: ( forms.ChoiceField, { "widget": forms.CheckboxSelectMultiple, "choices": CHOICES }, ), forms.FileInput: (forms.FileField, { "widget": forms.FileInput }), forms.ClearableFileInput: ( forms.FileField, { "widget": forms.ClearableFileInput }, ), } HELPTEXT = "This is a piece of helptext, maximized for helpfulness" ERRORS = [ "This is an example of an error", "This is a second errors, but actually none of them are real errors, so do not worry", ] def nicefieldname(cls): return re.sub(r"(?<!^)(?=[A-Z])", "_", cls.__name__) configform = ConfigForm(request.GET) if not configform.is_valid() or not request.GET: config = configform.initial config = configform.cleaned_data Form = type( "Form", (forms.Form, ), { nicefieldname(widget): field[0]( **field[1], **({ "help_text": HELPTEXT } if config["with_helptext"] else {}), disabled=config["disabled"]) for widget, field in widgets.items() }, ) return hg.BaseElement( hg.STYLE( hg.mark_safe(""" #backtotopBtn { position: fixed; right: 0; bottom: 0; z-index: 999; margin-right: 3rem; margin-bottom: 3rem; border-radius: 50%; } """)), layout.button.Button.from_link( Link(href="#", label=_("Back to top")), buttontype="secondary", icon="arrow--up", notext=True, id="backtotopBtn", ), tabs.Tabs( tabs.Tab( _("Layout"), layout.componentpreview.layout(request), ), tabs.Tab( _("Informational"), layout.componentpreview.informational(request), ), tabs.Tab( _("Interactive"), layout.componentpreview.interactive(request), ), tabs.Tab( _("Datatable"), layout.componentpreview.datatable_layout(request), ), tabs.Tab( _("Form"), hg.BaseElement( hg.H3(_("Widget preview")), layout.grid.Grid( layout.grid.Row( layout.grid.Col( hg.H4(_("Widgets")), layout.forms.Form( Form(), *[ F( nicefieldname(w), no_label=not config["with_label"], errors=ERRORS if config["with_errors"] else None, ) for w in widgets.keys() ]), ), layout.grid.Col( hg.H4(_("Configure preview")), layout.forms.Form( configform, F("with_label"), F("with_helptext"), F("with_errors"), F("disabled"), layout.forms.helpers.Submit(_("Apply")), method="GET", ), ), ), R( C( hg.H3(_("Tooltips")), hg.H4(_("Definition tooltip")), hg.DIV( layout.components.tooltip. DefinitionTooltip( "Definition tooltip (left aligned)", "Brief definition of the dotted, underlined word above.", align="left", )), hg.DIV( layout.components.tooltip. DefinitionTooltip( "Definition tooltip (center aligned)", "Brief definition of the dotted, underlined word above.", align="center", )), hg.DIV( layout.components.tooltip. DefinitionTooltip( "Definition tooltip (right aligned)", "Brief definition of the dotted, underlined word above.", align="right", )), hg.H4(_("Icon tooltip")), hg.DIV( layout.components.tooltip.IconTooltip( "Help", ), layout.components.tooltip.IconTooltip( "Filter", icon=Icon("filter"), ), layout.components.tooltip.IconTooltip( "Email", icon="email", ), ), hg.H4(_("Interactive tooltip")), hg.DIV( layout.components.tooltip. InteractiveTooltip( label="Tooltip label", body=(_( "This is some tooltip text. This box shows the maximum amount of text that should " "appear inside. If more room is needed please use a modal instead." )), heading="Heading within a Tooltip", button=(layout.components.button. Button("Button")), link=Link(href="#", label="link"), ), ), ), ), ), ), ), ), ) return hg.BaseElement()