Skip to content

Commit

Permalink
Merge pull request #68 from RedTurtle/us56319_prenotazioni_mediateca_…
Browse files Browse the repository at this point in the history
…formsupport

Add support for limit submit and unique field in store data option
  • Loading branch information
eikichi18 authored Sep 4, 2024
2 parents f6760ed + 84509d7 commit e5e64c8
Show file tree
Hide file tree
Showing 11 changed files with 464 additions and 15 deletions.
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()
259 changes: 259 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,259 @@
# -*- 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(reversed(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/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

0 comments on commit e5e64c8

Please sign in to comment.