Early implementation of friendly names. This be aware that this writes a new table to your plexWatch database.

Note: To edit friendly name, go to user page and click the pencil icon next to the user name. Currently only works on home stats and user info page.
Adjust some table styling issues.
Fix bug with user IP modal details not showing.
Fix users default list order.
This commit is contained in:
Tim 2015-06-29 01:35:17 +02:00
parent 762199344c
commit e7b305a1d5
11 changed files with 155 additions and 25 deletions

View file

@ -161,9 +161,7 @@ table.display {
table.display thead th { table.display thead th {
white-space:nowrap; white-space:nowrap;
padding: 0px 0px 0px 19px; padding: 0px 0px 0px 18px;
border-top: 1px solid #2d2d2d;
border-bottom: 1px solid #0e0e0e;
cursor: pointer; cursor: pointer;
color: #999; color: #999;
background-color: #212121; background-color: #212121;
@ -180,6 +178,7 @@ table.display thead .sorting_asc,
table.display thead .sorting_desc { table.display thead .sorting_desc {
cursor: pointer; cursor: pointer;
*cursor: hand; *cursor: hand;
height: 35px;
} }
table.display thead .sorting, table.display thead .sorting,
table.display thead .sorting_asc, table.display thead .sorting_asc,
@ -216,7 +215,7 @@ table.display tr.heading2 td {
} }
table.display td { table.display td {
padding: 5px 0 5px 20px; padding: 5px 0 5px 5px;
} }
table.display td.title { table.display td.title {

View file

@ -0,0 +1,56 @@
<%doc>
USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE
For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/
Filename: edit_user.html
Version: 0.1
Variable names: data [list]
data :: Usable parameters
== Global keys ==
user Return the real Plex username
friendly_name Returns the friendly edited Plex username
DOCUMENTATION :: END
</%doc>
% if data is not None:
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h3>Edit user <strong>${data['user']}</strong></h3>
</div>
<div class="modal-body" id="modal-text">
<div class="card-back">
<div class="form-group">
<label for="friendly_name">Friendly Name</label>
<input type="text" id="friendly_name" name="friendly_name" value="${data['friendly_name']}" size="30">
<p class="help-block">Replace all occurances of the username with this name.</p>
</div>
</div>
</div>
<div class="modal-footer">
<div>
<input type="button" id="save_user_name" class="btn btn-primary" value="Save">
<span id="edit-user-status-message"></span>
</div>
</div>
<script>
// Set new friendly name
$("#save_user_name").click(function() {
var friendly_name = $("#friendly_name").val();
$.ajax({
url: 'edit_user',
data: {user: '${data['user']}', friendly_name: friendly_name},
cache: false,
async: true,
success: function(data) {
$("#edit-user-status-message").html(data);
$(".set-username").html(friendly_name);
}
});
});
</script>
% endif

View file

@ -31,10 +31,10 @@ from plexpy import helpers
<th class="never" align='left' id="id">ID</th> <th class="never" align='left' id="id">ID</th>
<th class="all" align='left' id="time">Time</th> <th class="all" align='left' id="time">Time</th>
<th class="all" align='left' id="user">User</th> <th class="all" align='left' id="user">User</th>
<th class="min-tablet" align='left' id="platform">Platform</th> <th class="desktop" align='left' id="platform">Platform</th>
<th class="desktop" align='left' id="ip_address">IP Address</th> <th class="desktop" align='left' id="ip_address">IP Address</th>
<th class="min-tablet" align='left' id="title">Title</th> <th class="min-tablet" align='left' id="title">Title</th>
<th class="desktop" align='left' id="started">Started</th> <th class="min-tablet" align='left' id="started">Started</th>
<th class="desktop" align='left' id="paused_counter">Paused</th> <th class="desktop" align='left' id="paused_counter">Paused</th>
<th class="desktop" align='left' id="stopped">Stopped</th> <th class="desktop" align='left' id="stopped">Stopped</th>
<th class="desktop" align='left' id="duration">Duration</th> <th class="desktop" align='left' id="duration">Duration</th>

View file

@ -28,6 +28,7 @@ users_watched Returns the count for the associated stat.
== Only if 'stat_id' is 'top_user' == == Only if 'stat_id' is 'top_user' ==
thumb Returns url of the user's gravatar. Returns '' if none exists. thumb Returns url of the user's gravatar. Returns '' if none exists.
user Returns the username for the associated stat. user Returns the username for the associated stat.
friendly_name Returns the friendly name of the user for the associated stat.
== Only if 'stat_id' is 'top_platform' == == Only if 'stat_id' is 'top_platform' ==
platform_type Returns the platform name for the associated stat. platform_type Returns the platform name for the associated stat.
@ -112,12 +113,11 @@ DOCUMENTATION :: END
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Active User</h4> <h4>Most Active User</h4>
<a href="user?user=${a['rows'][0]['user']}"> <a href="user?user=${a['rows'][0]['user']}">
<h5>${a['rows'][0]['user']}</h5> <h5>${a['rows'][0]['friendly_name']}</h5>
</a> </a>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="user-platforms-instance-playcount">
<h3>${a['rows'][0]['total_plays']}</h3> <h3>${a['rows'][0]['total_plays']}</h3>
<p> plays</p> <p> plays</p>
</div> </div>
</li> </li>

View file

@ -190,10 +190,10 @@ from plexpy import helpers
<th class="never" align='left' id="id">ID</th> <th class="never" align='left' id="id">ID</th>
<th class="all" align='left' id="time">Time</th> <th class="all" align='left' id="time">Time</th>
<th class="all" align='left' id="user">User</th> <th class="all" align='left' id="user">User</th>
<th class="min-tablet" align='left' id="platform">Platform</th> <th class="desktop" align='left' id="platform">Platform</th>
<th class="desktop" align='left' id="ip_address">IP Address</th> <th class="desktop" align='left' id="ip_address">IP Address</th>
<th class="min-tablet" align='left' id="title">Title</th> <th class="min-tablet" align='left' id="title">Title</th>
<th class="desktop" align='left' id="started">Started</th> <th class="min-tablet" align='left' id="started">Started</th>
<th class="desktop" align='left' id="paused_counter">Paused</th> <th class="desktop" align='left' id="paused_counter">Paused</th>
<th class="desktop" align='left' id="stopped">Stopped</th> <th class="desktop" align='left' id="stopped">Stopped</th>
<th class="desktop" align='left' id="duration">Duration</th> <th class="desktop" align='left' id="duration">Duration</th>

View file

@ -33,7 +33,7 @@ user_ip_table_options = {
"targets": [1], "targets": [1],
"data":"ip_address", "data":"ip_address",
"width": "15%", "width": "15%",
"className": "modal-control", "className": "modal-control no-wrap",
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (isPrivateIP(cellData)) { if (isPrivateIP(cellData)) {
if (cellData != '') { if (cellData != '') {
@ -45,8 +45,7 @@ user_ip_table_options = {
$(td).html('<a href="#ip-info-modal" data-toggle="modal"><span data-toggle="ip-tooltip" data-placement="left" title="IP Address Info" id="ip-info"><i class="icon-map-marker icon-white"></i></span>&nbsp' + cellData +'</a>'); $(td).html('<a href="#ip-info-modal" data-toggle="modal"><span data-toggle="ip-tooltip" data-placement="left" title="IP Address Info" id="ip-info"><i class="icon-map-marker icon-white"></i></span>&nbsp' + cellData +'</a>');
} }
}, },
"width": "15%", "width": "15%"
"className": "no-wrap"
}, },
{ {
"targets": [2], "targets": [2],

View file

@ -14,7 +14,7 @@ users_list_table_options = {
"processing": false, "processing": false,
"serverSide": true, "serverSide": true,
"pageLength": 10, "pageLength": 10,
"order": [ 0, 'asc'], "order": [ 1, 'asc'],
"ajax": { "ajax": {
"url": "get_user_list" "url": "get_user_list"
}, },

View file

@ -10,6 +10,7 @@ Variable names: user [string]
user :: Usable parameters user :: Usable parameters
user Returns the name of the user. user Returns the name of the user.
friendly_name Returns the friendly name of the user.
DOCUMENTATION :: END DOCUMENTATION :: END
@ -35,7 +36,7 @@ from plexpy import helpers
<img src="interfaces/default/images/gravatar-default-80x80.png"> <img src="interfaces/default/images/gravatar-default-80x80.png">
</div> </div>
<div class="user-info-username"> <div class="user-info-username">
${user} <span class="set-username">${friendly_name}</span> <a href="#edit-user-modal" data-toggle="modal" id="toggle-edit-user-modal"><i class="fa fa-pencil"></i></a>
</div> </div>
<div class="user-info-nav"> <div class="user-info-nav">
<ul class="user-info-nav"> <ul class="user-info-nav">
@ -47,6 +48,9 @@ from plexpy import helpers
</div> </div>
</div> </div>
</div> </div>
<div id="edit-user-modal" class="modal hide fade" tabindex="-1" role="dialog"
aria-labelledby="edit-user-modal" aria-hidden="true">
</div>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="profile"> <div class="tab-pane active" id="profile">
@ -108,7 +112,7 @@ from plexpy import helpers
<div class="span12"> <div class="span12">
<div class="table-card-back"> <div class="table-card-back">
<h3>IP Addresses for <strong> <h3>IP Addresses for <strong>
${user} <span class="set-username">${friendly_name}</span>
</strong></h3> </strong></h3>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
@ -164,7 +168,7 @@ from plexpy import helpers
<div class="span12"> <div class="span12">
<div class="table-card-back"> <div class="table-card-back">
<h3>Watch History for <strong> <h3>Watch History for <strong>
${user} <span class="set-username">${friendly_name}</span>
</strong></h3> </strong></h3>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
@ -272,6 +276,19 @@ from plexpy import helpers
} }
} }
}); });
// Load edit user modal
$("#toggle-edit-user-modal").click(function() {
$.ajax({
url: 'edit_user',
data: {user: '${user}'},
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-user-modal").html(xhr.responseText);
}
});
});
}); });

View file

@ -307,6 +307,8 @@ def sig_handler(signum=None, frame=None):
def dbcheck(): def dbcheck():
conn = sqlite3.connect(plexpy.CONFIG.PLEXWATCH_DATABASE) conn = sqlite3.connect(plexpy.CONFIG.PLEXWATCH_DATABASE)
c = conn.cursor() c = conn.cursor()
c.execute('CREATE TABLE IF NOT EXISTS plexpy_users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'username TEXT NOT NULL UNIQUE, friendly_name TEXT)')
conn.commit() conn.commit()
c.close() c.close()

View file

@ -660,11 +660,14 @@ class PlexWatch(object):
elif 'top_users' in stat: elif 'top_users' in stat:
top_users = [] top_users = []
try: try:
query = 'SELECT user, COUNT(id) as total_plays, MAX(time) as last_watch ' \ s = self.get_history_table_name()
'FROM %s ' \ query = 'SELECT user, (case when friendly_name is null then user else friendly_name end) as friendly_name,' \
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \ 'COUNT(' + s + '.id) as total_plays, MAX(time) as last_watch ' \
'GROUP BY user ' \ 'FROM ' + s + ' ' \
'ORDER BY total_plays DESC LIMIT 10' % (self.get_history_table_name(), time_range) 'LEFT OUTER JOIN plexpy_users ON ' + s + '.user = plexpy_users.username ' \
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-' + time_range + ' days", "localtime") '\
'GROUP BY ' + s + '.user ' \
'ORDER BY total_plays DESC LIMIT 10'
result = myDB.select(query) result = myDB.select(query)
except: except:
logger.warn("Unable to open PlexWatch database.") logger.warn("Unable to open PlexWatch database.")
@ -673,8 +676,9 @@ class PlexWatch(object):
for item in result: for item in result:
thumb = self.get_user_gravatar_image(item[0]) thumb = self.get_user_gravatar_image(item[0])
row = {'user': item[0], row = {'user': item[0],
'total_plays': item[1], 'friendly_name': item[1],
'last_play': item[2], 'total_plays': item[2],
'last_play': item[3],
'thumb': thumb['user_thumb'] 'thumb': thumb['user_thumb']
} }
top_users.append(row) top_users.append(row)
@ -875,6 +879,24 @@ class PlexWatch(object):
'series': [series_1_output]} 'series': [series_1_output]}
return output return output
def set_user_friendly_name(self, user=None, friendly_name=None):
if user and friendly_name:
myDB = db.DBConnection()
control_value_dict = {"username": user}
new_value_dict = {"friendly_name": friendly_name}
myDB.upsert('plexpy_users', new_value_dict, control_value_dict)
def get_user_friendly_name(self, user=None):
if user:
myDB = db.DBConnection()
query = 'select friendly_name FROM plexpy_users WHERE username = "%s"' % user
result = myDB.select_single(query)
return result
# Taken from: # Taken from:
# https://stackoverflow.com/questions/18066269/group-by-and-aggregate-the-values-of-a-list-of-dictionaries-in-python # https://stackoverflow.com/questions/18066269/group-by-and-aggregate-the-values-of-a-list-of-dictionaries-in-python
@staticmethod @staticmethod

View file

@ -99,7 +99,42 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
def user(self, user=None): def user(self, user=None):
return serve_template(templatename="user.html", title="User", user=user) try:
plex_watch = plexwatch.PlexWatch()
friendly_name = plex_watch.get_user_friendly_name(user)
except:
logger.warn("Unable to retrieve friendly name for user %s " % user)
friendly_name = user
return serve_template(templatename="user.html", title="User", user=user, friendly_name=friendly_name)
@cherrypy.expose
def edit_user(self, user=None, friendly_name=None, **kwargs):
if user and friendly_name:
try:
plex_watch = plexwatch.PlexWatch()
plex_watch.set_user_friendly_name(user, friendly_name)
status_message = "Successfully updated user."
return status_message
except:
status_message = "Failed to updated user."
return status_message
elif user and not friendly_name:
try:
plex_watch = plexwatch.PlexWatch()
result = {'user': user,
'friendly_name': plex_watch.get_user_friendly_name(user)
}
status_message = ""
except:
result = {'user': user,
'friendly_name': ''
}
status_message = "There was an error."
return serve_template(templatename="edit_user.html", title="Edit User", data=result, status_message=status_message)
else:
return serve_template(templatename="edit_user.html", title="Edit User", data=user, status_message='Unknown error.')
@cherrypy.expose @cherrypy.expose
def get_stream_data(self, row_id=None, user=None, **kwargs): def get_stream_data(self, row_id=None, user=None, **kwargs):