Пример #1
0
    def helper(self):
        if self.accepted:
            self.clear()
        if not self.cached_helper:
            helper = self.param.formstyle(
                self.table,
                self.vars,
                self.errors,
                self.readonly,
                self.deletable,
                self.noncreate,
                show_id=self.show_id,
                kwargs=self.kwargs,
            )
            for item in self.param.sidecar:
                helper["form"][-1][-1].append(item)

                button_attributes = item.attributes
                button_attributes["_label"] = item.children[0]
                helper["json_controls"]["form_buttons"] += [button_attributes]

            if self.action:
                helper["form"]["_action"] = self.action

            if self.param.submit_value:
                helper["controls"]["submit"][
                    "_value"] = self.param.submit_value

            if self.form_name:
                helper["controls"]["hidden_widgets"]["formname"] = INPUT(
                    _type="hidden", _name="_formname", _value=self.form_name)
                helper["form"].append(
                    helper["controls"]["hidden_widgets"]["formname"])

                helper["json_controls"]["form_values"][
                    "_formname"] = self.form_name

            if self.formkey:
                helper["controls"]["hidden_widgets"]["formkey"] = INPUT(
                    _type="hidden", _name="_formkey", _value=self.formkey)
                helper["form"].append(
                    helper["controls"]["hidden_widgets"]["formkey"])

                helper["json_controls"]["form_values"][
                    "_formkey"] = self.formkey

            for key in self.param.hidden or {}:
                helper["controls"]["hidden_widgets"][key] = INPUT(
                    _type="hidden", _name=key, _value=self.param.hidden[key])
                helper["form"].append(
                    helper["controls"]["hidden_widgets"][key])

            helper["controls"]["begin"] = XML("".join(
                str(helper["controls"]["begin"]) +
                str(helper["controls"]["hidden_widgets"][hidden_field])
                for hidden_field in helper["controls"]["hidden_widgets"]))
            self.cached_helper = helper

        return self.cached_helper
Пример #2
0
 def __call__(self, id=None, redirect_url=None):
     """This method returns the element that can be included in the page.
     @param id: if an id is specified, the form is an update form for the
     specified record id.
     @param redirect_url: URL to which to redirect after success."""
     return XML(VueForm.FORM.format(url=URL(self.url, id, signer=self.signer),
                                    check_url=URL(self.url_check, id, signer=self.signer),
                                    redirect_url=redirect_url))
 def __call__(self, id=None):
     return XML(
         ThumbRater.THUMBRATER.format(url=URL(self.url,
                                              id,
                                              signer=self.signer),
                                      callback_url=URL(self.callback_url,
                                                       id,
                                                       signer=self.signer)))
Пример #4
0
    def render_action_button(
        self,
        url,
        button_text,
        icon,
        icon_size="small",
        additional_classes=None,
        additional_styles=None,
        override_classes=None,
        override_styles=None,
        message=None,
        onclick=None,
        row_id=None,
        name="grid-button",
        row=None,
        **attrs,
    ):
        separator = "?"
        if row_id:
            url += "/%s" % row_id

        classes = self.param.grid_class_style.classes.get(name, "")
        styles = self.param.grid_class_style.styles.get(name, "")

        def join(items):
            return (" ".join(items) if isinstance(items,
                                                  (list, tuple)) else " %s" %
                    items)

        if override_classes:
            classes = join(override_classes)
        elif additional_classes:
            classes += join(additional_classes)
        if override_styles:
            styles = join(override_styles)
        elif additional_styles:
            styles += join(additional_styles)

        if callable(url):
            url = url(row)

        attrs.update(self.attributes_plugin.link(url=url))
        link = A(
            I(_class="fa %s" % icon),
            _role="button",
            _class=classes,
            _message=message,
            _title=button_text,
            _style=styles,
            **attrs,
        )
        if self.param.include_action_button_text:
            link.append(
                XML('<span class="grid-action-button-text">&nbsp;%s</span>' %
                    button_text))

        return link
Пример #5
0
 def __call__(self, redirect_url=None):
     """This method returns the element that can be included in the page.
     The *args and **kwargs are used when subclassing, to allow for forms
     that are 'custom built' for some need."""
     return XML(
         VueForm.FORM.format(url=URL(self.url, signer=self.signer),
                             check_url=URL(self.url_check,
                                           signer=self.signer),
                             redirect_url=redirect_url))
Пример #6
0
 def grid(self, table):
     name = 'vue%s' % str(uuid.uuid4())[:8]
     return DIV(self.mtable(table),
                TAG.SCRIPT(_src=URL('static/js/axios.min.js')),
                TAG.SCRIPT(_src=URL('static/js/vue.min.js')),
                TAG.SCRIPT(_src=URL('static/js/utils.js')),
                TAG.SCRIPT(_src=URL('static/components/mtable.js')),
                TAG.SCRIPT(
                    XML('var app=utils.app("%s"); app.start()' % name)),
                _id=name)
Пример #7
0
def get_gantt_data(quests):
    projxml = "<project>"
    questlist = [x.question.id for x in quests]
    intlinks = getlinks(questlist)

    for i, row in enumerate(quests):
        projxml += convrow(row, getstrdepend(intlinks, row.question.id), True)

    projxml += '</project>'
    return XML(projxml)
Пример #8
0
def edit_dog():
    dog_id = request.query.get('id')
    form = Form(db.dog, record=dog_id, formstyle=FormStyleBulma)
    form.param.sidecar = [XML('<button class="button" onclick="close_modal();" '
                              'style="margin-left: .5rem;">Cancel</button>')]

    if form.accepted:
        url = URL('index')
        redirect(url)

    return dict(form=form)
Пример #9
0
 def __call__(self, id=None):
     """This method returns the element that can be included in the page.
     @param id: id of the file uploaded.  This can be useful if there are
     multiple instances of this form on the page."""
     return XML(
         ThumbRater.THUMBRATER.format(url=URL(self.url,
                                              id,
                                              signer=self.signer),
                                      callback_url=URL(self.callback_url,
                                                       id,
                                                       signer=self.signer)))
Пример #10
0
 def grid(self, table):
     name = "vue%s" % str(uuid.uuid4())[:8]
     return DIV(
         self.mtable(table),
         TAG.SCRIPT(_src=URL("static/js/axios.min.js")),
         TAG.SCRIPT(_src=URL("static/js/vue.min.js")),
         TAG.SCRIPT(_src=URL("static/js/utils.js")),
         TAG.SCRIPT(_src=URL("static/components/mtable.js")),
         TAG.SCRIPT(XML('var app={}; app.vue = new Vue({el:"#%s"});' %
                        name)),
         _id=name,
     )
Пример #11
0
 def __call__(self, img_id=None):  # turn our class into HTML
     return XML(
         MainAppComponent.MAINAPP.format(
             get_posts_url=URL(self.get_posts_url, signer=self.signer),
             get_profile_url=URL(self.get_profile_url, signer=self.signer),
             get_user_url=URL(self.get_user_url, signer=self.signer),
             create_post_url=URL(self.create_post_url, signer=self.signer),
             create_reply_url=URL(self.create_reply_url,
                                  signer=self.signer),
             get_about_url=URL(self.get_about_url, signer=self.signer),
             delete_all=URL(self.delete_all_posts_url, signer=self.signer),
             add_like_url=URL(self.add_like_url, signer=self.signer)))
Пример #12
0
 def __call__(self, id=None, cancel_url=""):
     """This method returns the element that can be included in the page.
     The *args and **kwargs are used when subclassing, to allow for forms
     that are 'custom built' for some need.
     """
     return XML(
         VueForm.FORM.format(
             url=self.url(id),
             check_url=self.check_url(id),
             cancel_url=cancel_url,
         )
     )
Пример #13
0
def hcaptcha_form():

    form = Form([Field(
        'dummy_form',
        'string',
    )])

    form.structure.append(
        XML('<div class="h-captcha" data-sitekey="{}"></div>'.format(
            HCAPTCHA_SITE_KEY)))
    if form.accepted:
        r = hCaptcha(request.forms.get('g-recaptcha-response'))
        if r == True:
            #do something with form data
            form.structure.append(
                XML('<div style="color:green">Captcha was solved succesfully!</font></div>'
                    ))
        else:
            form.structure.append(
                XML('<div class="py4web-validation-error">invalid captcha</div>'
                    ))

    return dict(form=form)
Пример #14
0
 def __call__(self, id=None):
     # a clear cut interface is better than dependence on global variables, but including the same user
     # every time might make data strained, adding user as a prop may be added later
     return XML(
         Comment.COMMENT.format(get_url=URL(self.get_url,
                                            id,
                                            signer=self.signer),
                                add_url=URL(self.add_url,
                                            signer=self.signer),
                                edit_url=URL(self.edit_url,
                                             signer=self.signer),
                                delete_url=URL(self.delete_url,
                                               signer=self.signer),
                                ticket_id=id))
Пример #15
0
 def test_tags(self):
     DIV = TAG.div
     IMG = TAG['img/']
     self.assertEqual(DIV().xml(), "<div></div>")
     self.assertEqual(IMG().xml(), "<img/>")
     self.assertEqual(DIV(_id="my_id").xml(), "<div id=\"my_id\"></div>")
     self.assertEqual(IMG(_src="crazy").xml(), "<img src=\"crazy\"/>")
     self.assertEqual(
         DIV(_class="my_class", _mytrueattr=True).xml(),
         "<div class=\"my_class\" mytrueattr=\"mytrueattr\"></div>")
     self.assertEqual(
         DIV(_id="my_id", _none=None, _false=False, without_underline="serius?").xml(),
         "<div id=\"my_id\"></div>")
     self.assertEqual(
         DIV("<b>xmlscapedthis</b>").xml(), "<div>&lt;b&gt;xmlscapedthis&lt;/b&gt;</div>")
     self.assertEqual(
         DIV(XML("<b>don'txmlscapedthis</b>")).xml(), "<div><b>don'txmlscapedthis</b></div>")
Пример #16
0
def view_project(pid='0'):
    projectrow = db(db.project.id == pid).select().first()
    session['projid'] = pid if projectrow else 0
    events = db(db.event.projid == pid).select(orderby=~db.event.startdatetime)
    actions = get_items(qtype='action', status='In Progress', project=pid)
    questions = get_items(qtype='quest', status='In Progress', project=pid)
    issues = get_items(qtype='issue', project=pid)
    res_actions = get_items(qtype='action',
                            status='Resolved',
                            project=pid,
                            execstatus='Incomplete')
    comp_actions = get_items(qtype='action',
                             status='Resolved',
                             project=pid,
                             execstatus='Completed')
    res_questions = get_items(qtype='question', status='Resolved', project=pid)

    if res_actions:
        projxml = get_gantt_data(res_actions)
    else:
        projxml = "<project></project>"

    db.comment.auth_userid.default = auth.user_id
    db.comment.parenttable.default = 'project'
    db.comment.parentid.default = pid
    commentform = Form(db.comment, formstyle=FormStyleBulma)
    return dict(projectid=pid,
                projectrow=projectrow,
                qactions=actions,
                questions=questions,
                issues=issues,
                res_actions=res_actions,
                res_questions=res_questions,
                comp_actions=comp_actions,
                events=events,
                get_class=get_class,
                get_disabled=get_disabled,
                myconverter=myconverter,
                project=XML(projxml),
                auth=auth,
                like=like,
                commentform=commentform)
Пример #17
0
    def helper(self):
        if self.accepted:
            self.clear()
        if not self.cached_helper:
            helper = self.param.formstyle(
                self.table,
                self.vars,
                self.errors,
                self.readonly,
                self.deletable,
                kwargs=self.kwargs,
            )
            for item in self.param.sidecar:
                helper["form"][-1][-1].append(item)
            if self.action:
                helper["form"]["_action"] = self.action
            if self.param.submit_value:
                helper["controls"]["submit"][
                    "_value"] = self.param.submit_value
            if self.form_name:
                helper["controls"]["hidden_widgets"]["formname"] = INPUT(
                    _type="hidden", _name="_formname", _value=self.form_name)
                helper["form"].append(
                    helper["controls"]["hidden_widgets"]["formname"])
            if self.formkey:
                helper["controls"]["hidden_widgets"]["formkey"] = INPUT(
                    _type="hidden", _name="_formkey", _value=self.formkey)
                helper["form"].append(
                    helper["controls"]["hidden_widgets"]["formkey"])
            for key in self.param.hidden or {}:
                helper["controls"]["hidden_widgets"][key] = INPUT(
                    _type="hidden", _name=key, _value=self.param.hidden[key])
                helper["form"].append(
                    helper["controls"]["hidden_widgets"][key])
            helper["controls"]["begin"] = XML("".join(
                str(helper["controls"]["begin"]) +
                str(helper["controls"]["hidden_widgets"][hidden_field])
                for hidden_field in helper["controls"]["hidden_widgets"]))
            self.cached_helper = helper

        return self.cached_helper
Пример #18
0
def employees(path=None):
    queries = [(db.employee.id > 0)]
    orderby = [db.employee.last_name, db.employee.first_name]

    search_queries = [['Search by Company', lambda val: db.company.id == val,
                       db.employee.company.requires],
                      ['Search by Department', lambda val: db.department.id == val,
                       db.employee.department.requires],
                      ['Search by Name', lambda val: "first_name || ' ' || last_Name LIKE '%%%s%%'" % val]]
    search = GridSearch(search_queries, queries)

    fields = [db.employee.id,
              db.employee.first_name,
              db.employee.last_name,
              db.company.name,
              db.department.name,
              db.employee.hired,
              db.employee.supervisor,
              db.employee.active]

    grid = Grid(path,
                search.query,
                search_form=search.search_form,
                fields=fields,
                left=[db.company.on(db.employee.company == db.company.id),
                      db.department.on(db.employee.department == db.department.id)],
                orderby=orderby,
                create=True,
                details=True,
                editable=True,
                deletable=True,
                **GRID_DEFAULTS)

    grid.formatters_by_type['boolean'] = lambda value: SPAN(I(_class='fas fa-check-circle')) if value else ""
    grid.formatters_by_type['date'] =lambda value: XML(
        '<script>document.write((new Date(%s,%s,%s)).toLocaleDateString({month: "2-digit", day: "2-digit", year: "numeric"}).split(",")[0])</script>'
        % (value.year, value.month, value.day,)
    )
    return dict(grid=grid)
Пример #19
0
    def produce(
        self,
        table,
        vars,
        errors,
        readonly,
        noncreate,
        deletable,
        classes=None,
        class_inner_exceptions=None,
        kwargs=None,
    ):
        self.classes.update(classes or {})
        self.class_inner_exceptions.update(class_inner_exceptions or {})
        kwargs = kwargs if kwargs else {}

        form_method = "POST"
        form_action = request.url
        form_enctype = "multipart/form-data"

        form = FORM(
            _method=form_method, _action=form_action, _enctype=form_enctype, **kwargs
        )

        controls = Param(
            labels=dict(),
            widgets=dict(),
            comments=dict(),
            hidden_widgets=dict(),
            placeholders=dict(),
            titles=dict(),
            errors=dict(),
            begin=XML(form.xml().split("</form>")[0]),
            submit="",
            delete="",
            end=XML("</form>"),
        )

        json_controls = dict(
            form_fields=[],
            form_values=dict(),
            form_buttons=[],
            form_method=form_method,
            form_action=form_action,
            form_enctype=form_enctype,
            **kwargs
        )

        class_label = self.classes["label"]
        class_outer = self.classes["outer"]
        class_inner = self.classes["inner"]
        class_error = self.classes["error"]
        class_info = self.classes["info"]

        for field in table:

            # Reset the json control fields.
            field_attributes = dict()
            field_value = None

            field_name = field.name
            field_type = field.type
            field_comment = field.comment if field.comment else ""
            field_label = field.label
            input_id = "%s_%s" % (field.tablename, field.name)
            value = vars.get(
                field.name,
                field.default() if callable(field.default) else field.default,
            )
            error = errors.get(field.name)
            field_class = "type-" + field.type.split()[0].replace(":", "-")
            placeholder = (
                field._placeholder if "_placeholder" in field.__dict__ else None
            )
            title = field._title if "_title" in field.__dict__ else None
            field_disabled = False

            # only diplay field if readable or writable
            if not field.readable and not field.writable:
                continue

            # if this is a reaonly field only show readable fields
            if readonly:
                if not field.readable:
                    continue

            # if this is an create form (unkown id) then only show writable fields. Some if an edit form was made from a list of fields and noncreate=True
            elif not vars.get("id") and noncreate:
                if not field.writable:
                    continue

            # ignore blob fields
            if field.type == "blob":  # never display blobs (mistake?)
                continue

            # ignore fields of type id its value is equal to None
            if field.type == "id" and value is None:
                field.writable = False
                continue

            # if the form is readonly or this is an id type field, display it as readonly
            if readonly or not field.writable or field.type == "id":
                if field.type == "boolean":

                    field_value = value
                    field_type = "checkbox"

                    control = INPUT(
                        _type=field_type,
                        _id=input_id,
                        _name=field_name,
                        _value="ON",
                        _disabled="",
                        _checked=value,
                        _title=title,
                    )
                else:

                    field_value = (
                        field.represent and field.represent(value) or value or ""
                    )
                    field_type = "represent"

                    control = DIV(field_value)

                field_disabled = True

            # if we have a widget for the field use it
            elif field.widget:
                control = field.widget(table, value)

                # Grab the custom widget attributes.
                field_attributes = control.attributes
                field_type = "widget"
                field_value = value

            # else pick the proper default widget
            elif field.type == "text":

                field_value = value or ""
                field_type = "text"

                control = TEXTAREA(
                    field_value,
                    _id=input_id,
                    _name=field_name,
                    _placeholder=placeholder,
                    _title=title,
                )

            elif field.type == "date":

                field_value = value
                field_type = "date"

                control = INPUT(
                    _value=field_value,
                    _type="date",
                    _id=input_id,
                    _name=field_name,
                    _placeholder=placeholder,
                    _title=title,
                )

            elif field.type == "datetime":
                helpervalue = str(value)
                helpervalue = helpervalue.replace(" ", "T")

                field_value = helpervalue
                field_type = "datetime-local"

                control = INPUT(
                    _value=helpervalue,
                    _type="datetime-local",
                    _id=input_id,
                    _name=field_name,
                    _placeholder=placeholder,
                    _title=title,
                )
            elif field.type == "time":

                field_value = value
                field_type = "time"

                control = INPUT(
                    _value=value,
                    _type="time",
                    _id=input_id,
                    _name=field_name,
                    _placeholder=placeholder,
                    _title=title,
                )

            elif field.type == "boolean":

                field_value = value
                field_type = "checkbox"
                field_attributes["_value"] = "ON"

                control = INPUT(
                    _type="checkbox",
                    _id=input_id,
                    _name=field_name,
                    _value=field_attributes["_value"],
                    _checked=value,
                    _title=title,
                )

            elif field.type == "upload":
                control = DIV()
                if value and not error:
                    download_div = DIV()

                    download_div.append(
                        LABEL(
                            "Currently:  ",
                        )
                    )
                    if getattr(field, "download_url", None):
                        url = field.download_url(value)
                    else:
                        url = "#"
                    download_div.append(A(" download ", _href=url))

                    # Set the download url.
                    field_attributes["_download_url"] = url

                    # Set the flag determining whether the file is an image.
                    field_attributes["_is_image"] = (url != "#") and Form.is_image(
                        value
                    )

                    delete_checkbox_name = "_delete_" + field_name

                    download_div.append(
                        INPUT(
                            _type="checkbox",
                            _value="ON",
                            _name=delete_checkbox_name,
                            _title=title,
                        )
                    )
                    download_div.append(" (check to remove)")

                    delete_field_attributes = dict()

                    delete_field_attributes["_label"] = "Remove"
                    delete_field_attributes["_value"] = "ON"
                    delete_field_attributes["_type"] = "checkbox"
                    delete_field_attributes["_name"] = delete_checkbox_name

                    json_controls["form_fields"] += [delete_field_attributes]
                    json_controls["form_values"][delete_checkbox_name] = None

                    control.append(download_div)

                control.append(LABEL("Change: "))
                control.append(INPUT(_type="file", _id=input_id, _name=field_name))

                field_value = None
                field_type = "file"

            elif get_options(field.requires) is not None and field.writable == True:
                multiple = field.type.startswith("list:")
                value = list(map(str, value if isinstance(value, list) else [value]))

                field_options = [
                    [k, v, (not k is None and k in value)]
                    for k, v in get_options(field.requires)
                ]
                option_tags = [
                    OPTION(v, _value=k, _selected=_selected)
                    for (k, v, _selected) in field_options
                ]

                control = SELECT(
                    *option_tags,
                    _id=input_id,
                    _name=field_name,
                    _multiple=multiple,
                    _title=title
                )

                field_value = value
                field_type = "options"
                field_attributes["_multiple"] = multiple
                field_attributes["_options"] = field_options

            else:

                field_type = "password" if field.type == "password" else "text"

                if field.type.startswith("list:"):
                    value = json.dumps(value or [])

                field_value = None if field.type == "password" else value
                field_autocomplete = "off" if field_type == "password" else "on"

                control = INPUT(
                    _type=field_type,
                    _id=input_id,
                    _name=field_name,
                    _value=field_value,
                    _class=field_class,
                    _placeholder=placeholder,
                    _title=title,
                    _autocomplete=field_autocomplete,
                )

                field_attributes["_autocomplete"] = field_autocomplete

            key = control.name.rstrip("/")

            if key == "input":
                key += "[type=%s]" % (control["_type"] or "text")

            control["_class"] = (
                control.attributes.get("_class", "") + " " + self.classes.get(key, "")
            ).strip()

            # Set the form controls.
            controls["labels"][field_name] = field_label
            controls["widgets"][field_name] = control
            controls["comments"][field_name] = field_comment
            controls["titles"][field_name] = title
            controls["placeholders"][field_name] = placeholder

            # Set the remain json field attributes.
            field_attributes["_title"] = title
            field_attributes["_label"] = field_label
            field_attributes["_comment"] = field_comment
            field_attributes["_id"] = input_id
            field_attributes["_class"] = field_class
            field_attributes["_name"] = field_name
            field_attributes["_type"] = field_type
            field_attributes["_placeholder"] = placeholder
            field_attributes["_error"] = error
            field_attributes["_disabled"] = field_disabled

            # Add to the json controls.
            json_controls["form_fields"] += [field_attributes]
            json_controls["form_values"][field_name] = field_value

            if error:
                controls["errors"][field.name] = error

            if field.type == "boolean":
                form.append(
                    DIV(
                        SPAN(control, _class=class_inner),
                        LABEL(
                            " " + field.label,
                            _for=input_id,
                            _class=class_label,
                            _style="display: inline !important",
                        ),
                        P(error, _class=class_error) if error else "",
                        P(field.comment or "", _class=class_info),
                        _class=class_outer,
                    )
                )
            else:
                form.append(
                    DIV(
                        LABEL(field.label, _for=input_id, _class=class_label),
                        DIV(
                            control,
                            _class=self.class_inner_exceptions.get(
                                control.name, class_inner
                            ),
                        ),
                        P(error, _class=class_error) if error else "",
                        P(field.comment or "", _class=class_info),
                        _class=class_outer,
                    )
                )

        if vars.get("id"):
            form.append(INPUT(_name="id", _value=vars["id"], _hidden=True))

        if deletable:

            deletable_record_attributes = dict()

            deletable_field_name = "_delete"
            deletable_field_type = "checkbox"

            # Set the deletable json field attributes.
            deletable_record_attributes["_label"] = " check to delete"
            deletable_record_attributes["_name"] = deletable_field_name
            deletable_record_attributes["_type"] = deletable_field_type
            deletable_record_attributes["_class"] = self.classes["input[type=checkbox]"]
            deletable_record_attributes["_value"] = "ON"

            # Add to the json controls.
            json_controls["form_fields"] += [deletable_record_attributes]
            json_controls["form_values"][deletable_field_name] = None

            controls["delete"] = INPUT(
                _type=deletable_field_type,
                _value=deletable_record_attributes["_value"],
                _name=deletable_field_name,
                _class=self.classes["input[type=checkbox]"],
            )

            form.append(
                DIV(
                    SPAN(
                        controls["delete"],
                        _class=class_inner,
                        _stye="vertical-align: middle;",
                    ),
                    P(
                        deletable_record_attributes["_label"],
                        _class="help",
                        _style="display: inline !important",
                    ),
                    _class=class_outer,
                )
            )

        submit_button_attributes = dict()

        submit_button_field_type = "submit"

        # Set the deletable json field attributes.
        submit_button_attributes["_label"] = "Submit"
        submit_button_attributes["_type"] = submit_button_field_type
        submit_button_attributes["_class"] = self.classes["input[type=submit]"]

        # Add to the json controls.
        json_controls["form_buttons"] += [submit_button_attributes]

        controls["submit"] = INPUT(
            _type=submit_button_field_type,
            _value="Submit",
            _class=self.classes["input[type=submit]"],
        )

        submit = DIV(
            DIV(
                controls["submit"],
                _class=class_inner,
            ),
            _class=class_outer,
        )
        form.append(submit)

        return dict(form=form, controls=controls, json_controls=json_controls)
Пример #20
0
    def produce(self, table, vars, errors, readonly, deletable, classes=None):
        self.classes.update(classes or {})
        form = FORM(
            _method="POST", _action=request.url, _enctype="multipart/form-data"
        )
        controls = dict(
            widgets=dict(),
            hidden_widgets=dict(),
            errors=dict(),
            begin=XML(form.xml().split("</form>")[0]),
            end=XML("</form>"),
        )
        class_label = self.classes["label"]
        class_outer = self.classes["outer"]
        class_inner = self.classes["inner"]
        class_error = self.classes["error"]
        class_info = self.classes["info"]

        for field in table:

            input_id = "%s_%s" % (field.tablename, field.name)
            value = vars.get(field.name, field.default)
            error = errors.get(field.name)
            field_class = field.type.split()[0].replace(":", "-")

            if not field.readable and not field.writable:
                continue
            if not readonly and not field.writable:
                continue
            if field.type == "blob":  # never display blobs (mistake?)
                continue
            if field.type == "id" and value is None:
                field.writable = False
                continue
            if readonly or field.type == "id":
                control = DIV(field.represent and field.represent(value) or value or "")
            elif field.widget:
                control = field.widget(table, value)
            elif field.type == "text":
                control = TEXTAREA(value or "", _id=input_id, _name=field.name)
            elif field.type == "date":
                control = INPUT(
                    _value=value, _type="date", _id=input_id, _name=field.name
                )
            elif field.type == "datetime":
                if isinstance(value, str):
                    value = value.replace(" ", "T")
                control = INPUT(
                    _value=value, _type="datetime-local", _id=input_id, _name=field.name
                )
            elif field.type == "time":
                control = INPUT(
                    _value=value, _type="time", _id=input_id, _name=field.name
                )
            elif field.type == "boolean":
                control = INPUT(
                    _type="checkbox",
                    _id=input_id,
                    _name=field.name,
                    _value="ON",
                    _checked=value,
                )
            elif field.type == "upload":
                control = DIV(INPUT(_type="file", _id=input_id, _name=field.name))
                if value:
                    control.append(A("download", _href=field.download_url(value)))
                    control.append(
                        INPUT(
                            _type="checkbox", _value="ON", _name="_delete_" + field.name
                        )
                    )
                    control.append("(check to remove)")
            elif get_options(field.requires) is not None:
                multiple = field.type.startswith("list:")
                value = list(map(str, value if isinstance(value, list) else [value]))
                option_tags = [
                    OPTION(v, _value=k, _selected=(not k is None and k in value))
                    for k, v in get_options(field.requires)
                    ]
                control = SELECT(
                    *option_tags, _id=input_id, _name=field.name, _multiple=multiple
                     )
            else:
                field_type = "password" if field.type == "password" else "text"
                control = INPUT(
                    _type=field_type,
                    _id=input_id,
                    _name=field.name,
                    _value=value,
                    _class=field_class,
                )

            key = control.name.rstrip("/")
            if key == "input":
                key += "[type=%s]" % (control["_type"] or "text")
            control["_class"] = self.classes.get(key, "")

            controls["widgets"][field.name] = control
            if error:
                controls["errors"][field.name] = error

            form.append(
                DIV(
                    LABEL(field.label, _for=input_id, _class=class_label),
                    DIV(control, _class=class_inner),
                    P(error, _class=class_error) if error else "",
                    P(field.comment or "", _class=class_info),
                    _class=class_outer,
                )
            )
            if 'id' in vars:
                form.append(INPUT(_name='id',_value=vars['id'],_hidden=True))
        if deletable:
            controls["delete"] = INPUT(
                _type="checkbox",
                _value="ON",
                _name="_delete",
                _class=self.classes["input[type=checkbox]"],
            )
            form.append(
                DIV(
                    DIV(controls["delete"], _class=class_inner,),
                    P("check to delete", _class="help"),
                    _class=class_outer,
                )
            )
        controls["submit"] = INPUT(
            _type="submit", _value="Submit", _class=self.classes["input[type=submit]"],
        )
        submit = DIV(DIV(controls["submit"], _class=class_inner,), _class=class_outer,)
        form.append(submit)
        return dict(form=form, controls=controls)
Пример #21
0
 def __call__(self, id=None):
     """This method returns the element that can be included in the page.
     @param id: if an id is specified, the form is an update form for the
     specified record id."""
     return XML(VueForm.FORM.format(url=self.url(id), check_url=self.check_url(id)))
Пример #22
0
 def __call__(self, bindCb="uploadedimage" , emitid=None):
     """This method returns the element that can be included in the page.
     @param id: id of the file uploaded.  This can be useful if there are
     multiple instances of this form on the page."""
     return XML(FileUpload.FILE_UPLOAD.format(url=URL(self.url, 1, signer=self.signer), bindCb=bindCb, emitid=emitid))
Пример #23
0
    def __call__(
        self,
        table,
        vars,
        errors,
        readonly,
        deletable,
        noncreate,
        show_id,
        kwargs=None,
    ):
        kwargs = kwargs if kwargs else {}

        form_method = "POST"
        form_action = request.url.split(":", 1)[1]
        form_enctype = "multipart/form-data"

        form = FORM(_method=form_method,
                    _action=form_action,
                    _enctype=form_enctype,
                    **kwargs)

        controls = Param(
            labels=dict(),
            widgets=dict(),
            wrappers=dict(),
            comments=dict(),
            hidden_widgets=dict(),
            placeholders=dict(),
            titles=dict(),
            errors=dict(),
            begin=XML(form.xml().split("</form>")[0]),
            submit="",
            delete="",
            end=XML("</form>"),
        )

        json_controls = dict(form_fields=[],
                             form_values=dict(),
                             form_buttons=[],
                             form_method=form_method,
                             form_action=form_action,
                             form_enctype=form_enctype,
                             **kwargs)

        class_label = self.classes["label"]
        class_outer = self.classes["outer"]
        class_inner = self.classes["inner"]
        class_error = self.classes["error"]
        class_info = self.classes["info"]

        all_fields = [x for x in table]
        if "_virtual_fields" in dir(table):
            all_fields += table._virtual_fields
        for field in all_fields:

            # Reset the json control fields.
            field_attributes = dict()
            field_value = None

            field_name = field.name
            field_type = field.type
            field_comment = field.comment if field.comment else ""
            field_label = field.label
            input_id = "%s_%s" % (field.tablename, field.name)
            if isinstance(field, FieldVirtual):
                value = None
            else:
                value = vars.get(
                    field.name,
                    field.default()
                    if callable(field.default) else field.default,
                )
            error = errors.get(field.name)
            field_class = "type-" + field.type.split()[0].replace(":", "-")
            placeholder = (field._placeholder
                           if "_placeholder" in field.__dict__ else None)

            title = field._title if "_title" in field.__dict__ else None
            field_disabled = False

            # only display field if readable or writable
            if not field.readable and not field.writable:
                continue

            # if this is a readonly field only show readable fields
            if readonly:
                if not field.readable:
                    continue

            # do not show the id if not desired
            if field.type == "id" and not show_id:
                continue

            #  if this is an create form (unkown id) then only show writable fields.
            #  Some if an edit form was made from a list of fields and noncreate=True
            elif not vars.get("id") and noncreate:
                if not field.writable:
                    continue

            # ignore blob fields
            if field.type == "blob":  # never display blobs (mistake?)
                continue

            # ignore fields of type id its value is equal to None
            if field.type == "id" and value is None:
                field.writable = False
                continue

            # if the form is readonly or this is an id type field, display it as readonly
            if (readonly or not field.writable or field.type == "id"
                    or isinstance(field, FieldVirtual)):
                # for boolean readonly we use a readonly checbox
                if field.type == "boolean":

                    control = CheckboxWidget().make(field,
                                                    value,
                                                    error,
                                                    title,
                                                    readonly=True)
                # for all othe readonly fields we use represent or a string
                else:
                    if isinstance(field, FieldVirtual):
                        field_value = field.f(vars)
                    else:
                        field_value = (field.represent
                                       and field.represent(value) or value
                                       or "")
                    field_type = "represent"
                    control = DIV(field_value)

                field_disabled = True

            # if we have a field.widget for the field use it but this logic is deprecated
            elif field.widget:
                control = field.widget(table, vars)
            # else we pick the right widget
            else:
                if field.name in self.widgets:
                    widget = self.widgets[field.name]
                elif field.type == "text":
                    widget = TextareaWidget()
                elif field.type == "datetime":
                    widget = DateTimeWidget()
                elif field.type == "boolean":
                    widget = CheckboxWidget()
                elif field.type == "upload":
                    widget = FileUploadWidget()
                    url = getattr(field, "download_url",
                                  lambda value: "#")(value)
                    # Set the download url.
                    field_attributes["_download_url"] = url
                    # Set the flag determining whether the file is an image.
                    field_attributes["_is_image"] = (
                        url != "#") and Form.is_image(value)
                    # do we need the variables below?
                    delete_field_attributes = dict()
                    delete_field_attributes["_label"] = "Remove"
                    delete_field_attributes["_value"] = "ON"
                    delete_field_attributes["_type"] = "checkbox"
                    delete_field_attributes["_name"] = "_delete_" + field.name
                    json_controls["form_fields"] += [delete_field_attributes]
                    json_controls["form_values"]["_delete_" +
                                                 field.name] = None
                elif get_options(field.requires) is not None:
                    widget = SelectWidget()
                elif field.type == "password":
                    widget = PasswordWidget()
                elif field.type.startswith("list:"):
                    widget = ListWidget()
                else:
                    widget = Widget()

                control = widget.make(field, value, error, title, placeholder)

            key = control.name.rstrip("/")

            if key == "input":
                key += "[type=%s]" % (control["_type"] or "text")

            control["_class"] = join_classes(control.attributes.get("_class"),
                                             self.classes.get(key))

            # Set the form controls.
            controls["labels"][field_name] = field_label
            controls["widgets"][field_name] = control
            controls["comments"][field_name] = field_comment
            controls["titles"][field_name] = title
            controls["placeholders"][field_name] = placeholder

            # Set the remain json field attributes.
            field_attributes["_title"] = title
            field_attributes["_label"] = field_label
            field_attributes["_comment"] = field_comment
            field_attributes["_id"] = to_id(field)
            field_attributes["_class"] = field_class
            field_attributes["_name"] = field.name
            field_attributes["_type"] = field.type
            field_attributes["_placeholder"] = placeholder
            field_attributes["_error"] = error
            field_attributes["_disabled"] = field_disabled

            # Add to the json controls.
            json_controls["form_fields"] += [field_attributes]
            json_controls["form_values"][field_name] = field_value

            if error:
                controls["errors"][field.name] = error

            if field.type == "boolean":
                controls.wrappers[field.name] = wrapped = SPAN(
                    control, _class=class_inner)
                form.append(
                    DIV(
                        wrapped,
                        LABEL(
                            " " + field.label,
                            _for=input_id,
                            _class=class_label,
                            _style="display: inline !important",
                        ),
                        P(error, _class=class_error) if error else "",
                        P(field.comment or "", _class=class_info),
                        _class=class_outer,
                    ))
            else:
                controls.wrappers[field.name] = wrapped = DIV(
                    control,
                    _class=self.class_inner_exceptions.get(
                        control.name, class_inner),
                )

                form.append(
                    DIV(
                        LABEL(field.label, _for=input_id, _class=class_label),
                        wrapped,
                        P(error, _class=class_error) if error else "",
                        P(field.comment or "", _class=class_info),
                        _class=class_outer,
                    ))

        if vars.get("id"):
            form.append(INPUT(_name="id", _value=vars["id"], _hidden=True))

        if deletable:

            deletable_record_attributes = dict()

            deletable_field_name = "_delete"
            deletable_field_type = "checkbox"

            # Set the deletable json field attributes.
            deletable_record_attributes["_label"] = " check to delete"
            deletable_record_attributes["_name"] = deletable_field_name
            deletable_record_attributes["_type"] = deletable_field_type
            deletable_record_attributes["_class"] = self.classes[
                "input[type=checkbox]"]
            deletable_record_attributes["_value"] = "ON"

            # Add to the json controls.
            json_controls["form_fields"] += [deletable_record_attributes]
            json_controls["form_values"][deletable_field_name] = None

            controls["delete"] = INPUT(
                _type=deletable_field_type,
                _value=deletable_record_attributes["_value"],
                _name=deletable_field_name,
                _class=self.classes["input[type=checkbox]"],
            )

            form.append(
                DIV(
                    SPAN(
                        controls["delete"],
                        _class=class_inner,
                        _stye="vertical-align: middle;",
                    ),
                    P(
                        deletable_record_attributes["_label"],
                        _class="help",
                        _style="display: inline !important",
                    ),
                    _class=class_outer,
                ))

        submit_button_attributes = dict()

        submit_button_field_type = "submit"

        # Set the deletable json field attributes.
        submit_button_attributes["_label"] = "Submit"
        submit_button_attributes["_type"] = submit_button_field_type
        submit_button_attributes["_class"] = self.classes["input[type=submit]"]

        # Add to the json controls.
        json_controls["form_buttons"] += [submit_button_attributes]

        controls["submit"] = INPUT(
            _type=submit_button_field_type,
            _value="Submit",
            _class=self.classes["input[type=submit]"],
        )

        submit = DIV(
            DIV(
                controls["submit"],
                _class=class_inner,
            ),
            _class=class_outer,
        )
        form.append(submit)

        return dict(form=form, controls=controls, json_controls=json_controls)
Пример #24
0
    def produce(self, table, vars, errors, readonly, deletable, classes=None):
        self.classes.update(classes or {})
        form = FORM(_method="POST", _action=request.url, _enctype="multipart/form-data")
        controls = Param(
            labels=dict(),
            widgets=dict(),
            comments=dict(),
            hidden_widgets=dict(),
            placeholders=dict(),
            titles=dict(),
            errors=dict(),
            begin=XML(form.xml().split("</form>")[0]),
            submit="",
            delete="",
            end=XML("</form>"),
        )
        class_label = self.classes["label"]
        class_outer = self.classes["outer"]
        class_inner = self.classes["inner"]
        class_error = self.classes["error"]
        class_info = self.classes["info"]

        for field in table:

            input_id = "%s_%s" % (field.tablename, field.name)
            value = vars.get(field.name, field.default)
            error = errors.get(field.name)
            field_class = "type-" + field.type.split()[0].replace(":", "-")
            placeholder = (
                field._placeholder if "_placeholder" in field.__dict__ else None
            )
            title = field._title if "_title" in field.__dict__ else None
            # only diplay field if readable or writable
            if not field.readable and not field.writable:
                continue
            # if this is a reaonly field only show readable fields
            if readonly:
                if not field.readable:
                    continue
            # if this is an create form (unkown id) then only show writable fields
            elif not vars.get("id"):
                if not field.writable:
                    continue
            # ignore blob fields
            if field.type == "blob":  # never display blobs (mistake?)
                continue
            # ignore fields of type id its value is equal to None
            if field.type == "id" and value is None:
                field.writable = False
                continue
            # if the form is readonly or this is an id type field, display it as readonly
            if readonly or not field.writable or field.type == "id":
                if field.type == "boolean":
                    control = INPUT(
                        _type="checkbox",
                        _id=input_id,
                        _name=field.name,
                        _value="ON",
                        _disabled="",
                        _checked=value,
                        _title=title,
                    )
                else:
                    control = DIV(
                        field.represent and field.represent(value) or value or ""
                    )
            # if we have a widget for the field use it
            elif field.widget:
                control = field.widget(table, value)
            # else pick the proper default widget
            elif field.type == "text":
                control = TEXTAREA(
                    value or "",
                    _id=input_id,
                    _name=field.name,
                    _placeholder=placeholder,
                    _title=title,
                )
            elif field.type == "date":
                control = INPUT(
                    _value=value,
                    _type="date",
                    _id=input_id,
                    _name=field.name,
                    _placeholder=placeholder,
                    _title=title,
                )
            elif field.type == "datetime":
                if isinstance(value, str):
                    value = value.replace(" ", "T")
                control = INPUT(
                    _value=value,
                    _type="datetime-local",
                    _id=input_id,
                    _name=field.name,
                    _placeholder=placeholder,
                    _title=title,
                )
            elif field.type == "time":
                control = INPUT(
                    _value=value,
                    _type="time",
                    _id=input_id,
                    _name=field.name,
                    _placeholder=placeholder,
                    _title=title,
                )
            elif field.type == "boolean":
                control = INPUT(
                    _type="checkbox",
                    _id=input_id,
                    _name=field.name,
                    _value="ON",
                    _checked=value,
                    _title=title,
                )
            elif field.type == "upload":
                control = DIV()
                if value and field.download_url is not None and not error:
                    download_div = DIV()
                    download_div.append(
                        LABEL(
                            "Currently:  ",
                        )
                    )
                    download_div.append(
                        A(" download ", _href=field.download_url(value))
                    )
                    download_div.append(
                        INPUT(
                            _type="checkbox",
                            _value="ON",
                            _name="_delete_" + field.name,
                            _title=title,
                        )
                    )
                    download_div.append(" (check to remove)")
                    control.append(download_div)
                control.append(LABEL("Change: "))
                control.append(INPUT(_type="file", _id=input_id, _name=field.name))
            elif get_options(field.requires) is not None and field.writable == True:
                multiple = field.type.startswith("list:")
                value = list(map(str, value if isinstance(value, list) else [value]))
                option_tags = [
                    OPTION(v, _value=k, _selected=(not k is None and k in value))
                    for k, v in get_options(field.requires)
                ]
                control = SELECT(
                    *option_tags,
                    _id=input_id,
                    _name=field.name,
                    _multiple=multiple,
                    _title=title
                )
            else:
                field_type = "password" if field.type == "password" else "text"
                if field.type.startswith("list:"):
                    value = json.dumps(value or [])
                control = INPUT(
                    _type=field_type,
                    _id=input_id,
                    _name=field.name,
                    _value=None if field.type == "password" else value,
                    _class=field_class,
                    _placeholder=placeholder,
                    _title=title,
                    _autocomplete="off" if field_type == "password" else "on",
                )

            key = control.name.rstrip("/")
            if key == "input":
                key += "[type=%s]" % (control["_type"] or "text")
            control["_class"] = (
                control.attributes.get("_class", "") + " " + self.classes.get(key, "")
            ).strip()
            controls["labels"][field.name] = field.label
            controls["widgets"][field.name] = control
            controls["comments"][field.name] = field.comment if field.comment else ""
            controls["titles"][field.name] = title
            controls["placeholders"][field.name] = placeholder
            if error:
                controls["errors"][field.name] = error

            if field.type == "boolean":
                form.append(
                    DIV(
                        SPAN(control, _class=class_inner),
                        LABEL(
                            " " + field.label,
                            _for=input_id,
                            _class=class_label,
                            _style="display: inline !important",
                        ),
                        P(error, _class=class_error) if error else "",
                        P(field.comment or "", _class=class_info),
                        _class=class_outer,
                    )
                )
            else:
                form.append(
                    DIV(
                        LABEL(field.label, _for=input_id, _class=class_label),
                        DIV(control, _class=class_inner),
                        P(error, _class=class_error) if error else "",
                        P(field.comment or "", _class=class_info),
                        _class=class_outer,
                    )
                )

            if vars.get("id"):
                form.append(INPUT(_name="id", _value=vars["id"], _hidden=True))
        if deletable:
            controls["delete"] = INPUT(
                _type="checkbox",
                _value="ON",
                _name="_delete",
                _class=self.classes["input[type=checkbox]"],
            )
            form.append(
                DIV(
                    SPAN(
                        controls["delete"],
                        _class=class_inner,
                        _stye="vertical-align: middle;",
                    ),
                    P(
                        " check to delete",
                        _class="help",
                        _style="display: inline !important",
                    ),
                    _class=class_outer,
                )
            )
        controls["submit"] = INPUT(
            _type="submit",
            _value="Submit",
            _class=self.classes["input[type=submit]"],
        )
        submit = DIV(
            DIV(
                controls["submit"],
                _class=class_inner,
            ),
            _class=class_outer,
        )
        form.append(submit)
        return dict(form=form, controls=controls)
Пример #25
0
 def __call__(self):
     """This method returns the element that can be included in the page."""
     return XML(Grid.GRID.format(url=self.url()))
Пример #26
0
    def render_table(self):
        html = DIV(**self.param.grid_class_style.get("grid-wrapper"))
        grid_header = DIV(**self.param.grid_class_style.get("grid-header"))

        #  build the New button if needed
        if self.param.create and self.param.create != "":
            if isinstance(self.param.create, str):
                create_url = self.param.create
            else:
                create_url = self.endpoint + "/new"

            create_url += "?%s" % self.referrer

            grid_header.append(
                self.render_action_button(
                    create_url,
                    self.param.new_action_button_text,
                    "fa-plus",
                    icon_size="normal",
                    override_classes=self.param.grid_class_style.classes.get(
                        "grid-new-button", ""),
                    override_styles=self.param.grid_class_style.get(
                        "new_button"),
                ))

        #  build the search form if provided
        if self.param.search_form:
            grid_header.append(self.render_search_form())
        elif self.param.search_queries and len(self.param.search_queries) > 0:
            grid_header.append(self.render_default_form())

        html.append(grid_header)

        table = TABLE(**self.param.grid_class_style.get("grid-table"))

        # build the header
        table.append(self.render_table_header())

        #  build the rows
        table.append(self.render_table_body())

        #  add the table to the html
        html.append(
            DIV(table,
                **self.param.grid_class_style.get("grid-table-wrapper")))

        #  add the row counter information
        footer = DIV(**self.param.grid_class_style.get("grid-footer"))

        row_count = DIV(**self.param.grid_class_style.get("grid-info"))
        row_count.append("Displaying rows %s thru %s of %s" % (
            self.page_start + 1 if self.number_of_pages > 1 else 1,
            self.page_end if self.page_end < self.total_number_of_rows else
            self.total_number_of_rows,
            self.total_number_of_rows,
        )) if self.number_of_pages > 0 else row_count.append(
            "No rows to display")
        footer.append(row_count)

        #  build the pager
        if self.number_of_pages > 1:
            footer.append(self.render_table_pager())

        html.append(footer)
        return XML(html)
Пример #27
0
class Grid:

    FORMATTERS_BY_TYPE = {
        "boolean":
        lambda value: INPUT(_type="checkbox", _checked=value) if value else "",
        "datetime":
        lambda value:
        XML("<script>document.write((new Date(%s,%s,%s,%s,%s,%s)).toLocaleString())</script>"
            % (
                value.year,
                value.month - 1,
                value.day,
                value.hour,
                value.minute,
                value.second,
            )) if value else "",
        "time":
        lambda value: XML(
            "<script>document.write((new Date(0, 0, 0,%s,%s,%s)).toLocaleString().split(', ')[1])</script>"
            % (value.hour, value.minute, value.second)) if value else "",
        "date":
        lambda value: XML(
            '<script>document.write((new Date(%s,%s,%s)).toLocaleString().split(",")[0])</script>'
            % (
                value.year,
                value.month - 1,
                value.day,
            )) if value else "",
        "list:string":
        lambda value: ", ".join(str(x) for x in value) if value else "",
        "list:integer":
        lambda value: ", ".join(x for x in value) if value else "",
        "default":
        lambda value: str(value) if value is not None else "",
    }

    def __init__(
        self,
        path,
        query,
        search_form=None,
        search_queries=None,
        fields=None,
        show_id=False,
        orderby=None,
        left=None,
        headings=None,
        create=True,
        details=True,
        editable=True,
        deletable=True,
        pre_action_buttons=None,
        post_action_buttons=None,
        auto_process=True,
        rows_per_page=15,
        include_action_button_text=True,
        search_button_text="Filter",
        formstyle=FormStyleDefault,
        grid_class_style=GridClassStyle,
    ):
        """
        Grid is a searchable/sortable/pageable grid

        :param path: The part of the URL to be parsed by this grid
        :param query: the query used to filter the data
        :param search_form: py4web FORM to be included as the search form
        :param search_queries: future use - pass a dict of name and a search query
        :param fields: list of fields to display on the list page, if blank, glean tablename from first query
        :              and use all fields of that table
        :param show_id: show the record id field on list page - default = False
        :param orderby: pydal orderby field or list of fields
        :param left: if joining other tables, specify the pydal left expression here
        :param headings: list of headings to be used for list page - if not provided use the field label
        :param create: URL to redirect to for creating records - set to False to not display the button
        :param editable: URL to redirect to for editing records - set to False to not display the button
        :param deletable: URL to redirect to for deleting records - set to False to not display the button
        :param pre_action_buttons: list of action_button instances to include before the standard action buttons
        :param post_action_buttons: list of action_button instances to include after the standard action buttons
        :param auto_process: True/False - automatically process the sql for the form - if False, user is
                              responsible for calling process().
        """

        # in case the query is a Table instead
        if isinstance(query, query._db.Table):
            query = query._id != None

        self.path = path or ""
        self.db = query._db
        self.param = Param(
            query=query,
            fields=fields,
            show_id=show_id,
            orderby=orderby,
            left=left,
            search_form=search_form,
            search_queries=search_queries,
            headings=headings or [],
            create=create,
            details=details,
            editable=editable,
            deletable=deletable,
            pre_action_buttons=pre_action_buttons,
            post_action_buttons=post_action_buttons,
            rows_per_page=rows_per_page,
            include_action_button_text=include_action_button_text,
            search_button_text=search_button_text,
            formstyle=formstyle,
            grid_class_style=grid_class_style,
            new_sidecar=None,
            new_submit_value=None,
            new_action_button_text="New",
            details_sidecar=None,
            details_submit_value=None,
            details_action_button_text="Details",
            edit_sidecar=None,
            edit_submit_value=None,
            edit_action_button_text="Edit",
            delete_action_button_text="Delete",
            htmx_target=None,
        )

        #  instance variables that will be computed
        self.action = None
        self.current_page_number = None
        self.endpoint = request.fullpath
        if self.path:
            self.endpoint = self.endpoint[:-len(self.path)].rstrip("/")
        self.hidden_fields = None
        self.form = None
        self.number_of_pages = None
        self.page_end = None
        self.page_start = None
        self.query_parms = request.params
        self.readonly_fields = None
        self.record_id = None
        self.rows = None
        self.tablename = None
        self.total_number_of_rows = None
        self.use_tablename = self.is_join()
        self.formatters = {}
        self.formatters_by_type = copy.copy(Grid.FORMATTERS_BY_TYPE)

        if auto_process:
            self.process()

    def process(self):
        query = None
        db = self.db
        if not self.param.search_form and self.param.search_queries:
            search_type = safe_int(request.query.get("search_type", 0),
                                   default=0)
            search_string = request.query.get("search_string")
            if search_type < len(self.param.search_queries) and search_string:
                query_lambda = self.param.search_queries[search_type][1]
                try:
                    query = query_lambda(search_string)
                except:
                    pass  # flash a message here

        if not query:
            query = self.param.query
        else:
            query &= self.param.query

        parts = self.path.split("/")
        self.action = parts[0] or "select"
        self.tablename = self.get_tablenames(
            self.param.query)[0]  # what if there ar 2?
        self.record_id = safe_int(parts[1] if len(parts) > 1 else None,
                                  default=None)

        if self.param.fields:
            if not isinstance(self.param.fields, list):
                self.param.fields = [self.param.fields]
        else:
            table = db[self.tablename]
            self.param.fields = [field for field in table if field.readable]

        self.readonly_fields = [
            field for field in self.param.fields if not field.writable
        ]
        self.referrer = None

        if not self.tablename:
            raise HTTP(400)

        if self.action in ["new", "details", "edit"]:

            # SECURITY: if the record does not exist or does not match query, than we are not allowed
            if self.record_id:
                if (db((db[self.tablename]._id == self.record_id)
                       & self.param.query).count() == 0):
                    redirect(self.endpoint)  ## maybe flash

            readonly = self.action == "details"
            for field in self.readonly_fields:
                db[field.tablename][field.name].writable = False

            if not self.param.show_id:
                #  if not show id, find the "id" field and set readable/writable to False
                for field in db[self.tablename]:
                    if field.type == "id":
                        db[self.tablename][field.name].readable = False
                        db[self.tablename][field.name].writable = False

            attrs = ({
                "_hx-post": request.url,
                "_hx-target": self.param.htmx_target,
                "_hx-swap": "innertHTML",
            } if self.param.htmx_target else {})
            self.form = Form(
                db[self.tablename],
                record=self.record_id,
                readonly=readonly,
                formstyle=self.param.formstyle,
                **attrs,
            )
            if self.action == "new":
                if self.param.new_sidecar:
                    self.form.param.sidecar.append(self.param.new_sidecar)
                if self.param.new_submit_value:
                    self.form.param.submit_value = self.param.new_submit_value
            if self.action == "details":
                if self.param.details_sidecar:
                    self.form.param.sidecar.append(self.param.details_sidecar)
                if self.param.details_submit_value:
                    self.form.param.submit_value = self.param.details_submit_value
            if self.action == "edit":
                if self.param.edit_sidecar:
                    self.form.param.sidecar.append(self.param.edit_sidecar)
                if self.param.edit_submit_value:
                    self.form.param.submit_value = self.param.edit_submit_value

            # SECURITY: if the new record was created but does not match filter, delete it
            if self.form.accepted and not self.record_id:
                new_record = db[self.tablename]._id == self.form.vars["id"]
                if db(new_record & self.param.query).count() == 0:
                    db(new_record).delete()
                    # TODO: SHOULD FLASH SOME MESSAGE
            # redirect to the referrer
            if self.form.accepted or (readonly and request.method == "POST"):
                referrer = request.query.get("_referrer")
                if referrer:
                    redirect(
                        base64.b16decode(
                            referrer.encode("utf8")).decode("utf8"))
                else:
                    redirect(self.endpoint)

        elif self.action == "delete":
            db(db[self.tablename].id == self.record_id).delete()

            url = parse_referer()
            if url and url.query:
                self.endpoint += "?%s" % url.query
            redirect(self.endpoint)

        elif self.action == "select":
            self.referrer = "_referrer=%s" % base64.b16encode(
                request.url.encode("utf8")).decode("utf8")

            #  find the primary key of the primary table
            pt = db[self.tablename]
            key_is_missing = True
            for field in self.param.fields:
                if (field.table._tablename == pt._tablename
                        and field.name == pt._id.name):
                    key_is_missing = False
            if key_is_missing:
                #  primary key wasn't included, add it and set show_id to False so it doesn't display
                self.param.fields.append(pt._id)
                self.param.show_id = False

            self.current_page_number = safe_int(request.query.get("page"),
                                                default=1)

            select_params = dict()
            #  try getting sort order from the request
            sort_order = request.query.get("orderby", "")

            try:
                parts = sort_order.lstrip("~").split(".")
                orderby = db[parts[0]][parts[1]]
                if sort_order.startswith("~"):
                    orderby = ~orderby
                select_params["orderby"] = orderby
            except (IndexError, KeyError, TypeError, AttributeError):
                select_params["orderby"] = self.param.orderby

            if self.param.left:
                select_params["left"] = self.param.left

            if self.param.left:
                # TODO: maybe this can be made more efficient
                self.total_number_of_rows = len(
                    db(query).select(db[self.tablename].id, **select_params))
            else:
                self.total_number_of_rows = db(query).count()

            #  if at a high page number and then filter causes less records to be displayed, reset to page 1
            if (self.current_page_number -
                    1) * self.param.rows_per_page > self.total_number_of_rows:
                self.current_page_number = 1

            if self.total_number_of_rows > self.param.rows_per_page:
                self.page_start = self.param.rows_per_page * (
                    self.current_page_number - 1)
                self.page_end = self.page_start + self.param.rows_per_page
                select_params["limitby"] = (self.page_start, self.page_end)
            else:
                self.page_start = 0
                if self.total_number_of_rows > 1:
                    self.page_start = 1
                self.page_end = self.total_number_of_rows

            if self.param.fields:
                self.rows = db(query).select(*self.param.fields,
                                             **select_params)
            else:
                self.rows = db(query).select(**select_params)

            self.number_of_pages = self.total_number_of_rows // self.param.rows_per_page
            if self.total_number_of_rows % self.param.rows_per_page > 0:
                self.number_of_pages += 1
        else:
            redirect(self.endpoint)

    def iter_pages(
        self,
        current_page,
        num_pages,
        left_current=1,
        right_current=1,
        left_edge=1,
        right_edge=1,
    ):
        """
        generator used to determine which page numbers should be shown on the Grid pager

        :param left_current: # of pages to add to the left of current page
        :param right_current: # of fpages to add to the right of current page
        :param left_edge: # of pages to add to the left end
        :param right_edge: # of fpages to add to the right end
        """
        left_range = set(range(1, min(2 + left_edge, num_pages + 1)))
        mid_range = set(
            range(
                max(1, current_page - left_current),
                min(current_page + right_current + 1, num_pages + 1),
            ))
        right_range = set(range(max(1, num_pages - right_edge), num_pages + 1))
        return list(sorted(left_range | mid_range | right_range))

    def render_action_button(
        self,
        url,
        button_text,
        icon,
        icon_size="small",
        additional_classes=None,
        additional_styles=None,
        override_classes=None,
        override_styles=None,
        message=None,
        onclick=None,
        row_id=None,
        name="grid-button",
        row=None,
        **attr,
    ):
        separator = "?"
        if row_id:
            url += "/%s" % row_id

        classes = self.param.grid_class_style.classes.get(name, "")
        styles = self.param.grid_class_style.styles.get(name, "")

        def join(items):
            return (" ".join(items) if isinstance(items,
                                                  (list, tuple)) else " %s" %
                    items)

        if override_classes:
            classes = join(override_classes)
        elif additional_classes:
            classes += join(additional_classes)
        if override_styles:
            styles = join(override_styles)
        elif additional_styles:
            styles += join(additional_styles)

        if callable(url):
            url = url(row)

        if self.param.htmx_target:
            attr["_hx-get"] = url
            attr["_hx-target"] = self.param.htmx_target
            attr["_hx-swap"] = "innertHTML"
        else:
            attr["_href"] = url
        link = A(
            I(_class="fa %s" % icon),
            _role="button",
            _class=classes,
            _message=message,
            _title=button_text,
            _style=styles,
            **attr,
        )
        if self.param.include_action_button_text:
            link.append(
                XML('<span class="grid-action-button-text">&nbsp;%s</span>' %
                    button_text))

        return link

    def render_default_form(self):
        search_type = safe_int(request.query.get("search_type", 0), default=0)
        search_string = request.query.get("search_string")
        options = [
            OPTION(items[0], _value=k, _selected=(k == search_type))
            for k, items in enumerate(self.param.search_queries)
        ]
        hidden_fields = [
            INPUT(_name=key, _value=request.query.get(key), _type="hidden")
            for key in request.query
            if not key in ("search_type", "search_string")
        ]
        if self.param.htmx_target:
            attrs = {
                "_hx-post": self.endpoint,
                "_hx-target": self.param.htmx_target,
                "_hx-swap": "innertHTML",
            }
        else:
            attrs = {"_method": "GET", "_action": self.endpoint}
        form = FORM(*hidden_fields, **attrs)
        select = SELECT(
            *options,
            **dict(_name="search_type", ),
        )
        input = INPUT(
            _type="text",
            _name="search_string",
            _value=search_string,
        )
        submit = INPUT(_type="submit", _value="Search")
        clear_script = "document.querySelector('[name=search_string]').value='';"
        clear = INPUT(_type="submit", _value="Clear", _onclick=clear_script)
        div = DIV(_id="grid-search",
                  **self.param.grid_class_style.get("grid-search"))

        # we do not need classes for these elements
        tr = TR()
        if len(options) > 1:
            tr.append(TD(select))
        tr.append(TD(input))
        tr.append(TD(submit, clear))
        form.append(TABLE(tr))
        div.append(form)
        return div

    def render_search_form(self):
        # TODO: Do we need this?
        div = DIV(_id="grid-search",
                  **self.param.grid_class_style.get("grid-search"))
        div.append(self.param.search_form.custom["begin"])
        tr = TR(**self.param.grid_class_style.get("search_form_tr"))
        for field in self.param.search_form.table:
            td = TD(**self.param.grid_class_style.get("search_form_td"))
            if field.type == "boolean":
                sb = DIV(**self.param.grid_class_style.get("search_boolean"))
                sb.append(self.param.search_form.custom["widgets"][field.name])
                sb.append(field.label)
                td.append(sb)
            else:
                td.append(self.param.search_form.custom["widgets"][field.name])
            if (field.name in self.param.search_form.custom["errors"]
                    and self.param.search_form.custom["errors"][field.name]):
                td.append(
                    DIV(
                        self.param.search_form.custom["errors"][field.name],
                        _style="color:#ff0000",
                    ))
            tr.append(td)
        if self.param.search_button_text:
            tr.append(
                TD(
                    INPUT(
                        _class="button",
                        _type="submit",
                        _value=self.param.search_button_text,
                    ),
                    **self.param.grid_class_style.get("search_form_td"),
                ))
        else:
            tr.append(
                TD(
                    self.param.search_form.custom["submit"],
                    **self.param.grid_class_style.get("search_form_td"),
                ))
        div.append(
            TABLE(tr, **self.param.grid_class_style.get("search_form_table")))
        for hidden_widget in self.param.search_form.custom[
                "hidden_widgets"].keys():
            div.append(
                self.param.search_form.custom["hidden_widgets"][hidden_widget])

        div.append(self.param.search_form.custom["end"])

        return div

    def render_table_header(self):

        up = I(**self.param.grid_class_style.get("grid-sorter-icon-up"))
        dw = I(**self.param.grid_class_style.get("grid-sorter-icon-down"))
        columns = []
        sort_order = request.query.get("orderby", "")

        for index, field in enumerate(self.param.fields):
            if field.readable and (field.type != "id" or self.param.show_id):
                key = "%s.%s" % (field.tablename, field.name)
                heading = (self.param.headings[index] if
                           index < len(self.param.headings) else field.label)
                heading = title(heading)
                #  add the sort order query parm
                sort_query_parms = dict(self.query_parms)

                if key == sort_order:
                    sort_query_parms["orderby"] = "~" + key
                    href = URL(self.endpoint, vars=sort_query_parms)
                    col = A(heading, up, _href=href)
                else:
                    sort_query_parms["orderby"] = key
                    href = URL(self.endpoint, vars=sort_query_parms)
                    col = A(heading,
                            dw if "~" + key == sort_order else "",
                            _href=href)
                columns.append((key, col))

        thead = THEAD(
            _class=self.param.grid_class_style.classes.get("grid-thead", ""))
        for key, col in columns:
            col_class = " grid-col-%s" % key
            thead.append(
                TH(
                    col,
                    _class=self.param.grid_class_style.classes.get(
                        "grid-th", "") + col_class,
                    _style=self.param.grid_class_style.styles.get("grid-th"),
                ))

        if (self.param.details or self.param.editable or self.param.deletable
                or self.param.pre_action_buttons
                or self.param.post_action_buttons):
            thead.append(
                TH("",
                   **self.param.grid_class_style.get("grid-th-action-button")))

        return thead

    def render_field(self, row, field, field_index):
        """
        Render a field

        if only 1 table in the query, the no table name needed when getting the row value - however, if there
        are multiple tables in the query (self.use_tablename == True) then we need to use the tablename as well
        when accessing the value in the row object

        the row object sent in can take
        :param row:
        :param field:
        :return:
        """
        if self.use_tablename:
            field_value = row[field.tablename][field.name]
        else:
            field_value = row[field.name]
        key = "%s.%s" % (field.tablename, field.name)
        formatter = (self.formatters.get(key)
                     or self.formatters_by_type.get(field.type)
                     or self.formatters_by_type.get("default"))

        class_type = "grid-cell-type-%s" % str(
            field.type).split(":")[0].split("(")[0]
        class_col = " grid-col-%s" % key
        td = TD(
            formatter(field_value),
            _class=(self.param.grid_class_style.classes.get("grid-td", "") +
                    " " + class_type if class_type
                    not in self.param.grid_class_style.classes.get(
                        class_type, "").split(" ") else "" + " " +
                    self.param.grid_class_style.classes.get(class_type, "") +
                    " " + class_col).strip(),
            _style=(self.param.grid_class_style.styles.get(class_type)
                    or self.param.grid_class_style.styles.get("grid-td")),
        )

        return td

    def render_table_body(self):
        tbody = TBODY()
        for row in self.rows:
            #  find the row id - there may be nested tables....
            if self.use_tablename and self.tablename in row and "id" not in row:
                row_id = row[self.tablename]["id"]
            else:
                row_id = row["id"]
                self.use_tablename = False

            tr = TR(
                _role="row",
                _class=self.param.grid_class_style.classes.get("grid-tr"),
                _style=self.param.grid_class_style.styles.get("grid-tr"),
            )
            #  add all the fields to the row
            for index, field in enumerate(self.param.fields):
                if field.readable and (field.type != "id"
                                       or self.param.show_id):
                    tr.append(self.render_field(row, field, index))

            td = None

            #  add the action buttons
            if ((self.param.details and self.param.details != "")
                    or (self.param.editable and self.param.editable != "")
                    or (self.param.deletable and self.param.deletable != "")
                    or (self.param.post_action_buttons
                        or self.param.pre_action_buttons)):
                classes = (
                    self.param.grid_class_style.classes.get("grid-td", "") +
                    " " + self.param.grid_class_style.classes.get(
                        "grid-td-action-button")).strip()
                styles = (
                    self.param.grid_class_style.styles.get("grid-td", "") +
                    " " + self.param.grid_class_style.styles.get(
                        "grid-td-action-button")).strip()
                td = TD(_class=classes, _style=styles)
                if self.param.pre_action_buttons:
                    for btn in self.param.pre_action_buttons:
                        if btn.onclick:
                            btn.url = None
                        td.append(
                            self.render_action_button(
                                btn.url,
                                btn.text,
                                btn.icon,
                                _onclick=btn.onclick,
                                additional_classes=btn.additional_classes,
                                message=btn.message,
                                row_id=row_id if btn.append_id else None,
                                row=row,
                            ))
                if self.param.details and self.param.details != "":
                    if isinstance(self.param.details, str):
                        details_url = self.param.details
                    else:
                        details_url = self.endpoint + "/details"
                    details_url += "/%s?%s" % (row_id, self.referrer)
                    td.append(
                        self.render_action_button(
                            details_url,
                            self.param.details_action_button_text,
                            "fa-id-card",
                            name="grid-details-button",
                        ))

                if self.param.editable and self.param.editable != "":
                    if isinstance(self.param.editable, str):
                        edit_url = self.param.editable
                    else:
                        edit_url = self.endpoint + "/edit"
                    edit_url += "/%s?%s" % (row_id, self.referrer)
                    td.append(
                        self.render_action_button(
                            edit_url,
                            self.param.edit_action_button_text,
                            "fa-edit",
                            name="grid-edit-button",
                        ))

                if self.param.deletable and self.param.deletable != "":
                    if isinstance(self.param.deletable, str):
                        delete_url = self.param.deletable
                    else:
                        delete_url = self.endpoint + "/delete"
                    delete_url += "/%s?%s" % (row_id, self.referrer)
                    td.append(
                        self.render_action_button(
                            delete_url,
                            self.param.delete_action_button_text,
                            "fa-trash",
                            additional_classes="confirmation",
                            message="Delete record",
                            name="grid-delete-button",
                            _onclick=
                            "if(!confirm('sure you want to delete')) return false;",
                        ))
                if self.param.post_action_buttons:
                    for btn in self.param.post_action_buttons:
                        if btn.onclick:
                            btn.url = None
                        td.append(
                            self.render_action_button(
                                btn.url,
                                btn.text,
                                btn.icon,
                                _onclick=btn.onclick,
                                additional_classes=btn.additional_classes,
                                message=btn.message,
                                row_id=row_id if btn.append_id else None,
                                row=row,
                            ))
                tr.append(td)
            tbody.append(tr)

        return tbody

    def render_table_pager(self):
        pager = DIV(**self.param.grid_class_style.get("grid-pagination"))
        previous_page_number = None
        for page_number in self.iter_pages(self.current_page_number,
                                           self.number_of_pages):
            pager_query_parms = dict(self.query_parms)
            pager_query_parms["page"] = page_number
            # if there is a gat add a spacer
            if previous_page_number and page_number - previous_page_number > 1:
                pager.append(SPAN("...", _style="margin:0 10px;"))
            is_current = self.current_page_number == page_number
            page_name = ("grid-pagination-button-current"
                         if is_current else "grid-pagination-button")
            attrs = ({
                "_hx-get": URL(self.endpoint, vars=pager_query_parms),
                "_hx-target": self.param.htmx_target,
                "_hx-swap": "innertHTML",
            } if self.param.htmx_target else {
                "_href": URL(self.endpoint, vars=pager_query_parms)
            })
            pager.append(
                A(
                    page_number,
                    **self.param.grid_class_style.get(page_name),
                    _role="button",
                    **attrs,
                ))
            previous_page_number = page_number
        return pager

    def render_table(self):
        html = DIV(**self.param.grid_class_style.get("grid-wrapper"))
        grid_header = DIV(**self.param.grid_class_style.get("grid-header"))

        #  build the New button if needed
        if self.param.create and self.param.create != "":
            if isinstance(self.param.create, str):
                create_url = self.param.create
            else:
                create_url = self.endpoint + "/new"

            create_url += "?%s" % self.referrer

            grid_header.append(
                self.render_action_button(
                    create_url,
                    self.param.new_action_button_text,
                    "fa-plus",
                    icon_size="normal",
                    override_classes=self.param.grid_class_style.classes.get(
                        "grid-new-button", ""),
                    override_styles=self.param.grid_class_style.get(
                        "new_button"),
                ))

        #  build the search form if provided
        if self.param.search_form:
            grid_header.append(self.render_search_form())
        elif self.param.search_queries and len(self.param.search_queries) > 0:
            grid_header.append(self.render_default_form())

        html.append(grid_header)

        table = TABLE(**self.param.grid_class_style.get("grid-table"))

        # build the header
        table.append(self.render_table_header())

        #  build the rows
        table.append(self.render_table_body())

        #  add the table to the html
        html.append(
            DIV(table,
                **self.param.grid_class_style.get("grid-table-wrapper")))

        #  add the row counter information
        footer = DIV(**self.param.grid_class_style.get("grid-footer"))

        row_count = DIV(**self.param.grid_class_style.get("grid-info"))
        row_count.append("Displaying rows %s thru %s of %s" % (
            self.page_start + 1 if self.number_of_pages > 1 else 1,
            self.page_end if self.page_end < self.total_number_of_rows else
            self.total_number_of_rows,
            self.total_number_of_rows,
        )) if self.number_of_pages > 0 else row_count.append(
            "No rows to display")
        footer.append(row_count)

        #  build the pager
        if self.number_of_pages > 1:
            footer.append(self.render_table_pager())

        html.append(footer)
        return XML(html)

    def render(self):
        """
        build the query table

        :return: html representation of the table or the py4web Form object
        """
        if self.action == "select":
            return self.render_table()
        elif self.action in ["new", "details", "edit"]:
            return self.form

    def data(self):
        """
        get the record that is being edited / displayed

        :return: DAL record of the record being edited
        """
        return (self.db[self.tablename](self.record_id)
                if self.tablename and self.record_id else None)

    def add_search_query(self, name, query, requires):
        if self.param.search_form:
            raise ValueError(
                "Cannot add search queries if a you provide a search_form to the grid call "
                "or if auto_process is set to True.  Ensure no search_form is set, set "
                "auto_process to False, add your search query and then call grid.process()."
            )

        if self.param.search_queries is None:
            self.param.search_queries = []
        self.param.search_queries.append([name, query, requires])

    def get_tablenames(self, *args):
        """Returns the tablenames used by this grid"""
        return list(self.db._adapter.tables(*args).keys())

    def is_join(self):
        items = [self.param.query]
        if self.param.left is not None:
            if isinstance(self.param.left, (list, tuple)):
                items += [item for item in self.param.left]
            else:
                items += [self.param.left]
        return len(self.get_tablenames(*items)) > 1
Пример #28
0
 def __call__(self, id=None):
     """This method returns the element that can be included in the page.
     @param id: id of the file uploaded.  This can be useful if there are
     multiple instances of this form on the page."""
     return XML(FileUpload.FILE_UPLOAD.format(url=self.url(id=id)))
Пример #29
0
    def test_sanitize(self):
        permitted_tags = [
            'div',
            'td',
            'b',
            'br/',
            'strong',
            'span',
            'img/',
            'a',
        ]
        allowed_attributes = {
            'a': ['href', 'title'],
            'img': ['src', 'alt'],
            'blockquote': ['type'],
            'td': ['colspan'],
        }
        # test permitted
        for x in permitted_tags:
            T = TAG[x]
            s_tag = T().xml()
            if x == "img/":  # alt or src attribute is required. src has to have a valid href
                s_tag = T(_alt="empty").xml()
                self.assertEqual(
                    XML(s_tag,
                        sanitize=True,
                        permitted_tags=['img/'],
                        allowed_attributes={
                            'img': ['src', 'alt']
                        }).xml(), "<img alt=\"empty\"/>")
                s_tag = T(_src="/image.png").xml()
                self.assertEqual(
                    XML(s_tag,
                        sanitize=True,
                        permitted_tags=['img/'],
                        allowed_attributes={
                            'img': ['src', 'alt']
                        }).xml(), "<img src=\"/image.png\"/>")
            elif x == "a":  # It has to have a valid href or title or not tag empty
                s_tag = T("this is a link", _href="http://web2py.com/").xml()
                self.assertEqual(
                    XML(s_tag,
                        sanitize=True,
                        permitted_tags=['a'],
                        allowed_attributes={
                            'a': ['href', 'title']
                        }).xml(),
                    "<a href=\"http://web2py.com/\">this is a link</a>")
                s_tag = T("without href", _title="this is a link?").xml()
                self.assertEqual(
                    XML(s_tag,
                        sanitize=True,
                        permitted_tags=['a'],
                        allowed_attributes={
                            'a': ['href', 'title']
                        }).xml(),
                    '<a title="this is a link?">without href</a>')
                s_tag = T(_title="empty_tag").xml()
                self.assertEqual(
                    XML(s_tag,
                        sanitize=True,
                        permitted_tags=['a'],
                        allowed_attributes={
                            'a': ['href', 'title']
                        }).xml(), '<a title="empty_tag"></a>')
            else:
                self.assertEqual(
                    XML(s_tag,
                        sanitize=True,
                        permitted_tags=permitted_tags,
                        allowed_attributes=allowed_attributes).xml(),
                    "<%s></%s>" % (x, x) if not x[-1] == "/" else "<%s>" % x)

        # test tag out of list
        out_of_list = [
            'blockquote', 'i', 'li', 'ol', 'ul', 'p', 'cite', 'code', 'pre',
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'table', 'tbody', 'thead',
            'tfoot', 'tr'
            'strong'
        ]
        for x in out_of_list:
            T = TAG[x]
            self.assertEqual(
                XML(T().xml(),
                    sanitize=True,
                    permitted_tags=permitted_tags,
                    allowed_attributes=allowed_attributes).xml(),
                "&lt;%s&gt;&lt;/%s&gt;" % (x, x))
        # test unusual tags
        for x in ["evil", "n0c1v3"]:
            T = TAG[x]
            self.assertEqual(
                XML(T().xml(),
                    sanitize=True,
                    permitted_tags=permitted_tags,
                    allowed_attributes=allowed_attributes).xml(),
                "&lt;%s&gt;&lt;/%s&gt;" % (x, x))
        # test allowed_attributes
        s_tag = TAG['td']("content_td", _colspan="2",
                          _extra_attr="invalid").xml()
        self.assertEqual(
            XML(s_tag,
                sanitize=True,
                permitted_tags=['td'],
                allowed_attributes={
                    'td': ['colspan']
                }).xml(), '<td colspan="2">content_td</td>')
        s_tag = TAG['a']("link", _href="http://web2py.com/",
                         _title="my_title").xml()
        self.assertEqual(
            XML(s_tag,
                sanitize=True,
                permitted_tags=['a'],
                allowed_attributes={
                    'a': ['href', 'title']
                }).xml(),
            '<a href="http://web2py.com/" title="my_title">link</a>')
        s_tag = TAG['img/'](_alt="empty", _src="/images/logo.png").xml()
        self.assertEqual(
            XML(s_tag,
                sanitize=True,
                permitted_tags=['img/'],
                allowed_attributes={
                    'img': ['src', 'alt']
                }).xml(), '<img src="/images/logo.png" alt="empty"/>')
        s_tag = TAG['div']("content", _style="{backgrond-color: red;}").xml()
        self.assertEqual(
            XML(s_tag,
                sanitize=True,
                permitted_tags=['div'],
                allowed_attributes={
                    'div': ['style']
                }).xml(), '<div style="{backgrond-color: red;}">content</div>')
        self.assertEqual(
            XML(TAG['a']("oh no!", _href="invalid_link").xml(),
                sanitize=True,
                permitted_tags=['a']).xml(), 'oh no!')
        self.assertEqual(
            XML(TAG['div']("", _onclick="evil()").xml(),
                sanitize=True,
                permitted_tags=['div']).xml(), '<div></div>')

        # valid inside invalid
        s_tag = TAG['evil'](TAG['div']('valid'),
                            _style="{backgrond-color: red;}").xml()
        self.assertEqual(
            XML(s_tag,
                sanitize=True,
                permitted_tags=['div'],
                allowed_attributes={
                    'div': ['style']
                }).xml(), '&lt;evil&gt;<div>valid</div>&lt;/evil&gt;')
        self.assertEqual(
            XML(TAG['a'](TAG['img/'](_src="/index.html"),
                         _class="teste").xml(),
                sanitize=True,
                permitted_tags=['a', 'img/']).xml(),
            '<img src="/index.html"/>')

        # tags deleted even allowed
        self.assertEqual(
            XML(TAG['img/']().xml(), sanitize=True,
                permitted_tags=['img']).xml(), "")
        self.assertEqual(
            XML(TAG['img/'](_src="invalid_url").xml(),
                sanitize=True,
                permitted_tags=['img']).xml(), "")
        self.assertEqual(
            XML(TAG['img/'](_class="teste").xml(),
                sanitize=True,
                permitted_tags=['img']).xml(), "")
        self.assertEqual(
            XML(TAG['a'](_href="invalid_link").xml(),
                sanitize=True,
                permitted_tags=['a']).xml(), "")
Пример #30
0
 def mtable(self, table):
     path = self.path.replace("<tablename>", table._tablename)
     return XML(MTABLE.format(url=URL(path), render={}))