plugin misc lists: templates:

Templates are a way to create lists with pre-filled schemas.
This commit is contained in:
Goffi 2021-02-19 15:49:58 +01:00
commit bdf4759d21
2 changed files with 278 additions and 4 deletions

View file

@ -15,11 +15,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import shortuuid
from typing import Tuple
from twisted.internet import defer
from twisted.words.xish import domish
from twisted.words.protocols.jabber import jid
from wokkel import data_form
from sat.core.i18n import _, D_
from sat.core.constants import Const as C
from twisted.internet import defer
from sat.tools.common import uri
import shortuuid
from sat.tools.common import data_format
from sat.core.log import getLogger
log = getLogger(__name__)
@ -27,6 +32,7 @@ log = getLogger(__name__)
# XXX: this plugin was formely named "tickets", thus the namespace keeps this
# name
APP_NS_TICKETS = "org.salut-a-toi.tickets:0"
NS_TICKETS_TYPE = "org.salut-a-toi.tickets#type:0"
PLUGIN_INFO = {
C.PI_NAME: _("Pubsub Lists"),
@ -39,6 +45,160 @@ PLUGIN_INFO = {
C.PI_DESCRIPTION: _("""Pubsub lists management plugin"""),
}
TEMPLATES = {
"todo": {
"name": D_("TODO List"),
"icon": "check",
"fields": [
{"name": "title"},
{"name": "author"},
{"name": "created"},
{"name": "updated"},
{"name": "time_limit"},
{"name": "labels", "type": "text-multi"},
{
"name": "status",
"label": D_("status"),
"type": "list-single",
"options": [
{
"label": D_("to do"),
"value": "todo"
},
{
"label": D_("in progress"),
"value": "in_progress"
},
{
"label": D_("done"),
"value": "done"
},
],
"value": "todo"
},
{
"name": "priority",
"label": D_("priority"),
"type": "list-single",
"options": [
{
"label": D_("major"),
"value": "major"
},
{
"label": D_("normal"),
"value": "normal"
},
{
"label": D_("minor"),
"value": "minor"
},
],
"value": "normal"
},
{"name": "body", "type": "xhtml"},
{"name": "comments_uri"},
]
},
"shopping": {
"name": D_("Shopping List"),
"icon": "basket",
"fields": [
{"name": "name", "label": D_("name")},
{"name": "quantity", "label": D_("quantity")},
{
"name": "status",
"label": D_("status"),
"type": "list-single",
"options": [
{
"label": D_("to buy"),
"value": "to_buy"
},
{
"label": D_("bought"),
"value": "bought"
},
],
"value": "to_buy"
},
]
},
"tickets": {
"name": D_("Tickets"),
"icon": "clipboard",
"fields": [
{"name": "title"},
{"name": "author"},
{"name": "created"},
{"name": "updated"},
{"name": "labels", "type": "text-multi"},
{
"name": "type",
"label": D_("type"),
"type": "list-single",
"options": [
{
"label": D_("bug"),
"value": "bug"
},
{
"label": D_("feature request"),
"value": "feature"
},
],
"value": "bug"
},
{
"name": "status",
"label": D_("status"),
"type": "list-single",
"options": [
{
"label": D_("queued"),
"value": "queued"
},
{
"label": D_("started"),
"value": "started"
},
{
"label": D_("review"),
"value": "review"
},
{
"label": D_("closed"),
"value": "closed"
},
],
"value": "queued"
},
{
"name": "priority",
"label": D_("priority"),
"type": "list-single",
"options": [
{
"label": D_("major"),
"value": "major"
},
{
"label": D_("normal"),
"value": "normal"
},
{
"label": D_("minor"),
"value": "minor"
},
],
"value": "normal"
},
{"name": "body", "type": "xhtml"},
{"name": "comments_uri"},
]
}
}
class PubsubLists:
@ -69,6 +229,7 @@ class PubsubLists:
"author": self._s.valueOrPublisherFilter,
"created": self._s.dateFilter,
"updated": self._s.dateFilter,
"time_limit": self._s.dateFilter,
},
profile_key=profile_key),
async_=True,
@ -91,6 +252,28 @@ class PubsubLists:
profile_key=profile_key),
async_=True,
)
host.bridge.addMethod(
"listTemplatesNamesGet",
".plugin",
in_sign="ss",
out_sign="s",
method=self._getTemplatesNames,
)
host.bridge.addMethod(
"listTemplateGet",
".plugin",
in_sign="sss",
out_sign="s",
method=self._getTemplate,
)
host.bridge.addMethod(
"listTemplateCreate",
".plugin",
in_sign="ssss",
out_sign="(ss)",
method=self._createTemplate,
async_=True,
)
def _set(self, service, node, values, schema=None, item_id=None, extra='',
profile_key=C.PROF_KEY_NONE):
@ -103,8 +286,10 @@ class PubsubLists:
d.addCallback(lambda ret: ret or "")
return d
async def set(self, client, service, node, values, schema=None, item_id=None, extra=None,
deserialise=False, form_ns=APP_NS_TICKETS):
async def set(
self, client, service, node, values, schema=None, item_id=None, extra=None,
deserialise=False, form_ns=APP_NS_TICKETS
):
"""Publish a tickets
@param node(unicode, None): Pubsub node to use
@ -152,3 +337,88 @@ class PubsubLists:
return await self._s.set(
client, service, node, values, schema, item_id, extra, deserialise, form_ns
)
def _getTemplatesNames(self, language, profile):
client = self.host.getClient(profile)
return data_format.serialise(self.getTemplatesNames(client, language))
def getTemplatesNames(self, client, language: str) -> list:
"""Retrieve well known list templates"""
templates = [{"id": tpl_id, "name": d["name"], "icon": d["icon"]}
for tpl_id, d in TEMPLATES.items()]
return templates
def _getTemplate(self, name, language, profile):
client = self.host.getClient(profile)
return data_format.serialise(self.getTemplate(client, name, language))
def getTemplate(self, client, name: str, language: str) -> dict:
"""Retrieve a well known template"""
return TEMPLATES[name]
def _createTemplate(self, template_id, name, access_model, profile):
client = self.host.getClient(profile)
d = defer.ensureDeferred(self.createTemplate(
client, template_id, name, access_model
))
d.addCallback(lambda node_data: (node_data[0].full(), node_data[1]))
return d
async def createTemplate(
self, client, template_id: str, name: str, access_model: str
) -> Tuple[jid.JID, str]:
"""Create a list from a template"""
name = name.strip()
if not name:
name = shortuuid.uuid()
template = TEMPLATES[template_id]
fields = [
data_form.Field(fieldType="hidden", var=NS_TICKETS_TYPE, value=template_id)
]
for field_data in template['fields']:
field_type = field_data.get('type', 'text-single')
kwargs = {
"fieldType": field_type,
"var": field_data["name"],
"label": field_data.get('label'),
"value": field_data.get("value"),
}
if field_type == "xhtml":
kwargs.update({
"fieldType": None,
"ext_type": "xml",
})
if kwargs["value"] is None:
kwargs["value"] = domish.Element((C.NS_XHTML, "div"))
elif "options" in field_data:
kwargs["options"] = [
data_form.Option(o["value"], o.get("label"))
for o in field_data["options"]
]
field = data_form.Field(**kwargs)
fields.append(field)
schema = data_form.Form(
"form",
formNamespace=APP_NS_TICKETS,
fields=fields
).toElement()
service = client.jid.userhostJID()
node = self._s.getSubmittedNS(f"{APP_NS_TICKETS}_{name}")
options = {
self._p.OPT_ACCESS_MODEL: access_model
}
await self._p.createNode(client, service, node, options)
await self._s.setSchema(client, service, node, schema)
list_elt = domish.Element((APP_NS_TICKETS, "list"))
list_elt["type"] = template_id
try:
await self.host.plugins['LIST_INTEREST'].registerPubsub(
client, APP_NS_TICKETS, service, node, creator=True,
name=name, element=list_elt)
except Exception as e:
log.warning(f"Can't add list to interests: {e}")
return service, node

View file

@ -321,6 +321,10 @@ class PubsubSchema(object):
for x_elt in item_elt.elements((data_form.NS_X_DATA, "x")):
form = data_form.Form.fromElement(x_elt)
if form_ns and form.formNamespace != form_ns:
log.debug(
f"form's namespace ({form.formNamespace!r}) differs from expected"
f"{form_ns!r}"
)
continue
prepend = [
("label", "id"),