first commit for v0.2

This commit is contained in:
Élie Bouttier 2014-08-30 15:38:06 -07:00
parent 6ba03afc73
commit 1463854a45
143 changed files with 20775 additions and 2764 deletions

7
accounts/admin.py Normal file
View file

@ -0,0 +1,7 @@
from django.contrib import admin
from accounts.models import *
admin.site.register(User)
admin.site.register(Team)

17
accounts/forms.py Normal file
View file

@ -0,0 +1,17 @@
from django.forms.models import modelform_factory
from django.forms.widgets import PasswordInput
from accounts.models import *
__all__ = [ 'UserForm', 'GroupForm', 'TeamForm' ]
UserForm = modelform_factory(User,
fields=['username', 'first_name', 'last_name',
'password', 'email', 'is_superuser'],
widgets={'password': PasswordInput})
GroupForm = modelform_factory(Group,
fields=['name'])
TeamForm = modelform_factory(Team,
fields=['name'])

View file

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
import django.core.validators
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, verbose_name='superuser status', help_text='Designates that this user has all permissions without explicitly assigning them.')),
('username', models.CharField(verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True)),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
('is_staff', models.BooleanField(default=False, verbose_name='staff status', help_text='Designates whether the user can log into this admin site.')),
('is_active', models.BooleanField(default=True, verbose_name='active', help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(to='auth.Group', verbose_name='groups', blank=True)),
('user_permissions', models.ManyToManyField(to='auth.Permission', verbose_name='user permissions', blank=True)),
],
options={
'ordering': ['username'],
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Team',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('name', models.CharField(max_length=128, unique=True)),
('users', models.ManyToManyField(null=True, to=settings.AUTH_USER_MODEL, blank=True)),
],
options={
'ordering': ['name'],
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Group',
fields=[
],
options={
'ordering': ['name'],
'proxy': True,
},
bases=('auth.group',),
),
migrations.AddField(
model_name='team',
name='groups',
field=models.ManyToManyField(null=True, to='accounts.Group', blank=True),
preserve_default=True,
),
]

72
accounts/models.py Normal file
View file

@ -0,0 +1,72 @@
from django.db import models
from django.db.models import Q
from django.contrib.auth.models import AbstractUser
from django.contrib import auth
from django.utils.encoding import python_2_unicode_compatible
__all__ = [ 'User', 'Group', 'Team' ]
@python_2_unicode_compatible
class User(AbstractUser):
class Meta:
ordering = [ 'username' ]
@property
def teams(self):
query = Q(groups__in=self.groups.all()) | Q(users=self)
return Team.objects.filter(query).distinct()
@property
def username_and_fullname(self):
fullname = self.fullname
if fullname:
return "%s (%s)" % (self.username, fullname)
else:
return self.username
@property
def fullname(self):
fullname = ''
if self.first_name:
fullname += self.first_name
if self.last_name:
if fullname:
fullname += ' '
fullname += self.last_name
return fullname
def __str__(self):
return self.username
class Group(auth.models.Group):
class Meta:
ordering = [ 'name' ]
proxy = True
@property
def users(self):
return User.objects.filter(groups=self)
@python_2_unicode_compatible
class Team(models.Model):
class Meta:
ordering = [ 'name' ]
name = models.CharField(max_length=128, unique=True)
# We dont want related field on User object because we use
# a special function that retrieve also team through group
users = models.ManyToManyField(User, blank=True, null=True,
related_name='+')
groups = models.ManyToManyField(Group, blank=True, null=True,
related_name='teams')
def __str__(self):
return self.name

1225
accounts/static/css/jquery-ui.css vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
/* This script is used to remove user from group,
* user from team or group from team dynamically
* with a ajax request. */
$('a[role="remove"]').on("click", function () {
var a = $(this);
var href = a.data('href');
var type = a.data('type');
a.html('removing...');
$.ajax(href)
.done(function(data, textStatus) {
a.parents('li').remove();
var counter = $('#' + type + '-counter');
var empty = $('#' + type + '-empty');
var count = parseInt(counter.html());
count--;
counter.html(count);
if (count < 0) {
// should not happen
window.location.reload();
} else if (count == 0) {
empty.removeClass('hidden');
} else {
empty.addClass('hidden');
}
})
.fail(function () {
window.location.reload();
});
});

View file

@ -1,3 +1,5 @@
/* This script set the action url of the deletion form
* and update messages. */
$('#confirm-delete').on('show.bs.modal', function(e) {
var item = $(e.relatedTarget).data('item');
if (!item) {

View file

@ -0,0 +1,13 @@
/* This script switch the visible add form when we
* change between user and group tab on team page. */
$('a[data-toggle="tab"]').on("show.bs.tab", function () {
var tab = $(this).data('tab');
var hiddentab;
if (tab == 'user') {
hiddentab = 'group';
} else {
hiddentab = 'user';
}
$('#add-' + hiddentab + '-form').addClass('hidden');
$('#add-' + tab + '-form').removeClass('hidden');
});

View file

View file

@ -0,0 +1,12 @@
from django import template
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.html import escape
register = template.Library()
@register.inclusion_tag('accounts/tags/delete_modal.html')
def delete_modal():
return {}

3
accounts/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

32
accounts/urls.py Normal file
View file

@ -0,0 +1,32 @@
from django.conf.urls import url, include
urlpatterns = [
# Profile
url(r'^profile$', 'accounts.views.profile', name='profile'),
# Users
url(r'^admin/users/$', 'accounts.views.user_list', name='list-user'),
url(r'^admin/users/add/$', 'accounts.views.user_edit', name='add-user'),
url(r'^admin/users/(?P<user>[0-9]+)/edit/$', 'accounts.views.user_edit', name='edit-user'),
url(r'^admin/users/(?P<user>[0-9]+)/delete/$', 'accounts.views.user_delete', name='delete-user'),
url(r'^admin/users/(?P<user>[0-9]+)/activate/$', 'accounts.views.user_activate', name='activate-user'),
url(r'^admin/users/(?P<user>[0-9]+)/disable/$', 'accounts.views.user_disable', name='disable-user'),
# Groups
url(r'^admin/groups/$', 'accounts.views.group_list', name='list-group'),
url(r'^admin/groups/add/$', 'accounts.views.group_edit', name='add-group'),
url(r'^admin/groups/(?P<group>[0-9]+)/$', 'accounts.views.group_details', name='show-group'),
url(r'^admin/groups/(?P<group>[0-9]+)/edit/$', 'accounts.views.group_edit', name='edit-group'),
url(r'^admin/groups/(?P<group>[0-9]+)/delete/$', 'accounts.views.group_delete', name='delete-group'),
url(r'^admin/groups/(?P<group>[0-9]+)/add-user/$', 'accounts.views.group_add_user', name='add-user-to-group'),
url(r'^admin/groups/(?P<group>[0-9]+)/remove-user/(?P<user>[0-9]+)/$', 'accounts.views.group_remove_user', name='remove-user-from-group'),
# Teams
url(r'^admin/teams/$', 'accounts.views.team_list', name='list-team'),
url(r'^admin/teams/add/$', 'accounts.views.team_edit', name='add-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/$', 'accounts.views.team_details', name='show-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/edit$', 'accounts.views.team_edit', name='edit-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/delete$', 'accounts.views.team_delete', name='delete-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/add-user/$', 'accounts.views.team_add_user', name='add-user-to-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/remove-user/(?P<user>[0-9]+)/$', 'accounts.views.team_remove_user', name='remove-user-from-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/add-group/$', 'accounts.views.team_add_group', name='add-group-to-team'),
url(r'^admin/teams/(?P<team>[0-9]+)/remove-group/(?P<group>[0-9]+)/$', 'accounts.views.team_remove_group', name='remove-group-from-team'),
]

329
accounts/views.py Normal file
View file

@ -0,0 +1,329 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.contrib import messages
from django.db.models import Q
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse, JsonResponse
from permissions.decorators import project_perm_required
from accounts.models import *
from accounts.forms import *
###########
# Profile #
###########
@login_required
def profile(request):
return render(request, 'accounts/profile.html')
#########
# Users #
#########
@project_perm_required('manage_user')
def user_list(request):
return render(request, 'accounts/user_list.html', {
'users': User.objects.all(),
})
@project_perm_required('manage_user')
def user_edit(request, user=None):
if user:
user = get_object_or_404(User, id=user)
form = UserForm(request.POST or None, instance=user)
if request.method == 'POST' and form.is_valid():
form.save()
if user:
messages.success(request, 'User modified successfully.')
else:
messages.success(request, 'User added successfully.')
return redirect('list-user')
return render(request, 'accounts/user_edit.html', {
'user': user,
'form': form,
})
@project_perm_required('manage_user')
def user_activate(request, user):
user = get_object_or_404(User, id=user)
if user.is_active:
messages.info(request, 'Account already activated.')
else:
user.is_active = True
user.save()
messages.success(request, 'Account activated successfully.')
return redirect('list-user')
@project_perm_required('manage_user')
def user_disable(request, user):
user = get_object_or_404(User, id=user)
if user.is_active:
user.is_active = False
user.save()
messages.success(request, 'Account disabled successfully.')
else:
messages.info(request, 'Account already disabled.')
return redirect('list-user')
@require_http_methods(["POST"])
@project_perm_required('manage_user')
def user_delete(request, user):
user = get_object_or_404(User, id=user)
user.delete()
messages.success(request, 'User deleted successfully.')
return redirect('list-user')
##########
# Groups #
##########
@project_perm_required('manage_group')
def group_list(request):
return render(request, 'accounts/group_list.html', {
'groups': Group.objects.all(),
})
@project_perm_required('manage_group')
def group_details(request, group):
return render(request, 'accounts/group_details.html', {
'group': get_object_or_404(Group, id=group),
})
@project_perm_required('manage_group')
def group_edit(request, group=None):
if group:
group = get_object_or_404(Group, id=group)
form = GroupForm(request.POST or None, instance=group)
if request.method == 'POST' and form.is_valid():
formgroup = form.save()
if group:
messages.success(request, 'Group modified successfully.')
else:
messages.success(request, 'Group added successfully.')
return redirect('show-group', formgroup.id)
return render(request, 'accounts/group_edit.html', {
'group': group,
'form': form,
})
@require_http_methods(["POST"])
@project_perm_required('manage_group')
def group_delete(request, group):
group = get_object_or_404(Group, id=group)
group.delete()
messages.success(request, 'Group deleted successfully.')
return redirect('list-group')
@project_perm_required('manage_group')
def group_add_user(request, group):
group = get_object_or_404(Group, id=group)
if request.method == 'POST':
user = request.POST.get('user')
if user:
try:
user = User.objects.get(username=user)
except ObjectDoesNotExist:
messages.error(request, 'User not found.')
else:
if group.users.filter(id=user.id).exists():
messages.info(request, 'User already in group.')
else:
user.groups.add(group)
user.save()
messages.success(request, 'User added to group successfully.')
else:
messages.error(request, 'User not found.')
return redirect('show-group', group.id)
else:
term = request.GET.get('term')
if not term:
return Http404()
query = Q(username__icontains=term) \
| Q(first_name__icontains=term) \
| Q(last_name__icontains=term)
users = User.objects.exclude(groups=group).filter(query)[:10]
response = []
for user in users:
response += [ {
'label': user.username_and_fullname,
'value': user.username,
}]
return JsonResponse(response, safe=False)
@project_perm_required('manage_group')
def group_remove_user(request, group, user):
group = get_object_or_404(Group, id=group)
user = get_object_or_404(User, id=user)
user.groups.remove(group)
user.save()
return HttpResponse()
#########
# Teams #
#########
@project_perm_required('manage_team')
def team_list(request):
return render(request, 'accounts/team_list.html', {
'teams': Team.objects.all(),
})
@project_perm_required('manage_team')
def team_details(request, team):
tab = request.session.pop('team-tab', 'user')
return render(request, 'accounts/team_details.html', {
'team': get_object_or_404(Team, pk=team),
'tab': tab,
})
@project_perm_required('manage_team')
def team_edit(request, team=None):
if team:
team = get_object_or_404(Team, pk=team)
form = TeamForm(request.POST or None, instance=team)
if request.method == 'POST' and form.is_valid():
formteam = form.save()
if team:
messages.success(request, 'Team modified successfully.')
else:
messages.success(request, 'Team added successfully.')
return redirect('show-team', formteam.id)
c = {
'team': team,
'form': form,
}
return render(request, 'accounts/team_edit.html', c)
@require_http_methods(["POST"])
@project_perm_required('manage_team')
def team_delete(request, team):
team = get_object_or_404(Team, pk=team)
team.delete()
messages.success(request, 'Team deleted successfully.')
return redirect('list-team')
@project_perm_required('manage_team')
def team_add_user(request, team):
team = get_object_or_404(Team, id=team)
if request.method == 'POST':
user = request.POST.get('user')
if user:
try:
user = User.objects.get(username=user)
except ObjectDoesNotExist:
messages.error(request, 'User not found.')
else:
if team.users.filter(id=user.id).exists():
messages.info(request, 'User already in team.')
else:
team.users.add(user)
team.save()
messages.success(request, 'User added to team successfully.')
else:
messages.error(request, 'User not found.')
request.session['team-tab'] = 'user'
return redirect('show-team', team.id)
else:
term = request.GET.get('term')
if not term:
return Http404()
query = Q(username__icontains=term) \
| Q(first_name__icontains=term) \
| Q(last_name__icontains=term)
users = User.objects \
.exclude(groups__in=team.groups.all()) \
.exclude(id__in=team.users.values('id')) \
.filter(query)[:10]
response = []
for user in users:
response += [ {
'label': user.username_and_fullname,
'value': user.username,
}]
return JsonResponse(response, safe=False)
@project_perm_required('manage_team')
def team_remove_user(request, team, user):
team = get_object_or_404(Team, pk=team)
user = get_object_or_404(User, pk=user)
team.users.remove(user)
team.save()
return HttpResponse()
@project_perm_required('manage_team')
def team_add_group(request, team):
team = get_object_or_404(Team, id=team)
if request.method == 'POST':
group = request.POST.get('group')
if group:
try:
group = Group.objects.get(name=group)
except ObjectDoesNotExist:
messages.error(request, 'Group not found.')
else:
if team.groups.filter(id=group.id).exists():
messages.info(request, 'Group already in team.')
else:
team.groups.add(group)
team.save()
messages.success(request, 'Group added to team successfully.')
else:
messages.error(request, 'Group not found.')
request.session['team-tab'] = 'group'
return redirect('show-team', team.id)
else:
term = request.GET.get('term')
if not term:
return Http404()
groups = Group.objects \
.exclude(id__in=team.groups.values('id')) \
.filter(name__icontains=term)[:10]
response = []
for group in groups:
response += [ {
'label': group.name,
'value': group.name,
}]
return JsonResponse(response, safe=False)
@project_perm_required('manage_team')
def team_remove_group(request, team, group):
team = get_object_or_404(Team, pk=team)
group = get_object_or_404(Group, pk=group)
team.groups.remove(group)
team.save()
return HttpResponse()

View file

@ -1 +0,0 @@
default_app_config = 'issue.apps.IssueConfig'

View file

@ -1,13 +0,0 @@
from django.contrib import admin
from issue.models import *
admin.site.register(User)
admin.site.register(Project)
admin.site.register(Issue)
admin.site.register(Event)
admin.site.register(Label)
admin.site.register(Milestone)
admin.site.register(Settings)
admin.site.register(Team)
admin.site.register(GlobalPermission)
admin.site.register(ProjectPermission)

View file

@ -1,10 +0,0 @@
from django.apps import AppConfig
class IssueConfig(AppConfig):
name = 'issue'
verbose_name = "Issue Tracker"
def ready(self):
import issue.signals

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,80 +0,0 @@
from django import forms
from django.forms.models import modelform_factory
from bootstrap3_datetime.widgets import DateTimePicker
from django_markdown.widgets import MarkdownWidget
from issue.models import *
AddProjectForm = modelform_factory(Project,
fields=['display_name', 'name', 'description', 'access'])
EditProjectForm = modelform_factory(Project,
fields=['display_name', 'description', 'access'])
LabelForm = modelform_factory(Label,
fields=['name', 'color', 'inverted'])
TeamForm = modelform_factory(Team,
fields=['name', 'users', 'groups'])
class MilestoneForm(forms.ModelForm):
class Meta:
model = Milestone
fields = ['name', 'due_date']
widgets = {
'due_date': DateTimePicker(format="%Y-%m-%d %H:%M"),
}
class IssueForm(forms.Form):
title = forms.CharField(max_length=128)
description = forms.CharField(widget=MarkdownWidget, required=False)
class CommentForm(forms.Form):
comment = forms.CharField(widget=MarkdownWidget)
class PermissionForm(forms.ModelForm):
class Meta:
model = PermissionModel
exclude = []
abstract = True
def clean(self):
data = super(PermissionForm, self).clean()
if 'grantee_name' not in data or 'grantee_type' not in data:
# a field required error will be printed so we dont care
return data
name = data['grantee_name']
if int(data['grantee_type']) == PermissionModel.GRANTEE_USER:
if not User.objects.filter(username=name).exists():
raise ValidationError("User '%s' does not exists." % name)
elif int(data['grantee_type']) == PermissionModel.GRANTEE_GROUP:
if not Group.objects.filter(name=name).exists():
raise ValidationError("Group '%s' does not exists." % name)
elif int(data['grantee_type']) == PermissionModel.GRANTEE_TEAM:
if not Team.objects.filter(name=name).exists():
raise ValidationError("Team '%s' does not exists." % name)
return data
class GlobalPermissionForm(PermissionForm):
class Meta:
model = GlobalPermission
exclude = []
class ProjectPermissionForm(PermissionForm):
class Meta:
model = ProjectPermission
exclude = []

View file

@ -1,230 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import colorful.fields
from django.conf import settings
import django.utils.timezone
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
('sites', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('password', models.CharField(verbose_name='password', max_length=128)),
('last_login', models.DateTimeField(verbose_name='last login', default=django.utils.timezone.now)),
('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')),
('username', models.CharField(unique=True, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30)),
('first_name', models.CharField(blank=True, verbose_name='first name', max_length=30)),
('last_name', models.CharField(blank=True, verbose_name='last name', max_length=30)),
('email', models.EmailField(blank=True, verbose_name='email address', max_length=75)),
('is_staff', models.BooleanField(verbose_name='staff status', default=False, help_text='Designates whether the user can log into this admin site.')),
('is_active', models.BooleanField(verbose_name='active', default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.')),
('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)),
('groups', models.ManyToManyField(verbose_name='groups', to='auth.Group', blank=True)),
('user_permissions', models.ManyToManyField(verbose_name='user permissions', to='auth.Permission', blank=True)),
],
options={
'verbose_name': 'user',
'abstract': False,
'verbose_name_plural': 'users',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Event',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('date', models.DateTimeField(auto_now_add=True)),
('code', models.IntegerField(default=0)),
('_args', models.CharField(max_length=1024, default='{}', blank=True)),
('additionnal_section', models.TextField(blank=True, default='')),
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='GlobalPermission',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('grantee_type', models.IntegerField(choices=[(0, 'User'), (1, 'Group'), (2, 'Team')], verbose_name='Type', default=0)),
('grantee_name', models.CharField(verbose_name='Name', max_length=50)),
('create_project', models.BooleanField(default=True)),
('modify_project', models.BooleanField(default=False)),
('delete_project', models.BooleanField(default=False)),
('add_team', models.BooleanField(default=True)),
('manage_team', models.BooleanField(default=False)),
('manage_global_permission', models.BooleanField(default=False)),
('manage_project_permission', models.BooleanField(default=False)),
('create_issue', models.BooleanField(default=True)),
('modify_issue', models.BooleanField(default=False)),
('manage_issue', models.BooleanField(default=False)),
('delete_issue', models.BooleanField(default=False)),
('create_comment', models.BooleanField(default=True)),
('modify_comment', models.BooleanField(default=False)),
('delete_comment', models.BooleanField(default=False)),
('manage_tags', models.BooleanField(default=False)),
('delete_tags', models.BooleanField(default=False)),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Issue',
fields=[
('global_id', models.AutoField(primary_key=True, serialize=False)),
('id', models.IntegerField(editable=False)),
('title', models.CharField(max_length=128)),
('opened_at', models.DateTimeField(auto_now_add=True)),
('closed', models.BooleanField(default=False)),
('assignee', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
('subscribers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='event',
name='issue',
field=models.ForeignKey(to='issue.Issue'),
preserve_default=True,
),
migrations.CreateModel(
name='Label',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('name', models.CharField(max_length=32)),
('deleted', models.BooleanField(default=False)),
('color', colorful.fields.RGBColorField(verbose_name='Background color', default='#000000')),
('inverted', models.BooleanField(verbose_name='Inverse text color', default=True)),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='issue',
name='labels',
field=models.ManyToManyField(blank=True, to='issue.Label', null=True),
preserve_default=True,
),
migrations.CreateModel(
name='Milestone',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('name', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(regex='^[a-z0-9_.-]+$', message='Please enter only lowercase characters, number, dot, underscores or hyphens.')])),
('due_date', models.DateTimeField(blank=True, null=True)),
('closed', models.BooleanField(default=False)),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='issue',
name='milestone',
field=models.ForeignKey(blank=True, to='issue.Milestone', null=True),
preserve_default=True,
),
migrations.CreateModel(
name='Project',
fields=[
('name', models.CharField(primary_key=True, verbose_name='Short name (used in URL, definitive)', validators=[django.core.validators.RegexValidator(regex='^[a-z0-9_-]+$', message='Please enter only lowercase characters, number, underscores or hyphens.')], serialize=False, max_length=32)),
('display_name', models.CharField(unique=True, verbose_name='Project name', max_length=32)),
('description', models.TextField(verbose_name='Description', default='', blank=True)),
('public', models.BooleanField(verbose_name='Do unregistered users have read access to this project?', default=True)),
('subscribers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='milestone',
name='project',
field=models.ForeignKey(to='issue.Project'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='milestone',
unique_together=set([('project', 'name')]),
),
migrations.AddField(
model_name='label',
name='project',
field=models.ForeignKey(to='issue.Project'),
preserve_default=True,
),
migrations.AddField(
model_name='issue',
name='project',
field=models.ForeignKey(to='issue.Project'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='issue',
unique_together=set([('project', 'id')]),
),
migrations.CreateModel(
name='ProjectPermission',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('grantee_type', models.IntegerField(choices=[(0, 'User'), (1, 'Group'), (2, 'Team')], verbose_name='Type', default=0)),
('grantee_name', models.CharField(verbose_name='Name', max_length=50)),
('manage_project_permission', models.BooleanField(default=False)),
('create_issue', models.BooleanField(default=True)),
('modify_issue', models.BooleanField(default=False)),
('manage_issue', models.BooleanField(default=False)),
('delete_issue', models.BooleanField(default=False)),
('create_comment', models.BooleanField(default=True)),
('modify_comment', models.BooleanField(default=False)),
('delete_comment', models.BooleanField(default=False)),
('manage_tags', models.BooleanField(default=False)),
('delete_tags', models.BooleanField(default=False)),
('project', models.ForeignKey(editable=False, to='issue.Project')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Settings',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('site', models.OneToOneField(to='sites.Site')),
],
options={
'verbose_name_plural': 'Settings',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Team',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('name', models.CharField(unique=True, max_length=128)),
('groups', models.ManyToManyField(blank=True, to='auth.Group', null=True)),
('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
],
options={
},
bases=(models.Model,),
),
]

View file

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('issue', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='project',
name='access',
field=models.IntegerField(choices=[(1, 'Public'), (2, 'Connected users'), (3, 'Private')], default=1),
preserve_default=True,
),
migrations.RemoveField(
model_name='project',
name='public',
),
]

View file

@ -1,19 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Permission denied
</h1>
</div>
<div class="panel-body">
<em>Sorry, you are not allowed to access this page.</em>
</div>
</div>
{% endblock %}

View file

@ -1,19 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Page not found
</h1>
</div>
<div class="panel-body">
<em>This is not the web page you are looking for.</em>
</div>
</div>
{% endblock %}

View file

@ -1,19 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Server error
</h1>
</div>
<div class="panel-body">
<em>Sorry, an error occured. Please try again in few minutes.</em>
</div>
</div>
{% endblock %}

View file

@ -1,123 +0,0 @@
{% load staticfiles %}
{% load bootstrap3 %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
{% comment %}<link rel="icon" href="{% static 'favicon.ico' %}">{% endcomment %}
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<title>{% block title %}PonyTracker{% endblock %}</title>
{% bootstrap_css %}
<link href="{% static 'css/issue.css' %}" rel="stylesheet">
{% block css %}{% endblock %}
<script src="{% bootstrap_jquery_url %}"></script>
{% block js %}{% endblock %}
{% block media %}{% endblock %}
</head>
<body>
<div class="container">
<!-- Static navbar -->
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'list-project' %}">{% block page_title %}PonyTracker{% endblock %}</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% block navbar %}{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Project <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
{% if projects.exists %}
{% for p in projects %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'list-issue' p.name %}">{{ p }}</a></li>
{% endfor %}
{% else %}
<li role="presentation"><a role="munitem" tabindex="-1" href="#"><em>No project</em></a></li>
{% endif %}
{% block projectmenu %}
{% if perm.create_project %}
<li role="presentation" class="divider"></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'add-project' %}"><span class="glyphicon glyphicon-plus"></span> New project…</a></li>
{% endif %}
{% endblock %}
</ul>
</li>
{% if request.user.is_authenticated %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ request.user.username }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li role="presentation"><a role="menuitem" href="{% url 'profile' %}"><span class="glyphicon glyphicon-user"></span>&nbsp;My profile</a></li>
<li role="presentation"><a role="menuitem" href="{% url 'list-team' %}"><span class="glyphicon glyphicon-dashboard"></span>&nbsp;Teams</a></li>
{% if perm.manage_global_permission %}
<li role="presentation"><a role="menuitem" href="{% url 'list-global-permission' %}"><span class="glyphicon glyphicon-cog"></span>&nbsp;Manage permissions</a></li>
{% endif %}
<li role="presentation" class="divider"></li>
<li role="presentation"><a role="menuitem" href="{% url 'logout' %}"><span class="glyphicon glyphicon-log-out"></span>&nbsp;Logout</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'login' %}?next={{ request.get_full_path }}&prev={{ request.get_full_path }}"><span class="glyphicon glyphicon-log-in"></span>&nbsp;Login</a></li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</div>
<div class="row">
{% bootstrap_messages %}
{% block content %}{% endblock %}
<div class="modal" id="confirm-delete" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="panel panel-danger">
<div class="panel-heading" id="confirm-delete-title">
Delete
</div>
<div class="panel-body">
<form action="#" method="post" role="form" id="confirm-delete-form" class="text-center">
{% csrf_token %}
<p id="confirm-delete-message">Are you sure?</p>
<button type="submit" class="btn btn-danger">{% block confirm-ok %}Confirm{% endblock %}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div> <!-- /container -->
{% bootstrap_javascript %}
<script src="{% static 'js/confirm.js' %}"></script>
{% block js_end %}{% endblock %}
</body>
</html>

View file

@ -1,4 +0,0 @@
Issue closed.
--
You can see the issue on PonyTracker: {{ uri }}

View file

@ -1,4 +0,0 @@
{{ comment }}
--
Respond on PonyTracker: {{ uri }}

View file

@ -1,8 +0,0 @@
{% if description %}
{{ description }}
{% else %}
No description.
{% endif %}
--
Comment it on PonyTracker: {{ uri }}

View file

@ -1,4 +0,0 @@
Issue reopened.
--
You can see the issue on PonyTracker: {{ uri }}

View file

@ -1,29 +0,0 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1>
Global permissions
<div class="pull-right">
<a href="{% url 'list-global-permission' %}" class="btn btn-warning">Go back to list</a>
</div>
</div>
<div class="panel-body">
<div class="col-md-offset-3 col-md-6">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,89 +0,0 @@
{% extends 'base.html' %}
{% load django_markdown %}
{% load issue_filters %}
{% load issue_tags %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Global permissions
<div class="pull-right">
<a href="{% url 'add-global-permission' %}" class="btn btn-success">Add new permission</a>
</div>
</h1>
</div>
{% if permissions.count %}
<table class="table table-bordered">
<tr>
<th rowspan="3" class="text-center">Type</th>
<th rowspan="3" class="text-center">Name</th>
<th colspan="6" class="text-center">Global</th>
<th colspan="10" class="text-center">Project</th>
<th rowspan="3" class="col-md-2"></th>
</tr>
<tr>
<th colspan="3" class="text-center">Project</th>
<th colspan="2" class="text-center">Team</th>
<th colspan="2" class="text-center">Permissions</th>
<th colspan="4" class="text-center">Issues</th>
<th colspan="3" class="text-center">Comments</th>
<th colspan="2" class="text-center">Tags</th>
</tr>
<tr>
<th class="text-center">{% vertical 'Create?' %}</th>
<th class="text-center">{% vertical 'Modify?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
<th class="text-center">{% vertical 'Create?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Create?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Modify?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
<th class="text-center">{% vertical 'Create?' %}</th>
<th class="text-center">{% vertical 'Modify?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
</tr>
{% for perm in permissions %}
<tr>
<td>{{ perm.type }}</td>
<td>{{ perm.name }}</td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'create-project' %}">{{ perm.create_project|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'modify-project' %}">{{ perm.modify_project|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'delete-project' %}">{{ perm.delete_project|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'add-team' %}">{{ perm.add_team|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'manage-team' %}">{{ perm.manage_team|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'manage-global-permission' %}">{{ perm.manage_global_permission|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'manage-project-permission' %}">{{ perm.manage_project_permission|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'create-issue' %}">{{ perm.create_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'manage-issue' %}">{{ perm.manage_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'modify-issue' %}">{{ perm.modify_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'delete-issue' %}">{{ perm.delete_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'create-comment' %}">{{ perm.create_comment|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'modify-comment' %}">{{ perm.modify_comment|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'delete-comment' %}">{{ perm.delete_comment|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'manage-tags' %}">{{ perm.manage_tags|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-global-permission' perm.id 'delete-tags' %}">{{ perm.delete_tags|boolean }}</a></td>
<td class="text-center">
<a href="{% url 'edit-global-permission' perm.id %}" class="btn btn-primary btn-xs"><span class="glyphicon glyphicon-edit"></span> Edit</a>
<a href=#" data-item="permission" data-action="{% url 'delete-global-permission' perm.id %}" data-toggle="modal" data-target="#confirm-delete" class="btn btn-danger btn-xs"><span class="glyphicon glyphicon-remove"></span> Delete</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body">
There aren't any permissions defined yet.
</div>
{% endif %}
{% endblock %}

View file

@ -1,17 +0,0 @@
{% extends 'issue/project.html' %}
{% load bootstrap3 %}
{% block media %}{{ form.media }}{% endblock %}
{% block issuetab %} class="active"{% endblock %}
{% block content %}
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock %}

View file

@ -1,43 +0,0 @@
{% extends 'issue/project.html' %}
{% load bootstrap3 %}
{% block issuetab %} class="active"{% endblock %}
{% block media %}
{{ form.media }}
{% endblock media %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
{% if issue %}
Edit issue
{% else %}
New issue
{% endif %}
<div class="pull-right">
{% if issue %}
<a href="{% url 'show-issue' project.name issue.id %}" class="btn btn-warning">Go back to issue</a>
{% else %}
<a href="{% url 'list-issue' project.name %}" class="btn btn-warning">Go back to issues</a>
{% endif %}
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-2 col-md-8">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,82 +0,0 @@
{% extends 'issue/project.html' %}
{% load humanize %}
{% load issue_tags %}
{% block issuetab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1>
Issues
<div class="pull-right">
<form class="form-inline" role="form" method="GET" action="" id="filter-form">
<div class="form-group">
<div class="input-group">
<div class="dropdown">
{% if is_all_query %}
<a href="{% url 'list-issue' project.name %}" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-remove"> Reset filter</span></a>
{% endif %}
<a href="{% url 'list-issue' project.name %}?q=is:open{% if is_all_query %} {{ is_all_query }}{% endif %}" class="btn btn-default btn-sm{{ is_open }}">Open</a>
<a href="{% url 'list-issue' project.name %}?q=is:close{% if is_all_query %} {{ is_all_query }}{% endif %}" class="btn btn-default btn-sm{{ is_close }}">Close</a>
<a href="{% url 'list-issue' project.name %}?q={% if is_all_query %}{{ is_all_query }}{% else %}*{% endif %}" class="btn btn-default btn-sm{{ is_all }}">Both</a>
</div>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input class="form-control" name="q" type="text" placeholder="filter" value="{{ query }}"/>
<div class="input-group-addon">
<a href="#" onclick="$('#filter-form').submit();"><span class="glyphicon glyphicon-search"></span></a>
</div>
</div>
</div>
{% if perm.create_issue %}
<div class="form-group">
<div class="input-group">
<a href="{% url 'add-issue' project.name %}" class="btn btn-success">New issue</a>
</div>
</div>
{% endif %}
</form>
</div>
</h1>
<i>{{ issues.count }} issue{{ issues.count|pluralize }} displayed</i>
</div>
{% if issues.count %}
<table class="table">
{% for issue in issues %}
<tr>
<td>
{% if issue.closed %}
<span class="text-danger"><span class="glyphicon glyphicon-ok-circle"></span></span>
{% else %}
<span class="text-success"><span class="glyphicon glyphicon-hand-right"></span></span>
{% endif %}
<a href="{% url 'show-issue' project.name issue.id %}"><b>{{ issue }}</b></a>
{% for label in issue.labels.all %}
<a href="{% same_label label %}" class="label" style="{% label_style label %}">{{ label }}</a>
{% endfor %}
<br />
<small>#{{ issue.id }} opened by <a href="{% same_author issue.author %}"><b>{{ issue.author.username }}</b></a> {{ issue.opened_at|naturaltime }}</small>
{% if issue.milestone %}
&#160;&#160;&#160;<span class="glyphicon glyphicon-road"></span> <a href="{% same_milestone issue.milestone %}"><b>{{ issue.milestone }}</b></a>
{% endif %}
&#160;&#160;&#160;<span class="badge">{{ issue.comments.count }}</span>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body">
No issues match your desired criteria.
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,36 +0,0 @@
{% extends 'issue/project.html' %}
{% load bootstrap3 %}
{% block labeltab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
{% if label %}
Edit label
{% else %}
New label
{% endif %}
<div class="pull-right">
<a href="{% url 'list-label' project.name %}" class="btn btn-warning">Go back to labels</a>
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-4 col-md-4">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,50 +0,0 @@
{% extends 'issue/project.html' %}
{% load issue_tags %}
{% block labeltab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1>
Labels
{% if perm.manage_tags %}
<div class="pull-right">
<a href="{% url 'add-label' project.name %}" class="btn btn-success">New label</a>
</div>
{% endif %}
</h1>
</div>
{% if labels.count %}
<table class="table">
{% for label in labels %}
<tr>
<td>
<a href="{% same_label label %}" class="btn" style="{% label_style label %}"><span class="glyphicon glyphicon-tag" style="font-size: 100%;"></span> {{ label }}</a>
{% if perm.manage_tags or perm.delete_tags %}
<div class="pull-right">
{% if perm.manage_tags %}
<a href="{% url 'edit-label' project.name label.id %}" class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> Edit</a>
{% endif %}
{% if perm.delete_tags %}
<a href="#" data-item="label" data-action="{% url 'delete-label' project.name label.id %}" data-toggle="modal" data-target="#confirm-delete" class="btn btn-danger"><span class="glyphicon glyphicon-remove"></span> Delete</a>
{% endif %}
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body">
There aren't any labels for this repository quite yet.
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,40 +0,0 @@
{% extends 'issue/project.html' %}
{% load bootstrap3 %}
{% block milestonetab %} class="active"{% endblock %}
{% block media %}
{{ form.media }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
{% if milestone %}
Edit milestone
{% else %}
New milestone
{% endif %}
<div class="pull-right">
<a href="{% url 'list-milestone' project.name %}" class="btn btn-warning">Go back to milestones</a>
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-4 col-md-4">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,68 +0,0 @@
{% extends 'issue/project.html' %}
{% block milestonetab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1>
Milestone
<div class="pull-right">
<a href="{% url 'list-milestone' project.name %}?show=open" class="btn btn-default btn-sm{% if show == 'open' %} active{% endif %}">Open</a>
<a href="{% url 'list-milestone' project.name %}?show=close" class="btn btn-default btn-sm{% if show == 'close' %} active{% endif %}">Close</a>
<a href="{% url 'list-milestone' project.name %}?show=all" class="btn btn-default btn-sm{% if show == 'all' %} active{% endif %}">All</a>
{% if perm.manage_tags %}
<a href="{% url 'add-milestone' project.name %}" class="btn btn-success">New milestone</a>
{% endif %}
</div>
</h1>
<i>{{ milestones.count }} milestone{{ milestones.count|pluralize }} displayed</i>
</div>
{% if milestones.count %}
<table class="table">
{% for milestone in milestones %}
<tr>
<td>
<div class="pull-right">
{% if perm.manage_tags %}
{% if milestone.closed %}
<a href="{% url 'reopen-milestone' project.name milestone.name %}"><button class="btn btn-info"><span class="glyphicon glyphicon-repeat"></span> Reopen</button></a>
{% else %}
<a href="{% url 'close-milestone' project.name milestone.name %}"><button class="btn btn-info"><span class="glyphicon glyphicon-ok-circle"></span> Close</button></a>
{% endif %}
<a href="{% url 'edit-milestone' project.name milestone.name %}"><button class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> Edit</button></a>
{% endif %}
{% if perm.delete_tags %}
<a href="#" data-item="milestone" data-action="{% url 'delete-milestone' project.name milestone.name %}" data-toggle="modal" data-target="#confirm-delete" class="btn btn-danger"><span class="glyphicon glyphicon-remove"></span> Delete</a>
{% endif %}
</div>
<b style="font-size: 200%;">
<span class="glyphicon glyphicon-road"></span> <a href="{% url 'list-issue' project.name %}?q=is:open%20milestone:{{ milestone.name }}">{{ milestone }}</a>
</b>
&#160;
<small>
<span style="white-space: nowrap;"><span class="glyphicon glyphicon-calendar"></span> {% if milestone.due_date %}Due by {{ milestone.due_date }}{% else %}No due date{% endif %}</span>
</small>
<br /><br />
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{ milestone.progress }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ milestone.progress }}%;">
{{ milestone.progress }}% complete
</div>
</div>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body">
{{ no_milestone_message }}
There aren't any milestones matching your desired criteria.
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,38 +0,0 @@
{% extends 'base.html' %}
{% load django_markdown %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>Profile</h1>
</div>
<div class="panel-body">
<h3>Your groups</h3>
{% if groups.exists %}
<ul>
{% for group in groups %}
<li>{{ group }}</li>
{% endfor %}
</ul>
{% else %}
<em>You belong to no groups.</em>
{% endif %}
<hr>
<h3>Your teams</h3>
{% if teams.exists %}
<ul>
{% for team in teams %}
<li>{{ team }}</li>
{% endfor %}
</ul>
{% else %}
<em>You belong to no teams.</em>
{% endif %}
</div>
{% endblock %}

View file

@ -1,36 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ project }} - PonyTracker{% endblock %}
{% block page_title %}{{ project }}{% endblock %}
{% block projectmenu %}
{% if request.user.is_authenticated or perm.manage_project_permission or perm.modify_project or perm.delete_project or perm.create_project %}
<li role="presentation" class="divider"></li>
{% if request.user in project.subscribers.all %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'unsubscribe-project' project.name %}?next={{ request.get_full_path }}"><span class="glyphicon glyphicon-eye-close"></span> Unsubscribe</a></li>
{% else %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'subscribe-project' project.name %}?next={{ request.get_full_path }}"><span class="glyphicon glyphicon-eye-open"></span> Subscribe</a></li>
{% endif %}
{% if perm.manage_project_permission %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'list-project-permission' project.name %}"><span class="glyphicon glyphicon-cog"></span> Manage permissions</a></li>
{% endif %}
{% if perm.modify_project %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'edit-project' project.name %}"><span class="glyphicon glyphicon-wrench"></span> Modify this project</a></li>
{% endif %}
{% if perm.delete_project %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="#" data-item="project" data-action="{% url 'delete-project' project.name %}" data-toggle="modal" data-target="#confirm-delete"><span class="glyphicon glyphicon-trash"></span> Delete this project</a></li>
{% endif %}
{% if perm.create_project %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'add-project' %}"><span class="glyphicon glyphicon-plus"></span> New project…</a></li>
{% endif %}
{% endif %}
{% endblock %}
{% block navbar %}
<li{% block issuetab %}{% endblock %}><a href="{% url 'list-issue' project.name %}">Issues</a></li>
<li{% block labeltab %}{% endblock %}><a href="{% url 'list-label' project.name %}">Labels</a></li>
<li{% block milestonetab %}{% endblock %}><a href="{% url 'list-milestone' project.name %}">Milestones</a></li>
{% if perm.manage_project_permission %}
<li{% block permissiontab %}{% endblock %}><a href="{% url 'list-project-permission' project.name %}">Permissions</a></li>
{% endif %}
{% endblock %}

View file

@ -1,30 +0,0 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
New project
<div class="pull-right">
<a href="{% url 'list-project' %}" class="btn btn-warning">Go back to list</a>
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-2 col-md-8">
<form action="" method="post" role="form">
{%bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,30 +0,0 @@
{% extends 'issue/project.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Edit project
<div class="pull-right">
<a href="{% url 'list-issue' project.name %}" class="btn btn-warning">Go back to issues</a>
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-2 col-md-8">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,53 +0,0 @@
{% extends 'base.html' %}
{% load django_markdown %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Projects
{% if perm.create_project %}
<div class="pull-right">
<a href="{% url 'add-project' %}" class="btn btn-success">Add project</a>
</div>
{% endif %}
</h1>
</div>
<div class="panel-body">
{% if projects.exists %}
<div class="row">
<div class="col-md-3">
<strong>Name</strong>
</div>
<div class="col-md-8">
<strong>Description</strong>
</div>
</div>
{% for project in projects %}
<hr>
<div class="row">
<div class="col-md-3">
<a href="{% url 'list-issue' project.name %}">{{ project }}</a>
</div>
<div class="col-md-9">
{% if project.description %}
{{ project.description|linebreaksbr }}
{% else %}
<em>No description provided.</em>
{% endif %}
</div>
</div>
{% endfor %}
{% elif user.is_authenticated %}
<em>Sorry, you have no access to any project.</em>
{% else %}
<em>There is not any public project. You should probably <a href="{% url 'login' %}">login</a>.</em>
{% endif %}
</div>
{% endblock %}

View file

@ -1,31 +0,0 @@
{% extends 'issue/project.html' %}
{% load bootstrap3 %}
{% block permissiontab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1>
Permissions of '{{ project }}' project
<div class="pull-right">
<a href="{% url 'list-project-permission' project.name %}" class="btn btn-warning">Go back to list</a>
</div>
</div>
<div class="panel-body">
<div class="col-md-offset-3 col-md-6">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,72 +0,0 @@
{% extends 'issue/project.html' %}
{% load django_markdown %}
{% load issue_filters %}
{% load issue_tags %}
{% block permissiontab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Permissions of '{{ project }}' project
<div class="pull-right">
<a href="{% url 'add-project-permission' project.name %}" class="btn btn-success">Add new permission</a>
</div>
</h1>
</div>
{% if permissions.count %}
<table class="table table-bordered">
<tr>
<th rowspan="2">Type</th>
<th rowspan="2">Name</th>
<th colspan="4" class="text-center">Issues</th>
<th colspan="3" class="text-center">Comments</th>
<th colspan="2" class="text-center">Labels &<br />Milestones</th>
<th class="text-center">Permissions</th>
<th rowspan="2" class="col-md-2"></th>
</tr>
<tr>
<th class="text-center">{% vertical 'Create?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Modify?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
<th class="text-center">{% vertical 'Create?' %}</th>
<th class="text-center">{% vertical 'Modify?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
<th class="text-center">{% vertical 'Delete?' %}</th>
<th class="text-center">{% vertical 'Manage?' %}</th>
</tr>
{% for perm in permissions %}
<tr>
<td>{{ perm.type }}</td>
<td>{{ perm.name }}</td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'create-issue' %}">{{ perm.create_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'manage-issue' %}">{{ perm.manage_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'modify-issue' %}">{{ perm.modify_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'delete-issue' %}">{{ perm.delete_issue|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'create-comment' %}">{{ perm.create_comment|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'modify-comment' %}">{{ perm.modify_comment|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'delete-comment' %}">{{ perm.delete_comment|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'manage-tags' %}">{{ perm.manage_tags|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'delete-tags' %}">{{ perm.delete_tags|boolean }}</a></td>
<td class="text-center"><a href="{% url 'toggle-project-permission' project.name perm.id 'manage-project-permission' %}">{{ perm.manage_project_permission|boolean }}</a></td>
<td class="text-center">
<a href="{% url 'edit-project-permission' project.name perm.id %}" class="btn btn-primary btn-xs"><span class="glyphicon glyphicon-edit"></span> Edit</a>
<a href="#" data-item="permission" data-action="{% url 'delete-project-permission' project.name perm.id %}" data-toggle="modal" data-target="#confirm-delete" class="btn btn-danger btn-xs"><span class="glyphicon glyphicon-remove"></span> Delete</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body">
There aren't any permissions defined yet.
</div>
{% endif %}
{% endblock %}

View file

@ -1,49 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Teams
<div class="pull-right">
<a href="{% url 'list-team' %}" class="btn btn-success">Go back to teams</a>
</div>
</h1>
</div>
<div class="panel-body">
<h3>Users</h3>
{% if team.users.exists %}
<ul>
{% for user in team.users.all %}
<li>{{ user }}</li>
{% endfor %}
</ul>
{% else %}
<em>There aren't any users in this team.</em>
{% endif %}
<hr />
<h3>Groups</h3>
{% if team.groups.exists %}
<ul>
{% for group in team.groups.all %}
<li>{{ group }}</li>
{% endfor %}
</ul>
{% else %}
<em>There aren't any groups in this team.</em>
{% endif %}
<hr />
<div class="row text-center">
<a href="{% url 'list-team' %}" class="btn btn-default"><span class="glyphicon glyphicon-chevron-left"></span> Go back to list</a>
{% if perm.manage_team %}
<a href="{% url 'edit-team' team.pk %}" class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> Modify team</a>
<a href="#" data-item="team" data-action="{% url 'delete-team' team.pk %}" data-toggle="modal" data-target="#confirm-delete" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> Delete team</a>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -1,38 +0,0 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
{% if team %}
Edit team
{% else %}
Add team
{% endif %}
<div class="pull-right">
{% if team %}
<a href="{% url 'show-team' team.pk %}" class="btn btn-warning">Go back to team</a>
{% else %}
<a href="{% url 'list-team' %}" class="btn btn-warning">Go back to teams</a>
{% endif %}
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-3 col-md-6">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,43 +0,0 @@
{% extends 'base.html' %}
{% load humanize %}
{% load issue_filters %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Teams
{% if perm.add_team %}
<div class="pull-right">
<a href="{% url 'add-team' %}" class="btn btn-success">Add team</a>
</div>
{% endif %}
</h1>
</div>
{% if teams.exists %}
<table class="table table-hover">
<tr>
<th class="col-md-2">Name</th>
<th class="col-md-5">Users</th>
<th class="col-md-5">Groups</th>
</tr>
{% for team in teams %}
<tr>
<td><a href="{% url 'show-team' team.pk %}"><b>{{ team }}</b></a></td>
<td>{{ team.users|first_few:'user' }}</td>
<td>{{ team.groups|first_few:'group' }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body">
<em>There aren't any teams quite yet.</em>
</div>
{% endif %}
{% endblock %}

View file

@ -1,30 +0,0 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h1>
Login
<div class="pull-right">
<a href="{% if request.GET.prev %}{{ request.GET.prev }}{% else %}{% url 'list-project' %}{% endif %}"><button class="btn btn-warning">Cancel</button></a>
</div>
</h1>
</div>
<div class="panel-body">
<div class="col-md-offset-4 col-md-4">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,31 +0,0 @@
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.filter
def boolean(value):
if value:
glyph = 'ok'
else:
glyph = 'remove'
return mark_safe('<span class="glyphicon glyphicon-'
+ glyph + '" style="vertical-align: middle;"></span>')
@register.filter
def first_few(items, arg='item', max_items=5):
if items.exists():
if items.count() <= max_items:
return ', '.join(map(lambda x: x.__str__(), items.all()))
else:
r = ', '.join(map(lambda x: x.__str__(),
items.all()[0:max_items - 1]))
plural = 's' if items.count() > max_items else ''
r += ', ... (%s other%s)' \
% (items.count() - max_items + 1, plural)
return r
else:
return 'no ' + arg + 's'

View file

@ -1,621 +0,0 @@
from django.test import TestCase, Client
from django import VERSION
from issue.models import *
class TestPermissions(TestCase):
fixtures = ['test_perms']
def test_team_user_membership(self):
user = User.objects.get(username='user1')
team = Team.objects.get(name='team1')
self.assertEqual(user.teams.count(), 1)
self.assertEqual(user.teams.first(), team)
def test_team_group_membership(self):
user = User.objects.get(username='user2')
team = Team.objects.get(name='team2')
self.assertEqual(user.teams.count(), 1)
self.assertEqual(user.teams.first(), team)
def test_global_no_perms(self):
user = User.objects.get(username='user4')
self.assertFalse(user.has_perm('create_project'))
self.assertFalse(user.has_perm('modify_project'))
self.assertFalse(user.has_perm('delete_project'))
def test_global_user_perms(self):
user = User.objects.get(username='user3')
self.assertTrue(user.has_perm('create_project'))
self.assertFalse(user.has_perm('modify_project'))
self.assertFalse(user.has_perm('delete_project'))
def test_global_group_perms(self):
user = User.objects.get(username='user2')
self.assertFalse(user.has_perm('create_project'))
self.assertTrue(user.has_perm('modify_project'))
self.assertFalse(user.has_perm('delete_project'))
def test_global_team_perms(self):
user = User.objects.get(username='user1')
self.assertFalse(user.has_perm('create_project'))
self.assertFalse(user.has_perm('modify_project'))
self.assertTrue(user.has_perm('delete_project'))
def test_project_no_perms(self):
user = User.objects.get(username='user4')
project = Project.objects.get(name='project-1')
self.assertFalse(user.has_perm('create_issue', project))
self.assertFalse(user.has_perm('modify_issue', project))
self.assertFalse(user.has_perm('delete_issue', project))
def test_project_user_perms(self):
user = User.objects.get(username='user3')
project = Project.objects.get(name='project-1')
self.assertTrue(user.has_perm('create_issue', project))
self.assertFalse(user.has_perm('modify_issue', project))
self.assertFalse(user.has_perm('delete_issue', project))
def test_project_group_perms(self):
user = User.objects.get(username='user2')
project = Project.objects.get(name='project-1')
self.assertFalse(user.has_perm('create_issue', project))
self.assertTrue(user.has_perm('modify_issue', project))
self.assertFalse(user.has_perm('delete_issue', project))
def test_project_team_perms(self):
user = User.objects.get(username='user1')
project = Project.objects.get(name='project-1')
self.assertFalse(user.has_perm('create_issue', project))
self.assertFalse(user.has_perm('modify_issue', project))
self.assertTrue(user.has_perm('delete_issue', project))
class TestNoProject(TestCase):
fixtures = ['test_no_project']
def test_ano(self):
url = reverse('list-project')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'There is not any public project')
def test_without_add_permission(self):
self.client.login(username='user1', password='user1')
url = reverse('list-project')
expected_url = reverse('add-project')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response,
'Sorry, you have no access to any project')
def test_with_add_permission(self):
self.client.login(username='user2', password='user2')
url = reverse('list-project')
expected_url = reverse('add-project')
response = self.client.get(url)
if VERSION >= (1, 7):
self.assertRedirects(response, expected_url,
# don't fetch redirect to don't loose message
fetch_redirect_response=False)
response = self.client.get(expected_url)
self.assertContains(response, 'Start by creating a project')
else:
self.assertRedirects(response, expected_url)
class TestGlobalViews(TestCase):
fixtures = ['test_perms']
def test_404(self):
response = self.client.get('/deliberately/broken')
self.assertEqual(response.status_code, 404)
def test_profile_ano(self):
url = reverse('profile')
expected_url = reverse('login') + '?next=' + url
response = self.client.get(url)
self.assertRedirects(response, expected_url)
def test_profile_user1(self):
self.client.login(username='user1', password='user1')
url = reverse('profile')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, 'group1')
self.assertContains(response, 'team1')
self.assertNotContains(response, 'team2')
def test_profile_user2(self):
self.client.login(username='user2', password='user2')
url = reverse('profile')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'group1')
self.assertNotContains(response, 'team1')
self.assertContains(response, 'team2')
class TestProjectsViews(TestCase):
fixtures = ['test_perms']
def test_home_as_anonymous(self):
expected = Project.objects.filter(name='project-1')
url = reverse('list-project')
self.assertEqual(url, '/')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertQuerysetEqual(expected, response.context['projects'],
lambda x: x)
def test_home_as_user1(self):
expected = Project.objects \
.filter(Q(name='project-1') | Q(name='project-3'))
self.client.login(username='user1', password='user1')
url = reverse('list-project')
self.assertEqual(url, '/')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertQuerysetEqual(expected, response.context['projects'],
lambda x: x, ordered=False)
self.assertNotContains(response, 'New project')
def test_home_as_user2(self):
expected = Project.objects.all()
self.client.login(username='user2', password='user2')
url = reverse('list-project')
self.assertEqual(url, '/')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertQuerysetEqual(expected, response.context['projects'],
lambda x: x, ordered=False)
self.assertNotContains(response, 'New project')
def test_home_as_user3(self):
expected = Project.objects \
.filter(Q(name='project-1') | Q(name='project-3'))
self.client.login(username='user3', password='user3')
url = reverse('list-project')
self.assertEqual(url, '/')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertQuerysetEqual(expected, response.context['projects'],
lambda x: x, ordered=False)
self.assertContains(response, 'New project')
def test_home_as_admin(self):
expected = Project.objects.all()
self.client.login(username='admin', password='admin')
url = reverse('list-project')
self.assertEqual(url, '/')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertQuerysetEqual(expected, response.context['projects'],
lambda x: x, ordered=False)
self.assertContains(response, 'New project')
def test_add_project_granted(self):
self.client.login(username='user3', password='user3')
expected_url = reverse('list-project-permission', args=['project-4'])
url = reverse('add-project')
response = self.client.post(url, {
'name': 'project-4',
'display_name': 'Project 4',
'description': 'This is the fourth project.',
'access': Project.ACCESS_PUBLIC,
})
self.assertRedirects(response, expected_url)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (1, 2, 3, 4)], lambda x: x.name, ordered=False)
def test_add_project_forbidden(self):
self.client.login(username='user1', password='user1')
url = reverse('add-project')
response = self.client.post(url, {
'name': 'project-4',
'display_name': 'Project 4',
'description': 'This is the foorth project.',
})
self.assertEqual(response.status_code, 403)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (1, 2, 3)], lambda x: x.name, ordered=False)
def test_add_project_forbidden_ano(self):
expected_url = reverse('login') + '?next=' + reverse('add-project')
url = reverse('add-project')
response = self.client.post(url, {
'name': 'project-4',
'display_name': 'Project 4',
'description': 'This is the foorth project.',
})
self.assertRedirects(response, expected_url)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (1, 2, 3)], lambda x: x.name, ordered=False)
def test_delete_project_get(self):
self.client.login(username='user1', password='user1')
expected_url = reverse('list-project')
url = reverse('delete-project', args=['project-1'])
response = self.client.get(url)
self.assertEqual(response.status_code, 405)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (1, 2, 3)], lambda x: x.name, ordered=False)
def test_delete_project_granted(self):
self.client.login(username='user1', password='user1')
expected_url = reverse('list-project')
url = reverse('delete-project', args=['project-1'])
response = self.client.post(url)
self.assertRedirects(response, expected_url)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (2, 3)], lambda x: x.name, ordered=False)
def test_delete_project_forbidden(self):
self.client.login(username='user2', password='user2')
url = reverse('delete-project', args=['project-1'])
response = self.client.post(url)
self.assertEqual(response.status_code, 403)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (1, 2, 3)], lambda x: x.name, ordered=False)
def test_delete_project_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('delete-project', args=['project-1'])
url = reverse('delete-project', args=['project-1'])
response = self.client.post(url)
self.assertRedirects(response, expected_url)
self.assertQuerysetEqual(Project.objects.all(), ['project-%s' % x
for x in (1, 2, 3)], lambda x: x.name, ordered=False)
class TestIssuesViews(TestCase):
fixtures = ['test_perms']
def test_list_issue_granted(self):
self.client.login(username='user2', password='user2')
url = reverse('list-issue', args=['project-2'])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_list_issue_forbidden(self):
self.client.login(username='user1', password='user1')
expected_url = reverse('login') + '?next=' \
+ reverse('list-issue', args=['project-2'])
url = reverse('list-issue', args=['project-2'])
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
def test_list_issue_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('list-issue', args=['project-2'])
url = reverse('list-issue', args=['project-2'])
response = self.client.get(url)
self.assertRedirects(response, expected_url)
def test_show_issue_granted(self):
self.client.login(username='user2', password='user2')
url = reverse('show-issue', args=['project-2', 1])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_show_issue_granted_ano(self):
url = reverse('show-issue', args=['project-1', 1])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_show_issue_forbidden(self):
self.client.login(username='user1', password='user1')
expected_url = reverse('login') + '?next=' \
+ reverse('show-issue', args=['project-2', 1])
url = reverse('show-issue', args=['project-2', 1])
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
def test_add_issue_granted(self):
self.client.login(username='user5', password='user5')
expected_url = reverse('show-issue', args=['project-2', 3])
url = reverse('add-issue', args=['project-2'])
response = self.client.post(url, {
'title': 'Issue 3',
'description': 'This is the third issue.',
})
self.assertRedirects(response, expected_url)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2', 'Issue 3'],
lambda x: x.title, ordered=False)
def test_add_issue_forbidden(self):
self.client.login(username='user6', password='user6')
url = reverse('add-issue', args=['project-2'])
response = self.client.post(url, {
'title': 'Issue 3',
'description': 'This is the third issue.',
})
self.assertEqual(response.status_code, 403)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'],
lambda x: x.title, ordered=False)
def test_add_issue_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('add-issue', args=['project-2'])
url = reverse('add-issue', args=['project-2'])
response = self.client.post(url, {
'title': 'Issue 3',
'description': 'This is the third issue.',
})
self.assertRedirects(response, expected_url)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'],
lambda x: x.title, ordered=False)
def test_delete_issue_get(self):
self.client.login(username='user8', password='user8')
expected_url = reverse('list-issue', args=['project-2'])
url = reverse('delete-issue', args=['project-2', 2])
response = self.client.get(url)
self.assertEqual(response.status_code, 405)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'],
lambda x: x.title, ordered=False)
def test_delete_issue_granted(self):
self.client.login(username='user8', password='user8')
expected_url = reverse('list-issue', args=['project-2'])
url = reverse('delete-issue', args=['project-2', 2])
response = self.client.post(url)
self.assertRedirects(response, expected_url)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1'],
lambda x: x.title, ordered=False)
def test_delete_issue_forbidden(self):
self.client.login(username='user5', password='user5')
url = reverse('delete-issue', args=['project-2', 2])
response = self.client.post(url)
self.assertEqual(response.status_code, 403)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'],
lambda x: x.title, ordered=False)
def test_delete_issue_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('delete-issue', args=['project-2', 2])
url = reverse('delete-issue', args=['project-2', 2])
response = self.client.post(url)
self.assertRedirects(response, expected_url)
issues = Issue.objects.filter(project__name='project-2')
self.assertQuerysetEqual(issues, ['Issue 1', 'Issue 2'],
lambda x: x.title, ordered=False)
def test_close_issue_granted(self):
self.client.login(username='user6', password='user6')
expected_url = reverse('list-issue', args=['project-2'])
url = reverse('close-issue', args=['project-2', 1])
response = self.client.get(url)
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
self.assertEqual(issue.closed, True)
def test_close_issue_forbidden(self):
self.client.login(username='user5', password='user5')
url = reverse('close-issue', args=['project-2', 1])
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
issue = Issue.objects.get(project__name='project-2', id=1)
self.assertEqual(issue.closed, False)
def test_close_issue_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('close-issue', args=['project-2', 1])
url = reverse('close-issue', args=['project-2', 1])
response = self.client.get(url)
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
self.assertEqual(issue.closed, False)
def test_reopen_issue_granted(self):
self.client.login(username='user6', password='user6')
expected_url = reverse('show-issue', args=['project-2', 2])
url = reverse('reopen-issue', args=['project-2', 2])
response = self.client.get(url)
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=2)
self.assertEqual(issue.closed, False)
def test_reopen_issue_forbidden(self):
self.client.login(username='user5', password='user5')
url = reverse('reopen-issue', args=['project-2', 2])
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
issue = Issue.objects.get(project__name='project-2', id=2)
self.assertEqual(issue.closed, True)
def test_reopen_issue_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('reopen-issue', args=['project-2', 2])
url = reverse('reopen-issue', args=['project-2', 2])
response = self.client.get(url)
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=2)
self.assertEqual(issue.closed, True)
def test_modify_issue_granted(self):
self.client.login(username='user7', password='user7')
expected_url = reverse('show-issue', args=['project-2', 1])
url = reverse('edit-issue', args=['project-2', 1])
response = self.client.post(url, {
'title': '*THE* Issue 1',
'description': 'This is *THE* first issue.',
})
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
self.assertEqual(issue.title, "*THE* Issue 1")
self.assertEqual(issue.description, "This is *THE* first issue.")
def test_modify_issue_forbidden(self):
self.client.login(username='user5', password='user5')
url = reverse('edit-issue', args=['project-2', 1])
response = self.client.post(url, {
'title': '*THE* Issue 1',
'description': 'This is *THE* first issue.',
})
self.assertEqual(response.status_code, 403)
issue = Issue.objects.get(project__name='project-2', id=1)
self.assertEqual(issue.title, "Issue 1")
self.assertEqual(issue.description, "This is the first issue.")
def test_modify_issue_forbidden_ano(self):
expected_url = reverse('login') + '?next=' \
+ reverse('edit-issue', args=['project-2', 1])
url = reverse('edit-issue', args=['project-2', 1])
response = self.client.post(url, {
'title': '*THE* Issue 1',
'description': 'This is *THE* first issue.',
})
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
self.assertEqual(issue.title, "Issue 1")
self.assertEqual(issue.description, "This is the first issue.")
class TestCommentsViews(TestCase):
fixtures = ['test_perms']
def test_comment_issue_granted(self):
self.client.login(username='user9', password='user9')
msg = 'I have a lot to say.'
expected_url = reverse('show-issue', args=['project-2', 1])
url = reverse('comment-issue', args=['project-2', 1])
response = self.client.post(url, {
'comment': msg,
})
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
self.assertEqual(event.additionnal_section, msg)
def test_comment_issue_forbidden(self):
self.client.login(username='user10', password='user10')
msg = 'I have a lot to say.'
url = reverse('comment-issue', args=['project-2', 1])
response = self.client.post(url, {
'comment': msg,
})
self.assertEqual(response.status_code, 403)
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
self.assertEqual(event.additionnal_section, 'Missing things')
def test_edit_comment_granted(self):
self.client.login(username='user10', password='user10')
msg = 'Missing a lot of things'
expected_url = reverse('show-issue', args=['project-2', 1])
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
url = reverse('edit-comment', args=['project-2', issue.id, event.id])
response = self.client.post(url, {
'comment': msg,
})
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
self.assertEqual(event.additionnal_section, msg)
def test_edit_comment_forbidden(self):
self.client.login(username='user9', password='user9')
msg = 'Missing a lot of things'
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
url = reverse('edit-comment', args=['project-2', issue.id, event.id])
response = self.client.post(url, {
'comment': msg,
})
self.assertEqual(response.status_code, 403)
def test_delete_comment_granted_get(self):
self.client.login(username='user11', password='user11')
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
url = reverse('delete-comment', args=['project-2', issue.id, event.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 405)
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
self.assertEqual(event.additionnal_section, 'Missing things')
def test_delete_comment_granted(self):
self.client.login(username='user11', password='user11')
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
url = reverse('delete-comment', args=['project-2', issue.id, event.id])
expected_url = reverse('show-issue', args=['project-2', 1])
response = self.client.post(url)
self.assertRedirects(response, expected_url)
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
self.assertEqual(event.additionnal_section, 'Done')
def test_delete_comment_forbidden(self):
self.client.login(username='user9', password='user9')
msg = 'Missing a lot of things'
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
url = reverse('delete-comment', args=['project-2', issue.id, event.id])
response = self.client.post(url, {
'comment': msg,
})
self.assertEqual(response.status_code, 403)
issue = Issue.objects.get(project__name='project-2', id=1)
event = Event.objects.filter(issue=issue, code=Event.COMMENT).last()
self.assertEqual(event.additionnal_section, 'Missing things')
class TestLabelsViews(TestCase):
fixtures = ['test_perms']
def test_list(self):
self.client.login(username='user2', password='user2')
url = reverse('list-label', args=['project-2'])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'documentation')
class TestMilestonesViews(TestCase):
fixtures = ['test_perms']
def test_list(self):
self.client.login(username='user2', password='user2')
url = reverse('list-milestone', args=['project-2'])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'v1.0')
class TestPermissionsViews(TestCase):
fixtures = ['test_perms']
def test_global_list(self):
self.client.login(username='user15', password='user15')
url = reverse('list-global-permission')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Global permissions')
def test_project_list(self):
self.client.login(username='user14', password='user14')
url = reverse('list-project-permission', args=['project-2'])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Permissions of 'Project 2' project")

View file

@ -1,58 +0,0 @@
from django.conf.urls import url
urlpatterns = [
url(r'^$', 'issue.views.project_list', name='list-project'),
url(r'^add$', 'issue.views.project_add', name='add-project'),
url(r'^(?P<project>[a-z0-9_-]+)/edit$', 'issue.views.project_edit', name='edit-project'),
url(r'^(?P<project>[a-z0-9_-]+)/delete$', 'issue.views.project_delete', name='delete-project'),
url(r'^(?P<project>[a-z0-9_-]+)/subscribe$', 'issue.views.project_subscribe', name='subscribe-project'),
url(r'^(?P<project>[a-z0-9_-]+)/unsubscribe$', 'issue.views.project_unsubscribe', name='unsubscribe-project'),
url(r'^(?P<project>[a-z0-9_-]+)/issues$', 'issue.views.issue_list', name='list-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/add$', 'issue.views.issue_edit', name='add-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)$', 'issue.views.issue', name='show-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/edit$', 'issue.views.issue_edit', name='edit-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/close$', 'issue.views.issue_close', name='close-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/reopen$', 'issue.views.issue_reopen', name='reopen-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/comment$', 'issue.views.issue_edit_comment', name='comment-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/comments/(?P<comment>[0-9]+)/edit$', 'issue.views.issue_edit_comment', name='edit-comment'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/comments/(?P<comment>[0-9]+)/delete$', 'issue.views.issue_delete_comment', name='delete-comment'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/delete$', 'issue.views.issue_delete', name='delete-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/subscribe$', 'issue.views.issue_subscribe', name='subscribe-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/unsubscribe$', 'issue.views.issue_unsubscribe', name='unsubscribe-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/add-label/(?P<label>[0-9]+)$', 'issue.views.issue_add_label', name='add-label-to-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/remove-label/(?P<label>[0-9]+)$', 'issue.views.issue_remove_label', name='remove-label-from-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/add-milestone/(?P<milestone>[a-z0-9_.-]+)$', 'issue.views.issue_add_milestone', name='add-milestone-to-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/issues/(?P<issue>[0-9]+)/remove-milestone/(?P<milestone>[a-z0-9_.-]+)$', 'issue.views.issue_remove_milestone', name='remove-milestone-from-issue'),
url(r'^(?P<project>[a-z0-9_-]+)/labels$', 'issue.views.label_list', name='list-label'),
url(r'^(?P<project>[a-z0-9_-]+)/labels/add$', 'issue.views.label_edit', name='add-label'),
url(r'^(?P<project>[a-z0-9_-]+)/labels/(?P<id>[0-9]+)/edit$', 'issue.views.label_edit', name='edit-label'),
url(r'^(?P<project>[a-z0-9_-]+)/labels/(?P<id>[0-9]+)/delete$', 'issue.views.label_delete', name='delete-label'),
url(r'^(?P<project>[a-z0-9_-]+)/milestones$', 'issue.views.milestone_list', name='list-milestone'),
url(r'^(?P<project>[a-z0-9_-]+)/milestones/add$', 'issue.views.milestone_edit', name='add-milestone'),
url(r'^(?P<project>[a-z0-9_-]+)/milestones/(?P<name>[a-z0-9_.-]+)/edit$', 'issue.views.milestone_edit', name='edit-milestone'),
url(r'^(?P<project>[a-z0-9_-]+)/milestones/(?P<name>[a-z0-9_.-]+)/close$', 'issue.views.milestone_close', name='close-milestone'),
url(r'^(?P<project>[a-z0-9_-]+)/milestones/(?P<name>[a-z0-9_.-]+)/reopen$', 'issue.views.milestone_reopen', name='reopen-milestone'),
url(r'^(?P<project>[a-z0-9_-]+)/milestones/(?P<name>[a-z0-9_.-]+)/delete$', 'issue.views.milestone_delete', name='delete-milestone'),
url(r'^(?P<project>[a-z0-9_-]+)/permissions$', 'issue.views.project_permission_list', name='list-project-permission'),
url(r'^(?P<project>[a-z0-9_-]+)/permissions/add$', 'issue.views.project_permission_edit', name='add-project-permission'),
url(r'^(?P<project>[a-z0-9_-]+)/permissions/(?P<id>[0-9]+)/edit$', 'issue.views.project_permission_edit', name='edit-project-permission'),
url(r'^(?P<project>[a-z0-9_-]+)/permissions/(?P<id>[0-9]+)/toggle/(?P<perm>[a-z-]+)$', 'issue.views.project_permission_toggle', name='toggle-project-permission'),
url(r'^(?P<project>[a-z0-9_-]+)/permissions/(?P<id>[0-9]+)/delete$', 'issue.views.project_permission_delete', name='delete-project-permission'),
url(r'^permissions$', 'issue.views.global_permission_list', name='list-global-permission'),
url(r'^permissions/add$', 'issue.views.global_permission_edit', name='add-global-permission'),
url(r'^permissions/(?P<id>[0-9]+)/edit$', 'issue.views.global_permission_edit', name='edit-global-permission'),
url(r'^permissions/(?P<id>[0-9]+)/toggle/(?P<perm>[a-z-]+)$', 'issue.views.global_permission_toggle', name='toggle-global-permission'),
url(r'^permissions/(?P<id>[0-9]+)/delete$', 'issue.views.global_permission_delete', name='delete-global-permission'),
url(r'^teams$', 'issue.views.team_list', name='list-team'),
url(r'^teams/add$', 'issue.views.team_edit', name='add-team'),
url(r'^teams/(?P<team>[0-9]+)$', 'issue.views.team', name='show-team'),
url(r'^teams/(?P<team>[0-9]+)/edit$', 'issue.views.team_edit', name='edit-team'),
url(r'^teams/(?P<team>[0-9]+)/users/(?P<user>[0-9]+)/add$', 'issue.views.team_add_user', name='add-user-to-team'),
url(r'^teams/(?P<team>[0-9]+)/users/(?P<user>[0-9]+)/delete$', 'issue.views.team_remove_user', name='remove-user-from-team'),
url(r'^teams/(?P<team>[0-9]+)/groups/(?P<group>[0-9]+)/add$', 'issue.views.team_add_group', name='add-group-to-team'),
url(r'^teams/(?P<team>[0-9]+)/groups/(?P<group>[0-9]+)/delete$', 'issue.views.team_remove_group', name='remove-group-from-team'),
url(r'^teams/(?P<team>[0-9]+)/delete$', 'issue.views.team_delete', name='delete-team'),
url(r'^login$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login'),
url(r'^profile$', 'issue.views.profile', name='profile'),
url(r'^logout$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout'),
]

0
permissions/__init__.py Normal file
View file

7
permissions/admin.py Normal file
View file

@ -0,0 +1,7 @@
from django.contrib import admin
from permissions.models import *
admin.site.register(GlobalPermission)
admin.site.register(ProjectPermission)

View file

@ -1,6 +1,7 @@
from django.contrib.auth.backends import ModelBackend
from issue.models import *
from tracker.models import Project
from permissions.models import GlobalPermission
def user_has_perm(user, perm, perms):
@ -10,7 +11,7 @@ def user_has_perm(user, perm, perms):
return True
class ProjectBackend(ModelBackend):
class Backend(ModelBackend):
def has_perm(self, user, perm, obj=None):

View file

@ -1,12 +1,4 @@
from issue.models import Project
def projects(request):
if hasattr(request, 'projects'):
return {'projects': request.projects}
else:
return {}
from tracker.models import Project
class PermWrapper:

View file

@ -1,9 +1,9 @@
from functools import wraps
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from issue.models import Project
from functools import wraps
from tracker.models import Project
def project_perm_required(perm):

68
permissions/forms.py Normal file
View file

@ -0,0 +1,68 @@
from django import forms
from django.core.exceptions import ValidationError
from django.forms.widgets import HiddenInput
from permissions.models import *
from permissions.models import PermissionModel
from accounts.models import *
__all__ = [ 'GlobalPermissionForm', 'ProjectPermissionForm' ]
class PermissionForm(forms.ModelForm):
class Meta:
abstract = True
# The grantee_id is a hidden field.
# The user complete the grantee_name field, and the validation
# method will set the correct value is the grantee_id.
grantee_name = forms.CharField(max_length=32)
def clean(self):
data = super(PermissionForm, self).clean()
if 'grantee_name' not in data or 'grantee_type' not in data:
# a field required error will be printed so we dont care
return data
name = data['grantee_name']
if int(data['grantee_type']) == PermissionModel.GRANTEE_USER:
grantees = User.objects.filter(username=name)
if not grantees.exists():
raise ValidationError("User '%s' does not exists." % name)
elif int(data['grantee_type']) == PermissionModel.GRANTEE_GROUP:
grantees = Group.objects.filter(name=name)
if not grantees.exists():
raise ValidationError("Group '%s' does not exists." % name)
elif int(data['grantee_type']) == PermissionModel.GRANTEE_TEAM:
grantees = Team.objects.filter(name=name)
if not grantees.exists():
raise ValidationError("Team '%s' does not exists." % name)
data['grantee_id'] = grantees.first().id
return data
class GlobalPermissionForm(PermissionForm):
class Meta:
model = GlobalPermission
fields = [ 'grantee_type', 'grantee_id' ]
widgets = {
'grantee_id': HiddenInput,
}
class ProjectPermissionForm(PermissionForm):
class Meta:
model = ProjectPermission
fields = [ 'grantee_type', 'grantee_id' ]
widgets = {
'grantee_id': HiddenInput,
}

View file

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('tracker', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='GlobalPermission',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('grantee_type', models.IntegerField(default=0, verbose_name='Grantee type', choices=[(0, 'User'), (1, 'Group'), (2, 'Team')])),
('grantee_id', models.IntegerField(blank=True)),
('create_project', models.BooleanField(default=True)),
('modify_project', models.BooleanField(default=False)),
('delete_project', models.BooleanField(default=False)),
('manage_settings', models.BooleanField(default=False)),
('manage_user', models.BooleanField(default=False)),
('manage_group', models.BooleanField(default=False)),
('manage_team', models.BooleanField(default=False)),
('manage_global_permission', models.BooleanField(default=False)),
('create_issue', models.BooleanField(default=True)),
('modify_issue', models.BooleanField(default=False)),
('manage_issue', models.BooleanField(default=False)),
('delete_issue', models.BooleanField(default=False)),
('create_comment', models.BooleanField(default=True)),
('modify_comment', models.BooleanField(default=False)),
('delete_comment', models.BooleanField(default=False)),
('manage_tags', models.BooleanField(default=False)),
('delete_tags', models.BooleanField(default=False)),
('manage_project_permission', models.BooleanField(default=False)),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='globalpermission',
unique_together=set([('grantee_type', 'grantee_id')]),
),
migrations.CreateModel(
name='ProjectPermission',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('grantee_type', models.IntegerField(default=0, verbose_name='Grantee type', choices=[(0, 'User'), (1, 'Group'), (2, 'Team')])),
('grantee_id', models.IntegerField(blank=True)),
('manage_project_permission', models.BooleanField(default=False)),
('create_issue', models.BooleanField(default=True)),
('modify_issue', models.BooleanField(default=False)),
('manage_issue', models.BooleanField(default=False)),
('delete_issue', models.BooleanField(default=False)),
('create_comment', models.BooleanField(default=True)),
('modify_comment', models.BooleanField(default=False)),
('delete_comment', models.BooleanField(default=False)),
('manage_tags', models.BooleanField(default=False)),
('delete_tags', models.BooleanField(default=False)),
('project', models.ForeignKey(editable=False, to='tracker.Project')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='projectpermission',
unique_together=set([('grantee_type', 'grantee_id')]),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('permissions', '0001_initial'),
]
operations = [
migrations.AlterUniqueTogether(
name='projectpermission',
unique_together=set([('project', 'grantee_type', 'grantee_id')]),
),
]

View file

144
permissions/models.py Normal file
View file

@ -0,0 +1,144 @@
from django.db import models
from django.db.models import Q
from django.utils.encoding import python_2_unicode_compatible
from tracker.models import Project
from accounts.models import *
__all__ = [ 'GlobalPermission', 'ProjectPermission' ]
@python_2_unicode_compatible
class PermissionModel(models.Model):
GRANTEE_USER = 0
GRANTEE_GROUP = 1
GRANTEE_TEAM = 2
GRANTEE_TYPE = (
(GRANTEE_USER, 'User'),
(GRANTEE_GROUP, 'Group'),
(GRANTEE_TEAM, 'Team'),
)
grantee_type = models.IntegerField(choices=GRANTEE_TYPE,
default=GRANTEE_USER, verbose_name="Grantee type")
grantee_id = models.IntegerField(blank=True)
def get_grantee(self):
if self.grantee_type == self.GRANTEE_USER:
Model = User
elif self.grantee_type == self.GRANTEE_GROUP:
Model = Group
else:
Model = Team
return Model.objects.get(id=self.grantee_id)
def set_grantee(self, grantee):
if isinstance(grantee, User):
self.grantee_type = self.GRANTEE_USER
elif isinstance(grantee, Group):
self.grantee_type = self.GRANTEE_GROUP
elif isinstance(grantee, Team):
self.grantee_type = self.GRANTEE_TEAM
else:
raise ValueError('Grantee object must be '
'an User, a Group or a Team instance.')
self.grantee_id = grantee.id
grantee = property(get_grantee, set_grantee)
class Meta:
abstract = True
def granted_to(self, user):
if not user.is_authenticated():
return False
if self.grantee_type == self.GRANTEE_USER:
return user.id == self.grantee_id
elif self.grantee_type == self.GRANTEE_GROUP:
return user.groups.filter(id=self.grantee_id).exists()
elif self.grantee_type == self.GRANTEE_TEAM:
return Team.objects.filter(id=self.grantee_id) \
.filter(Q(groups__in=user.groups.all()) | Q(users=user)) \
.exists()
else:
return False
@property
def type(self):
return self.get_grantee_type_display()
# return dict(self.GRANTEE_TYPE)[self.grantee_type]
@property
def name(self):
return self.grantee.__str__()
def __str__(self):
return self.grantee.__str__() + "'s permissions"
@python_2_unicode_compatible
class GlobalPermission(PermissionModel):
class Meta:
unique_together = [ 'grantee_type', 'grantee_id' ]
# Global permissions
create_project = models.BooleanField(default=True)
modify_project = models.BooleanField(default=False)
delete_project = models.BooleanField(default=False)
manage_settings = models.BooleanField(default=False)
manage_user = models.BooleanField(default=False)
manage_group = models.BooleanField(default=False)
manage_team = models.BooleanField(default=False)
manage_global_permission = models.BooleanField(default=False)
# Project permissions, given on ALL projects
create_issue = models.BooleanField(default=True)
modify_issue = models.BooleanField(default=False)
manage_issue = models.BooleanField(default=False)
delete_issue = models.BooleanField(default=False)
create_comment = models.BooleanField(default=True)
modify_comment = models.BooleanField(default=False)
delete_comment = models.BooleanField(default=False)
manage_tags = models.BooleanField(default=False)
delete_tags = models.BooleanField(default=False)
manage_project_permission = models.BooleanField(default=False)
def __str__(self):
return self.grantee.__str__() + "'s global permissions"
@python_2_unicode_compatible
class ProjectPermission(PermissionModel):
class Meta:
unique_together = [ 'project', 'grantee_type', 'grantee_id' ]
project = models.ForeignKey(Project, editable=False,
related_name='permissions')
manage_project_permission = models.BooleanField(default=False)
create_issue = models.BooleanField(default=True)
modify_issue = models.BooleanField(default=False)
manage_issue = models.BooleanField(default=False)
delete_issue = models.BooleanField(default=False)
create_comment = models.BooleanField(default=True)
modify_comment = models.BooleanField(default=False)
delete_comment = models.BooleanField(default=False)
manage_tags = models.BooleanField(default=False)
delete_tags = models.BooleanField(default=False)
def __str__(self):
return self.grantee.__str__() + "'s permissions on " \
+ self.project.name + " project"

View file

@ -0,0 +1,13 @@
$('.perm-toggle').click(function() {
var a = $(this);
a.html('<span class="glyphicon glyphicon-time"></span>');
var href = a.data('href');
$.ajax(href)
.done(function(data, textStatus) {
if (data == '1') {
a.html('<span class="glyphicon glyphicon-ok"></span>');
} else {
a.html('<span class="glyphicon glyphicon-remove"></span>');
}
});
});

View file

View file

@ -0,0 +1,15 @@
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.filter
def boolean(value):
if value:
glyph = 'ok'
else:
glyph = 'remove'
return mark_safe('<span class="glyphicon glyphicon-'
+ glyph + '" style="vertical-align: middle;"></span>')

View file

@ -0,0 +1,24 @@
from django import template
from django.core.urlresolvers import reverse
register = template.Library()
@register.inclusion_tag('permissions/tags/perm_form.html', takes_context=True)
def add_perm_form(context):
return {
'form': context['add_form'],
'type': 'add',
'title': 'Add permission',
'action': reverse('add-global-permission'),
}
@register.inclusion_tag('permissions/tags/perm_form.html', takes_context=True)
def edit_perm_form(context):
return {
'form': context['edit_form'],
'type': 'edit',
'title': 'Edit permission',
}

3
permissions/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

17
permissions/urls.py Normal file
View file

@ -0,0 +1,17 @@
from django.conf.urls import url, include
urlpatterns = [
# Global permissions
url(r'^admin/permissions/$', 'permissions.views.global_perm_list', name='list-global-permission'),
url(r'^admin/permissions/add/$', 'permissions.views.global_perm_edit', name='add-global-permission'),
url(r'^admin/permissions/(?P<id>[0-9]+)/edit/$', 'permissions.views.global_perm_edit', name='edit-global-permission'),
url(r'^admin/permissions/(?P<id>[0-9]+)/delete/$', 'permissions.views.global_perm_delete', name='delete-global-permission'),
url(r'^admin/permissions/(?P<id>[0-9]+)/toggle/(?P<perm>[a-z-]+)/$', 'permissions.views.global_perm_toggle', name='toggle-global-permission'),
# Project permissions
url(r'^(?P<project>[-\w]+)/permissions/$', 'permissions.views.project_perm_list', name='list-project-permission'),
url(r'^(?P<project>[-\w]+)/permissions/add/$', 'permissions.views.project_perm_edit', name='add-project-permission'),
url(r'^(?P<project>[-\w]+)/permissions/(?P<id>[0-9]+)/edit/$', 'permissions.views.project_perm_edit', name='edit-project-permission'),
url(r'^(?P<project>[-\w]+)/permissions/(?P<id>[0-9]+)/delete/$', 'permissions.views.project_perm_delete', name='delete-project-permission'),
url(r'^(?P<project>[-\w]+)/permissions/(?P<id>[0-9]+)/toggle/(?P<perm>[a-z-]+)/$', 'permissions.views.project_perm_toggle', name='toggle-project-permission'),
]

131
permissions/views.py Normal file
View file

@ -0,0 +1,131 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_http_methods
from django.contrib import messages
from django.http import Http404, HttpResponse
from permissions.models import *
from permissions.forms import *
from permissions.decorators import project_perm_required
@project_perm_required('manage_global_permission')
def global_perm_list(request):
return render(request, 'permissions/global_perm_list.html', {
'permissions': GlobalPermission.objects.all(),
})
@project_perm_required('manage_global_permission')
def global_perm_edit(request, id=None):
if id:
perm = get_object_or_404(GlobalPermission, id=id)
else:
perm = None
form = GlobalPermissionForm(request.POST or None, instance=perm)
if request.method == 'POST' and form.is_valid():
form.save()
if id:
messages.success(request, 'Permission updated successfully.')
else:
messages.success(request, 'Permission added successfully.')
return redirect('list-global-permission')
name = request.POST.get('grantee_name')
if not name:
if perm:
name = perm.grantee.__str__()
else:
name = ''
return render(request, 'permissions/global_perm_edit.html', {
'perm': perm,
'form': form,
'name': name,
})
@require_http_methods(["POST"])
@project_perm_required('manage_global_permission')
def global_perm_delete(request, id):
perm = get_object_or_404(GlobalPermission, id=id)
perm.delete()
messages.success(request, 'Permission deleted successfully.')
return redirect('list-global-permission')
@project_perm_required('manage_global_permission')
def global_perm_toggle(request, id, perm):
permission = get_object_or_404(GlobalPermission, id=id)
# to be sure to dont modify other attribut with the following trick
if '-' not in perm:
raise Http404
perm = perm.replace('-', '_')
if hasattr(permission, perm):
state = not getattr(permission, perm)
setattr(permission, perm, state)
permission.save()
return HttpResponse('1' if state else '0')
else:
raise Http404
@project_perm_required('manage_project_permission')
def project_perm_list(request, project):
return render(request, 'permissions/project_perm_list.html', {
'project': project,
'permissions': ProjectPermission.objects.filter(project=project).all(),
})
@project_perm_required('manage_project_permission')
def project_perm_edit(request, project, id=None):
if id:
perm = get_object_or_404(ProjectPermission, project=project, id=id)
else:
perm = None
form = ProjectPermissionForm(request.POST or None, instance=perm)
if request.method == 'POST' and form.is_valid():
if id:
form.save()
messages.success(request, 'Permission updated successfully.')
else:
perm = form.save(commit=False)
perm.project = project
perm.save()
messages.success(request, 'Permission added successfully.')
return redirect('list-project-permission', project.name)
name = request.POST.get('grantee_name')
if not name:
if perm:
name = perm.grantee.__str__()
else:
name = ''
return render(request, 'permissions/project_perm_edit.html', {
'project': project,
'perm': perm,
'form': form,
'name': name,
})
@project_perm_required('manage_project_permission')
def project_perm_delete(request, project, id):
perm = get_object_or_404(ProjectPermission, project=project, id=id)
perm.delete()
messages.success(request, 'Permission deleted successfully.')
return redirect('list-project-permission', project.name)
@project_perm_required('manage_project_permission')
def project_perm_toggle(request, project, id, perm):
permission = get_object_or_404(ProjectPermission, project=project, id=id)
# to be sure to dont modify other attribut with the following trick
if '-' not in perm:
raise Http404
perm = perm.replace('-', '_')
if hasattr(permission, perm):
state = not getattr(permission, perm)
setattr(permission, perm, state)
permission.save()
return HttpResponse('1' if state else '0')
else:
raise Http404

View file

@ -24,6 +24,14 @@ DEBUG = True
TEMPLATE_DEBUG = True
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates'),
)
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
ALLOWED_HOSTS = []
@ -44,7 +52,9 @@ INSTALLED_APPS = (
'bootstrap3_datetime',
'bootstrap3',
'colorful',
'issue',
'accounts',
'permissions',
'tracker',
)
from django import VERSION
@ -61,7 +71,7 @@ if VERSION >= (1, 7):
MIDDLEWARE_CLASSES += (
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'issue.middleware.ProjectMiddleware',
'tracker.middleware.ProjectMiddleware',
)
ROOT_URLCONF = 'ponytracker.urls'
@ -106,13 +116,13 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.messages.context_processors.messages',
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.request',
'issue.context_processors.projects',
'issue.context_processors.perm',
'tracker.context_processors.projects',
'permissions.context_processors.perm',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'issue.backends.ProjectBackend',
'permissions.backends.Backend',
)
SITE_ID = 1
@ -150,4 +160,6 @@ CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
AUTH_USER_MODEL = 'issue.User'
AUTH_USER_MODEL = 'accounts.User'
RESERVED_PROJECT_NAME = [ 'login', 'logout', 'profile', 'admin', 'django-admin' ]

View file

@ -2,7 +2,17 @@ from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
# django admin
url(r'^django-admin/', include(admin.site.urls)),
# markdown preview
url(r'^markdown/', include('django_markdown.urls')),
url(r'^', include('issue.urls')),
# tracker
url(r'^', include('tracker.urls')),
# permissions managment
url(r'^', include('permissions.urls')),
# account managment
url(r'^', include('accounts.urls')),
# login / logout
url(r'^login$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login'),
url(r'^logout$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout'),
)

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before After
Before After

16375
static/js/jquery-ui.js vendored Normal file

File diff suppressed because it is too large Load diff

13
static/js/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
static/js/ponytracker.js Normal file
View file

@ -0,0 +1,4 @@
/* activate tooltips */
$(function () {
$("[rel='tooltip']").tooltip();
})

13
templates/403.html Normal file
View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<div class="page-header">
<h1>
Permission denied
</h1>
</div>
<p>Sorry, you are not allowed to access this page.</p>
{% endblock %}

13
templates/404.html Normal file
View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<div class="page-header">
<h1>
Page not found
</h1>
</div>
<p>This is not the web page you are looking for.</p>
{% endblock %}

13
templates/500.html Normal file
View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<div class="page-header">
<h1>
Server error
</h1>
</div>
<p>Sorry, an error occured. Please try again in few minutes.</p>
{% endblock %}

View file

@ -0,0 +1,76 @@
{% extends 'base_settings.html' %}
{% load staticfiles %}
{% load humanize %}
{% load bootstrap3 %}
{% load accounts_tags %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/jquery-ui.css' %}">
{% endblock %}
{% block grouptab %} class="active"{% endblock %}
{% block moretabs %}
<a href="{% url 'add-group' %}" class="pull-right btn btn-success">Add group</a>
{% endblock %}
{% block tabcontent %}
<div class="page-header">
<h1>
Group {{ group }}
&#160;
<small><span id="users-counter">{{ group.users.count }}</span> users</small>
<div class="pull-right">
<a href="{% url 'edit-group' group.id %}" class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> edit</a>
<a href="javascript:void(0);" data-item="group" data-action="{% url 'delete-group' group.id %}" data-toggle="modal" data-target="#confirm-delete" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> delete</a>
</div>
</h1>
</div>
<ul class="nav nav-tabs" role="tablist">
<li class="active"><a href="#" role="tab">Members</a></li>
<form class="form-inline pull-right" method="post" action="{% url 'add-user-to-group' group.id %}" role="form" id="add-user-form">
{% csrf_token %}
<div class="form-group">
<div class="input-group ui-widget">
<input type="text" class="form-control" name="user" placeholder="add users to group" value="">
<div class="input-group-addon">
<a href="javascript:void(0);" onclick="$('#add-user-form').submit();"><span class="glyphicon glyphicon-plus"></span></a>
</div>
</div>
</div>
</form>
</ul>
<br />
<div class="tab-pane">
<ul class="list-group">
<li class="list-group-item{% if group.users.exists %} hidden{% endif %}" id="users-empty">
<em>No users belong to this group.</em>
</li>
{% for user in group.users.all %}
<li class="list-group-item">
{{ user.username }}
{% if user.fullname %}
<span class="text-muted">— {{ user.fullname }}</span>
{% endif %}
<a href="javascript:void(0);" data-href="{% url 'remove-user-from-group' group.id user.id %}" data-type="users" class="pull-right btn btn-danger btn-xs" role="remove">remove</a>
</li>
{% endfor %}
</ul>
</div>
{% delete_modal %}
<script src="{% static 'js/jquery-ui.min.js' %}"></script>
<script type="text/javascript">
$('input[name="user"]').autocomplete({
source: "{% url 'add-user-to-group' group.id %}"
});
</script>
<script src="{% static 'js/accounts.js' %}"></script>
{% endblock %}

View file

@ -0,0 +1,42 @@
{% extends 'base_settings.html' %}
{% load staticfiles %}
{% load humanize %}
{% load bootstrap3 %}
{% block grouptab %} class="active"{% endblock %}
{% block tabcontent %}
<div class="row">
<div class="col-md-offset-3 col-md-6">
<div class="page-header">
<h1>
{% if group %}
Edit group
{% else %}
New group
{% endif %}
</h1>
</div>
<form action="#" method="post" class="form">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
{% if group %}
<a href="{% url 'show-group' group.id %}" class="btn btn-default">Cancel</a>
{% else %}
<a href="{% url 'list-group' %}" class="btn btn-default">Cancel</a>
{% endif %}
{% endbuttons %}
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,31 @@
{% extends 'base_settings.html' %}
{% load staticfiles %}
{% load humanize %}
{% load bootstrap3 %}
{% block grouptab %} class="active"{% endblock %}
{% block moretabs %}
<a href="{% url 'add-group' %}" class="pull-right btn btn-success">Add group</a>
{% endblock %}
{% block tabcontent %}
<ul class="list-group">
{% if not groups.exists %}
<li class="list-group-item">
<em>There are no groups quit yet.</em>
</li>
{% endif %}
{% for group in groups %}
<a class="list-group-item" href="{% url 'show-group' group.id %}">
<strong>
{{ group }}
</strong>
— {{ group.users.count }} user{{ group.users.count|pluralize }}
</a>
{% endfor %}
</ul>
{% endblock %}

View file

@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% load django_markdown %}
{% block content %}
<div class="page-header">
<h1>Profile</h1>
</div>
<ul class="list-group">
<li class="list-group-item disabled">
<h3>Your groups</h3>
</li>
{% for group in request.user.groups.all %}
<li class="list-group-item">
{{ group }}
<div class="pull-right">
<a href="javascript:alert('Not yet implemented.');" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-log-out"></span> leave
</a>
</div>
</li>
{% endfor %}
{% if not request.user.groups.exists %}
<li class="list-group-item">
<em>You belong to no groups.</em>
</li>
{% endif %}
</ul>
<ul class="list-group">
<li class="list-group-item disabled">
<h3>Your teams</h3>
</li>
{% for team in request.user.teams.all %}
<li class="list-group-item">
{{ team }}
<div class="pull-right">
<a href="javascript:alert('Not yet implemented.');" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-log-out"></span> leave
</a>
</div>
</li>
{% endfor %}
{% if not request.user.teams.exists %}
<li class="list-group-item">
<em>You belong to no teams.</em>
</li>
{% endif %}
</ul>
<ul class="list-group">
<li class="list-group-item disabled">
<h3>Projects that you are watching</h3>
</li>
{% for project in request.user.subscribed_projects.all %}
<li class="list-group-item">
{{ project }}
<div class="pull-right">
<a href="{% url 'unsubscribe-project' project.name %}?next={{ request.path }}" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-eye-close"></span> unwatch
</a>
</div>
</li>
{% endfor %}
{% if not request.user.subscribed_projects.exists %}
<li class="list-group-item">
<em>You are not watching any projects.</em>
</li>
{% endif %}
</ul>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% load staticfiles %}
<div class="modal" id="confirm-delete" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="panel panel-danger">
<div class="panel-heading" id="confirm-delete-title">
Delete
</div>
<div class="panel-body">
<form action="#" method="post" role="form" id="confirm-delete-form" class="text-center">
{% csrf_token %}
<p id="confirm-delete-message">Are you sure?</p>
<button type="submit" class="btn btn-danger">{% block confirm-ok %}Confirm{% endblock %}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</form>
</div>
</div>
</div>
</div>
<script src="{% static 'js/delete_modal.js' %}"></script>

Some files were not shown because too many files have changed in this diff Show more