Skip to content
This repository was archived by the owner on Aug 17, 2023. It is now read-only.

Admin Portal Related #5

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,5 @@ ENV/
keys
tests/resources/keys/*.pem
.DS_Store

.idea/
19 changes: 18 additions & 1 deletion fence/blueprints/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,24 @@ def get_all_users():
Retrieve the information regarding the buckets created within a project.
Returns a json object.
"""
return jsonify(admin.get_all_users(current_session))
keyword = request.args.get('keyword')
return jsonify(admin.get_all_users(current_session, keyword))


@blueprint.route("/paginated_users", methods=["GET"])
@admin_login_required
@debug_log
def get_paginated_users():
"""
Retrieve the information regarding the buckets created within a project.
Returns a json object.
"""
keyword = request.args.get('keyword')
page = request.args.get('page')
page_size = request.args.get('page_size')
return jsonify(
admin.get_paginated_user(current_session, page, page_size, keyword)
)


@blueprint.route("/users", methods=["POST"])
Expand Down
147 changes: 147 additions & 0 deletions fence/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from math import ceil

from flask import request, abort


class Pagination(object):
"""
Refs https://github.com/pallets/flask-sqlalchemy/blob/master/flask_sqlalchemy/__init__.py#L310
Internal helper class returned by :meth:`BaseQuery.paginate`. You
can also construct it from any other SQLAlchemy query object if you are
working with other libraries. Additionally it is possible to pass `None`
as query object in which case the :meth:`prev` and :meth:`next` will
no longer work.
"""

def __init__(self, query, page, per_page, total, items):
#: the unlimited query object that was used to create this
#: pagination object.
self.query = query
#: the current page number (1 indexed)
self.page = page
#: the number of items to be displayed on a page.
self.per_page = per_page
#: the total number of items matching the query
self.total = total
#: the items for the current page
self.items = items

@property
def pages(self):
"""The total number of pages"""
if self.per_page == 0:
pages = 0
else:
pages = int(ceil(self.total / float(self.per_page)))
return pages

@property
def has_next(self):
"""True if a next page exists."""
return self.page < self.pages

@property
def next_num(self):
"""Number of the next page"""
if not self.has_next:
return 0
return self.page + 1

def iter_pages(self, left_edge=2, left_current=2,
right_current=5, right_edge=2):
"""Iterates over the page numbers in the pagination. The four
parameters control the thresholds how many numbers should be produced
from the sides. Skipped page numbers are represented as `None`.
This is how you could render such a pagination in the templates:

.. sourcecode:: html+jinja

{% macro render_pagination(pagination, endpoint) %}
<div class=pagination>
{%- for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{%- endfor %}
</div>
{% endmacro %}
"""
last = 0
for num in range(1, self.pages + 1):
if (
num <= left_edge or
self.page - left_current - 1 < num <
self.page + right_current
or num > self.pages - right_edge
):
if last + 1 != num:
yield None
yield num
last = num


def paginate(query, page=None, per_page=None, error_out=True):
"""
Refs https://github.com/pallets/flask-sqlalchemy/blob/master/flask_sqlalchemy/__init__.py#L435
Returns ``per_page`` items from page ``page``.

If no items are found and ``page`` is greater than 1, or if page is
less than 1, it aborts with 404.
This behavior can be disabled by passing ``error_out=False``.

If ``page`` or ``per_page`` are ``None``, they will be retrieved from
the request query.
If the values are not ints and ``error_out`` is ``True``, it aborts
with 404.
If there is no request or they aren't in the query, they default to 1
and 20 respectively.

Returns a :class:`Pagination` object.
"""

if request:
if page is None:
try:
page = int(request.args.get('page', 1))
except (TypeError, ValueError):
if error_out:
abort(404)

page = 1

if per_page is None:
try:
per_page = int(request.args.get('per_page', 10))
except (TypeError, ValueError):
if error_out:
abort(404)

per_page = 20
else:
if page is None:
page = 1

if per_page is None:
per_page = 20

if error_out and page < 1:
abort(404)

items = query.limit(per_page).offset((page - 1) * per_page).all()

if not items and page != 1 and error_out:
abort(404)

# No need to count if we're on the first page and there are fewer
# items than we expected.
if page == 1 and len(items) < per_page:
total = len(items)
else:
total = query.order_by(None).count()
return Pagination(query, page, per_page, total, items)
37 changes: 35 additions & 2 deletions fence/resources/admin/admin_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"connect_user_to_project",
"get_user_info",
"get_all_users",
"get_paginated_user",
"get_user_groups",
"create_user",
"update_user",
Expand Down Expand Up @@ -70,20 +71,52 @@ def get_user_info(current_session, username):
return us.get_user_info(current_session, username)


def get_all_users(current_session):
users = udm.get_all_users(current_session)
def get_all_users(current_session, keyword=None):
users = udm.get_all_users(current_session, keyword)
users_names = []
for user in users:
# TODO Add created at, return all attrs in a dump function
new_user = {}
new_user["name"] = user.username
if user.is_admin:
new_user["role"] = "admin"
else:
new_user["role"] = "user"
new_user["display_name"] = user.display_name
new_user["phone_number"] = user.phone_number
new_user["active"] = user.active if user.active is not None else True
new_user["email"] = user.email
users_names.append(new_user)
return {"users": users_names}


def get_paginated_user(current_session, page, page_size, keyword=None):
pagination = udm.get_paginated_users(current_session, page, page_size, keyword)
users_names = []
for user in pagination.items:
# TODO Add created at, return all attrs in a dump function
new_user = {}
new_user["name"] = user.username
if user.is_admin:
new_user["role"] = "admin"
else:
new_user["role"] = "user"
new_user["display_name"] = user.display_name
new_user["phone_number"] = user.phone_number
new_user["active"] = user.active if user.active is not None else True
new_user["email"] = user.email
users_names.append(new_user)
return {
"users": users_names,
"pagination": {
"page": pagination.page,
"page_size": pagination.per_page,
"next_page": pagination.next_num,
"total_count": pagination.total,
}
}


def get_user_groups(current_session, username):
user_groups = us.get_user_groups(current_session, username)["groups"]
user_groups_info = []
Expand Down
40 changes: 36 additions & 4 deletions fence/resources/userdatamodel/userdatamodel_user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sqlalchemy import func
import flask
from sqlalchemy import func, or_

from fence.errors import NotFound, UserError
from fence.models import (
Expand All @@ -13,13 +14,14 @@
UserToGroup,
query_for_user,
)

from fence.pagination import paginate

__all__ = [
"get_user",
"get_user_accesses",
"create_user_by_username_project",
"get_all_users",
"get_paginated_users",
"get_user_groups",
]

Expand Down Expand Up @@ -72,8 +74,38 @@ def create_user_by_username_project(current_session, new_user, proj):
return {"user": new_user, "project": project, "privileges": priv}


def get_all_users(current_session):
return current_session.query(User).all()
def _get_user_query(current_session, keyword=None):
q = current_session.query(User)
if keyword:
keyword = keyword.replace(' ', '').lower()
q = q.filter(
or_(
func.replace(User.display_name, ' ', '').ilike(
'%{}%'.format(keyword)),
func.replace(User.email, ' ', '').ilike(
'%{}%'.format(keyword)),
)
)
return q


def get_all_users(current_session, keyword=None):
q = _get_user_query(current_session, keyword)
return q.order_by(User.id.desc()).all()


def get_paginated_users(current_session, page, page_size, keyword=None):
q = _get_user_query(current_session, keyword)
q = q.order_by(User.id.desc())
page = int(page)
page_size = int(page_size)
pagination = paginate(
query=q,
page=page,
per_page=page_size,
error_out=False
)
return pagination


def get_user_groups(current_session, username):
Expand Down