Skip to content

Commit

Permalink
Issue mozilla#108: Progress on team application approval process
Browse files Browse the repository at this point in the history
  • Loading branch information
lmorchard committed Feb 20, 2015
1 parent b10c44e commit 273a2b9
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 93 deletions.
2 changes: 2 additions & 0 deletions badgus/base/static/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ form ul li div.clearablefileinput .clear label { float: inherit;
display: inline; margin: inherit; text-align: inherit; width: auto }
form#edit_badge ul li div.clearablefileinput .clear { display: none; }

form.inline { display: inline; }

section.sample-badge { }
section.sample-badge dl.badge p { font-size: 0.95em; margin: 1.15em 0 0 0;
padding: 0; width: 66% }
Expand Down
11 changes: 9 additions & 2 deletions badgus/teams/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from badgus.base.utils import UPLOADS_URL, show_unicode, show_image

from teamwork.admin import related_roles_link, RoleInline
from .models import BadgeTeam
from .models import *


class BadgeTeamAdmin(admin.ModelAdmin):
Expand All @@ -23,5 +23,12 @@ class BadgeTeamAdmin(admin.ModelAdmin):
search_fields = ('name', 'description',)
inlines = (RoleInline,)


admin.site.register(BadgeTeam, BadgeTeamAdmin)


class BadgeTeamApplicationAdmin(admin.ModelAdmin):
fields = ('team', 'creator', 'approver', 'created', 'comment')
list_display = ('team', 'creator', 'approver', 'created', 'comment')
search_fields = ('creator__username', 'approver__username')

admin.site.register(BadgeTeamApplication, BadgeTeamApplicationAdmin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models


class Migration(SchemaMigration):

def forwards(self, orm):
# Adding field 'BadgeTeamApplication.approver'
db.add_column(u'teams_badgeteamapplication', 'approver',
self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='approved_badgeteam_applications', null=True, to=orm['auth.User']),
keep_default=False)


def backwards(self, orm):
# Deleting field 'BadgeTeamApplication.approver'
db.delete_column(u'teams_badgeteamapplication', 'approver_id')


models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'teams.badgeteam': {
'Meta': {'object_name': 'BadgeTeam', '_ormbases': [u'teamwork.Team']},
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}),
u'team_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['teamwork.Team']", 'unique': 'True', 'primary_key': 'True'})
},
u'teams.badgeteamapplication': {
'Meta': {'object_name': 'BadgeTeamApplication'},
'approver': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'approved_badgeteam_applications'", 'null': 'True', 'to': u"orm['auth.User']"}),
'comment': ('django.db.models.fields.TextField', [], {}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'submitted_badgeteam_applications'", 'null': 'True', 'to': u"orm['auth.User']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'applications'", 'to': u"orm['teams.BadgeTeam']"})
},
u'teamwork.team': {
'Meta': {'object_name': 'Team'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'founder': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
}
}

complete_apps = ['teams']
17 changes: 15 additions & 2 deletions badgus/teams/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
BADGETEAM_INVALID_NAMES = ('new',)

BADGETEAM_DEFAULT_ROLES = {
"awarder": (
"helper": (
'teams.view_badgeteam',
'badger.award_badge',
'badger.nominate_badge',
'badger.manage_deferredawards',
Expand All @@ -38,6 +39,7 @@
'teams.invite_badgeteam',
'teams.list_badgeteamapplication',
'teams.view_badgeteamapplication',
'teams.approve_badgeteamapplication',
'badger.change_badge',
'badger.delete_badge',
'badger.award_badge',
Expand Down Expand Up @@ -79,6 +81,7 @@ class Meta:

('list_badgeteamapplication', 'Can list badge team applications'),
('view_badgeteamapplication', 'Can view badge team applications'),
('approve_badgeteamapplication', 'Can approve badge team applications'),
)

def filter_permissions(self, user, permissions):
Expand Down Expand Up @@ -124,7 +127,10 @@ class BadgeTeamApplication(models.Model):
comment = models.TextField(blank=False)
team = models.ForeignKey(BadgeTeam, blank=False, null=False,
related_name='applications')
creator = models.ForeignKey(User, blank=False, null=True)
approver = models.ForeignKey(User, blank=True, null=True,
related_name='approved_badgeteam_applications')
creator = models.ForeignKey(User, blank=False, null=True,
related_name='submitted_badgeteam_applications')
created = models.DateTimeField(auto_now_add=True, blank=False)
modified = models.DateTimeField(auto_now=True, blank=False)

Expand All @@ -138,6 +144,13 @@ def get_absolute_url(self):
return reverse('teams.team_application_detail', kwargs=dict(
team_slug=self.team.slug, pk=self.pk))

def approve(self, approver, role_name='helper'):
self.approver = approver
self.save()
role = self.team.role_set.get(name=role_name)
role.users.add(self.creator)



"""
class BadgeTeamInvitation(models.Model):
Expand Down
2 changes: 1 addition & 1 deletion badgus/teams/templates/teams/badgeteam_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ <h2 class="team-name">{{ _("Team: {name}") | fe(name=object.name) }}</h2>
</header>

<ul class="btn-list">
{% if existing_application %}
{% if existing_application and not existing_application.approver %}
<li><a class="action btn btn-info view-team-application"
href="{{ existing_application.get_absolute_url() }}">{{ _('Review your application to this team') }}</a></li>
{% elif request.user.has_perm('teams.apply_badgeteam', object) and not object.founder == request.user and not object.has_user(request.user) %}
Expand Down
12 changes: 11 additions & 1 deletion badgus/teams/templates/teams/badgeteam_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@ <h2>

<ul class="teams">
{% for team in object_list %}
{% include "teams/includes/badgeteam_listitem.html" %}
<li class="team">
{% if team and team.image %}
{% set image_url = team.image.url %}
{% else %}
{# TODO: Put the URL for default team image in settings #}
{% set image_url = static("img/default-badge.png") %}
{% endif %}
<a href="{{ team.get_absolute_url() }}" class="image"><img src="{{ image_url }}"
alt="{{ team.name }}" width="128" height="128" /></a>
<a href="{{ team.get_absolute_url() }}" class="label"><span class="title">{{ team.name }}</span></a>
</li>
{% else %}
<li>{{ _("No teams, yet.") }}</li>
{% endfor %}
Expand Down
11 changes: 10 additions & 1 deletion badgus/teams/templates/teams/badgeteamapplication_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ <h2 class="team-name">{{ _("Application for Team: {name}") | fe(name=object.team

<ul class="btn-list">
{% if request.user.has_perm('teams.delete_badgeteamapplication', object) %}
<li><a class="action btn btn-danger" href="{{ url('teams.team_application_delete', team_slug=object.team.slug, pk=object.pk) }}">{{ _('Cancel application') }}</a></li>
<li><a class="action btn btn-danger delete-application" href="{{ url('teams.team_application_delete', team_slug=object.team.slug, pk=object.pk) }}">{{ _('Delete application') }}</a></li>
{% endif %}
{% if request.user.has_perm('teams.approve_badgeteamapplication', object) %}
<li>
<form class="inline" method="POST" action="{{ url('teams.team_application_approve', team_slug=object.team.slug, pk=object.pk) }}">{{ csrf() }}<input type="submit" class="btn btn-success approve-application submit" value="{{ _("Approve application") }}"></form>
</li>
{% endif %}
</ul>

Expand All @@ -23,6 +28,10 @@ <h2 class="team-name">{{ _("Application for Team: {name}") | fe(name=object.team
<dd class="teamname"><a href="{{ object.team.get_absolute_url() }}">{{ object.team }}</a></dd>
<dt>{{_("Applicant")}}</dt>
<dd class="creator"><a href="{{ object.creator.get_absolute_url() }}"><img src="{{ user_avatar(object.creator) }}" class="avatar" alt="{{ object.creator }}" width="28" height="28" />{{ object.creator }}</a></dd>
{% if object.approver %}
<dt>{{_("Approved by")}}</dt>
<dd class="approver"><a href="{{ object.approver.get_absolute_url() }}"><img src="{{ user_avatar(object.approver) }}" class="avatar" alt="{{ object.approver }}" width="28" height="28" />{{ object.approver }}</a></dd>
{% endif %}
<dt>{{_("Comment")}}</dt>
<dd class="comment">{{ object.comment }}</dd>
<dt>{{_("Created")}}</dt>
Expand Down
13 changes: 11 additions & 2 deletions badgus/teams/templates/teams/badgeteamapplication_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@
<section class="applications_list">
<header class="page-header">
<h2>
<span>{{ _("Applications for Team: {team}") | fe(team=team.name) }}</span>
{% if 'approved' in request.GET %}
<span class="approved-title">{{ _("Approved Applications for Team: {team}") | fe(team=team.name) }}</span>
{% else %}
<span class="pending-title">{{ _("Pending Applications for Team: {team}") | fe(team=team.name) }}</span>
{% endif %}
<small>
{% if 'approved' in request.GET %}
<a class="btn btn-info btn-xs" href="{{ url('teams.team_applications_list', team_slug=team.slug) }}">{{ _('View pending') }}</a>
{% else %}
<a class="btn btn-info btn-xs" href="{{ url('teams.team_applications_list', team_slug=team.slug) }}?approved">{{ _('View approved') }}</a>
{% endif %}
</small>
</h2>
</header>

<ul class="applications">
{% for object in object_list %}
<li class="application">
<a href="{{ object.get_absolute_url() }}"><img src="{{ user_avatar(object.creator) }}" alt="{{ object.creator }}" width="28" height="28" /> {{ object.creator }} applied on {{ object.created }}</a>
<a href="{{ object.get_absolute_url() }}"><img src="{{ user_avatar(object.creator) }}" alt="{{ object.creator }}" width="28" height="28" /> {{ object.creator }} applied on {{ object.created }}</a> {{object.approver}}
</li>
{% else %}
<li class="empty">{{ _("No applications, yet.") }}</li>
Expand Down
11 changes: 0 additions & 11 deletions badgus/teams/templates/teams/includes/badgeteam_listitem.html

This file was deleted.

37 changes: 37 additions & 0 deletions badgus/teams/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import logging

from django.test import TestCase

from django.test.client import Client
from django.contrib.auth.models import User

from badgus.teams.models import BadgeTeam, BadgeTeamApplication


class BadgeTeamTestCase(TestCase):

def client_login(self, name='user'):
self.client.login(username=self.users[name].username,
password=self.users[name].username)

def setUp(self):
self.client = Client()

self.users = dict()
users_data = {
"founder": ("founder1", "founder1", "[email protected]"),
"user": ("user1", "user1", "[email protected]"),
"member": ("member1", "member1", "[email protected]")
}
for name, (username, password, email) in users_data.items():
(user,created) = User.objects.get_or_create(username=username, email=email)
user.set_password(password)
user.save()
self.users[name] = user

self.team = BadgeTeam(name='randoteam1', founder=self.users['founder'])
self.team.save()
self.team_url = self.team.get_absolute_url()

self.role = self.team.role_set.get(name='leader')
self.role.users.add(self.users['member'])
29 changes: 21 additions & 8 deletions badgus/teams/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
from django.utils.functional import wraps
from django.db import models
from django.contrib.auth.models import User
from django import test
from django.core.cache import cache
from django.core.exceptions import ValidationError


from badgus.base.utils import slugify
from badgus.teams.models import (BadgeTeam, BadgeTeamInvitation,
BadgeTeamApplication)
from badgus.teams.models import BadgeTeam, BadgeTeamApplication
from badgus.teams.tests import BadgeTeamTestCase


class BadgeTeamTest(test.TestCase):
class BadgeTeamTest(BadgeTeamTestCase):

@raises(ValidationError)
def test_team_invalid_names(self):
Expand Down Expand Up @@ -73,5 +70,21 @@ def test_default_roles_created(self):
eq_(expected_perm_names, result_perm_names)


class BadgeTeamApplicationTest(test.TestCase):
pass
class BadgeTeamApplicationTest(BadgeTeamTestCase):

def test_approve_application(self):
"""Approving an application should result in the applicant as a member of the team"""
team = BadgeTeam(name="test_applications")
team.save()

application = BadgeTeamApplication(
team=self.team, creator=self.users['user'])
application.save()

ok_(not team.has_user(self.users['user']))
ok_(not application.approver)

application.approve(approver=self.users['founder'])

ok_(self.team.has_user(self.users['user']))
eq_(self.users['founder'], application.approver)
Loading

0 comments on commit 273a2b9

Please sign in to comment.