Commit 5531552b by Kálmán Viktor

Merge branch 'feature-profile-view'

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
	circle/dashboard/urls.py
	circle/dashboard/views.py
parents 1acfd3ea fea2c505
......@@ -26,7 +26,7 @@ from django.contrib.auth.signals import user_logged_in
from django.core.urlresolvers import reverse
from django.db.models import (
Model, ForeignKey, OneToOneField, CharField, IntegerField, TextField,
DateTimeField, permalink,
DateTimeField, permalink, BooleanField
)
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _, override, ugettext
......@@ -83,13 +83,15 @@ class Profile(Model):
unique=True, blank=True, null=True, max_length=64,
help_text=_('Unique identifier of the person, e.g. a student number.'))
instance_limit = IntegerField(default=5)
use_gravatar = BooleanField(default=False)
def notify(self, subject, template, context={}, valid_until=None):
return Notification.send(self.user, subject, template, context,
valid_until)
def get_absolute_url(self):
return reverse("dashboard.views.profile")
return reverse("dashboard.views.profile",
kwargs={'username': self.user.username})
class GroupProfile(AclBase):
......
......@@ -692,3 +692,25 @@ textarea[name="list-new-namelist"] {
#vm-details-connection-string-copy {
cursor: pointer;
}
.dashboard-profile-vm-list, .dashboard-profile-group-list {
list-style: none;
padding-left: 28px;
}
.dashboard-profile-vm-list a, .dashboard-profile-vm-list a:hover {
text-decoration: none;
color: #555;
}
#group-detail-user-table td:nth-child(2) a,
#group-detail-perm-table td:nth-child(2) a,
#vm-access-table td:nth-child(2) a,
.no-style-link, .no-style-link:hover {
color: #555 !important;
text-decoration: none;
}
#dashboard-profile-avatar {
max-width: 200px;
}
$(function() {
// change user avatar
$("#dashboard-profile-use-gravatar").click(function() {
var checked = $(this).prop("checked");
var user = $(this).data("user");
$.ajax({
type: 'POST',
url:"/dashboard/profile/" + user + "/use_gravatar/",
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re) {
if(re.new_avatar_url) {
$("#dashboard-profile-avatar").prop("src", re.new_avatar_url);
}
},
error: function(xhr, textStatus, error) {
if(xhr.status == 403) {
addMessage(gettext("You have no permission to change this profile."), "danger");
} else {
addMessage(gettext("Unknown error."), "danger");
}
}
});
});
});
......@@ -47,9 +47,13 @@
</ul>
</li>
</ul>
{% if user.is_authenticated %}
{% if user.is_authenticated and user.pk %}
<a class="navbar-brand pull-right" href="{% url "logout" %}?next={% url "login" %}" style="color: white; font-size: 10px;"><i class="icon-signout icon-sign-out"></i> {% trans "Log out" %}</a>
<a class="navbar-brand pull-right" href="{% url "dashboard.views.profile" %}" title="{% trans "User profile" %}" style="color: white; font-size: 10px;"><i class="icon-user "></i> {{user}}</a>
<a class="navbar-brand pull-right" href="{% url "dashboard.views.profile" username=user.username %}"
title="{% trans "User profile" %}" style="color: white; font-size: 10px;">
<i class="icon-user"></i>
{% include "dashboard/_display-name.html" with user=user show_org=True %}
</a>
{% if user.is_superuser %}
<a class="navbar-brand pull-right" href="/network/" style="color: white; font-size: 10px;"><i class="icon-globe"></i> {% trans "Network" %}</a>
<a class="navbar-brand pull-right" href="/admin/" style="color: white; font-size: 10px;"><i class="icon-cogs"></i> {% trans "Admin" %}</a>
......
{% load i18n %}
{% if user.get_full_name|length > 0 %}
{% if user and user.pk %}
{% if user.get_full_name %}
{{ user.get_full_name }}
{% else %}
{% else %}
{{ user.username }}
{% endif %}
{% endif %}
{% if show_org %}
{% if user.profile and user.profile.org_id|length > 0 %}
{% if show_org %}
{% if user.profile and user.profile.org_id %}
({{ user.profile.org_id }})
{% else %}
({% trans "username" %}: {{ user.username }})
{% endif %}
{% endif %}
{% endif %}
......@@ -54,8 +54,8 @@
<i class="icon-user"></i>
</td>
<td>
<strong>{{i.username}}</strong>,
{% include "dashboard/_display-name.html" with user=i show_org=True %}
<a href="{% url "dashboard.views.profile" username=i.username %}" title="{{ i.username }}"
>{% include "dashboard/_display-name.html" with user=i show_org=True %}</a>
</td>
<td>
<a data-group_pk="{{ group.pk }}" data-member_pk="{{i.pk}}" href="{% url "dashboard.views.remove-user" member_pk=i.pk group_pk=group.pk %}" class="real-link delete-from-group btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a>
......@@ -93,8 +93,8 @@
<i class="icon-user"></i>
</td>
<td>
<strong>{{i.user}}</strong>,
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
<a href="{% url "dashboard.views.profile" username=i.user.username %}" title="{{ i.user.username }}"
>{% include "dashboard/_display-name.html" with user=i.user show_org=True %}</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
......@@ -109,7 +109,12 @@
{% for i in acl.groups %}
<tr>
<td><i class="icon-group"></i></td><td>{{ i.group }}</td>
<td>
<i class="icon-group"></i>
</td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}">{{ i.group }}</a>
</td>
<td>
<select class="form-control" name="perm-g-{{ i.group.pk }}">
{% for id, name in acl.levels %}
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{{ profile.username}} | {% trans "Profile" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.index" %}">{% trans "Back" %}</a>
<h3 class="no-margin">
<i class="icon-user"></i>
{% include "dashboard/_display-name.html" with user=profile show_org=True %}
</h3>
</div>
<div class="panel-body">
<div>
<div class="" style="float: left">
<img id="dashboard-profile-avatar" src="{{ avatar_url }}" class="img-rounded"/>
</div>
<div class="" style="padding-left: 215px;">
<p>{% trans "Username" %}: {{ profile.username }}</p>
<p>{% trans "Organization ID" %}: {{ profile.profile.org_id|default:"-" }}</p>
<p>{% trans "First name" %}: {{ profile.first_name|default:"-" }}</p>
<p>{% trans "Last name" %}: {{ profile.last_name|default:"-" }}</p>
<p>
{# if the group list is not empty the logged in user is somewhat related to the user #}
{% if perm_group_list %}
{% trans "Email address" %}: {{ profile.email }}
{% else %}
-
{% endif %}
</p>
{% if request.user == profile %}
<p>
{% trans "Use email address as Gravatar profile image" %}:
<input id="dashboard-profile-use-gravatar" data-user="{{ profile.username }}"
{% if profile.profile.use_gravatar %}checked="checked"{% endif %}
type="checkbox"/> <a href="https://gravatar.com">{% trans "What's Gravatar?" %}</a>
</p>
<a href="{% url "dashboard.views.profile-preferences" %}">{% trans "Change password and language" %}</a>
{% endif %}
</div>
<div class="clearfix"></div>
</div>
{% if perm_group_list %}
<hr />
<h4>
<i class="icon-group"></i> {% trans "Groups" %}
</h4>
<ul class="dashboard-profile-group-list">
{% for g in groups %}
<li>{{ g.name }}</li>
{% empty %}
{% trans "This user is not in any group." %}
{% endfor %}
</ul>
{% endif %}
<hr />
<h4>
<i class="icon-desktop"></i>
{% trans "Virtual machines owned by the user" %} ({{ instances_owned|length }})
</h4>
<ul class="dashboard-profile-vm-list">
{% for i in instances_owned %}
<li>
<a href="{{ i.get_absolute_url }}">
<i class="icon-li {{ i.get_status_icon }}"></i>
{{ i }}
</a>
</li>
{% empty %}
<li>
{% trans "This user have no virtual machines." %}
</li>
{% endfor %}
</ul>
<hr />
<h4>
<i class="icon-desktop"></i>
{% trans "Virtual machines with access" %} ({{ instances_with_access|length }})
</h4>
<ul class="dashboard-profile-vm-list">
{% for i in instances_with_access %}
<li>
<a href="{{ i.get_absolute_url }}">
<i class="icon-li {{ i.get_status_icon }}"></i>
{{ i }}
</a>
</li>
{% empty %}
<li>
{% trans "This user have no access to any virtual machine." %}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/profile.js"></script>
{% endblock %}
......@@ -8,9 +8,10 @@
{% if user.is_superuser %}<a href="{{ a.get_absolute_url }}">{% endif %}
{{ a.get_readable_name }}{% if user.is_superuser %}</a>{% endif %}
</strong>
{{ a.started|date:"Y-m-d H:i" }}
{% if a.user %},
{{ a.started|date:"Y-m-d H:i" }}{% if a.user %},
<a class="no-style-link" href="{% url "dashboard.views.profile" username=a.user.username %}">
{% include "dashboard/_display-name.html" with user=a.user show_org=True %}
</a>
{% endif %}
{% if a.is_abortable_for_user %}
<form action="{{ a.instance.get_absolute_url }}" method="POST" class="pull-right">
......
......@@ -26,7 +26,10 @@
{% for i in acl.users %}
<tr>
<td><i class="icon-user"></i></td>
<td>{{i.user}}</td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}" title="{{ i.user.username }}"
>{% include "dashboard/_display-name.html" with user=i.user show_org=True %}</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
......@@ -41,7 +44,11 @@
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="icon-group"></i></td><td>{{i.group}}</td>
<td><i class="icon-group"></i></td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}"
>{{ i.group.name }}</a>
</td>
<td>
<select class="form-control" name="perm-g-{{i.group.id}}">
{% for id, name in acl.levels %}
......
......@@ -49,6 +49,7 @@ class ViewUserTestCase(unittest.TestCase):
with patch.object(InstanceActivityDetail, 'get_object') as go:
act = MagicMock(spec=InstanceActivity)
act._meta.object_name = "InstanceActivity"
act.user.pk = 1
go.return_value = act
view = InstanceActivityDetail.as_view()
self.assertEquals(view(request, pk=1234).render().status_code, 200)
......
......@@ -34,7 +34,8 @@ from .views import (
GroupCreate,
TemplateChoose,
UserCreationView,
get_vm_screenshot
get_vm_screenshot,
ProfileView, toggle_use_gravatar,
)
urlpatterns = patterns(
......@@ -136,7 +137,11 @@ urlpatterns = patterns(
name="dashboard.views.interface-delete"),
url(r'^profile/$', MyPreferencesView.as_view(),
name="dashboard.views.profile-preferences"),
url(r'^profile/(?P<username>\w+)/$', ProfileView.as_view(),
name="dashboard.views.profile"),
url(r'^profile/(?P<username>\w+)/use_gravatar/$', toggle_use_gravatar),
url(r'^group/(?P<group_pk>\d+)/remove/acl/user/(?P<member_pk>\d+)/$',
GroupRemoveAclUserView.as_view(),
name="dashboard.views.remove-acluser"),
......
......@@ -21,6 +21,7 @@ from os import getenv
import json
import logging
import re
from hashlib import md5
import requests
from django.conf import settings
......@@ -36,7 +37,7 @@ from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.http import require_GET
from django.views.decorators.http import require_GET, require_POST
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import (TemplateView, DetailView, View, DeleteView,
UpdateView, CreateView, ListView)
......@@ -46,6 +47,7 @@ from django.utils.translation import ungettext as __
from django.template.defaultfilters import title as title_filter
from django.template.loader import render_to_string
from django.template import RequestContext
from django.templatetags.static import static
from django.forms.models import inlineformset_factory
from django_tables2 import SingleTableView
......@@ -2487,7 +2489,7 @@ class MyPreferencesView(UpdateView):
def post(self, request, *args, **kwargs):
self.ojbect = self.get_object()
redirect_response = HttpResponseRedirect(
reverse("dashboard.views.profile"))
reverse("dashboard.views.profile-preferences"))
if "preferred_language" in request.POST:
form = MyProfileForm(request.POST, instance=self.get_object())
if form.is_valid():
......@@ -2689,3 +2691,70 @@ def get_vm_screenshot(request, pk):
raise Http404()
return HttpResponse(image, mimetype="image/png")
class ProfileView(LoginRequiredMixin, DetailView):
template_name = "dashboard/profile.html"
model = User
slug_field = "username"
slug_url_kwarg = "username"
def get_context_data(self, **kwargs):
context = super(ProfileView, self).get_context_data(**kwargs)
user = self.get_object()
context['profile'] = user
context['avatar_url'] = get_user_avatar_url(user)
context['instances_owned'] = Instance.get_objects_with_level(
"owner", user, disregard_superuser=True).filter(destroyed_at=None)
context['instances_with_access'] = Instance.get_objects_with_level(
"user", user, disregard_superuser=True
).filter(destroyed_at=None).exclude(pk__in=context['instances_owned'])
group_profiles = GroupProfile.get_objects_with_level(
"operator", self.request.user)
groups = Group.objects.filter(groupprofile__in=group_profiles)
context['groups'] = user.groups.filter(pk__in=groups)
# permissions
# show groups only if the user is superuser, or have access
# to any of the groups the user belongs to
context['perm_group_list'] = (
self.request.user.is_superuser or len(context['groups']) > 0)
# filter the virtual machine list
# if the logged in user is not superuser or not the user itself
# filter the list so only those virtual machines are shown that are
# originated from templates the logged in user is operator or higher
if not (self.request.user.is_superuser or self.request.user == user):
it = InstanceTemplate.get_objects_with_level("operator",
self.request.user)
context['instances_owned'] = context['instances_owned'].filter(
template__in=it)
context['instances_with_access'] = context[
'instances_with_access'].filter(template__in=it)
return context
@require_POST
def toggle_use_gravatar(request, **kwargs):
user = get_object_or_404(User, username=kwargs['username'])
if not request.user == user:
raise PermissionDenied()
profile = user.profile
profile.use_gravatar = not profile.use_gravatar
profile.save()
new_avatar_url = get_user_avatar_url(user)
return HttpResponse(
json.dumps({'new_avatar_url': new_avatar_url}),
content_type="application/json",
)
def get_user_avatar_url(user):
if user.profile.use_gravatar:
gravatar_hash = md5(user.email).hexdigest()
return "https://secure.gravatar.com/avatar/%s?s=200" % gravatar_hash
else:
return static("dashboard/img/avatar.png")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment