From f02378069d53e7d91b80cabd987e2e20d883ad0e Mon Sep 17 00:00:00 2001 From: Pat Pannuto Date: Mon, 7 Mar 2016 23:49:10 -0500 Subject: [PATCH] Load MComunnity details on admin user page This isn't the fastest query, so we load it via Ajax. AFAICT, there isn't much more interesting information we can pull down without asking ITS for more permissions. Closes #218. --- chezbetty/__init__.py | 1 + chezbetty/models/user.py | 49 ++++++++++++++++--- .../static/js/chezbetty-common-onload.js | 6 +++ chezbetty/templates/admin/macro_ajax.jinja2 | 7 +++ chezbetty/templates/admin/user.jinja2 | 28 +++++++---- chezbetty/templates/admin/user_details.jinja2 | 18 +++++++ chezbetty/views_admin.py | 11 +++++ 7 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 chezbetty/templates/admin/macro_ajax.jinja2 create mode 100644 chezbetty/templates/admin/user_details.jinja2 diff --git a/chezbetty/__init__.py b/chezbetty/__init__.py index c2afeb1..18b7baf 100644 --- a/chezbetty/__init__.py +++ b/chezbetty/__init__.py @@ -190,6 +190,7 @@ def debug(request): config.add_route('admin_users_email_oneperson', '/admin/users/email/oneperson') config.add_route('admin_users_email_all', '/admin/users/email/all') config.add_route('admin_user', '/admin/user/{user_id}') + config.add_route('admin_user_details', '/admin/user/{user_id}/details') config.add_route('admin_user_search_json', '/admin/user/search/{search}/json') config.add_route('admin_user_balance_edit', '/admin/user/balance/edit') config.add_route('admin_user_balance_edit_submit', '/admin/user/balance/edit/submit') diff --git a/chezbetty/models/user.py b/chezbetty/models/user.py index c1d0b3b..a668c14 100644 --- a/chezbetty/models/user.py +++ b/chezbetty/models/user.py @@ -24,6 +24,19 @@ class LDAPLookup(object): BASE_DN = "ou=People,dc=umich,dc=edu" PASSWORD = None ATTRIBUTES = ["uid", "entityid", "displayName"] + # http://www.itcs.umich.edu/itcsdocs/r1463/attributes-for-ldap.html + DETAILS_ATTRIBUTES = [ + # Could be interesting but require extra perm's from ITS + #"umichInstRoles", + #"umichAlumStatus", + #"umichAAAcadProgram", + #"umichAATermStatus", + #"umichHR", + "notice", + "ou", + "umichDescription", + "umichTitle", + ] def __init__(self): self.__conn = None @@ -38,14 +51,14 @@ def __connect(self): ) - def __lookup(self, k, v): + def __do_lookup(self, k, v, attributes, full_dict=False): self.__connect() query = "(%s=%s)" % (k, v) try: self.__conn.search(self.BASE_DN, query, ldap3.SEARCH_SCOPE_WHOLE_SUBTREE, - attributes=self.ATTRIBUTES + attributes=attributes ) except: # sometimes our connections time out @@ -54,22 +67,41 @@ def __lookup(self, k, v): self.__conn.search(self.BASE_DN, query, ldap3.SEARCH_SCOPE_WHOLE_SUBTREE, - attributes=self.ATTRIBUTES + attributes=attributes ) if len(self.__conn.response) == 0: raise InvalidUserException() + + if full_dict: + return self.__conn.response[0]["attributes"] + return { "umid":self.__conn.response[0]["attributes"]["entityid"], "uniqname":self.__conn.response[0]["attributes"]["uid"][0], "name":self.__conn.response[0]["attributes"]["displayName"][0] } - def lookup_umid(self, umid): - return self.__lookup("entityid", umid) + def __lookup(self, k, v): + return self.__do_lookup(k, v, self.ATTRIBUTES) - def lookup_uniqname(self, uniqname): - return self.__lookup("uid", uniqname) + def __detail_lookup(self, k, v): + return self.__do_lookup(k, v, + self.ATTRIBUTES + self.DETAILS_ATTRIBUTES, + full_dict=True, + ) + + def lookup_umid(self, umid, details=False): + if details: + return self.__detail_lookup("entityid", umid) + else: + return self.__lookup("entityid", umid) + + def lookup_uniqname(self, uniqname, details=False): + if details: + return self.__detail_lookup("uid", uniqname) + else: + return self.__lookup("uid", uniqname) class User(account.Account): @@ -104,6 +136,9 @@ def __str__(self): def from_id(cls, id): return DBSession.query(cls).filter(cls.id == id).one() + def get_details(self): + return self.__ldap.lookup_uniqname(self.uniqname, details=True) + @classmethod def from_uniqname(cls, uniqname, local_only=False): u = DBSession.query(cls).filter(cls.uniqname == uniqname).first() diff --git a/chezbetty/static/js/chezbetty-common-onload.js b/chezbetty/static/js/chezbetty-common-onload.js index 90e2bfe..ebd5d1b 100644 --- a/chezbetty/static/js/chezbetty-common-onload.js +++ b/chezbetty/static/js/chezbetty-common-onload.js @@ -92,3 +92,9 @@ $(".rotate-divs").each(function () { }, parseInt(showing_div.attr('data-rotate-div-timeout'))); }); +$(".ajax-fill").each(function () { + console.log($(this)); + $(this).removeClass("ajax-fill"); + $(this).load($(this).attr("data-path")); +}); + diff --git a/chezbetty/templates/admin/macro_ajax.jinja2 b/chezbetty/templates/admin/macro_ajax.jinja2 new file mode 100644 index 0000000..98cf651 --- /dev/null +++ b/chezbetty/templates/admin/macro_ajax.jinja2 @@ -0,0 +1,7 @@ +{% macro fill(path, load_text=None) %} +
+ {% if load_text %} +

{{ load_text }}

+ {% endif %} +
+{% endmacro %} diff --git a/chezbetty/templates/admin/user.jinja2 b/chezbetty/templates/admin/user.jinja2 index a308792..d6a56e0 100644 --- a/chezbetty/templates/admin/user.jinja2 +++ b/chezbetty/templates/admin/user.jinja2 @@ -1,5 +1,6 @@ {% extends "base.jinja2" %} {% import "macro_graph.jinja2" as graph %} +{% import "macro_ajax.jinja2" as ajax %} {% set active_page = 'users' %} {% block title %}User{% endblock %} @@ -14,16 +15,23 @@

User Details

-
-
Name
{{ user.name }}
-
uniqname
{{ user.uniqname }}
-
UMID
{{ user.umid }}
-
Balance
{{ user.balance|format_currency|safe }}
-
Lifetime Discounts
{{ user.lifetime_discounts|format_currency|safe }}
-
Lifetime Fees
{{ user.lifetime_fees|format_currency|safe }}
-
Joined
{{ user.created_at|pretty_date|safe }}
-
Enabled
{{ button.onoff_switch("user", "enabled", user.id, user.enabled) }}
-
+
+
+
+
Name
{{ user.name }}
+
uniqname
{{ user.uniqname }}
+
UMID
{{ user.umid }}
+
Balance
{{ user.balance|format_currency|safe }}
+
Lifetime Discounts
{{ user.lifetime_discounts|format_currency|safe }}
+
Lifetime Fees
{{ user.lifetime_fees|format_currency|safe }}
+
Joined
{{ user.created_at|pretty_date|safe }}
+
Enabled
{{ button.onoff_switch("user", "enabled", user.id, user.enabled) }}
+
+
+
+ {{ ajax.fill("/admin/user/" + user.id|string + "/details", "Loading user details from LDAP...") }} +
+

{% if user.has_password == False %} {{ button.ajax_singleuse_button("Create and Email Password", "/admin/user/"~user.id~"/password/create") }} diff --git a/chezbetty/templates/admin/user_details.jinja2 b/chezbetty/templates/admin/user_details.jinja2 new file mode 100644 index 0000000..55da309 --- /dev/null +++ b/chezbetty/templates/admin/user_details.jinja2 @@ -0,0 +1,18 @@ +
+ {% if umichTitle %} +
Title
{{ umichTitle|join(", ") }}
+ {% endif %} + {% if ou %} +
Affiliations
{{ ou|join(", ") }}
+ {% endif %} + {# This is text that the user enters in the MCommunity Directory. They might + indicate the best way to contact them, who their administrative assistant is, + or any other sort of notice that they want. #} + {% if notice %} +
Notice
{{ notice }}
+ {% endif %} + {% if umichDescription %} +
About Me
{{ umichDescription }}
+ {% endif %} +
+ diff --git a/chezbetty/views_admin.py b/chezbetty/views_admin.py index 490bcaf..c7da05f 100644 --- a/chezbetty/views_admin.py +++ b/chezbetty/views_admin.py @@ -1984,6 +1984,17 @@ def admin_user(request): request.session.flash('Invalid user?', 'error') return HTTPFound(location=request.route_url('admin_index')) +@view_config(route_name='admin_user_details', + renderer='templates/admin/user_details.jinja2', + permission='admin') +def admin_user_details(request): + try: + user = User.from_id(request.matchdict['user_id']) + details = user.get_details() + return details + except Exception as e: + if request.debug: raise(e) + return '

Unknown error loading user detail.

' @view_config(route_name='admin_user_purchase_add', renderer='templates/admin/user_purchase_add.jinja2',