Improved UserAction display

This commit is contained in:
Jeremy Stretch 2016-05-24 09:45:40 -04:00
parent b614a1ae67
commit 651f97af81
8 changed files with 177 additions and 90 deletions

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-24 13:24
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('extras', '0004_useraction'),
]
operations = [
migrations.AlterField(
model_name='useraction',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='actions', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Template, Context from django.template import Template, Context
from django.utils.safestring import mark_safe
from dcim.models import Site from dcim.models import Site
@ -156,7 +157,7 @@ class UserAction(models.Model):
A record of an action (add, edit, or delete) performed on an object by a User. A record of an action (add, edit, or delete) performed on an object by a User.
""" """
time = models.DateTimeField(auto_now_add=True, editable=False) time = models.DateTimeField(auto_now_add=True, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, related_name='actions', on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True)
action = models.PositiveSmallIntegerField(choices=ACTION_CHOICES) action = models.PositiveSmallIntegerField(choices=ACTION_CHOICES)
@ -166,3 +167,18 @@ class UserAction(models.Model):
class Meta: class Meta:
ordering = ['-time'] ordering = ['-time']
def __unicode__(self):
if self.message:
return ' '.join([self.user, self.message])
return ' '.join([self.user, self.get_action_display(), self.content_type])
def icon(self):
if self.action in [ACTION_CREATE, ACTION_IMPORT]:
return mark_safe('<i class="glyphicon glyphicon-plus text-success"></i>')
elif self.action in [ACTION_EDIT, ACTION_BULK_EDIT]:
return mark_safe('<i class="glyphicon glyphicon-pencil text-warning"></i>')
elif self.action in [ACTION_DELETE, ACTION_BULK_DELETE]:
return mark_safe('<i class="glyphicon glyphicon-remove text-danger"></i>')
else:
return ''

View File

@ -36,7 +36,7 @@ def home(request):
return render(request, 'home.html', { return render(request, 'home.html', {
'stats': stats, 'stats': stats,
'recent_activity': UserAction.objects.all()[:20] 'recent_activity': UserAction.objects.all()[:15]
}) })

View File

@ -47,100 +47,104 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-7">
<div class="panel panel-default"> <div class="row">
<div class="panel-heading"> <div class="col-md-6">
<strong>DCIM</strong> <div class="panel panel-default">
<div class="panel-heading">
<strong>DCIM</strong>
</div>
<div class="list-group">
<div class="list-group-item">
<span class="badge pull-right">{{ stats.site_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'dcim:site_list' %}">Sites</a></h4>
<p class="list-group-item-text text-muted">Geographic locations</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.rack_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'dcim:rack_list' %}">Racks</a></h4>
<p class="list-group-item-text text-muted">Equipment racks, optionally organized by group</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.device_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'dcim:device_list' %}">Devices</a></h4>
<p class="list-group-item-text text-muted">Rack-mounted network equipment, servers, and other devices</p>
</div>
<div class="list-group-item">
<h4 class="list-group-item-heading">Connections</h4>
<span class="badge pull-right">{{ stats.interface_connections_count }}</span>
<p style="padding-left: 20px;"><a href="{% url 'dcim:interface_connections_list' %}">Interfaces</a></p>
<span class="badge pull-right">{{ stats.console_connections_count }}</span>
<p style="padding-left: 20px;"><a href="{% url 'dcim:console_connections_list' %}">Console</a></p>
<span class="badge pull-right">{{ stats.power_connections_count }}</span>
<p class="list-group-item-text" style="padding-left: 20px;"><a href="{% url 'dcim:power_connections_list' %}">Power</a></p>
</div>
</div>
</div>
{% if perms.secrets %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secrets</strong>
</div>
<div class="list-group">
<div class="list-group-item">
<span class="badge pull-right">{{ stats.secret_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'secrets:secret_list' %}">Secrets</a></h4>
<p class="list-group-item-text text-muted">Sensitive data (such as passwords) which has been stored securely</p>
</div>
</div>
</div>
{% endif %}
</div> </div>
<div class="list-group"> <div class="col-md-6">
<div class="list-group-item"> <div class="panel panel-default">
<span class="badge pull-right">{{ stats.site_count }}</span> <div class="panel-heading">
<h4 class="list-group-item-heading"><a href="{% url 'dcim:site_list' %}">Sites</a></h4> <strong>IPAM</strong>
<p class="list-group-item-text text-muted">Geographic locations</p> </div>
<div class="list-group">
<div class="list-group-item">
<span class="badge pull-right">{{ stats.aggregate_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></h4>
<p class="list-group-item-text text-muted">Top-level IP allocations</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.prefix_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></h4>
<p class="list-group-item-text text-muted">IPv4 and IPv6 network assignments</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.ipaddress_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></h4>
<p class="list-group-item-text text-muted">Individual IPv4 and IPv6 addresses</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.vlan_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vlan_list' %}">VLANs</a></h4>
<p class="list-group-item-text text-muted">Layer two domains, identified by VLAN ID</p>
</div>
</div>
</div> </div>
<div class="list-group-item"> <div class="panel panel-default">
<span class="badge pull-right">{{ stats.rack_count }}</span> <div class="panel-heading">
<h4 class="list-group-item-heading"><a href="{% url 'dcim:rack_list' %}">Racks</a></h4> <strong>Circuits</strong>
<p class="list-group-item-text text-muted">Equipment racks, optionally organized by group</p> </div>
</div> <div class="list-group">
<div class="list-group-item"> <div class="list-group-item">
<span class="badge pull-right">{{ stats.device_count }}</span> <span class="badge pull-right">{{ stats.provider_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'dcim:device_list' %}">Devices</a></h4> <h4 class="list-group-item-heading"><a href="{% url 'circuits:provider_list' %}">Providers</a></h4>
<p class="list-group-item-text text-muted">Rack-mounted network equipment, servers, and other devices</p> <p class="list-group-item-text text-muted">Organizations which provide circuit connectivity</p>
</div> </div>
<div class="list-group-item"> <div class="list-group-item">
<h4 class="list-group-item-heading">Connections</h4> <span class="badge pull-right">{{ stats.circuit_count }}</span>
<span class="badge pull-right">{{ stats.interface_connections_count }}</span> <h4 class="list-group-item-heading"><a href="{% url 'circuits:circuit_list' %}">Circuits</a></h4>
<p style="padding-left: 20px;"><a href="{% url 'dcim:interface_connections_list' %}">Interfaces</a></p> <p class="list-group-item-text text-muted">Communication links for Internet transit, peering, and other services</p>
<span class="badge pull-right">{{ stats.console_connections_count }}</span> </div>
<p style="padding-left: 20px;"><a href="{% url 'dcim:console_connections_list' %}">Console</a></p>
<span class="badge pull-right">{{ stats.power_connections_count }}</span>
<p class="list-group-item-text" style="padding-left: 20px;"><a href="{% url 'dcim:power_connections_list' %}">Power</a></p>
</div>
</div>
</div>
{% if perms.secrets %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secrets</strong>
</div>
<div class="list-group">
<div class="list-group-item">
<span class="badge pull-right">{{ stats.secret_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'secrets:secret_list' %}">Secrets</a></h4>
<p class="list-group-item-text text-muted">Sensitive data (such as passwords) which has been stored securely</p>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<strong>IPAM</strong>
</div>
<div class="list-group">
<div class="list-group-item">
<span class="badge pull-right">{{ stats.aggregate_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></h4>
<p class="list-group-item-text text-muted">Top-level IP allocations</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.prefix_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></h4>
<p class="list-group-item-text text-muted">IPv4 and IPv6 network assignments</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.ipaddress_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></h4>
<p class="list-group-item-text text-muted">Individual IPv4 and IPv6 addresses</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.vlan_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vlan_list' %}">VLANs</a></h4>
<p class="list-group-item-text text-muted">Layer two domains, identified by VLAN ID</p>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Circuits</strong>
</div>
<div class="list-group">
<div class="list-group-item">
<span class="badge pull-right">{{ stats.provider_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'circuits:provider_list' %}">Providers</a></h4>
<p class="list-group-item-text text-muted">Organizations which provide circuit connectivity</p>
</div>
<div class="list-group-item">
<span class="badge pull-right">{{ stats.circuit_count }}</span>
<h4 class="list-group-item-heading"><a href="{% url 'circuits:circuit_list' %}">Circuits</a></h4>
<p class="list-group-item-text text-muted">Communication links for Internet transit, peering, and other services</p>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-5">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Recent Activity</strong> <strong>Recent Activity</strong>
@ -150,7 +154,7 @@
<tr> <tr>
<td>{{ a.time|date:"Y-m-d H:i" }}</td> <td>{{ a.time|date:"Y-m-d H:i" }}</td>
<td>{{ a.user }}</td> <td>{{ a.user }}</td>
<td>{{ a.message|safe }}</td> <td>{{ a.icon }} {{ a.message|safe }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -2,4 +2,5 @@
<li{% ifequal active_tab "profile" %} class="active"{% endifequal %}><a href="{% url 'users:profile' %}">Profile</a></li> <li{% ifequal active_tab "profile" %} class="active"{% endifequal %}><a href="{% url 'users:profile' %}">Profile</a></li>
<li{% ifequal active_tab "change_password" %} class="active"{% endifequal %}><a href="{% url 'users:change_password' %}">Change Password</a></li> <li{% ifequal active_tab "change_password" %} class="active"{% endifequal %}><a href="{% url 'users:change_password' %}">Change Password</a></li>
<li{% ifequal active_tab "userkey" %} class="active"{% endifequal %}><a href="{% url 'users:userkey' %}">User Key</a></li> <li{% ifequal active_tab "userkey" %} class="active"{% endifequal %}><a href="{% url 'users:userkey' %}">User Key</a></li>
<li{% ifequal active_tab "recent_activity" %} class="active"{% endifequal %}><a href="{% url 'users:recent_activity' %}">Recent Activity</a></li>
</ul> </ul>

View File

@ -0,0 +1,35 @@
{% extends '_base.html' %}
{% load form_helpers %}
{% block title %}Recent Activity{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1>Recent Activity</h1>
</div>
</div>
<div class="row">
<div class="col-md-2 col-md-offset-2">
{% include 'users/inc/profile_nav.html' with active_tab="recent_activity" %}
</div>
<div class="col-md-6">
<table class="table table-hover">
<thead>
<tr>
<th>Time</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for action in recent_activity %}
<tr>
<td>{{ action.time|date:"Y-m-d H:i" }}</td>
<td>{{ action.icon }} {{ action.message|safe }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -10,5 +10,6 @@ urlpatterns = [
url(r'^profile/password/$', views.change_password, name='change_password'), url(r'^profile/password/$', views.change_password, name='change_password'),
url(r'^profile/user-key/$', views.userkey, name='userkey'), url(r'^profile/user-key/$', views.userkey, name='userkey'),
url(r'^profile/user-key/edit/$', views.userkey_edit, name='userkey_edit'), url(r'^profile/user-key/edit/$', views.userkey_edit, name='userkey_edit'),
url(r'^profile/recent-activity/$', views.recent_activity, name='recent_activity'),
] ]

View File

@ -116,3 +116,11 @@ def userkey_edit(request):
'userkey': userkey, 'userkey': userkey,
'form': form, 'form': form,
}) })
@login_required()
def recent_activity(request):
return render(request, 'users/recent_activity.html', {
'recent_activity': request.user.actions.all()[:50]
})