Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into feature-reviews
Conflicts: symposion_project/settings.py symposion_project/templates/dashboard.html symposion_project/urls.py
- Loading branch information
Showing
17 changed files
with
628 additions
and
49 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from django.contrib import admin | ||
|
||
import reversion | ||
|
||
from symposion.teams.models import Team, Membership | ||
|
||
admin.site.register(Team, | ||
prepopulated_fields={"slug": ("name",)}, | ||
) | ||
|
||
|
||
class MembershipAdmin(reversion.VersionAdmin): | ||
list_display = ["team", "user", "state"] | ||
list_filter = ["team"] | ||
search_fields = ["user__username"] | ||
|
||
admin.site.register(Membership, MembershipAdmin) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from django.db.models import Q | ||
|
||
from .models import Team | ||
|
||
|
||
class TeamPermissionsBackend(object): | ||
|
||
def authenticate(self, username=None, password=None): | ||
return None | ||
|
||
def get_team_permissions(self, user_obj, obj=None): | ||
""" | ||
Returns a set of permission strings that this user has through his/her | ||
team memberships. | ||
""" | ||
if user_obj.is_anonymous() or obj is not None: | ||
return set() | ||
if not hasattr(user_obj, "_team_perm_cache"): | ||
memberships = Team.objects.filter( | ||
Q(memberships__user=user_obj), | ||
Q(memberships__state="manager") | Q(memberships__state="member"), | ||
) | ||
perms = memberships.values_list( | ||
"permissions__content_type__app_label", | ||
"permissions__codename" | ||
).order_by() | ||
user_obj._team_perm_cache = set(["%s.%s" % (ct, name) for ct, name in perms]) | ||
return user_obj._team_perm_cache | ||
|
||
def has_perm(self, user_obj, perm, obj=None): | ||
if not user_obj.is_active: | ||
return False | ||
return perm in self.get_team_permissions(user_obj, obj) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from django import forms | ||
|
||
from django.contrib.auth.models import User | ||
|
||
from symposion.teams.models import Membership | ||
|
||
|
||
class TeamInvitationForm(forms.Form): | ||
|
||
email = forms.EmailField(help_text="email address must be that of a user on the site") | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.team = kwargs.pop("team") | ||
super(TeamInvitationForm, self).__init__(*args, **kwargs) | ||
|
||
def clean(self): | ||
cleaned_data = super(TeamInvitationForm, self).clean() | ||
email = cleaned_data.get("email") | ||
|
||
if email is None: | ||
raise forms.ValidationError("valid email address required") | ||
|
||
try: | ||
user = User.objects.get(email=email) | ||
except User.DoesNotExist: | ||
# eventually we can invite them but for now assume they are | ||
# already on the site | ||
raise forms.ValidationError("no known user with email address %s" % email) | ||
|
||
state = self.team.get_state_for_user(user) | ||
|
||
if state in ["member", "manager"]: | ||
raise forms.ValidationError("user already in team") | ||
|
||
if state in ["invited"]: | ||
raise forms.ValidationError("user already invited to team") | ||
|
||
self.user = user | ||
self.state = state | ||
|
||
return cleaned_data | ||
|
||
def invite(self): | ||
if self.state is None: | ||
Membership.objects.create(team=self.team, user=self.user, state="invited") | ||
elif self.state == "applied": | ||
# if they applied we shortcut invitation process | ||
membership = Membership.objects.get(team=self.team, user=self.user) | ||
membership.state = "member" | ||
membership.save() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import datetime | ||
|
||
from django.db import models | ||
|
||
import reversion | ||
|
||
from django.contrib.auth.models import Permission, User | ||
|
||
|
||
TEAM_ACCESS_CHOICES = [ | ||
("open", "open"), | ||
("application", "by application"), | ||
("invitation", "by invitation") | ||
] | ||
|
||
|
||
class Team(models.Model): | ||
|
||
slug = models.SlugField(unique=True) | ||
name = models.CharField(max_length=100) | ||
description = models.TextField(blank=True) | ||
access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES) | ||
|
||
# member permissions | ||
permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams") | ||
|
||
# manager permissions | ||
manager_permissions = models.ManyToManyField(Permission, blank=True, related_name="manager_teams") | ||
|
||
created = models.DateTimeField(default=datetime.datetime.now, editable=False) | ||
|
||
def __unicode__(self): | ||
return self.name | ||
|
||
def get_state_for_user(self, user): | ||
try: | ||
return self.memberships.get(user=user).state | ||
except Membership.DoesNotExist: | ||
return None | ||
|
||
def applicants(self): | ||
return self.memberships.filter(state="applied") | ||
|
||
def invitees(self): | ||
return self.memberships.filter(state="invited") | ||
|
||
def members(self): | ||
return self.memberships.filter(state="member") | ||
|
||
def managers(self): | ||
return self.memberships.filter(state="manager") | ||
|
||
|
||
MEMBERSHIP_STATE_CHOICES = [ | ||
("applied", "applied"), | ||
("invited", "invited"), | ||
("declined", "declined"), | ||
("rejected", "rejected"), | ||
("member", "member"), | ||
("manager", "manager"), | ||
] | ||
|
||
|
||
class Membership(models.Model): | ||
|
||
user = models.ForeignKey(User, related_name="memberships") | ||
team = models.ForeignKey(Team, related_name="memberships") | ||
state = models.CharField(max_length=20, choices=MEMBERSHIP_STATE_CHOICES) | ||
message = models.TextField(blank=True) | ||
|
||
|
||
reversion.register(Membership) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from django import template | ||
|
||
from symposion.teams.models import Team | ||
|
||
register = template.Library() | ||
|
||
|
||
class AvailableTeamsNode(template.Node): | ||
|
||
@classmethod | ||
def handle_token(cls, parser, token): | ||
bits = token.split_contents() | ||
if len(bits) == 3 and bits[1] == "as": | ||
return cls(bits[2]) | ||
else: | ||
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) | ||
|
||
def __init__(self, context_var): | ||
self.context_var = context_var | ||
|
||
def render(self, context): | ||
request = context["request"] | ||
teams = [] | ||
for team in Team.objects.all(): | ||
state = team.get_state_for_user(request.user) | ||
if team.access == "open" and state is None: | ||
teams.append(team) | ||
elif request.user.is_staff and state is None: | ||
teams.append(team) | ||
context[self.context_var] = teams | ||
return u"" | ||
|
||
|
||
@register.tag | ||
def available_teams(parser, token): | ||
""" | ||
{% available_teams as available_teams %} | ||
""" | ||
return AvailableTeamsNode.handle_token(parser, token) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from django.conf.urls.defaults import * | ||
|
||
|
||
urlpatterns = patterns("symposion.teams.views", | ||
# team specific | ||
url(r"^(?P<slug>[\w\-]+)/$", "team_detail", name="team_detail"), | ||
url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"), | ||
url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"), | ||
url(r"^(?P<slug>[\w\-]+)/apply/$", "team_apply", name="team_apply"), | ||
|
||
# membership specific | ||
url(r"^promote/(?P<pk>\d+)/$", "team_promote", name="team_promote"), | ||
url(r"^demote/(?P<pk>\d+)/$", "team_demote", name="team_demote"), | ||
url(r"^accept/(?P<pk>\d+)/$", "team_accept", name="team_accept"), | ||
url(r"^reject/(?P<pk>\d+)/$", "team_reject", name="team_reject"), | ||
) |
Oops, something went wrong.