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

removed Extra Metadata (JSON), replaced with Wiki Dick and User Group #1568

Merged
merged 1 commit into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 11 additions & 21 deletions docs/admin/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1068,16 +1068,12 @@ The WikiGroups backend is enabled by default so there is no need to add the foll
To create a WikiGroup that can be used in an ACL rule:

* Create a wiki item with a name ending in "Group" (the content of the item is not relevant)
* Edit the metadata and add an entry for "usergroup" under the heading "Extra Metadata (JSON)"::

{
"itemid": "36b6cd973d7e4daa9cfa265dcf751e79",
"namespace": "",
"usergroup": [
"JaneDoe",
"JohnDoe"
]
}
* Edit the metadata and add entries under the heading "Wiki Groups", one entry per line.
* Leading and trailing spaces are ignored, internal spaces are accepted.::

JaneDoe
JohnDoe
SomeOtherGroup

* Use the new group name in one or more ACL rules.

Expand Down Expand Up @@ -1120,17 +1116,11 @@ The WikiDicts backend is enabled by default so there is no need to add the follo
To create a WikiDict that can be used in an GetVal macro:

* Create a wiki item with a name ending in "Dict" (the content of the item is not relevant)
* Edit the metadata and add an entry for "somedict" under the heading "Extra Metadata (JSON)"::

{
"itemid": "332458ceab334991868de8970980494e",
"namespace": "",
"somedict": {
"apple": "red",
"banana": "yellow",
"pear": "green"
}
}
* Edit the metadata and add an entry under the heading "Wiki Dict"::

apple=red
banana=yellow
pear=green

The ConfigDicts backend uses dicts defined in the configuration file. Adding the
following to wikiconfig creates a OneDict and a NumbersDict and prevents
Expand Down
10 changes: 8 additions & 2 deletions src/moin/constants/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
# keys for storing group and dict information
# group of user names, e.g. for ACLs:
USERGROUP = "usergroup"
# needs more precise name / use case:
SOMEDICT = "somedict"
WIKIDICT = "wikidict"

# TODO review plural constants
CONTENTTYPE = "contenttype"
Expand Down Expand Up @@ -66,6 +65,8 @@
WIKINAME = "wikiname"
CONTENT = "content"
REFERS_TO = "refers_to"
# list of metadata fields that editors cannot modify
# excludes COMMENT, SUMMARY, TAG, USERGROUP and WIKIDICT
IMMUTABLE_KEYS = [
ACTION,
ADDRESS,
Expand All @@ -82,6 +83,11 @@
SIZE,
USERID,
WIKINAME,
CONTENTTYPE,
ITEMID,
ITEMTYPE,
NAMESPACE,
REV_NUMBER,
]

# magic REVID for current revision:
Expand Down
10 changes: 5 additions & 5 deletions src/moin/datastructures/backends/_tests/test_wiki_dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from moin.datastructures.backends._tests import DictsBackendTest
from moin.datastructures.backends import wiki_dicts
from moin.constants.keys import SOMEDICT
from moin.constants.keys import WIKIDICT
from moin._tests import become_trusted, update_item

import pytest
Expand All @@ -29,15 +29,15 @@ class TestWikiDictsBackend(DictsBackendTest):
def custom_setup(self):
become_trusted()

somedict = {"First": "first item",
wikidict = {"First": "first item",
"text with spaces": "second item",
'Empty string': '',
"Last": "last item"}
update_item('SomeTestDict', {SOMEDICT: somedict}, DATA)
update_item('SomeTestDict', {WIKIDICT: wikidict}, DATA)

somedict = {"One": "1",
wikidict = {"One": "1",
"Two": "2"}
update_item('SomeOtherTestDict', {SOMEDICT: somedict}, DATA)
update_item('SomeOtherTestDict', {WIKIDICT: wikidict}, DATA)

def test__retrieve_items(self):
wikidict_obj = wiki_dicts.WikiDicts()
Expand Down
12 changes: 7 additions & 5 deletions src/moin/datastructures/backends/wiki_dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

from flask import g as flaskg

from moin.constants.keys import CURRENT, SOMEDICT
from moin.constants.keys import CURRENT, WIKIDICT
from moin.datastructures.backends import BaseDict, BaseDictsBackend, DictDoesNotExistError
from flask import flash


class WikiDict(BaseDict):
Expand All @@ -26,9 +27,10 @@ def _load_dict(self):
item = flaskg.unprotected_storage[dict_name]
try:
rev = item[CURRENT]
somedict = rev.meta.get(SOMEDICT, {})
return somedict
wikidict = rev.meta.get(WIKIDICT, {})
return wikidict
except KeyError:
flash('WikiDict "{dict_name}" has invalid syntax within metadata.'.format(dict_name=dict_name))
raise DictDoesNotExistError(dict_name)


Expand All @@ -43,5 +45,5 @@ def __getitem__(self, dict_name):
def _retrieve_items(self, dict_name):
item = flaskg.unprotected_storage[dict_name]
rev = item.get_revision(CURRENT)
somedict = rev.meta.get(SOMEDICT, {})
return somedict
wikidict = rev.meta.get(WIKIDICT, {})
return wikidict
25 changes: 13 additions & 12 deletions src/moin/help/en/WikiDict.data
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
= WikiDict =

MOINTODO: There is no longer a way to add or view arbitrary metadata. This feature must be reworked. One suggestion is to follow moin 1.9 implementation and add data to item content rather than item metadata.
This item is a WikiDict because the item name ends with "Dict" and it has metadata defined under the "Wiki Dict" heading that visible when this item is edited.

This item is a WikiDict because it has a "somedict" defined in "extra" metadata:
To add metadata enter key=value pairs one per line.

{{{
{
"itemid": "932eec2324c3494c9ac8b9dcb2e46359",
"namespace": "",
"somedict": {
"var1": "value1",
"var2": "value2",
"var3": "value3"
}
}
var1=value1
var2=value2
var3=value3
}}}

The above is used by the GetVal macro:

{{{
<<GetVal(help-en/WikiDict, var1)>>
<<GetVal(help-en/WikiDict, var1)>>
}}}

<<GetVal(help-en/WikiDict, var1)>>

Clicking the Meta link under Item Views is an alternative way to view this item's metadata.

{{{
Wiki Dict: {'var1': 'value1', 'var2': 'value2', 'var3': 'value3'}
}}}
16 changes: 8 additions & 8 deletions src/moin/help/en/WikiDict.meta
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@
"address": "127.0.0.1",
"comment": "",
"contenttype": "text/x.moin.wiki;charset=utf-8",
"dataid": "d69dcb7cfc524e2fadaf4322293ae9d5",
"dataid": "30cc072ac1e342c8970aa4cb49571b87",
"externallinks": [],
"itemid": "932eec2324c3494c9ac8b9dcb2e4635a",
"itemlinks": [],
"itemtransclusions": [],
"itemtype": "default",
"mtime": 1681742420,
"mtime": 1704050058,
"name": [
"WikiDict"
],
"name_old": [],
"namespace": "help-en",
"rev_number": 1,
"revid": "56f6ff214ca549f0bc8895bd1a11caf0",
"sha1": "b89a611ce9754260976617b0720c21e202dba9b7",
"size": 613,
"somedict": {
"revid": "519deb6d2d7747d8979bde44c54d5ee6",
"sha1": "d9428a030ba7c34296ed9b8bd4174b91f38d5f06",
"size": 606,
"summary": "",
"tags": [],
"wikidict": {
"var1": "value1",
"var2": "value2",
"var3": "value3"
},
"summary": "",
"tags": [],
"wikiname": "MyMoinMoin"
}
86 changes: 71 additions & 15 deletions src/moin/items/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@
from moin.utils.registry import RegistryBase
from moin.utils.diff_html import diff as html_diff
from moin.utils import diff3
from moin.forms import RequiredText, OptionalText, JSON, Tags, Names, validate_name, NameNotValidError
from moin.forms import (
RequiredText, OptionalText, Tags, Names, validate_name,
NameNotValidError, OptionalMultilineText
)
from moin.constants.keys import (
NAME, NAMES, NAMENGRAM, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, ITEMTYPE,
CONTENTTYPE, SIZE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID, NAMESPACE,
IMMUTABLE_KEYS, UFIELDS_TYPELIST, UFIELDS, TRASH, REV_NUMBER,
UFIELDS_TYPELIST, UFIELDS, TRASH, REV_NUMBER,
ACTION_SAVE, ACTION_REVERT, ACTION_TRASH, ACTION_RENAME, TAGS, TEMPLATE,
LATEST_REVS, EDIT_ROWS, FQNAMES
LATEST_REVS, EDIT_ROWS, FQNAMES, USERGROUP, WIKIDICT
)
from moin.constants.chartypes import CHARS_UPPER, CHARS_LOWER
from moin.constants.namespaces import NAMESPACE_ALL, NAMESPACE_USERPROFILES
Expand Down Expand Up @@ -217,6 +220,38 @@ def _verify_parents(self, new_name, namespace, old_name=''):
))


def str_to_dict(strg):
"""
Convert wikidicts from multi-line input form:
'First=first item\ntext with spaces=second item\nEmpty string=\nLast=last item\n',
To dictionary:
{'Last': 'last item', 'text with spaces': 'second item', 'Empty string': '', 'First': 'first item'}
"""
new_dict = {}
lines = strg.splitlines()
for kv in lines:
try:
k, v = kv.split('=', 1)
new_dict[k] = v
except ValueError:
flash(_('Invalid line in wikidict meta data; ignored: "%(data)s"}', data=kv), 'error')
return new_dict


def dict_to_str(dic):
"""
convert dict:
{'First': 'first item', 'text with spaces': 'second item', 'Empty string': '', 'Last': 'last item'}
to str:
'First=first item\ntext with spaces=second item\nEmpty string=\nLast=last item\n'
"""
new_str = []
for k, v in dic.items():
new_str.append(k + '=' + v)
new_str = '\r\n'.join(new_str)
return new_str


class RegistryItem(RegistryBase):
class Entry(namedtuple('Entry', 'factory itemtype display_name description order')):
def __call__(self, itemtype, *args, **kw):
Expand Down Expand Up @@ -790,7 +825,8 @@ class _ModifyForm(BaseModifyForm):
ModifyForm.
"""
meta_form = BaseMetaForm
extra_meta_text = JSON.using(label=L_("Extra MetaData (JSON)")).with_properties(rows=ROWS_META, cols=COLS)
wikidict = OptionalMultilineText.using(label=L_("Wiki Dict")).with_properties(rows=ROWS_META, cols=COLS)
usergroup = OptionalMultilineText.using(label=L_("User Group")).with_properties(rows=ROWS_META, cols=COLS)
meta_template = 'modify_meta.html'

def _load(self, item):
Expand All @@ -805,11 +841,19 @@ def _load(self, item):
# policy to 'duck' suppresses this behavior.
if 'acl' not in meta:
meta['acl'] = "None"

self['meta_form'].set(meta, policy='duck')
for k in list(self['meta_form'].field_schema_mapping.keys()) + IMMUTABLE_KEYS:
meta.pop(k, None)
self['extra_meta_text'].set(item.meta_dict_to_text(meta))
if meta[NAME][0].endswith('Dict'):
try:
self[WIKIDICT].set(dict_to_str(item.meta[WIKIDICT]))
except KeyError:
pass
if meta[NAME][0].endswith('Group'):
try:
user_group = '\r\n'.join(item.meta[USERGROUP])
self[USERGROUP].set(user_group)
except KeyError:
pass

self['content_form']._load(item.content)

def _dump(self, item):
Expand All @@ -827,11 +871,10 @@ def _dump(self, item):
# e.g. we get PARENTID in here
meta = item.meta_filter(item.prepare_meta_for_modify(item.meta))
meta.update(self['meta_form'].value)
try:
meta.update(item.meta_text_to_dict(self['extra_meta_text'].value))
except TypeError:
# only items with names ending in Group or Dict have extra_meta.test
pass
if item.name.endswith('Dict'):
meta.update({WIKIDICT: str_to_dict(self[WIKIDICT].value)})
if item.name.endswith('Group'):
meta.update({USERGROUP: self[USERGROUP].value.splitlines()})
data, contenttype_guessed = self['content_form']._dump(item.content)
comment = self['comment'].value
return meta, data, contenttype_guessed, comment
Expand Down Expand Up @@ -873,7 +916,6 @@ def _save(self, meta, data=None, names=None, action=ACTION_SAVE, contenttype_gue
# this is treated as a rule which matches nothing
elif meta['acl'] == 'Empty':
meta['acl'] = ''

# we store the previous (if different) and current item names into revision metadata
# this is useful for deletes, rename history and backends that use item uids internally
if self.fqname.field == NAME_EXACT:
Expand Down Expand Up @@ -1239,14 +1281,28 @@ def doc_link(self, content_name, link_text):
def meta_changed(self, meta):
"""
Return true if user changed any of the following meta data:
comment, ACL, summary, tags, names
comment, ACL, summary, tags, names, extra_meta wikidict, usergroup
"""
if request.values.get(COMMENT):
return True
if request.values.get('meta_form_acl') != meta.get('acl', 'None'):
return True
if request.values.get('meta_form_summary') != meta.get('summary', None):
return True
if meta[NAME][0].endswith('Group'):
try:
new = request.values.get(USERGROUP).splitlines()
except KeyError:
new = None
old = meta.get(USERGROUP, None)
if new != old:
return True
if meta[NAME][0].endswith('Dict'):
new = request.values.get(WIKIDICT)
new = str_to_dict(new)
old = meta.get(WIKIDICT, None)
if new != old:
return True
new_tags = request.values.get('meta_form_tags').replace(" ", "").split(',')
if new_tags == [""]:
new_tags = []
Expand Down
3 changes: 3 additions & 0 deletions src/moin/macros/GetVal.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ def macro(self, content, arguments, page_url, alternative):
except DictDoesNotExistError:
raise ValueError(_("GetVal: dict not found: ") + item_name)
result = d.get(key, '')
if not result:
raise ValueError(_('GetVal macro is invalid, {item_name} missing key: {key_name}').
format(item_name=item_name, key_name=key))
return result
6 changes: 3 additions & 3 deletions src/moin/macros/_tests/test_GetVal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
from flask import g as flaskg

from moin.macros.GetVal import Macro
from moin.constants.keys import SOMEDICT
from moin.constants.keys import WIKIDICT
from moin._tests import become_trusted, update_item


class TestMacro:
@pytest.fixture
def test_dict(self):
become_trusted()
somedict = {"One": "1",
wikidict = {"One": "1",
"Two": "2"}
update_item('TestDict', {SOMEDICT: somedict}, "This is a dict item.")
update_item('TestDict', {WIKIDICT: wikidict}, "This is a dict item.")

return "TestDict"

Expand Down
Loading