Orgs: show "My Organizations" in the user's menu
This is shown only when the user is member of or administrator for one or more organizations, otherwise it's hidden.
This commit is contained in:
parent
95dc799692
commit
b9ae4396e5
@ -361,6 +361,21 @@ class OrgManager:
|
|||||||
projection={'_id': 1, 'full_name': 1, 'email': 1})
|
projection={'_id': 1, 'full_name': 1, 'email': 1})
|
||||||
return list(users)
|
return list(users)
|
||||||
|
|
||||||
|
def user_has_organizations(self, user_id: bson.ObjectId) -> bool:
|
||||||
|
"""Returns True iff the user has anything to do with organizations.
|
||||||
|
|
||||||
|
That is, if the user is admin for and/or member of any organization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
org_coll = current_app.db('organizations')
|
||||||
|
|
||||||
|
org_count = org_coll.count({'$or': [
|
||||||
|
{'admin_uid': user_id},
|
||||||
|
{'members': user_id}
|
||||||
|
]})
|
||||||
|
|
||||||
|
return bool(org_count)
|
||||||
|
|
||||||
|
|
||||||
def setup_app(app):
|
def setup_app(app):
|
||||||
from . import patch, hooks
|
from . import patch, hooks
|
||||||
|
@ -42,11 +42,14 @@ class UserClass(flask_login.UserMixin):
|
|||||||
self.group_ids: typing.List[bson.ObjectId] = []
|
self.group_ids: typing.List[bson.ObjectId] = []
|
||||||
self.capabilities: typing.Set[str] = set()
|
self.capabilities: typing.Set[str] = set()
|
||||||
|
|
||||||
|
# Lazily evaluated
|
||||||
|
self._has_organizations: typing.Optional[bool] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def construct(cls, token: str, db_user: dict) -> 'UserClass':
|
def construct(cls, token: str, db_user: dict) -> 'UserClass':
|
||||||
"""Constructs a new UserClass instance from a Mongo user document."""
|
"""Constructs a new UserClass instance from a Mongo user document."""
|
||||||
|
|
||||||
user = UserClass(token)
|
user = cls(token)
|
||||||
|
|
||||||
user.user_id = db_user['_id']
|
user.user_id = db_user['_id']
|
||||||
user.roles = db_user.get('roles') or []
|
user.roles = db_user.get('roles') or []
|
||||||
@ -63,7 +66,7 @@ class UserClass(flask_login.UserMixin):
|
|||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def __str__(self):
|
def __repr__(self):
|
||||||
return f'UserClass(user_id={self.user_id})'
|
return f'UserClass(user_id={self.user_id})'
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
@ -138,6 +141,15 @@ class UserClass(flask_login.UserMixin):
|
|||||||
|
|
||||||
return not bool(require_roles) or bool(intersection)
|
return not bool(require_roles) or bool(intersection)
|
||||||
|
|
||||||
|
def has_organizations(self) -> bool:
|
||||||
|
"""Returns True iff this user administers or is member of any organization."""
|
||||||
|
|
||||||
|
if self._has_organizations is None:
|
||||||
|
assert self.user_id
|
||||||
|
self._has_organizations = current_app.org_manager.user_has_organizations(self.user_id)
|
||||||
|
|
||||||
|
return bool(self._has_organizations)
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser(flask_login.AnonymousUserMixin, UserClass):
|
class AnonymousUser(flask_login.AnonymousUserMixin, UserClass):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -149,6 +161,9 @@ class AnonymousUser(flask_login.AnonymousUserMixin, UserClass):
|
|||||||
def has_cap(self, *capabilities):
|
def has_cap(self, *capabilities):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def has_organizations(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _load_user(token) -> typing.Union[UserClass, AnonymousUser]:
|
def _load_user(token) -> typing.Union[UserClass, AnonymousUser]:
|
||||||
"""Loads a user by their token.
|
"""Loads a user by their token.
|
||||||
|
@ -4,6 +4,7 @@ import logging
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
import flask_login
|
||||||
import jinja2.filters
|
import jinja2.filters
|
||||||
import jinja2.utils
|
import jinja2.utils
|
||||||
import werkzeug.exceptions as wz_exceptions
|
import werkzeug.exceptions as wz_exceptions
|
||||||
@ -157,3 +158,4 @@ def setup_jinja_env(jinja_env):
|
|||||||
jinja_env.filters['repr'] = repr
|
jinja_env.filters['repr'] = repr
|
||||||
jinja_env.globals['url_for_node'] = do_url_for_node
|
jinja_env.globals['url_for_node'] = do_url_for_node
|
||||||
jinja_env.globals['session'] = flask.session
|
jinja_env.globals['session'] = flask.session
|
||||||
|
jinja_env.globals['current_user'] = flask_login.current_user
|
||||||
|
@ -62,7 +62,14 @@ li(class="dropdown")
|
|||||||
title="My Projects")
|
title="My Projects")
|
||||||
i.pi-star
|
i.pi-star
|
||||||
| My Projects
|
| My Projects
|
||||||
|
| {% if current_user.has_organizations() %}
|
||||||
|
li
|
||||||
|
a.navbar-item(
|
||||||
|
href="{{ url_for('pillar.web.organizations.index') }}"
|
||||||
|
title="My Organizations")
|
||||||
|
i.pi-users
|
||||||
|
| My Organizations
|
||||||
|
| {% endif %}
|
||||||
li
|
li
|
||||||
a.navbar-item(
|
a.navbar-item(
|
||||||
href="{{ url_for('users.settings_profile') }}"
|
href="{{ url_for('users.settings_profile') }}"
|
||||||
|
@ -203,6 +203,8 @@ class OrganizationPatchTest(AbstractPillarTest):
|
|||||||
org_doc = om.create_new_org('Хакеры', admin_uid, 25)
|
org_doc = om.create_new_org('Хакеры', admin_uid, 25)
|
||||||
org_id = org_doc['_id']
|
org_id = org_doc['_id']
|
||||||
|
|
||||||
|
self.assertFalse(om.user_has_organizations(member1_uid))
|
||||||
|
|
||||||
# Try the PATCH
|
# Try the PATCH
|
||||||
resp = self.patch(f'/api/organizations/{org_id}',
|
resp = self.patch(f'/api/organizations/{org_id}',
|
||||||
json={
|
json={
|
||||||
@ -218,6 +220,9 @@ class OrganizationPatchTest(AbstractPillarTest):
|
|||||||
self.assertEqual([member1_uid], db_org['members'])
|
self.assertEqual([member1_uid], db_org['members'])
|
||||||
self.assertEqual([str(member1_uid)], new_org_doc['members'])
|
self.assertEqual([str(member1_uid)], new_org_doc['members'])
|
||||||
|
|
||||||
|
# The user should now have an organization
|
||||||
|
self.assertTrue(om.user_has_organizations(member1_uid))
|
||||||
|
|
||||||
def test_assign_users_access_denied(self):
|
def test_assign_users_access_denied(self):
|
||||||
self.enter_app_context()
|
self.enter_app_context()
|
||||||
|
|
||||||
@ -299,6 +304,7 @@ class OrganizationPatchTest(AbstractPillarTest):
|
|||||||
org_id = org_doc['_id']
|
org_id = org_doc['_id']
|
||||||
|
|
||||||
om.assign_users(org_id, ['member1@example.com', 'member2@example.com'])
|
om.assign_users(org_id, ['member1@example.com', 'member2@example.com'])
|
||||||
|
self.assertTrue(om.user_has_organizations(member_uid))
|
||||||
|
|
||||||
# Try the PATCH to remove a known user
|
# Try the PATCH to remove a known user
|
||||||
resp = self.patch(f'/api/organizations/{org_id}',
|
resp = self.patch(f'/api/organizations/{org_id}',
|
||||||
@ -318,6 +324,8 @@ class OrganizationPatchTest(AbstractPillarTest):
|
|||||||
self.assertEqual([], new_org_doc['members'])
|
self.assertEqual([], new_org_doc['members'])
|
||||||
self.assertEqual(['member2@example.com'], new_org_doc['unknown_members'])
|
self.assertEqual(['member2@example.com'], new_org_doc['unknown_members'])
|
||||||
|
|
||||||
|
self.assertFalse(om.user_has_organizations(member_uid))
|
||||||
|
|
||||||
# Try the PATCH to remove an unknown user
|
# Try the PATCH to remove an unknown user
|
||||||
resp = self.patch(f'/api/organizations/{org_id}',
|
resp = self.patch(f'/api/organizations/{org_id}',
|
||||||
json={
|
json={
|
||||||
|
Loading…
x
Reference in New Issue
Block a user