commit
						687e3ace51
					
				
					 13 changed files with 562 additions and 0 deletions
				
			
		
							
								
								
									
										0
									
								
								symposion/teams/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								symposion/teams/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								symposion/teams/admin.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								symposion/teams/admin.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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)
 | 
				
			||||||
							
								
								
									
										33
									
								
								symposion/teams/backends.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								symposion/teams/backends.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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)
 | 
				
			||||||
							
								
								
									
										50
									
								
								symposion/teams/forms.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								symposion/teams/forms.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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()
 | 
				
			||||||
							
								
								
									
										66
									
								
								symposion/teams/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								symposion/teams/models.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					    permissions = models.ManyToManyField(Permission, blank=True)
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
							
								
								
									
										0
									
								
								symposion/teams/templatetags/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								symposion/teams/templatetags/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										39
									
								
								symposion/teams/templatetags/teams_tags.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								symposion/teams/templatetags/teams_tags.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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)
 | 
				
			||||||
							
								
								
									
										16
									
								
								symposion/teams/urls.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								symposion/teams/urls.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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"),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										182
									
								
								symposion/teams/views.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								symposion/teams/views.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,182 @@
 | 
				
			||||||
 | 
					from django.http import Http404
 | 
				
			||||||
 | 
					from django.shortcuts import render, redirect, get_object_or_404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib.auth.decorators import login_required
 | 
				
			||||||
 | 
					from django.contrib import messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from symposion.teams.forms import TeamInvitationForm
 | 
				
			||||||
 | 
					from symposion.teams.models import Team, Membership
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## perm checks
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# @@@ these can be moved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def can_join(team, user):
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(user)
 | 
				
			||||||
 | 
					    if team.access == "open" and state is None:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    elif state == "invited":
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    elif user.is_staff and state is None:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def can_leave(team, user):
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(user)
 | 
				
			||||||
 | 
					    if state == "member":  # managers can't leave at the moment
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def can_apply(team, user):
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(user)
 | 
				
			||||||
 | 
					    if team.access == "application" and state is None:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def can_invite(team, user):
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(user)
 | 
				
			||||||
 | 
					    if team.access == "invitation":
 | 
				
			||||||
 | 
					        if state == "manager" or user.is_staff:
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_detail(request, slug):
 | 
				
			||||||
 | 
					    team = get_object_or_404(Team, slug=slug)
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					    if team.access == "invitation" and state is None and not request.user.is_staff:
 | 
				
			||||||
 | 
					        raise Http404()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if can_invite(team, request.user):
 | 
				
			||||||
 | 
					        if request.method == "POST":
 | 
				
			||||||
 | 
					            form = TeamInvitationForm(request.POST, team=team)
 | 
				
			||||||
 | 
					            if form.is_valid():
 | 
				
			||||||
 | 
					                form.invite()
 | 
				
			||||||
 | 
					                messages.success(request, "Invitation created.")
 | 
				
			||||||
 | 
					                return redirect("team_detail", slug=slug)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            form = TeamInvitationForm(team=team)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        form = None
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return render(request, "teams/team_detail.html", {
 | 
				
			||||||
 | 
					        "team": team,
 | 
				
			||||||
 | 
					        "state": state,
 | 
				
			||||||
 | 
					        "invite_form": form,
 | 
				
			||||||
 | 
					        "can_join": can_join(team, request.user),
 | 
				
			||||||
 | 
					        "can_leave": can_leave(team, request.user),
 | 
				
			||||||
 | 
					        "can_apply": can_apply(team, request.user),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_join(request, slug):
 | 
				
			||||||
 | 
					    team = get_object_or_404(Team, slug=slug)
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					    if team.access == "invitation" and state is None and not request.user.is_staff:
 | 
				
			||||||
 | 
					        raise Http404()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if can_join(team, request.user) and request.method == "POST":
 | 
				
			||||||
 | 
					        membership, created = Membership.objects.get_or_create(team=team, user=request.user)
 | 
				
			||||||
 | 
					        membership.state = "member"
 | 
				
			||||||
 | 
					        membership.save()
 | 
				
			||||||
 | 
					        messages.success(request, "Joined team.")
 | 
				
			||||||
 | 
					        return redirect("team_detail", slug=slug)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return redirect("team_detail", slug=slug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_leave(request, slug):
 | 
				
			||||||
 | 
					    team = get_object_or_404(Team, slug=slug)
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					    if team.access == "invitation" and state is None and not request.user.is_staff:
 | 
				
			||||||
 | 
					        raise Http404()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if can_leave(team, request.user) and request.method == "POST":
 | 
				
			||||||
 | 
					        membership = Membership.objects.get(team=team, user=request.user)
 | 
				
			||||||
 | 
					        membership.delete()
 | 
				
			||||||
 | 
					        messages.success(request, "Left team.")
 | 
				
			||||||
 | 
					        return redirect("dashboard")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return redirect("team_detail", slug=slug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_apply(request, slug):
 | 
				
			||||||
 | 
					    team = get_object_or_404(Team, slug=slug)
 | 
				
			||||||
 | 
					    state = team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					    if team.access == "invitation" and state is None and not request.user.is_staff:
 | 
				
			||||||
 | 
					        raise Http404()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if can_apply(team, request.user) and request.method == "POST":
 | 
				
			||||||
 | 
					        membership, created = Membership.objects.get_or_create(team=team, user=request.user)
 | 
				
			||||||
 | 
					        membership.state = "applied"
 | 
				
			||||||
 | 
					        membership.save()
 | 
				
			||||||
 | 
					        messages.success(request, "Applied to join team.")
 | 
				
			||||||
 | 
					        return redirect("team_detail", slug=slug)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return redirect("team_detail", slug=slug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_promote(request, pk):
 | 
				
			||||||
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					        membership = get_object_or_404(Membership, pk=pk)
 | 
				
			||||||
 | 
					        state = membership.team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					        if request.user.is_staff or state == "manager":
 | 
				
			||||||
 | 
					            if membership.state == "member":
 | 
				
			||||||
 | 
					                membership.state = "manager"
 | 
				
			||||||
 | 
					                membership.save()
 | 
				
			||||||
 | 
					                messages.success(request, "Promoted to manager.")
 | 
				
			||||||
 | 
					    return redirect("team_detail", slug=membership.team.slug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_demote(request, pk):
 | 
				
			||||||
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					        membership = get_object_or_404(Membership, pk=pk)
 | 
				
			||||||
 | 
					        state = membership.team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					        if request.user.is_staff or state == "manager":
 | 
				
			||||||
 | 
					            if membership.state == "manager":
 | 
				
			||||||
 | 
					                membership.state = "member"
 | 
				
			||||||
 | 
					                membership.save()
 | 
				
			||||||
 | 
					                messages.success(request, "Demoted from manager.")
 | 
				
			||||||
 | 
					    return redirect("team_detail", slug=membership.team.slug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_accept(request, pk):
 | 
				
			||||||
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					        membership = get_object_or_404(Membership, pk=pk)
 | 
				
			||||||
 | 
					        state = membership.team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					        if request.user.is_staff or state == "manager":
 | 
				
			||||||
 | 
					            if membership.state == "applied":
 | 
				
			||||||
 | 
					                membership.state = "member"
 | 
				
			||||||
 | 
					                membership.save()
 | 
				
			||||||
 | 
					                messages.success(request, "Accepted application.")
 | 
				
			||||||
 | 
					    return redirect("team_detail", slug=membership.team.slug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def team_reject(request, pk):
 | 
				
			||||||
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					        membership = get_object_or_404(Membership, pk=pk)
 | 
				
			||||||
 | 
					        state = membership.team.get_state_for_user(request.user)
 | 
				
			||||||
 | 
					        if request.user.is_staff or state == "manager":
 | 
				
			||||||
 | 
					            if membership.state == "applied":
 | 
				
			||||||
 | 
					                membership.state = "rejected"
 | 
				
			||||||
 | 
					                membership.save()
 | 
				
			||||||
 | 
					                messages.success(request, "Rejected application.")
 | 
				
			||||||
 | 
					    return redirect("team_detail", slug=membership.team.slug)
 | 
				
			||||||
| 
						 | 
					@ -167,6 +167,7 @@ INSTALLED_APPS = [
 | 
				
			||||||
    "symposion.boxes",
 | 
					    "symposion.boxes",
 | 
				
			||||||
    "symposion.proposals",
 | 
					    "symposion.proposals",
 | 
				
			||||||
    "symposion.speakers",
 | 
					    "symposion.speakers",
 | 
				
			||||||
 | 
					    "symposion.teams",
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # project
 | 
					    # project
 | 
				
			||||||
    "symposion_project.proposals",
 | 
					    "symposion_project.proposals",
 | 
				
			||||||
| 
						 | 
					@ -193,6 +194,10 @@ ACCOUNT_LOGOUT_REDIRECT_URL = "home"
 | 
				
			||||||
ACCOUNT_USER_DISPLAY = lambda user: user.email
 | 
					ACCOUNT_USER_DISPLAY = lambda user: user.email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTHENTICATION_BACKENDS = [
 | 
					AUTHENTICATION_BACKENDS = [
 | 
				
			||||||
 | 
					    # Permissions Backends
 | 
				
			||||||
 | 
					    "symposion.teams.backends.TeamPermissionsBackend",
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Auth backends
 | 
				
			||||||
    "account.auth_backends.EmailAuthenticationBackend",
 | 
					    "account.auth_backends.EmailAuthenticationBackend",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% load i18n %}
 | 
					{% load i18n %}
 | 
				
			||||||
{% load proposal_tags %}
 | 
					{% load proposal_tags %}
 | 
				
			||||||
 | 
					{% load teams_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block head_title %}Dashboard{% endblock %}
 | 
					{% block head_title %}Dashboard{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,4 +122,53 @@
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="dashboard-panel">
 | 
				
			||||||
 | 
					        <div class="dashboard-panel-header">
 | 
				
			||||||
 | 
					            <i class="icon-group"></i>
 | 
				
			||||||
 | 
					            <h3>{% trans "Teams" %}</h3>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <div class="dashboard-panel-content">
 | 
				
			||||||
 | 
					            {% if user.memberships.exists %}
 | 
				
			||||||
 | 
					                <h4>Your Teams</h4>
 | 
				
			||||||
 | 
					                <table class="table table-striped">
 | 
				
			||||||
 | 
					                    {% for membership in user.memberships.all %}
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                <a href="{% url team_detail membership.team.slug %}">{{ membership.team.name }}</a>
 | 
				
			||||||
 | 
					                                {% if membership.team.description %}<br>{{ membership.team.description }}{% endif %}
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                <span class="label{% if membership.state == 'invited' %} label-info{% endif %}">{{ membership.get_state_display }}</span>
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                {% if membership.state == "manager" or user.is_staff %}
 | 
				
			||||||
 | 
					                                    {% if membership.team.applicants %}{{ membership.team.applicants.count }} applicant{{ membership.team.applicants.count|pluralize }}{% endif %}
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </table>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					            {% available_teams as available_teams %}
 | 
				
			||||||
 | 
					            {% if available_teams %}
 | 
				
			||||||
 | 
					                <h4>Available Teams</h4>
 | 
				
			||||||
 | 
					                <table class="table table-striped">
 | 
				
			||||||
 | 
					                    {% for team in available_teams %}
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                <a href="{% url team_detail team.slug %}">{{ team }}</a>
 | 
				
			||||||
 | 
					                                {% if team.description %}<br>{{ team.description }}{% endif %}
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                <span class="label">{{ team.get_access_display }}</span>
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </table>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										103
									
								
								symposion_project/templates/teams/team_detail.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								symposion_project/templates/teams/team_detail.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,103 @@
 | 
				
			||||||
 | 
					{% extends "site_base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% load bootstrap_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head_title %}{{ team.name }}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block body_outer %}
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="span12">
 | 
				
			||||||
 | 
					            <div class="pull-right">
 | 
				
			||||||
 | 
					            {% if can_join %}
 | 
				
			||||||
 | 
					                <form method="post" action="{% url team_join team.slug %}">
 | 
				
			||||||
 | 
					                    {% csrf_token %}
 | 
				
			||||||
 | 
					                    <input type="submit" class="btn btn-primary" value="join">
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            {% if can_leave %}
 | 
				
			||||||
 | 
					                <form method="post" action="{% url team_leave team.slug %}">
 | 
				
			||||||
 | 
					                    {% csrf_token %}
 | 
				
			||||||
 | 
					                    <input type="submit" class="btn" value="leave">
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            {% if can_apply %}
 | 
				
			||||||
 | 
					                <form method="post" action="{% url team_apply team.slug %}"> 
 | 
				
			||||||
 | 
					                    {% csrf_token %}
 | 
				
			||||||
 | 
					                    <input type="submit" class="btn btn-primary" value="apply">
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <h1>{{ team.name }}{% if state %} <span class="label">{{ state }}</span>{% endif %}</h1>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            {% if team.description %}<p>{{ team.description }}</p>{% endif %}
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            {% if state == "invited" %}<p>You have been invited to join this team. Click <b>join</b> to the right to accept.</p>{% endif %}
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            {% if user.is_staff or state == "manager" %}
 | 
				
			||||||
 | 
					                {% if team.managers %}
 | 
				
			||||||
 | 
					                    <h2>Managers</h2>
 | 
				
			||||||
 | 
					                    <table class="table table-striped">
 | 
				
			||||||
 | 
					                        {% for membership in team.managers %}
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>{{ membership.user.email }}{% if user == membership.user %} <span class="label label-info">you</span>{% endif %}</td>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <form style="margin: 0;" method="post" action="{% url team_demote membership.pk %}">{% csrf_token %}<button type="submit" class="btn btn-mini">demote</button></form>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                    </table>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if team.members %}
 | 
				
			||||||
 | 
					                    <h2>Team Members</h2>
 | 
				
			||||||
 | 
					                    <table class="table table-striped">
 | 
				
			||||||
 | 
					                        {% for membership in team.members %}
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>{{ membership.user.email }}{% if user == membership.user %} <span class="label label-info">you</span>{% endif %}</td>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <form style="margin: 0;" method="post" action="{% url team_promote membership.pk %}">{% csrf_token %}<button type="submit" class="btn btn-mini">promote</button></form>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                    </table>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if team.applicants and team.access == "application" %}
 | 
				
			||||||
 | 
					                    <h2>Applicants</h2>
 | 
				
			||||||
 | 
					                    <table class="table table-striped">
 | 
				
			||||||
 | 
					                        {% for membership in team.applicants %}
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>{{ membership.user.email }}</td>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <form style="margin: 0; float: left;" method="post" action="{% url team_accept membership.pk %}">{% csrf_token %}<button type="submit" class="btn btn-mini">accept</button></form>
 | 
				
			||||||
 | 
					                                    <form style="margin: 0; float: left;" method="post" action="{% url team_reject membership.pk %}">{% csrf_token %}<button type="submit" class="btn btn-mini">reject</button></form>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                    </table>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if team.invitees %}
 | 
				
			||||||
 | 
					                    <h2>Invitees</h2>
 | 
				
			||||||
 | 
					                    <table class="table table-striped">
 | 
				
			||||||
 | 
					                        {% for membership in team.invitees %}
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>{{ membership.user.email }}</td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                    </table>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if invite_form %}
 | 
				
			||||||
 | 
					                    <form method="POST" action="" class="form-horizontal">
 | 
				
			||||||
 | 
					                        {% csrf_token %}
 | 
				
			||||||
 | 
					                        <legend>Invite User to Team</legend>
 | 
				
			||||||
 | 
					                        {{ invite_form|as_bootstrap }}
 | 
				
			||||||
 | 
					                        <div class="form-actions">
 | 
				
			||||||
 | 
					                            <input class="btn btn-primary" type="submit" value="Invite" />
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </form>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,7 @@ urlpatterns = patterns("",
 | 
				
			||||||
    url(r"^proposals/", include("symposion.proposals.urls")),
 | 
					    url(r"^proposals/", include("symposion.proposals.urls")),
 | 
				
			||||||
    url(r"^sponsors/", include("symposion.sponsorship.urls")),
 | 
					    url(r"^sponsors/", include("symposion.sponsorship.urls")),
 | 
				
			||||||
    url(r"^boxes/", include("symposion.boxes.urls")),
 | 
					    url(r"^boxes/", include("symposion.boxes.urls")),
 | 
				
			||||||
 | 
					    url(r"^teams/", include("symposion.teams.urls")),
 | 
				
			||||||
    url(r"^markitup/", include("markitup.urls")),
 | 
					    url(r"^markitup/", include("markitup.urls")),
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    url(r"^", include("symposion.cms.urls")),
 | 
					    url(r"^", include("symposion.cms.urls")),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue