changeset 2673:c040fb080073

merged
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sat, 19 Jul 2014 17:22:05 +0200
parents 06f564821562 (current diff) 3f729c4a241c (diff)
children 4ad3b2654ce3 27cbb9d8f08c e130fc605c63 aaf385f07d0a
files MoinMoin/themes/__init__.py MoinMoin/themes/basic/static/css/basic.css MoinMoin/themes/basic/static/custom-less/basic.less MoinMoin/themes/basic/templates/modify.html
diffstat 19 files changed, 417 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/admin/group_acl_report.html	Sat Jul 19 17:22:05 2014 +0200
@@ -0,0 +1,30 @@
+{% extends theme("layout.html") %}
+{% import "utils.html" as utils %}
+{% block content %}
+    <h1>{{ _("Group ACL Report") }}</h1>
+    <h2>{{ _("Group Name") }}: {{ group_name }}</h2>
+    <table class="table table-hover tablesorter tablesorter-default moin-sortable" data-sortlist="[[0,0]]">
+        <thead>
+            <tr>
+                <th>{{ _("Item Names") }}</th>
+                <th>{{ _("read") }}</th>
+                <th>{{ _("write") }}</th>
+                <th>{{ _("create") }}</th>
+                <th>{{ _("destroy") }}</th>
+                <th>{{ _("admin") }}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for item in group_items %}
+                <tr>
+                    <td><a href="{{ url_for('frontend.modify_item', item_name=item['fqname']) }}">{% if item['name'] %}{{ item['name']|join(', ') }}{% else %}{{ _("Item Id") }}: {{ item['itemid'] }}{% endif %}</a></td>
+                    <td>{% if 'read' in item['rights'] %}{{ _("read") }}{% endif %}</td>
+                    <td>{% if 'write' in item['rights'] %}{{ _("write") }}{% endif %}</td>
+                    <td>{% if 'create' in item['rights'] %}{{ _("create") }}{% endif %}</td>
+                    <td>{% if 'destroy' in item['rights'] %}{{ _("destroy") }}{% endif %}</td>
+                    <td>{% if 'admin' in item['rights'] %}{{ _("admin") }}{% endif %}</td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/admin/groupbrowser.html	Sat Jul 19 17:22:05 2014 +0200
@@ -0,0 +1,25 @@
+{% extends theme("layout.html") %}
+{% import "utils.html" as utils %}
+{% block content %}
+    <h1>{{ _("Groups") }}</h1>
+    <table class="table table-hover tablesorter tablesorter-default moin-sortable" data-sortlist="[[0,0],[1,0]]">
+        <thead>
+            <tr>
+                <th>{{ _("Group Names") }}</th>
+                <th>{{ _("Member Users") }}</th>
+                <th>{{ _("Member Groups") }}</th>
+                <th>{{ _("Group ACL Report") }}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for group in groups %}
+                <tr>
+                    <td>{% if group['grouptype'] == 'WikiGroup' %}<a href="{{ url_for('frontend.modify_item', item_name=group['name']) }}">{% endif %}{{ group['name'] }}</a></td>
+                    <td>{{ group['member_users']|sort|join(', ') }}</td>
+                    <td>{{ group['member_groups']|sort|join(', ') }}</td>
+                    <td><a href="{{ url_for('admin.group_acl_report', group_name=group['name']) }}">{{ _("ACL Report") }}</a></td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+{% endblock %}
--- a/MoinMoin/apps/admin/templates/admin/index.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/apps/admin/templates/admin/index.html	Sat Jul 19 17:22:05 2014 +0200
@@ -6,5 +6,7 @@
     <li><a href="{{ url_for('admin.wikiconfig') }}">{{ _("Show Wiki Configuration") }}</a></li>
     <li><a href="{{ url_for('admin.wikiconfighelp') }}">{{ _("Wiki Configuration Help") }}</a></li>
     <li><a href="{{ url_for('admin.trash', namespace='all') }}">{{ _("Trash") }}</a></li>
+    <li><a href="{{ url_for('admin.groupbrowser') }}">{{ _("Groups") }}</a></li>
+    <li><a href="{{ url_for('admin.item_acl_report') }}">{{ _("Item ACL Report") }}</a></li>
 </ul>
 {% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/admin/item_acl_report.html	Sat Jul 19 17:22:05 2014 +0200
@@ -0,0 +1,21 @@
+{% extends theme("layout.html") %}
+{% import "utils.html" as utils %}
+{% block content %}
+    <h1>{{ _("Item ACL Report") }}</h1>
+    <table class="table table-hover tablesorter tablesorter-default moin-sortable" data-sortlist="[[0,0]]">
+        <thead>
+            <tr>
+                <th>{{ _("Item Names") }}</th>
+                <th>{{ _("ACL") }}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for item in items_acls %}
+                <tr>
+                    <td><a href="{{ url_for('frontend.modify_item', item_name=item['fqname']) }}">{% if item['name'] %}{{ item['name'] | join(', ') }}{% else %}{{ _("Item Id") }}: {{ item['itemid'] }}{% endif %}</a></td>
+                    <td>{{ item['acl'] }}</td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/admin/user_acl_report.html	Sat Jul 19 17:22:05 2014 +0200
@@ -0,0 +1,30 @@
+{% extends theme("layout.html") %}
+{% import "utils.html" as utils %}
+{% block content %}
+    <h1>{{ _("User ACL Report") }}</h1>
+    <h2>{{ _("User Names") }}: {{ user_names|join(', ') }}</h2>
+    <table class="table table-hover tablesorter tablesorter-default moin-sortable" data-sortlist="[[0,0],[1,0]]">
+        <thead>
+            <tr>
+                <th>{{ _("Item Names") }}</th>
+                <th>{{ _("read") }}</th>
+                <th>{{ _("write") }}</th>
+                <th>{{ _("create") }}</th>
+                <th>{{ _("destroy") }}</th>
+                <th>{{ _("admin") }}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for item in itemwise_acl %}
+                <tr>
+                    <td><a href="{{ url_for('frontend.modify_item', item_name=item['fqname']) }}">{% if item['name'] %}{{ item['name']|join(', ') }}{% else %}Item Id: {{ item['itemid'] }}{% endif %}</a></td>
+                    <td>{% if item['read'] %}{{ _("read") }}{% endif %}</td>
+                    <td>{% if item['write'] %}{{ _("write") }}{% endif %}</td>
+                    <td>{% if item['create'] %}{{ _("create") }}{% endif %}</td>
+                    <td>{% if item['destroy'] %}{{ _("destroy") }}{% endif %}</td>
+                    <td>{% if item['admin'] %}{{ _("admin") }}{% endif %}</td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+{% endblock %}
--- a/MoinMoin/apps/admin/templates/admin/userbrowser.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/apps/admin/templates/admin/userbrowser.html	Sat Jul 19 17:22:05 2014 +0200
@@ -1,35 +1,42 @@
 {% extends theme("layout.html") %}
 {% block content %}
     <h1>{{ _("Users") }}</h1>
-    <table class="zebra">
-    <tr>
-        <th>{{ _("User name") }}</th>
-        <th>{{ _("Member of Groups") }}</th>
-        <th>{{ _("Email address") }}</th>
-        <th>{{ _("Actions") }}</th>
-    </tr>
-    {% for u in user_accounts %}
-    <tr>
-        <td><a href="{{ url_for('frontend.show_item', item_name=u.fqname) }}">{{ u.name|join(',') }}</a>{{ u.disabled and " (%s)" % _("disabled") or ""}}</td>
-        <td>{{ u.groups|join(',') }}</td>
-        <td>
-            {% if u.email %}
-            <a href="mailto:{{ u.email|e }}" class="mailto">{{ u.email|e }}</a>
-            {% endif %}
-        </td>
-        <td>
-            <form action="{{ url_for('admin.userprofile', user_name=u.name[0]) }}" method="POST">
-                <input type="hidden" name="key" value="disabled" />
-                <input type="hidden" name="val" value="{{ u.disabled and "0" or "1" }}" />
-                <input type="submit" name="userprofile" value="{{ u.disabled and _("Enable user") or _("Disable user") }}" />
-            </form>
-            <form action="{{ url_for('admin.mail_recovery_token') }}" method="GET">
-                <input type="hidden" name="email" value="{{ u.email }}" />
-                <input type="hidden" name="account_sendmail" value="1" />
-                <input type="submit" name="recoverpass" value="{{ _("Mail account data") }}" />
-            </form>
-        </td>
-    </tr>
-    {% endfor %}
-</table>
+    <table class="table table-hover tablesorter tablesorter-default moin-sortable" data-sortlist="[[0,0],[1,0]]">
+        <tr>
+            <th>{{ _("User name") }}</th>
+            <th>{{ _("Member of Groups") }}</th>
+            <th>{{ _("Email address") }}</th>
+            <th colspan="3">{{ _("Actions") }}</th>
+        </tr>
+        {% for u in user_accounts %}
+            <tr>
+                <td><a href="{{ url_for('frontend.show_item', item_name=u.fqname) }}">{{ u.name|join(', ') }}</a>{{ u.disabled and " (%s)" % _("disabled") or ""}}</td>
+                <td>{{ u.groups|join(', ') }}</td>
+                <td>
+                    {% if u.email %}
+                        <a href="mailto:{{ u.email|e }}" class="mailto">{{ u.email|e }}</a>
+                    {% endif %}
+                </td>
+                <td>
+                    <form action="{{ url_for('admin.userprofile', user_name=u.name[0]) }}" method="POST">
+                        <input type="hidden" name="key" value="disabled" />
+                        <input type="hidden" name="val" value="{{ u.disabled and "0" or "1" }}" />
+                        <input type="submit" name="userprofile" value="{{ u.disabled and _("Enable user") or _("Disable user") }}" />
+                    </form>
+                </td>
+                <td>
+                    <form action="{{ url_for('admin.mail_recovery_token') }}" method="GET">
+                        <input type="hidden" name="email" value="{{ u.email }}" />
+                        <input type="hidden" name="account_sendmail" value="1" />
+                        <input type="submit" name="recoverpass" value="{{ _("Mail account data") }}" />
+                    </form>
+                </td>
+                <td>
+                    <form action="{{ url_for('admin.user_acl_report', uid=u.uid) }}" method="GET">
+                        <input type="submit" name="userwise_acl" value="{{ _("User ACL Info") }}" />
+                    </form>
+                </td>
+            </tr>
+        {% endfor %}
+    </table>
 {% endblock %}
--- a/MoinMoin/apps/admin/views.py	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/apps/admin/views.py	Sat Jul 19 17:22:05 2014 +0200
@@ -20,11 +20,13 @@
 from MoinMoin.themes import render_template, get_editor_info
 from MoinMoin.apps.admin import admin
 from MoinMoin import user
-from MoinMoin.constants.keys import NAME, ITEMID, SIZE, EMAIL, DISABLED, NAME_EXACT, WIKINAME, TRASH, NAMESPACE, NAME_OLD, REVID, MTIME, COMMENT
+from MoinMoin.constants.keys import NAME, ITEMID, SIZE, EMAIL, DISABLED, NAME_EXACT, WIKINAME, TRASH, NAMESPACE, NAME_OLD, REVID, MTIME, COMMENT, LATEST_REVS, EMAIL_UNVALIDATED, ACL
 from MoinMoin.constants.namespaces import NAMESPACE_USERPROFILES, NAMESPACE_DEFAULT, NAMESPACE_ALL
-from MoinMoin.constants.rights import SUPERUSER
-from MoinMoin.security import require_permission
+from MoinMoin.constants.rights import SUPERUSER, ACL_RIGHTS_CONTENTS
+from MoinMoin.security import require_permission, ACLStringIterator
 from MoinMoin.util.interwiki import CompositeName
+from MoinMoin.datastruct.backends.wiki_groups import WikiGroup
+from MoinMoin.datastruct.backends import GroupDoesNotExistError
 
 
 @admin.route('/superuser')
@@ -46,13 +48,21 @@
     """
     groups = flaskg.groups
     revs = user.search_users()  # all users
-    user_accounts = [dict(uid=rev.meta[ITEMID],
-                          name=rev.meta[NAME],
-                          fqname=CompositeName(NAMESPACE_USERPROFILES, NAME_EXACT, rev.name),
-                          email=rev.meta[EMAIL],
-                          disabled=rev.meta[DISABLED],
-                          groups=[groupname for groupname in groups if rev.meta[NAME] in groups[groupname]],
-                     ) for rev in revs]
+    user_accounts = []
+    for rev in revs:
+        user_groups = []
+        user_names = rev.meta[NAME]
+        for groupname in groups:
+            group = groups[groupname]
+            for name in user_names:
+                if name in group:
+                    user_groups.append(groupname)
+        user_accounts.append(dict(uid=rev.meta[ITEMID],
+                                  name=user_names,
+                                  fqname=CompositeName(NAMESPACE_USERPROFILES, NAME_EXACT, rev.name),
+                                  email=rev.meta[EMAIL] if EMAIL in rev.meta else rev.meta[EMAIL_UNVALIDATED],
+                                  disabled=rev.meta[DISABLED],
+                                  groups=user_groups))
     return render_template('admin/userbrowser.html', user_accounts=user_accounts, title_name=_(u"Users"))
 
 
@@ -249,3 +259,110 @@
         meta = rev.meta
         results.append(trashedEntry(rev.fqname, meta[NAME_OLD], meta[REVID], meta[MTIME], meta[COMMENT], get_editor_info(meta)))
     return results
+
+
+@admin.route('/user_acl_report/<uid>', methods=['GET'])
+@require_permission(SUPERUSER)
+def user_acl_report(uid):
+    all_items = flaskg.storage.documents(wikiname=app.cfg.interwikiname)
+    groups = flaskg.groups
+    theuser = user.User(uid=uid)
+    itemwise_acl = []
+    for item in all_items:
+        fqname = CompositeName(item.meta.get(NAMESPACE), u'itemid', item.meta.get(ITEMID))
+        itemwise_acl.append({'name': item.meta.get(NAME),
+                             'itemid': item.meta.get(ITEMID),
+                             'fqname': fqname,
+                             'read': theuser.may.read(fqname),
+                             'write': theuser.may.write(fqname),
+                             'create': theuser.may.create(fqname),
+                             'admin': theuser.may.admin(fqname),
+                             'destroy': theuser.may.destroy(fqname)})
+    return render_template('admin/user_acl_report.html',
+                           title_name=_(u'User ACL Report'),
+                           user_names=theuser.name,
+                           itemwise_acl=itemwise_acl)
+
+
+@admin.route('/groupbrowser', methods=['GET'])
+@require_permission(SUPERUSER)
+def groupbrowser():
+    """
+    Display list of all groups and their members
+    """
+    all_groups = flaskg.groups
+    groups = []
+    for group in all_groups:
+        group_type = ''
+        if isinstance(all_groups[group], WikiGroup):
+            group_type = 'WikiGroup'
+        else:
+            group_type = 'ConfigGroup'
+        groups.append(dict(name=all_groups[group].name,
+                           member_users=all_groups[group].members,
+                           member_groups=all_groups[group].member_groups,
+                           grouptype=group_type))
+    return render_template('admin/groupbrowser.html',
+                           title_name=_(u'Groups'),
+                           groups=groups)
+
+
+@admin.route('/item_acl_report', methods=['GET'])
+@require_permission(SUPERUSER)
+def item_acl_report():
+    """
+    Return a list of all items in the wiki along with the ACL Meta-data
+    """
+    all_items = flaskg.storage.documents(wikiname=app.cfg.interwikiname)
+    items_acls = []
+    for item in all_items:
+        item_namespace = item.meta.get(NAMESPACE)
+        item_id = item.meta.get(ITEMID)
+        item_name = item.meta.get(NAME)
+        item_acl = item.meta.get(ACL)
+        fqname = CompositeName(item_namespace, u'itemid', item_id)
+        if item_acl is None:
+            for namespace, acl_config in app.cfg.acl_mapping:
+                if item_namespace == namespace or item_namespace == 'userprofiles' and namespace == 'userprofiles/':
+                    item_acl = 'Default ({0})'.format(acl_config['default'])
+        items_acls.append({'name': item_name,
+                           'itemid': item_id,
+                           'fqname': fqname,
+                           'acl': item_acl})
+    return render_template('admin/item_acl_report.html',
+                           title_name=_('Item ACL Report'),
+                           items_acls=items_acls)
+
+
+def search_group(group_name):
+    groups = flaskg.groups
+    if groups[group_name]:
+            return groups[group_name]
+    else:
+        raise GroupDoesNotExistError(group_name)
+
+
+@admin.route('/group_acl_report/<group_name>')
+@require_permission(SUPERUSER)
+def group_acl_report(group_name):
+    """
+    Display a 2-column table of items and ACLs, where the ACL rule specifies any
+    WikiGroup or ConfigGroup name.
+    """
+    group = search_group(group_name)
+    all_items = flaskg.storage.documents(wikiname=app.cfg.interwikiname)
+    group_items = []
+    for item in all_items:
+        acl_iterator = ACLStringIterator(ACL_RIGHTS_CONTENTS, item.meta.get(ACL, ''))
+        for modifier, entries, rights in acl_iterator:
+            if group_name in entries:
+                item_id = item.meta.get(ITEMID)
+                fqname = CompositeName(item.meta.get(NAMESPACE), u'itemid', item_id)
+                group_items.append(dict(name=item.meta.get(NAME),
+                                        itemid=item_id,
+                                        fqname=fqname,
+                                        rights=rights))
+    return render_template('admin/group_acl_report.html',
+                           title_name=_(u'Group ACL Report'),
+                           group_items=group_items,
+                           group_name=group_name)
--- a/MoinMoin/apps/frontend/views.py	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/apps/frontend/views.py	Sat Jul 19 17:22:05 2014 +0200
@@ -551,7 +551,7 @@
                            contenttype=item.contenttype,
                            first_rev_id=first_rev,
                            last_rev_id=last_rev,
-                           meta_rendered=Markup(item._render_meta()),
+                           meta=item._meta_info(),
                            show_revision=show_revision,
                            show_navigation=show_navigation,
     )
--- a/MoinMoin/items/__init__.py	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/items/__init__.py	Sat Jul 19 17:22:05 2014 +0200
@@ -376,8 +376,8 @@
     def contenttype(self):
         return self.content.contenttype if self.content else None
 
-    def _render_meta(self):
-        return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False)))
+    def _meta_info(self):
+        return self.meta_to_dict(self.meta, use_filter=False)
 
     def meta_filter(self, meta):
         """ kill metadata entries that we set automatically when saving """
@@ -397,6 +397,13 @@
             meta.pop(key, None)
         return meta
 
+    def meta_to_dict(self, meta, use_filter=True):
+        """ convert meta data from storage object to python dict """
+        meta = dict(meta)
+        if use_filter:
+            meta = self.meta_filter(meta)
+        return meta
+
     def meta_text_to_dict(self, text):
         """ convert meta data from a text fragment to a dict """
         meta = json.loads(text)
@@ -404,9 +411,7 @@
 
     def meta_dict_to_text(self, meta, use_filter=True):
         """ convert meta data from a dict to a text fragment """
-        meta = dict(meta)
-        if use_filter:
-            meta = self.meta_filter(meta)
+        meta = self.meta_to_dict(meta, use_filter)
         return json.dumps(meta, sort_keys=True, indent=2, ensure_ascii=False)
 
     def prepare_meta_for_modify(self, meta):
--- a/MoinMoin/static/js/common.js	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/static/js/common.js	Sat Jul 19 17:22:05 2014 +0200
@@ -663,6 +663,7 @@
 
     moin.enhanceUserSettings();
     moin.enhanceEdit();
+    $('.moin-sortable').tablesorter();
     // placing initToggleComments after enhanceEdit prevents odd autoscroll issue when editing hidden comments
     moin.initToggleComments();
 });
--- a/MoinMoin/templates/base.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/templates/base.html	Sat Jul 19 17:22:05 2014 +0200
@@ -81,6 +81,7 @@
     <script src="{{ url_for('frontend.template', filename='dictionary.js') }}"></script>
     <script src="{{ url_for('serve.files', name='bootstrap', filename='js/bootstrap.min.js') }}"></script>
     <script src="{{ url_for('serve.files', name='autosize', filename='jquery.autosize-min.js') }}"></script>
+    <script src="{{ url_for('serve.files', name='jquery_tablesorter', filename='jquery.tablesorter.js') }}"></script>
     <script src="{{ url_for('static', filename='js/common.js') }}"></script>
     {{ scripts }}
     <!--[if lt IE 9]>
--- a/MoinMoin/templates/meta.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/templates/meta.html	Sat Jul 19 17:22:05 2014 +0200
@@ -1,4 +1,5 @@
 {% extends theme("show.html") %}
+{% import "utils.html" as utils with context %}
 
 {% set title = _("Metadata of '%(item_name)s'", item_name=item_name) %}
 
@@ -11,9 +12,9 @@
     {{ title }}
     {% if show_revision %}({{ _("Revision") }} {{ rev.revid | shorten_id }}){% endif %}
 </h1>
-{% if meta_rendered %}
+{% if meta %}
 <div id="moin-content-meta">
-    {{ meta_rendered }}
+    {{ utils.meta_info(meta) }}
 </div>
 {% endif %}
 {% endblock %}
--- a/MoinMoin/templates/utils.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/templates/utils.html	Sat Jul 19 17:22:05 2014 +0200
@@ -101,3 +101,83 @@
             {{ forms.render_errors(form) }}
     {{ gen.form.close() }}
 {% endmacro %}
+
+{% macro meta_info(meta) %}
+<!-- Bootstrap classes list-group and list-group-item used to display the metadata info in an unordered list -->
+    <ul class="list-group">
+        <li class="list-group-item">Action: {{ meta['action'] }}</li>
+        <li class="list-group-item">Address: {{ meta['address'] }}</li>
+        <li class="list-group-item">Comment: '{{ meta['comment'] }}'</li>
+        <li class="list-group-item">Content Type: {{ meta['contenttype']|shorten_ctype }} [{{ meta['contenttype'] }}]</li>
+        <li class="list-group-item">Data ID: {{ meta['dataid'] }}</li>
+        <li class="list-group-item">External Links:
+        {% if meta['externallinks'] %}
+            {% for item in meta['externallinks'] %}
+                <a href="{{ item|safe }}">
+                    {{ item }}
+                </a>,
+            {% endfor %}
+        {% else %}
+            (None)
+        {% endif %}
+        </li>
+        <li class="list-group-item">Item ID: {{ meta['itemid'] }}</li>
+        <li class="list-group-item">Item Links:
+        {% if meta['itemlinks'] %}
+            {% for item in meta['itemlinks'] %}
+                <a href="{{ url_for('frontend.show_item', item_name=item) }}" {% if not theme_supp.item_exists(item) %}class="moin-nonexistent"{% endif %}>
+                    {{ item }}
+                </a>,
+            {% endfor %}
+        {% else %}
+            (None)
+        {% endif %}
+        </li>
+        <li class="list-group-item">Item Transclusions:
+        {% if meta['itemtransclusions'] %}
+            {% for item in meta['itemtransclusions'] %}
+                <a href="{{ url_for('frontend.show_item', item_name=item) }}" {% if not theme_supp.item_exists(item) %}class="moin-nonexistent"{% endif %}>
+                {{ item }}
+                </a>,
+            {% endfor %}
+        {% else %}
+            (None)
+        {% endif %}
+        </li>
+        <li class="list-group-item">Item Type: {{ meta['itemtype'] }}</li>
+        <li class="list-group-item">Modified Time: {{ meta['mtime']|datetimeformat }}</li>
+        <li class="list-group-item">Name:
+        {% if meta['name'] %}
+            {% for name in meta['name'] %}
+                {{ name }}
+            {% endfor %}
+        {% else %}
+            (None)
+        {% endif %}
+        </li>
+        <li class="list-group-item">Old Name:
+        {% if meta['name_old'] %}
+            {% for name in meta['name_old'] %}
+                {{ name }}
+            {% endfor %}
+        {% else %}
+            (None)
+        {% endif %}
+        </li>
+        <li class="list-group-item">Namespace: '{{ meta['namespace'] }}'</li>
+        <li class="list-group-item">Revision ID: {{ meta['revid'] }}</li>
+        <li class="list-group-item">SHA1: {{ meta['sha1'] }}</li>
+        <li class="list-group-item">Size: {{ meta['size']|filesizeformat }}</li>
+        <li class="list-group-item">Summary: '{{ meta['summary'] }}'</li>
+        <li class="list-group-item">Tags:
+        {% if meta['tags'] %}
+            {% for tag in meta['tags'] %}
+                {{ tag }}
+            {% endfor %}
+        {% else %}
+            (None)
+        {% endif %}
+        </li>
+        <li class="list-group-item">Wiki Name: {{ meta['wikiname'] }}</li>
+    </ul>
+{% endmacro %}
--- a/MoinMoin/themes/__init__.py	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/themes/__init__.py	Sat Jul 19 17:22:05 2014 +0200
@@ -476,6 +476,15 @@
                 namespace_root_mapping.append((namespace or '~', fq_namespace.get_root_fqname()))
         return namespace_root_mapping
 
+    def item_exists(self, itemname):
+        """
+        Check whether the item pointed to by the given itemname exists or not
+
+        :rtype: boolean
+        :returns: whether item pointed to by the link exists or not
+        """
+        return self.storage.has_item(itemname)
+
 
 def get_editor_info(meta, external=False):
     addr = meta.get(ADDRESS)
--- a/MoinMoin/themes/basic/static/css/basic.css	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/themes/basic/static/css/basic.css	Sat Jul 19 17:22:05 2014 +0200
@@ -6978,6 +6978,10 @@
 .tablesorter-header.sorter-false {
   background-image: None;
 }
+.tablesorter-header-inner {
+  padding-left: 20px;
+  cursor: pointer;
+}
 #ticket-summary {
   width: 50%;
 }
--- a/MoinMoin/themes/basic/static/custom-less/basic.less	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/themes/basic/static/custom-less/basic.less	Sat Jul 19 17:22:05 2014 +0200
@@ -706,6 +706,10 @@
 .tablesorter-header.sorter-false {
   background-image: None;
 }
+.tablesorter-header-inner {
+  padding-left: 20px;
+  cursor: pointer;
+}
 #ticket-summary {
   width: 50%;
 }
--- a/MoinMoin/themes/basic/templates/meta.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/themes/basic/templates/meta.html	Sat Jul 19 17:22:05 2014 +0200
@@ -1,4 +1,5 @@
 {% extends theme("layout.html") %}
+{% import "utils.html" as utils with context %}
 {% import theme("itemviews.html") as itemviews with context %}
 
 {% set title = _("Metadata of '%(item_name)s'", item_name=item_name) %}
@@ -16,9 +17,9 @@
     {{ title }}
     {% if show_revision %}({{ _("Revision") }} {{ rev.revid | shorten_id }}){% endif %}
 </h2>
-{% if meta_rendered %}
+{% if meta %}
 <div id="moin-content-meta">
-    {{ meta_rendered }}
+    {{ utils.meta_info(meta) }}
 </div>
 {% endif %}
 {% endblock %}
--- a/MoinMoin/themes/basic/templates/modify.html	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/themes/basic/templates/modify.html	Sat Jul 19 17:22:05 2014 +0200
@@ -38,7 +38,9 @@
     <ul class="moin-nav nav-tabs moin-shadow">
         <li class="active"><a href="#editor" data-toggle="tab">Edit Content</a></li>
         <li><a href="#meta" data-toggle="tab">Edit Meta</a></li>
-        <li><a href="#acl" data-toggle="tab">Edit ACL</a></li>
+        {% if user.may.admin(fqname) %}
+            <li><a href="#acl" data-toggle="tab">Edit ACL</a></li>
+        {% endif %}
         <li><a href="#help" data-toggle="tab">Help</a></li>
     </ul>
     <div class="tab-content">
@@ -59,17 +61,19 @@
                 </div>
             </div>
         </div>
-        <div class="tab-pane active" id="acl">
-            <div class="row">
-                {% set field = form['meta_form']['acl'] %}
-                <div class="col-lg-6">
-                    <div class="form-group">
-                        {{ gen.label(field) }}
-                        {{ gen.textarea(field, rows='1', class='form-control') }}
+        {% if user.may.admin(fqname) %}
+            <div class="tab-pane active" id="acl">
+                <div class="row">
+                    {% set field = form['meta_form']['acl'] %}
+                    <div class="col-lg-6">
+                        <div class="form-group">
+                            {{ gen.label(field) }}
+                            {{ gen.textarea(field, rows='1', class='form-control') }}
+                        </div>
                     </div>
                 </div>
             </div>
-        </div>
+        {% endif %}
         <div class="tab-pane active" id="help">
             {% if form['content_form'].help %}
                 <pre id="moin-editor-help">{{ form['content_form'].help }}</pre>
--- a/MoinMoin/util/notifications.py	Sat Jul 19 09:59:42 2014 +0530
+++ b/MoinMoin/util/notifications.py	Sat Jul 19 17:22:05 2014 +0200
@@ -12,6 +12,7 @@
 from whoosh.query import Term, And
 
 from flask import url_for, g as flaskg
+from flask import abort
 
 from MoinMoin.constants.keys import (ACTION_COPY, ACTION_RENAME, ACTION_REVERT,
                                      ACTION_SAVE, ACTION_TRASH, ALL_REVS, CONTENTTYPE,
@@ -97,14 +98,19 @@
             contenttype = self.meta[CONTENTTYPE]
             oldfile, newfile = self.revs[0].data, BytesIO("")
         else:
-            newfile = self.revs[0].data
-            if len(self.revs) == 1:
-                contenttype = self.revs[0].meta[CONTENTTYPE]
-                oldfile = BytesIO("")
+            # if user does not have permission to read object,
+            # get_item_last_revisions() returns an empty list to self.revs
+            if len(self.revs) > 0:
+                newfile = self.revs[0].data
+                if len(self.revs) == 1:
+                    contenttype = self.revs[0].meta[CONTENTTYPE]
+                    oldfile = BytesIO("")
+                else:
+                    from MoinMoin.apps.frontend.views import _common_type
+                    contenttype = _common_type(self.revs[0].meta[CONTENTTYPE], self.revs[1].meta[CONTENTTYPE])
+                    oldfile = self.revs[1].data
             else:
-                from MoinMoin.apps.frontend.views import _common_type
-                contenttype = _common_type(self.revs[0].meta[CONTENTTYPE], self.revs[1].meta[CONTENTTYPE])
-                oldfile = self.revs[1].data
+                abort(403)
         content = Content.create(contenttype)
         return content._get_data_diff_text(oldfile, newfile)
 
@@ -185,6 +191,8 @@
     :param fqname: the fqname of the item
     :return: a list of revisions
     """
+    # TODO: Implement AccessDenied or similar error in case the user does not have access to item
+    # and to also to handle the case where the item has no revisions
     terms = [Term(WIKINAME, app.cfg.interwikiname), Term(fqname.field, fqname.value), ]
     query = And(terms)
     return list(