Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for limit submit and unique field in store data option #68

Merged
merged 8 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ repos:
types: [python]
args: ["--max-complexity=30", "--max-line-length=88", "--ignore=E203,DJ01,DJ08,W503,ANN101", "--exclude=docs/*", "src/", "setup.py"]
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
Expand Down
2 changes: 2 additions & 0 deletions src/design/plone/policy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
"""Init and utils."""
from .patches import apply as apply_patches
from .sensitive import apply
from zope.i18nmessageid import MessageFactory


_ = MessageFactory("design.plone.policy")
apply()
apply_patches()
15 changes: 15 additions & 0 deletions src/design/plone/policy/patches/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from design.plone.policy.patches.collective_volto_formsupport import (
patch_FormDataExportGet_get_data,
)
from design.plone.policy.patches.collective_volto_formsupport import (
patch_FormDataStore_methods,
)
from design.plone.policy.patches.collective_volto_formsupport import (
patch_SubmitPost_reply,
)


def apply():
patch_FormDataExportGet_get_data()
patch_SubmitPost_reply()
patch_FormDataStore_methods()
258 changes: 258 additions & 0 deletions src/design/plone/policy/patches/collective_volto_formsupport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
# -*- coding: utf-8 -*-
"""
We use this file to change the base behavior of collective.volto.formsupport
to support some new feature:
- limit on form submit
- unique field in one form

Why do we use monkeypatch instead of overriding the classes?
Because it's temporary, until collective.volto.formsupport can support backend
validation for data
"""
from collective.volto.formsupport import _
from collective.volto.formsupport.datamanager.catalog import FormDataStore
from collective.volto.formsupport.interfaces import IFormDataStore
from collective.volto.formsupport.restapi.services.form_data.csv import (
FormDataExportGet,
)
from collective.volto.formsupport.restapi.services.submit_form.post import logger
from collective.volto.formsupport.restapi.services.submit_form.post import (
PostEventService,
)
from collective.volto.formsupport.restapi.services.submit_form.post import SubmitPost
from datetime import datetime
from io import StringIO
from plone.protect.interfaces import IDisableCSRFProtection
from plone.restapi.serializer.converters import json_compatible
from souper.soup import Record
from zExceptions import BadRequest
from zope.component import getMultiAdapter
from zope.event import notify
from zope.i18n import translate
from zope.interface import alsoProvides

import csv


SKIP_ATTRS = ["block_id", "fields_labels", "fields_order"]


def get_data(self):
store = getMultiAdapter((self.context, self.request), IFormDataStore)
sbuf = StringIO()
fixed_columns = ["date"]
columns = []
# start patch
custom_colums = []
if self.form_block.get("limit", None) is not None:
limit = int(self.form_block["limit"])
if limit > -1:
custom_colums.append("waiting_list")
# end patch

rows = []
# start patch
for index, item in enumerate(store.search()):
# end patch
data = {}
fields_labels = item.attrs.get("fields_labels", {})
for k in self.get_ordered_keys(item):
if k in SKIP_ATTRS:
continue
value = item.attrs.get(k, None)
label = fields_labels.get(k, k)
if label not in columns and label not in fixed_columns:
columns.append(label)
data[label] = json_compatible(value)
for k in fixed_columns:
# add fixed columns values
value = item.attrs.get(k, None)
data[k] = json_compatible(value)
# start patch
if "waiting_list" in custom_colums:
data.update(
{
"waiting_list": (
translate(_("yes_label", default="Yes"))
if not (index < limit)
else translate(_("no_label", default="No"))
)
}
)
# end patch

rows.append(data)
columns.extend(fixed_columns)
columns.extend(custom_colums)
writer = csv.DictWriter(sbuf, fieldnames=columns, quoting=csv.QUOTE_ALL)
writer.writeheader()
for row in rows:
writer.writerow(row)
res = sbuf.getvalue()
sbuf.close()
return res


def patch_FormDataExportGet_get_data():
logger.info(
"Patch get_data methos of class FormDataExporterGet from collective.volto.formsupport" # noqa
)
FormDataExportGet.get_data = get_data


def reply(self):
self.validate_form()

# start patch
self.store_action = self.block.get("store", False)
self.send_action = self.block.get("send", [])
self.submit_limit = int(self.block.get("limit", "-1"))

# Disable CSRF protection
alsoProvides(self.request, IDisableCSRFProtection)

notify(PostEventService(self.context, self.form_data))
data = self.form_data.get("data", [])
# end patch

if self.send_action:
try:
self.send_data()
except BadRequest as e:
raise e
except Exception as e:
logger.exception(e)
message = translate(
_(
"mail_send_exception",
default="Unable to send confirm email. Please retry later or contact site administrator.", # noqa
),
context=self.request,
)
self.request.response.setStatus(500)
return dict(type="InternalServerError", message=message)
# start patch
if self.store_action:
try:
data = self.store_data()
except ValueError as e:
logger.exception(e)
message = translate(
_(
"save_data_exception",
default="Unable to save data. Value not unique: '${fields}'",
mapping={"fields": e.args[0]},
),
context=self.request,
)
self.request.response.setStatus(500)
return dict(type="InternalServerError", message=message)

return {"data": data}
# end patch


def store_data(self):
store = getMultiAdapter((self.context, self.request), IFormDataStore)
# start patch
data = {"form_data": self.filter_parameters()}

res = store.add(data=data)
if not res:
raise BadRequest("Unable to store data")

waiting_list = (
self.submit_limit is not None and -1 < self.submit_limit < self.count_data()
)
data.update({"waiting_list": waiting_list})

return data


def count_data(self):
store = getMultiAdapter((self.context, self.request), IFormDataStore)
return store.count()


# end patch


def patch_SubmitPost_reply():
logger.info(
"Patch reply method of class SubmitPost from collective.volto.formsupport"
)
SubmitPost.reply = reply
SubmitPost.store_data = store_data
SubmitPost.count_data = count_data


def add(self, data):
form_fields = self.get_form_fields()
if not form_fields:
logger.error(
'Block with id {} and type "form" not found in context: {}.'.format(
self.block_id, self.context.absolute_url()
)
)
return None

fields = {
x["field_id"]: x.get("custom_field_id", x.get("label", x["field_id"]))
for x in form_fields
}
record = Record()
fields_labels = {}
fields_order = []
# start patch
for field_data in data["form_data"]:
# end patch
field_id = field_data.get("field_id", "")
value = field_data.get("value", "")
if field_id in fields:
record.attrs[field_id] = value
fields_labels[field_id] = fields[field_id]
fields_order.append(field_id)
record.attrs["fields_labels"] = fields_labels
record.attrs["fields_order"] = fields_order
record.attrs["date"] = datetime.now()
record.attrs["block_id"] = self.block_id

# start patch
keys = [(x["field_id"], x["label"]) for x in form_fields if x.get("unique", False)]
if keys:
saved_data = self.soup.data.values()
for saved_record in saved_data:
unique = False
for key in keys:
if record.attrs.storage[key[0]] != saved_record.attrs.storage[key[0]]:
unique = True
break

if not unique:
raise ValueError(f" {', '.join([x[1] for x in keys])}")
# end patch

return self.soup.add(record)


# start patch
def count(self, query=None):
records = []
if not query:
records = self.soup.data.values()

return len(records)


# end patch


def patch_FormDataStore_methods():
logger.info(
"Patch method add of FormDataStore class for collective.volto.formsupport"
)
FormDataStore.add = add
logger.info(
"Add method count of FormDataStore class for collective.volto.formsupport"
)
FormDataStore.count = count
4 changes: 2 additions & 2 deletions src/design/plone/policy/profiles/default/registry.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
<element>data-element</element>
</value>
</records>

<record field="show_author_info"
interface="plone.base.interfaces.syndication.ISiteSyndicationSettings"
interface="plone.base.interfaces.syndication.ISiteSyndicationSettings"
name="plone.base.interfaces.syndication.ISiteSyndicationSettings.show_author_info"
purge="True"
>
Expand Down
4 changes: 3 additions & 1 deletion src/design/plone/policy/profiles/default/rolemap.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<rolemap>
<permissions>
<permission name="Access inactive portal content" acquire="True">
<permission acquire="True"
name="Access inactive portal content"
>
<role name="Editor" />
<role name="Owner" />
<role name="Reviewer" />
Expand Down
2 changes: 1 addition & 1 deletion src/design/plone/policy/restapi/search_filters/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from plone.registry.interfaces import IRegistry
from plone.restapi.interfaces import ISerializeToJsonSummary
from plone.restapi.services import Service
from Products.CMFPlone.interfaces import ISearchSchema
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.interfaces import ISearchSchema
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.i18n import translate
Expand Down
2 changes: 1 addition & 1 deletion src/design/plone/policy/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
import collective.volto.dropdownmenu
import collective.volto.formsupport
import collective.volto.secondarymenu
import collective.volto.slimheader
import collective.volto.socialsettings
import collective.volto.subfooter
import collective.volto.subsites
import collective.volto.slimheader
import collective.z3cform.datagridfield
import design.plone.contenttypes
import design.plone.policy
Expand Down
2 changes: 1 addition & 1 deletion src/design/plone/policy/tests/test_initial_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from design.plone.policy.utils import TASSONOMIA_SERVIZI
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from plone.restapi.behaviors import IBlocks
from plone.i18n.normalizer.interfaces import IURLNormalizer
from plone.restapi.behaviors import IBlocks
from zope.component import getUtility

import unittest
Expand Down
Loading
Loading