Teams work.
parent
002fbc45c5
commit
ae1d532c06
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0005_auto_20150930_2124'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='plan',
|
||||||
|
field=models.CharField(default=b'snipt-teams-25-monthly', max_length=100, choices=[(b'snipt-teams-25-monthly', b'25 users, monthly'), (b'snipt-teams-100-monthly', b'100 users, monthly'), (b'snipt-teams-250-monthly', b'250 users, monthly'), (b'snipt-teams-unlimited-monthly', b'Unlimited users, monthly'), (b'snipt-teams-25-yearly', b'25 users, yearly'), (b'snipt-teams-100-yearly', b'100 users, yearly'), (b'snipt-teams-250-yearly', b'250 users, yearly'), (b'snipt-teams-unlimited-yearly', b'Unlimited users, yearly')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,18 @@ from snipts.utils import slugify_uniquely
|
||||||
|
|
||||||
|
|
||||||
class Team(models.Model):
|
class Team(models.Model):
|
||||||
|
|
||||||
|
PLANS = (
|
||||||
|
('snipt-teams-25-monthly', '25 users, monthly'),
|
||||||
|
('snipt-teams-100-monthly', '100 users, monthly'),
|
||||||
|
('snipt-teams-250-monthly', '250 users, monthly'),
|
||||||
|
('snipt-teams-unlimited-monthly', 'Unlimited users, monthly'),
|
||||||
|
('snipt-teams-25-yearly', '25 users, yearly'),
|
||||||
|
('snipt-teams-100-yearly', '100 users, yearly'),
|
||||||
|
('snipt-teams-250-yearly', '250 users, yearly'),
|
||||||
|
('snipt-teams-unlimited-yearly', 'Unlimited users, yearly'),
|
||||||
|
)
|
||||||
|
|
||||||
email = models.EmailField(max_length=255)
|
email = models.EmailField(max_length=255)
|
||||||
members = models.ManyToManyField(User, related_name='member', blank=True)
|
members = models.ManyToManyField(User, related_name='member', blank=True)
|
||||||
name = models.CharField(max_length=30)
|
name = models.CharField(max_length=30)
|
||||||
|
@ -11,6 +23,8 @@ class Team(models.Model):
|
||||||
slug = models.SlugField(max_length=255, blank=True)
|
slug = models.SlugField(max_length=255, blank=True)
|
||||||
stripe_id = models.CharField(max_length=100, null=True, blank=True)
|
stripe_id = models.CharField(max_length=100, null=True, blank=True)
|
||||||
user = models.OneToOneField(User, blank=True, null=True)
|
user = models.OneToOneField(User, blank=True, null=True)
|
||||||
|
plan = models.CharField(max_length=100, default='snipt-teams-25-monthly',
|
||||||
|
choices=PLANS)
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True, editable=False)
|
created = models.DateTimeField(auto_now_add=True, editable=False)
|
||||||
modified = models.DateTimeField(auto_now=True, editable=False)
|
modified = models.DateTimeField(auto_now=True, editable=False)
|
||||||
|
@ -22,3 +36,21 @@ class Team(models.Model):
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def member_limit(self):
|
||||||
|
plan_map = {
|
||||||
|
'snipt-teams-25-monthly': 25,
|
||||||
|
'snipt-teams-100-monthly': 100,
|
||||||
|
'snipt-teams-250-monthly': 250,
|
||||||
|
'snipt-teams-unlimited-monthly': float('inf'),
|
||||||
|
'snipt-teams-25-yearly': 25,
|
||||||
|
'snipt-teams-100-yearly': 100,
|
||||||
|
'snipt-teams-250-yearly': 250,
|
||||||
|
'snipt-teams-unlimited-yearly': float('inf')
|
||||||
|
}
|
||||||
|
|
||||||
|
if plan_map[self.plan] == float('inf'):
|
||||||
|
return 'Unlimited'
|
||||||
|
else:
|
||||||
|
return plan_map[self.plan]
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<li>Public team posts are public to the world, as they are now.</li>
|
<li>Public team posts are public to the world, as they are now.</li>
|
||||||
<li>Private team posts are editable by all team members.</li>
|
<li>Private team posts are editable by all team members.</li>
|
||||||
<li>All team members are automatically granted personal <a href="/pro/"><span class="pro">Pro</span></a> accounts.</li>
|
<li>All team members are automatically granted personal <a href="/pro/"><span class="pro">Pro</span></a> accounts.</li>
|
||||||
<li>Plans from $49/month, all with a 7-day free trial.</li>
|
<li>Plans from $49/month, all with a 14-day free trial.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% if not request.user.is_authenticated %}
|
{% if not request.user.is_authenticated %}
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label" for="email">Team email:</label>
|
<label class="control-label" for="email">Team email:</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input required type="text" class="input-xlarge" name="email" id="email" />
|
<input required type="email" class="input-xlarge" name="email" id="email" />
|
||||||
<p class="sub" style="margin-top: 3px; color: #999999;">
|
<p class="sub" style="margin-top: 3px; color: #999999;">
|
||||||
For billing and your team's Gravatar. Will remain private.
|
For billing and your team's Gravatar. Will remain private.
|
||||||
</p>
|
</p>
|
||||||
|
@ -72,12 +72,12 @@
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<select name="plan" type="text" class="input-xlarge" id="plan">
|
<select name="plan" type="text" class="input-xlarge" id="plan">
|
||||||
<option value="snipt-teams-25-monthly">25 users - $49/month</option>
|
<option value="snipt-teams-25-monthly">25 users - $49/month</option>
|
||||||
<option value="snipt-teams-25-yearly">25 users - $588/year</option>
|
|
||||||
<option value="snipt-teams-100-monthly">100 users - $149/month</option>
|
<option value="snipt-teams-100-monthly">100 users - $149/month</option>
|
||||||
<option value="snipt-teams-100-yearly">100 users - $1,788/year</option>
|
|
||||||
<option value="snipt-teams-250-monthly">250 users - $299/month</option>
|
<option value="snipt-teams-250-monthly">250 users - $299/month</option>
|
||||||
<option value="snipt-teams-250-yearly">250 users - $3,588/year</option>
|
|
||||||
<option value="snipt-teams-unlimited-monthly">Unlimited users - $499/month</option>
|
<option value="snipt-teams-unlimited-monthly">Unlimited users - $499/month</option>
|
||||||
|
<option value="snipt-teams-25-yearly">25 users - $588/year</option>
|
||||||
|
<option value="snipt-teams-100-yearly">100 users - $1,788/year</option>
|
||||||
|
<option value="snipt-teams-250-yearly">250 users - $3,588/year</option>
|
||||||
<option value="snipt-teams-unlimited-yearly">Unlimited users - $5,988/year</option>
|
<option value="snipt-teams-unlimited-yearly">Unlimited users - $5,988/year</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block page-title %}Team Billing{% endblock %}
|
||||||
|
|
||||||
|
{% block body-class %}account {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumb %}
|
||||||
|
<li><a href="/{{ team.user.username }}/">{{ team.user.username }}</a></li>
|
||||||
|
<span class="prompt">/</span> <li><a href="/{{ team.user.username }}/billing/">Billing</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="snipts" id="snipts"></section>
|
||||||
|
<section class="profile group">
|
||||||
|
<aside>
|
||||||
|
<ul class="nav nav-list ng-cloak" ng-cloak>
|
||||||
|
<li class="nav-header">Team: {{ team.name }}</li>
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.slug }}/members/">Members</a>
|
||||||
|
</li>
|
||||||
|
<li class="active">
|
||||||
|
<a href="/{{ team.slug }}/billing/">Billing</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
<section class="content">
|
||||||
|
<div class="def" data-title="Plan">
|
||||||
|
25 users
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Price">
|
||||||
|
$49.00 USD / month
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Card">
|
||||||
|
xxxx-xxxx-xxxx-4242
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Status">
|
||||||
|
Active
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Team since">
|
||||||
|
August 29, 2015
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Next bill date" ng-show="user.stripeAccount.status != 'inactive'">
|
||||||
|
November 29, 2015
|
||||||
|
</div>
|
||||||
|
<p class="alert alert-info group" style="padding-right: 8px;">
|
||||||
|
<a href="/{{ team.slug }}/billing/cancel/" class="btn btn-danger pull-right">Cancel subscription</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block analytics %}
|
||||||
|
{% if not debug %}
|
||||||
|
window.ll('tagScreen', 'Team billing view');
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
{% block page-title %}Team Members{% endblock %}
|
{% block page-title %}Team Members{% endblock %}
|
||||||
|
|
||||||
{% block body-class %}{{ block.super }}{% endblock %}
|
{% block body-class %}account {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb %}
|
{% block breadcrumb %}
|
||||||
<li><a href="/{{ team.user.username }}/">{{ team.user.username }}</a></li>
|
<li><a href="/{{ team.user.username }}/">{{ team.user.username }}</a></li>
|
||||||
|
@ -10,7 +10,32 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<section class="snipts" id="snipts"></section>
|
||||||
|
<section class="profile group">
|
||||||
|
<aside>
|
||||||
|
<ul class="nav nav-list ng-cloak" ng-cloak>
|
||||||
|
<li class="nav-header">Team: {{ team.name }}</li>
|
||||||
|
<li class="active">
|
||||||
|
<a href="/{{ team.slug }}/members/">Members</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.slug }}/billing/">Billing</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
<section class="content">
|
||||||
|
<div class="def" data-title="Owner">
|
||||||
|
{{ team.owner }}
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Members ({{ team.members.all|length }} of {{ team.member_limit }})">
|
||||||
|
<ul style="margin-bottom: 0">
|
||||||
|
{% for member in team.members.all %}
|
||||||
|
<li>{{ member }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block analytics %}
|
{% block analytics %}
|
||||||
|
|
|
@ -8,4 +8,7 @@ urlpatterns = \
|
||||||
url(r'^for-teams/complete/$', views.for_teams_complete),
|
url(r'^for-teams/complete/$', views.for_teams_complete),
|
||||||
url(r'^(?P<username>[^/]+)/members/$',
|
url(r'^(?P<username>[^/]+)/members/$',
|
||||||
views.team_members,
|
views.team_members,
|
||||||
name='team-members'))
|
name='team-members'),
|
||||||
|
url(r'^(?P<username>[^/]+)/billing/$',
|
||||||
|
views.team_billing,
|
||||||
|
name='team-billing'))
|
||||||
|
|
|
@ -5,7 +5,7 @@ import uuid
|
||||||
from annoying.decorators import render_to
|
from annoying.decorators import render_to
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpResponseRedirect, HttpResponseBadRequest
|
from django.http import Http404, HttpResponseRedirect, HttpResponseBadRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from teams.models import Team
|
from teams.models import Team
|
||||||
|
|
||||||
|
@ -19,9 +19,19 @@ def for_teams(request):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@render_to('teams/team-billing.html')
|
||||||
|
def team_billing(request, username):
|
||||||
|
team = get_object_or_404(Team, slug=username)
|
||||||
|
return {
|
||||||
|
'team': team
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@render_to('teams/team-members.html')
|
@render_to('teams/team-members.html')
|
||||||
def team_members(request, username):
|
def team_members(request, username):
|
||||||
team = get_object_or_404(Team, slug=username)
|
team = get_object_or_404(Team, slug=username)
|
||||||
|
if team.owner != request.user:
|
||||||
|
raise Http404
|
||||||
return {
|
return {
|
||||||
'team': team
|
'team': team
|
||||||
}
|
}
|
||||||
|
@ -49,6 +59,7 @@ def for_teams_complete(request):
|
||||||
|
|
||||||
team = Team(name=request.POST['team-name'],
|
team = Team(name=request.POST['team-name'],
|
||||||
email=request.POST['email'],
|
email=request.POST['email'],
|
||||||
|
plan=plan,
|
||||||
owner=request.user)
|
owner=request.user)
|
||||||
team.stripe_id = customer.id
|
team.stripe_id = customer.id
|
||||||
team.save()
|
team.save()
|
||||||
|
|
Loading…
Reference in New Issue