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

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)

29
permissions/backends.py Normal file
View file

@ -0,0 +1,29 @@
from django.contrib.auth.backends import ModelBackend
from tracker.models import Project
from permissions.models import GlobalPermission
def user_has_perm(user, perm, perms):
for p in perms:
# this perm allow that action and the user is concerned by this perm
if hasattr(p, perm) and getattr(p, perm) and p.granted_to(user):
return True
class Backend(ModelBackend):
def has_perm(self, user, perm, obj=None):
if isinstance(obj, Project):
# get permissions concerning this project
perms = obj.permissions.all()
if user_has_perm(user, perm, perms):
return True
# get global permissions
perms = GlobalPermission.objects.all()
if user_has_perm(user, perm, perms):
return True
return False

View file

@ -0,0 +1,26 @@
from tracker.models import Project
class PermWrapper:
def __init__(self, user, project):
self.user = user
self.project = project
def __getitem__(self, perm):
return self.user.has_perm(perm, self.project)
def __iter__(self):
raise TypeError("PermWrapper is not iterable.")
def __contains__(self, perm):
return self[perm]
def perm(request):
if hasattr(request, 'project'):
project = request.project
else:
project = None
wrapper = PermWrapper(request.user, project)
return {'perm': wrapper}

26
permissions/decorators.py Normal file
View file

@ -0,0 +1,26 @@
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from functools import wraps
from tracker.models import Project
def project_perm_required(perm):
def decorator(view):
@wraps(view)
def wrapper(request, *args, **kwargs):
if 'project' in kwargs.keys():
project = kwargs['project']
else:
project = None
if request.user.has_perm(perm, project):
return view(request, *args, **kwargs)
elif request.user.is_authenticated():
raise PermissionDenied()
else:
return login_required(view)(request, *args, **kwargs)
return wrapper
return decorator

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