commit
e846b4d753
12
Makefile
12
Makefile
|
@ -34,9 +34,9 @@ assets:
|
||||||
> media/css/snipt.css
|
> media/css/snipt.css
|
||||||
@cat media/js/src/account.js > media/js/src/account.min.js
|
@cat media/js/src/account.js > media/js/src/account.min.js
|
||||||
@cat media/js/src/snipts.js > media/js/src/snipts.min.js
|
@cat media/js/src/snipts.js > media/js/src/snipts.min.js
|
||||||
@cat media/js/src/search.js > media/js/src/search.min.js
|
|
||||||
@cat media/js/src/jobs.js > media/js/src/jobs.min.js
|
@cat media/js/src/jobs.js > media/js/src/jobs.min.js
|
||||||
@cat media/js/src/application.js > media/js/src/application.min.js
|
@cat media/js/src/application.js > media/js/src/application.min.js
|
||||||
|
@cat media/js/src/team.js > media/js/src/team.min.js
|
||||||
@cat media/js/src/modules/site.js > media/js/src/modules/site.min.js
|
@cat media/js/src/modules/site.js > media/js/src/modules/site.min.js
|
||||||
@cat media/js/src/modules/snipt.js > media/js/src/modules/snipt.min.js
|
@cat media/js/src/modules/snipt.js > media/js/src/modules/snipt.min.js
|
||||||
@cat media/js/src/pro.js > media/js/src/pro.min.js
|
@cat media/js/src/pro.js > media/js/src/pro.min.js
|
||||||
|
@ -146,13 +146,16 @@ vagrant:
|
||||||
@$(ssh-vagrant) '$(pm) rebuild_index --noinput;'
|
@$(ssh-vagrant) '$(pm) rebuild_index --noinput;'
|
||||||
|
|
||||||
pulldb:
|
pulldb:
|
||||||
# @ssh nick@snipt.net -p 55555 'sudo su -c "pg_dump snipt|gzip > /tmp/snipt.dump" postgres'
|
@ssh nick@snipt.net -p 55555 'sudo su -c "pg_dump snipt|gzip > /tmp/snipt.dump" postgres'
|
||||||
# @scp -q -P 55555 nick@snipt.net:/tmp/snipt.dump snipt.dump.gz
|
@scp -q -P 55555 nick@snipt.net:/tmp/snipt.dump snipt.dump.gz
|
||||||
# @dropdb snipt
|
@dropdb snipt
|
||||||
@createdb snipt
|
@createdb snipt
|
||||||
@cat snipt.dump.gz | gunzip | psql snipt
|
@cat snipt.dump.gz | gunzip | psql snipt
|
||||||
@rm snipt.dump.gz
|
@rm snipt.dump.gz
|
||||||
|
|
||||||
|
sass:
|
||||||
|
sass --sourcemap=none --watch -t compressed --scss media/css/style.scss:media/css/style.css
|
||||||
|
|
||||||
.PHONY: assets, \
|
.PHONY: assets, \
|
||||||
db, \
|
db, \
|
||||||
deploy, \
|
deploy, \
|
||||||
|
@ -162,5 +165,6 @@ pulldb:
|
||||||
provision-vagrant, \
|
provision-vagrant, \
|
||||||
salt-server, \
|
salt-server, \
|
||||||
salt-vagrant, \
|
salt-vagrant, \
|
||||||
|
sass, \
|
||||||
server, \
|
server, \
|
||||||
vagrant
|
vagrant
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
from annoying.functions import get_object_or_None
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from itertools import chain
|
||||||
from snipts.models import Snipt
|
from snipts.models import Snipt
|
||||||
|
from teams.models import Team
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
|
@ -110,9 +113,37 @@ class UserProfile(models.Model):
|
||||||
public=True).count() > 0 \
|
public=True).count() > 0 \
|
||||||
else False
|
else False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_a_team(self):
|
||||||
|
if get_object_or_None(Team, user=self.user, disabled=False):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def teams(self):
|
||||||
|
teams_owned = Team.objects.filter(owner=self.user, disabled=False)
|
||||||
|
teams_in = Team.objects.filter(members=self.user, disabled=False)
|
||||||
|
return list(chain(teams_owned, teams_in))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_teams(self):
|
||||||
|
if (len(self.teams()) > 0):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def get_account_age(self):
|
def get_account_age(self):
|
||||||
delta = datetime.now().replace(tzinfo=None) - \
|
delta = datetime.now().replace(tzinfo=None) - \
|
||||||
self.user.date_joined.replace(tzinfo=None)
|
self.user.date_joined.replace(tzinfo=None)
|
||||||
return delta.days
|
return delta.days
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_pro(self):
|
||||||
|
if (self.is_pro or
|
||||||
|
self.has_teams or
|
||||||
|
self.is_a_team):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
|
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<li ng-class="{active: route.current.scope.section == 'Editor'}">
|
<li ng-class="{active: route.current.scope.section == 'Editor'}">
|
||||||
<a href="/account/editor/">Editor</a>
|
<a href="/account/editor/">Editor</a>
|
||||||
</li>
|
</li>
|
||||||
<li ng-show="user.is_pro && user.stripe_id && user.stripe_id != 'COMP'" ng-class="{active: route.current.scope.section == 'Billing'}">
|
<li ng-show="user.has_pro && user.stripe_id && user.stripe_id != 'COMP'" ng-class="{active: route.current.scope.section == 'Billing'}">
|
||||||
<a href="/account/billing/">Billing</a>
|
<a href="/account/billing/">Billing</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import stripe
|
||||||
from annoying.decorators import ajax_request, render_to
|
from annoying.decorators import ajax_request, render_to
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.mail import send_mail
|
||||||
from snipts.models import Snipt
|
from snipts.models import Snipt
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +31,15 @@ def cancel_subscription(request):
|
||||||
profile.stripe_id = None
|
profile.stripe_id = None
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
|
send_mail('[Snipt] User cancelled Pro: {}'.format(request.user.username),
|
||||||
|
"""
|
||||||
|
User: https://snipt.net/{}
|
||||||
|
Email: {}
|
||||||
|
""".format(request.user.username, request.user.email),
|
||||||
|
'support@snipt.net',
|
||||||
|
['nick@snipt.net'],
|
||||||
|
fail_silently=False)
|
||||||
|
|
||||||
return {'deleted': True}
|
return {'deleted': True}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ class BlogMiddleware:
|
||||||
get_object_or_404(User, username__iexact=blog_user)
|
get_object_or_404(User, username__iexact=blog_user)
|
||||||
|
|
||||||
if request.blog_user is None:
|
if request.blog_user is None:
|
||||||
|
# TODO: This needs to check profile.has_pro() instead.
|
||||||
pro_users = User.objects.filter(userprofile__is_pro=True)
|
pro_users = User.objects.filter(userprofile__is_pro=True)
|
||||||
|
|
||||||
for pro_user in pro_users:
|
for pro_user in pro_users:
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
<script type="text/javascript" src="//pagead2.googlesyndication.com/pagead/show_ads.js"></script>
|
<script type="text/javascript" src="//pagead2.googlesyndication.com/pagead/show_ads.js"></script>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not blog_user.profile.is_pro %}
|
{% if not blog_user.profile.has_pro %}
|
||||||
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
|
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
|
||||||
<ul class="powered">
|
<ul class="powered">
|
||||||
<li class="snipt"><a href="https://snipt.net/blogging/">Blog powered by Snipt</a></li>
|
<li class="snipt"><a href="https://snipt.net/blogging/">Blog powered by Snipt</a></li>
|
||||||
|
|
|
@ -32,9 +32,9 @@ cat media/css/bootstrap.min.css \
|
||||||
|
|
||||||
cat media/js/src/account.js|jsmin > media/js/src/account.min.js
|
cat media/js/src/account.js|jsmin > media/js/src/account.min.js
|
||||||
cat media/js/src/snipts.js|jsmin > media/js/src/snipts.min.js
|
cat media/js/src/snipts.js|jsmin > media/js/src/snipts.min.js
|
||||||
cat media/js/src/search.js|jsmin > media/js/src/search.min.js
|
|
||||||
cat media/js/src/jobs.js|jsmin > media/js/src/jobs.min.js
|
cat media/js/src/jobs.js|jsmin > media/js/src/jobs.min.js
|
||||||
cat media/js/src/application.js|jsmin > media/js/src/application.min.js
|
cat media/js/src/application.js|jsmin > media/js/src/application.min.js
|
||||||
|
cat media/js/src/team.js|jsmin > media/js/src/team.min.js
|
||||||
cat media/js/src/modules/site.js|jsmin > media/js/src/modules/site.min.js
|
cat media/js/src/modules/site.js|jsmin > media/js/src/modules/site.min.js
|
||||||
cat media/js/src/modules/snipt.js|jsmin > media/js/src/modules/snipt.min.js
|
cat media/js/src/modules/snipt.js|jsmin > media/js/src/modules/snipt.min.js
|
||||||
cat media/js/src/pro.js|jsmin > media/js/src/pro.min.js
|
cat media/js/src/pro.js|jsmin > media/js/src/pro.min.js
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -209,6 +209,61 @@ header.main {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 13px;
|
margin-right: 13px;
|
||||||
}
|
}
|
||||||
|
&.teams-nav, &.add-snipt {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> ul {
|
||||||
|
background: transparent url('../img/aside-nav-open-bottom-bg.gif') top left repeat;
|
||||||
|
display: none;
|
||||||
|
left: -5px;
|
||||||
|
padding: 10px 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 48px;
|
||||||
|
width: 189px;
|
||||||
|
@include multi-border-radius(0, 0, 10px, 10px);
|
||||||
|
|
||||||
|
li {
|
||||||
|
float: none;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
a {
|
||||||
|
border: none;
|
||||||
|
color: #B0D7DD;
|
||||||
|
display: block;
|
||||||
|
float: none;
|
||||||
|
font: bold 12px $Helvetica;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 7px 20px 7px 20px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(#103A42, .5);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
margin-right: 9px;
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
> a {
|
||||||
|
background: #406064;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
> ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.as {
|
||||||
|
color: #7C8D8E;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -890,11 +945,11 @@ article.snipt {
|
||||||
font: normal 12px/16px $Consolas;
|
font: normal 12px/16px $Consolas;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 220px;
|
min-height: 220px;
|
||||||
|
min-width: 589px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 4px 0 4px 0;
|
padding: 4px 0 4px 0;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
width: 589px;
|
|
||||||
@include border-radius(0);
|
@include border-radius(0);
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
@ -1615,6 +1670,34 @@ div.profile {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.team-settings {
|
||||||
|
margin-top: -20px;
|
||||||
|
|
||||||
|
span.title {
|
||||||
|
color: #949494;
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
background: #dbdbdb;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
width: 100%;
|
||||||
|
@include border-radius(3px);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #c8c8c8;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div.empty-snipts {
|
div.empty-snipts {
|
||||||
background: rgba(128, 128, 128, .08);
|
background: rgba(128, 128, 128, .08);
|
||||||
|
@ -1938,9 +2021,10 @@ body.pro {
|
||||||
div.payment-loading {
|
div.payment-loading {
|
||||||
background: rgba(#F2F2F2, .6);
|
background: rgba(#F2F2F2, .6);
|
||||||
display: none;
|
display: none;
|
||||||
height: 287px;
|
height: 410px;
|
||||||
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 261px;
|
top: 254px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
@ -1955,6 +2039,10 @@ body.pro {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
@include border-radius;
|
@include border-radius;
|
||||||
}
|
}
|
||||||
|
&.-teams {
|
||||||
|
height: 581px;
|
||||||
|
top: 449px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div.stripe {
|
div.stripe {
|
||||||
color: #C2C2C2;
|
color: #C2C2C2;
|
||||||
|
@ -1979,6 +2067,19 @@ body.pro {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
div.login-first {
|
||||||
|
background: #FFF;
|
||||||
|
border: 3px solid #3299B7;
|
||||||
|
color: #666;
|
||||||
|
display: block;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 auto 10px auto;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
width: 400px;
|
||||||
|
@include border-radius;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body.search {
|
body.search {
|
||||||
div.empty-snipts {
|
div.empty-snipts {
|
||||||
|
@ -2988,3 +3089,73 @@ a.snipt-promo {
|
||||||
background: #55a955;
|
background: #55a955;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.team-controller {
|
||||||
|
div.add-member {
|
||||||
|
input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin: 12px 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
li.user {
|
||||||
|
box-sizing: border-box;
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px;
|
||||||
|
width: 42%;
|
||||||
|
@include border-radius(3px);
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
a.btn {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: #ebebeb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul.member-list {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.payment-form {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 86%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.with-teams-search {
|
||||||
|
display: inline-block;
|
||||||
|
width: 87%;
|
||||||
|
|
||||||
|
input.search-query {
|
||||||
|
width: 69% !important;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
display: inline-block;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.team-search {
|
||||||
|
form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 15px 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ if (typeof angular !== 'undefined') {
|
||||||
AccountStorage.getAccount().then(function(response) {
|
AccountStorage.getAccount().then(function(response) {
|
||||||
$scope.user = response.data;
|
$scope.user = response.data;
|
||||||
|
|
||||||
if ($scope.user.is_pro && $scope.user.stripe_id && $scope.user.stripe_id !== 'COMP') {
|
if ($scope.user.has_pro && $scope.user.stripe_id && $scope.user.stripe_id !== 'COMP') {
|
||||||
AccountStorage.getStripeAccount().then(function(response) {
|
AccountStorage.getStripeAccount().then(function(response) {
|
||||||
$scope.user.stripeAccount = response.data;
|
$scope.user.stripeAccount = response.data;
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,20 @@ var snipt = {
|
||||||
jQuery(function($) {
|
jQuery(function($) {
|
||||||
var SiteView = snipt.module('site').SiteView;
|
var SiteView = snipt.module('site').SiteView;
|
||||||
window.site = new SiteView();
|
window.site = new SiteView();
|
||||||
|
|
||||||
|
var $pres = $('td.code pre');
|
||||||
|
$pres.each(function(i) {
|
||||||
|
var pre = $pres.eq(i);
|
||||||
|
pre.width(pre.parents('section.code').width() - 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('form#cancel-team-subscription').submit(function() {
|
||||||
|
if (confirm('Are you sure you want to cancel your subscription?\n\nYou will no longer be able to create new Snipts under this team. This action is effective immediately and we unfortunately cannot issue any refunds.')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Angular app init.
|
// Angular app init.
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!cancelled">
|
<div ng-show="!cancelled">
|
||||||
<div ng-show="!user.is_pro">
|
<div ng-show="!user.has_pro">
|
||||||
<p class="alert alert-info">
|
<p class="alert alert-info">
|
||||||
You're not a Pro yet, so we have nothing to show you here.<br />
|
You're not a Pro yet, so we have nothing to show you here.<br />
|
||||||
<br /><a class="btn btn-success" href="/pro/">Signup for Pro »</a>
|
<br /><a class="btn btn-success" href="/pro/">Signup for Pro »</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="user.is_pro">
|
<div ng-show="user.has_pro">
|
||||||
<div class="def" data-title="Plan" ng-show="user.stripeAccount.status != 'inactive'">
|
<div class="def" data-title="Plan" ng-show="user.stripeAccount.status != 'inactive'">
|
||||||
{[{ user.stripeAccount.name || 'Loading...' }]}
|
{[{ user.stripeAccount.name || 'Loading...' }]}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
<div class="control-group" ng-class="{error: errors.blog_domain}">
|
<div class="control-group" ng-class="{error: errors.blog_domain}">
|
||||||
<label class="control-label" for="id_blog_domain">Blog domain:</label>
|
<label class="control-label" for="id_blog_domain">Blog domain:</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input ng-disabled="!user.is_pro" id="id_blog_domain" type="text" ng-model="user.blog_domain" maxlength="250">
|
<input ng-disabled="!user.has_pro" id="id_blog_domain" type="text" ng-model="user.blog_domain" maxlength="250">
|
||||||
<span ng-show="user.is_pro" class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME to `snipt.net` or A-record to `96.126.110.160`. You can use multiple domains here: separate each domain with a space. The first domain will be your primary domain. All other domains will redirect to your primary domain.</span>
|
<span ng-show="user.has_pro" class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME to `snipt.net` or A-record to `96.126.110.160`. You can use multiple domains here: separate each domain with a space. The first domain will be your primary domain. All other domains will redirect to your primary domain.</span>
|
||||||
<span ng-show="!user.is_pro" class="help-block"><a href="https://snipt.net/pro/">Go Pro</a> to enable a custom domain for your <a href="https://snipt.net/blogging/">Snipt blog</a>.</span>
|
<span ng-show="!user.has_pro" class="help-block"><a href="https://snipt.net/pro/">Go Pro</a> to enable a custom domain for your <a href="https://snipt.net/blogging/">Snipt blog</a>.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,6 +42,8 @@
|
||||||
this.$html_body = this.$body.add(this.$html);
|
this.$html_body = this.$body.add(this.$html);
|
||||||
this.$aside_main = $('aside.main', this.$body);
|
this.$aside_main = $('aside.main', this.$body);
|
||||||
this.$aside_nav = $('aside.nav', this.$body);
|
this.$aside_nav = $('aside.nav', this.$body);
|
||||||
|
this.$teams_nav = $('li.teams-nav', this.$body);
|
||||||
|
this.$add_snipt = $('li.add-snipt', this.$body);
|
||||||
this.$aside_nav_ul = $('ul', this.$aside_nav);
|
this.$aside_nav_ul = $('ul', this.$aside_nav);
|
||||||
this.$search_form = $('form.search', this.$body);
|
this.$search_form = $('form.search', this.$body);
|
||||||
this.$search_query = $('input#search-query', this.$body);
|
this.$search_query = $('input#search-query', this.$body);
|
||||||
|
@ -69,6 +71,8 @@
|
||||||
window.from_modal = false;
|
window.from_modal = false;
|
||||||
}
|
}
|
||||||
that.$aside_nav.removeClass('open');
|
that.$aside_nav.removeClass('open');
|
||||||
|
that.$teams_nav.removeClass('open');
|
||||||
|
that.$add_snipt.removeClass('open');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$aside_nav_ul.click(function(e) {
|
this.$aside_nav_ul.click(function(e) {
|
||||||
|
@ -96,7 +100,6 @@
|
||||||
var $form = $('form#pro-signup');
|
var $form = $('form#pro-signup');
|
||||||
var $submit = $('button[type="submit"]', $form);
|
var $submit = $('button[type="submit"]', $form);
|
||||||
|
|
||||||
var $name = $('input#name');
|
|
||||||
var $cardNumber = $('input#number');
|
var $cardNumber = $('input#number');
|
||||||
var $expMonth = $('select#exp-month');
|
var $expMonth = $('select#exp-month');
|
||||||
var $expYear = $('select#exp-year');
|
var $expYear = $('select#exp-year');
|
||||||
|
@ -135,7 +138,6 @@
|
||||||
$('.payment-loading').show();
|
$('.payment-loading').show();
|
||||||
|
|
||||||
Stripe.createToken({
|
Stripe.createToken({
|
||||||
name: $name.val(),
|
|
||||||
number: $cardNumber.val(),
|
number: $cardNumber.val(),
|
||||||
cvc: $cvc.val(),
|
cvc: $cvc.val(),
|
||||||
exp_month: $expMonth.val(),
|
exp_month: $expMonth.val(),
|
||||||
|
@ -190,7 +192,8 @@
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
'showKeyboardShortcuts': 'showKeyboardShortcuts',
|
'showKeyboardShortcuts': 'showKeyboardShortcuts',
|
||||||
'click a.mini-profile': 'toggleMiniProfile'
|
'click a.mini-profile': 'toggleMiniProfile',
|
||||||
|
'click a.teams-nav': 'toggleTeamsNav'
|
||||||
},
|
},
|
||||||
|
|
||||||
keyboardShortcuts: function() {
|
keyboardShortcuts: function() {
|
||||||
|
@ -237,6 +240,10 @@
|
||||||
this.$aside_nav.toggleClass('open');
|
this.$aside_nav.toggleClass('open');
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
toggleTeamsNav: function(e) {
|
||||||
|
this.$teams_nav.toggleClass('open');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
inFieldLabels: function () {
|
inFieldLabels: function () {
|
||||||
$('div.infield label', this.$body).inFieldLabels({
|
$('div.infield label', this.$body).inFieldLabels({
|
||||||
fadeDuration: 200
|
fadeDuration: 200
|
||||||
|
|
|
@ -134,10 +134,10 @@
|
||||||
$('div.alert-not-pro').hide();
|
$('div.alert-not-pro').hide();
|
||||||
if ($checkbox.is(':checked')) {
|
if ($checkbox.is(':checked')) {
|
||||||
$label.removeClass('is-private').addClass('is-public');
|
$label.removeClass('is-private').addClass('is-public');
|
||||||
if (!window.user_is_pro) $('div.alert-not-pro').hide();
|
if (!window.user_has_pro) $('div.alert-not-pro').hide();
|
||||||
} else {
|
} else {
|
||||||
$label.addClass('is-private').removeClass('is-public');
|
$label.addClass('is-private').removeClass('is-public');
|
||||||
if (!window.user_is_pro) $('div.alert-not-pro').show();
|
if (!window.user_has_pro) $('div.alert-not-pro').show();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}).change();
|
}).change();
|
||||||
|
@ -221,15 +221,23 @@
|
||||||
|
|
||||||
if (window.editor_theme != 'default') {
|
if (window.editor_theme != 'default') {
|
||||||
$selectTheme.val(window.editor_theme);
|
$selectTheme.val(window.editor_theme);
|
||||||
$selectTheme.trigger('liszt:updated');
|
$selectTheme.trigger('chosen:updated');
|
||||||
$selectTheme.trigger('change');
|
$selectTheme.trigger('change');
|
||||||
}
|
}
|
||||||
if (window.default_editor != 'codemirror') {
|
if (window.default_editor != 'codemirror') {
|
||||||
$selectEditor.val(window.default_editor);
|
$selectEditor.val(window.default_editor);
|
||||||
$selectEditor.trigger('liszt:updated');
|
$selectEditor.trigger('chosen:updated');
|
||||||
$selectEditor.trigger('change');
|
$selectEditor.trigger('change');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init user
|
||||||
|
if (window.teams.length) {
|
||||||
|
var $selectUser = $('select#id_user', window.site.$main_edit);
|
||||||
|
$selectUser.chosen();
|
||||||
|
$selectUser.val(window.intended_user);
|
||||||
|
$selectUser.trigger('chosen:updated');
|
||||||
|
}
|
||||||
|
|
||||||
// Full-screen mode.
|
// Full-screen mode.
|
||||||
this.setupCodeMirrorFullScreen();
|
this.setupCodeMirrorFullScreen();
|
||||||
|
|
||||||
|
@ -474,11 +482,19 @@
|
||||||
code = window.editor.getValue();
|
code = window.editor.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var intendedUser;
|
||||||
|
if (window.teams.length) {
|
||||||
|
intendedUser = $('select[name="user"]').val();
|
||||||
|
} else {
|
||||||
|
intendedUser = window.intended_user;
|
||||||
|
}
|
||||||
|
|
||||||
that.model.save({
|
that.model.save({
|
||||||
'title': $('input#snipt_title').val(),
|
'title': $('input#snipt_title').val(),
|
||||||
'tags': $('label.tags textarea').val(),
|
'tags': $('label.tags textarea').val(),
|
||||||
'tags_list': $('label.tags textarea').val(),
|
'tags_list': $('label.tags textarea').val(),
|
||||||
'lexer': $('select[name="lexer"]').val(),
|
'lexer': $('select[name="lexer"]').val(),
|
||||||
|
'intended_user': intendedUser,
|
||||||
'lexer_name': $('select[name="lexer"] option:selected').text(),
|
'lexer_name': $('select[name="lexer"] option:selected').text(),
|
||||||
'code': code,
|
'code': code,
|
||||||
'description': $('textarea[name="description"]').val(),
|
'description': $('textarea[name="description"]').val(),
|
||||||
|
@ -489,9 +505,20 @@
|
||||||
success: function(model, response) {
|
success: function(model, response) {
|
||||||
$('button.save, button.save-and-close, button.delete, button.cancel',
|
$('button.save, button.save-and-close, button.delete, button.cancel',
|
||||||
window.site.$main_edit).removeAttr('disabled');
|
window.site.$main_edit).removeAttr('disabled');
|
||||||
|
that.model.set('new_from_js', false);
|
||||||
|
|
||||||
|
var $pres = $('td.code pre');
|
||||||
|
$pres.each(function(i) {
|
||||||
|
var pre = $pres.eq(i);
|
||||||
|
pre.width(pre.parents('section.code').width() - 30);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error: function(model, response) {
|
error: function(model, response) {
|
||||||
alert(JSON.stringify(response.responseJSON.snipt));
|
if (response.responseJSON) {
|
||||||
|
alert(JSON.stringify(response.responseJSON.snipt));
|
||||||
|
} else {
|
||||||
|
alert(JSON.stringify(response.statusText));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -590,8 +617,22 @@
|
||||||
}
|
}
|
||||||
$('span.cmd-ctrl').text(cmd);
|
$('span.cmd-ctrl').text(cmd);
|
||||||
|
|
||||||
$('button#add-snipt').click(function() {
|
var $buttonAddSnipt = $('button#add-snipt');
|
||||||
that.addNewSnipt();
|
$buttonAddSnipt.click(function(e) {
|
||||||
|
if (window.teams.length) {
|
||||||
|
e.stopPropagation();
|
||||||
|
$buttonAddSnipt.parent().toggleClass('open');
|
||||||
|
} else {
|
||||||
|
that.addNewSnipt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var $addSniptTeams = $('ul.add-snipt-teams a');
|
||||||
|
$addSniptTeams.click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.intended_user = $(e.target).attr('data-intended-user') ||
|
||||||
|
$(e.target).parent().attr('data-intended-user');
|
||||||
|
that.addNewSnipt();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -603,7 +644,7 @@
|
||||||
var $public = $('div.public', $el);
|
var $public = $('div.public', $el);
|
||||||
var $blog_post = $('div.blog-post', $el);
|
var $blog_post = $('div.blog-post', $el);
|
||||||
var $publish_date = $('div.publish-date', $el);
|
var $publish_date = $('div.publish-date', $el);
|
||||||
var $user = $('li.author a', $el);
|
var $user = $('li.author > a', $el);
|
||||||
|
|
||||||
var is_public = $public.text() === 'True' ? true : false;
|
var is_public = $public.text() === 'True' ? true : false;
|
||||||
var is_blog_post = $blog_post.text() === 'True' ? true : false;
|
var is_blog_post = $blog_post.text() === 'True' ? true : false;
|
||||||
|
@ -619,7 +660,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_pro = $user.siblings('span.pro').length ? true : false;
|
var has_pro = $user.siblings('span.pro').length ? true : false;
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
code: $('textarea.raw', $el).text(),
|
code: $('textarea.raw', $el).text(),
|
||||||
|
@ -647,7 +688,7 @@
|
||||||
absolute_url: $user.attr('href'),
|
absolute_url: $user.attr('href'),
|
||||||
username: $user.text(),
|
username: $user.text(),
|
||||||
profile: {
|
profile: {
|
||||||
is_pro: is_pro
|
has_pro: has_pro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -685,7 +726,7 @@
|
||||||
user: {
|
user: {
|
||||||
username: '',
|
username: '',
|
||||||
profile: {
|
profile: {
|
||||||
is_pro: window.user_is_pro
|
has_pro: window.user_has_pro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -831,22 +872,6 @@
|
||||||
$document.bind('keydown', 'esc', function() {
|
$document.bind('keydown', 'esc', function() {
|
||||||
that.escapeUI();
|
that.escapeUI();
|
||||||
});
|
});
|
||||||
$document.bind('keydown', 'g', function() {
|
|
||||||
if (!window.ui_halted) {
|
|
||||||
if (window.$selected) {
|
|
||||||
window.$selected.trigger('deselect');
|
|
||||||
}
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$document.bind('keydown', 'Shift+g', function() {
|
|
||||||
if (!window.ui_halted) {
|
|
||||||
if (window.$selected) {
|
|
||||||
window.$selected.trigger('deselect');
|
|
||||||
}
|
|
||||||
window.scrollTo(0, document.body.scrollHeight);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$document.bind('keydown', 'n', function() {
|
$document.bind('keydown', 'n', function() {
|
||||||
if (!window.ui_halted) {
|
if (!window.ui_halted) {
|
||||||
var $anc = $('li.next a');
|
var $anc = $('li.next a');
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
(function() {
|
|
||||||
|
|
||||||
if (typeof angular !== 'undefined') {
|
|
||||||
|
|
||||||
var root = this;
|
|
||||||
var $ = root.jQuery;
|
|
||||||
var controllers = {};
|
|
||||||
var app = root.app;
|
|
||||||
|
|
||||||
// Services.
|
|
||||||
app.factory('SearchService', function() {
|
|
||||||
return {
|
|
||||||
mineOnly: false,
|
|
||||||
query: ''
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Controllers.
|
|
||||||
controllers.HeaderSearchController = function($scope, SearchService) {
|
|
||||||
|
|
||||||
$scope.search = SearchService;
|
|
||||||
|
|
||||||
};
|
|
||||||
controllers.SearchController = function($scope, SearchService) {
|
|
||||||
|
|
||||||
$scope.search = SearchService;
|
|
||||||
|
|
||||||
$scope.$watch('search.query', function(query) {
|
|
||||||
if (query.indexOf('--mine') !== -1) {
|
|
||||||
$scope.search.mineOnly = true;
|
|
||||||
} else {
|
|
||||||
$scope.search.mineOnly = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.toggleMineOnly = function() {
|
|
||||||
if ($scope.search.mineOnly) {
|
|
||||||
|
|
||||||
// Make sure '--mine' exists somewhere in the query.
|
|
||||||
if ($scope.search.query.indexOf('--mine') === -1) {
|
|
||||||
$scope.search.query = $scope.search.query.trim() + ' --mine';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$scope.search.query = $scope.search.query.replace('--mine', '').trim();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Assign the controllers.
|
|
||||||
app.controller(controllers);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
(function() { 'use strict';
|
||||||
|
|
||||||
|
if (typeof angular !== 'undefined') {
|
||||||
|
|
||||||
|
var root = this;
|
||||||
|
var $ = root.jQuery;
|
||||||
|
var controllers = {};
|
||||||
|
var app = root.app;
|
||||||
|
|
||||||
|
// Services.
|
||||||
|
app.factory('TeamStorage', function($http, $q) {
|
||||||
|
return {
|
||||||
|
searchUsers: function(query) {
|
||||||
|
var promise = $http({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/public/user/?format=json&limit=100&username__contains=' + query
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Controllers.
|
||||||
|
controllers.TeamController = function($scope, $timeout, TeamStorage) {
|
||||||
|
$scope.users = [];
|
||||||
|
$scope.search = '';
|
||||||
|
$scope.$watch('search', function(val) {
|
||||||
|
$timeout.cancel($scope.timeout);
|
||||||
|
|
||||||
|
if (!val) return $scope.users = [];
|
||||||
|
|
||||||
|
$scope.timeout = $timeout(function() {
|
||||||
|
TeamStorage.searchUsers(val).then(function(response) {
|
||||||
|
$scope.users = response.data.objects;
|
||||||
|
});
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign the controllers.
|
||||||
|
app.controller(controllers);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -5,6 +5,7 @@ Django==1.8.3
|
||||||
django-annoying==0.8.3
|
django-annoying==0.8.3
|
||||||
django-bcrypt==0.9.2
|
django-bcrypt==0.9.2
|
||||||
django-debug-toolbar==1.3.2
|
django-debug-toolbar==1.3.2
|
||||||
|
django-extensions==1.5.7
|
||||||
django-haystack==2.4.0
|
django-haystack==2.4.0
|
||||||
django-markdown-deux==1.0.5
|
django-markdown-deux==1.0.5
|
||||||
django-pagination==1.0.7
|
django-pagination==1.0.7
|
||||||
|
|
|
@ -32,7 +32,7 @@ ALLOWED_HOSTS = ['*']
|
||||||
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
|
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
|
||||||
AUTHENTICATION_BACKENDS = ('utils.backends.EmailOrUsernameModelBackend',)
|
AUTHENTICATION_BACKENDS = ('utils.backends.EmailOrUsernameModelBackend',)
|
||||||
BASE_PATH = os.path.dirname(__file__)
|
BASE_PATH = os.path.dirname(__file__)
|
||||||
CSRF_COOKIE_DOMAIN = '.snipt.net'
|
CSRF_COOKIE_DOMAIN = '.snipt.net' if 'USE_SSL' in os.environ else False
|
||||||
CSRF_COOKIE_SECURE = True if 'USE_SSL' in os.environ else False
|
CSRF_COOKIE_SECURE = True if 'USE_SSL' in os.environ else False
|
||||||
DEBUG = True if 'DEBUG' in os.environ else False
|
DEBUG = True if 'DEBUG' in os.environ else False
|
||||||
DEFAULT_FROM_EMAIL = 'support@snipt.net'
|
DEFAULT_FROM_EMAIL = 'support@snipt.net'
|
||||||
|
@ -97,6 +97,7 @@ INSTALLED_APPS = (
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django_extensions',
|
||||||
'gunicorn',
|
'gunicorn',
|
||||||
'haystack',
|
'haystack',
|
||||||
'markdown_deux',
|
'markdown_deux',
|
||||||
|
@ -108,6 +109,7 @@ INSTALLED_APPS = (
|
||||||
'storages',
|
'storages',
|
||||||
'taggit',
|
'taggit',
|
||||||
'tastypie',
|
'tastypie',
|
||||||
|
'teams',
|
||||||
'typogrify',
|
'typogrify',
|
||||||
'user-admin',
|
'user-admin',
|
||||||
'utils',
|
'utils',
|
||||||
|
@ -125,6 +127,7 @@ LOGGING = {
|
||||||
}
|
}
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
|
|
@ -56,25 +56,30 @@ class PrivateSniptAuthorization(Authorization):
|
||||||
return object_list.filter(user=bundle.request.user)
|
return object_list.filter(user=bundle.request.user)
|
||||||
|
|
||||||
def read_detail(self, object_list, bundle):
|
def read_detail(self, object_list, bundle):
|
||||||
return bundle.obj.user == bundle.request.user
|
return bundle.obj.is_authorized_user(bundle.request.user)
|
||||||
|
|
||||||
def create_list(self, object_list, bundle):
|
def create_list(self, object_list, bundle):
|
||||||
raise Unauthorized()
|
raise Unauthorized()
|
||||||
|
|
||||||
def create_detail(self, object_list, bundle):
|
def create_detail(self, object_list, bundle):
|
||||||
return bundle.obj.user == bundle.request.user
|
user = bundle.obj.user
|
||||||
|
if user == bundle.request.user:
|
||||||
|
return True
|
||||||
|
if user.profile.is_a_team:
|
||||||
|
return user.team.user_is_member(bundle.request.user)
|
||||||
|
return False
|
||||||
|
|
||||||
def update_list(self, object_list, bundle):
|
def update_list(self, object_list, bundle):
|
||||||
raise Unauthorized()
|
raise Unauthorized()
|
||||||
|
|
||||||
def update_detail(self, object_list, bundle):
|
def update_detail(self, object_list, bundle):
|
||||||
return bundle.obj.user == bundle.request.user
|
return bundle.obj.is_authorized_user(bundle.request.user)
|
||||||
|
|
||||||
def delete_list(self, object_list, bundle):
|
def delete_list(self, object_list, bundle):
|
||||||
raise Unauthorized()
|
raise Unauthorized()
|
||||||
|
|
||||||
def delete_detail(self, object_list, bundle):
|
def delete_detail(self, object_list, bundle):
|
||||||
return bundle.obj.user == bundle.request.user
|
return bundle.obj.is_authorized_user(bundle.request.user)
|
||||||
|
|
||||||
|
|
||||||
class PrivateUserProfileAuthorization(Authorization):
|
class PrivateUserProfileAuthorization(Authorization):
|
||||||
|
@ -145,7 +150,7 @@ class SniptValidation(Validation):
|
||||||
def is_valid(self, bundle, request=None):
|
def is_valid(self, bundle, request=None):
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if request.user.profile.is_pro is False:
|
if request.user.profile.has_pro is False:
|
||||||
if ('public' not in bundle.data or bundle.data['public'] is False):
|
if ('public' not in bundle.data or bundle.data['public'] is False):
|
||||||
errors['not-pro'] = ("You'll need to go Pro "
|
errors['not-pro'] = ("You'll need to go Pro "
|
||||||
"(https://snipt.net/pro/) "
|
"(https://snipt.net/pro/) "
|
||||||
|
@ -176,7 +181,7 @@ class PublicUserResource(ModelResource):
|
||||||
fields = ['id', 'username']
|
fields = ['id', 'username']
|
||||||
include_absolute_url = True
|
include_absolute_url = True
|
||||||
allowed_methods = ['get']
|
allowed_methods = ['get']
|
||||||
filtering = {'username': 'exact'}
|
filtering = {'username': ['contains', 'exact']}
|
||||||
max_limit = 200
|
max_limit = 200
|
||||||
cache = SimpleCache()
|
cache = SimpleCache()
|
||||||
|
|
||||||
|
@ -302,7 +307,7 @@ class PrivateUserProfileResource(ModelResource):
|
||||||
bundle.data['username'] = bundle.obj.user.username
|
bundle.data['username'] = bundle.obj.user.username
|
||||||
bundle.data['user_id'] = bundle.obj.user.id
|
bundle.data['user_id'] = bundle.obj.user.id
|
||||||
bundle.data['api_key'] = bundle.obj.user.api_key.key
|
bundle.data['api_key'] = bundle.obj.user.api_key.key
|
||||||
bundle.data['is_pro'] = bundle.obj.user.profile.is_pro
|
bundle.data['has_pro'] = bundle.obj.user.profile.has_pro
|
||||||
return bundle
|
return bundle
|
||||||
|
|
||||||
|
|
||||||
|
@ -327,7 +332,7 @@ class PrivateUserResource(ModelResource):
|
||||||
bundle.data['email_md5'] = hashlib \
|
bundle.data['email_md5'] = hashlib \
|
||||||
.md5(bundle.obj.email.lower()) \
|
.md5(bundle.obj.email.lower()) \
|
||||||
.hexdigest()
|
.hexdigest()
|
||||||
bundle.data['is_pro'] = bundle.obj.profile.is_pro
|
bundle.data['has_pro'] = bundle.obj.profile.has_pro
|
||||||
bundle.data['stats'] = {
|
bundle.data['stats'] = {
|
||||||
'public_snipts': Snipt.objects.filter(user=bundle.obj.id,
|
'public_snipts': Snipt.objects.filter(user=bundle.obj.id,
|
||||||
public=True).count(),
|
public=True).count(),
|
||||||
|
@ -359,7 +364,12 @@ class PrivateUserResource(ModelResource):
|
||||||
|
|
||||||
class PrivateSniptResource(ModelResource):
|
class PrivateSniptResource(ModelResource):
|
||||||
user = fields.ForeignKey(PrivateUserResource, 'user', full=True)
|
user = fields.ForeignKey(PrivateUserResource, 'user', full=True)
|
||||||
|
last_user_saved = fields.ForeignKey(PrivateUserResource,
|
||||||
|
'last_user_saved',
|
||||||
|
full=False)
|
||||||
tags_list = ListField()
|
tags_list = ListField()
|
||||||
|
tags = fields.ToManyField(PublicTagResource, 'tags', related_name='tag',
|
||||||
|
full=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
queryset = Snipt.objects.all().order_by('-created')
|
queryset = Snipt.objects.all().order_by('-created')
|
||||||
|
@ -408,20 +418,31 @@ class PrivateSniptResource(ModelResource):
|
||||||
return bundle
|
return bundle
|
||||||
|
|
||||||
def obj_create(self, bundle, **kwargs):
|
def obj_create(self, bundle, **kwargs):
|
||||||
|
bundle.data['last_user_saved'] = bundle.request.user
|
||||||
bundle.data['tags_list'] = bundle.data.get('tags')
|
bundle.data['tags_list'] = bundle.data.get('tags')
|
||||||
bundle.data['tags'] = ''
|
bundle.data['tags'] = []
|
||||||
|
bundle.data['user'] = \
|
||||||
|
User.objects.get(username=bundle.data['intended_user'])
|
||||||
|
|
||||||
if 'blog_post' in bundle.data:
|
if 'blog_post' in bundle.data:
|
||||||
bundle = self._clean_publish_date(bundle)
|
bundle = self._clean_publish_date(bundle)
|
||||||
|
|
||||||
return super(PrivateSniptResource, self) \
|
return super(PrivateSniptResource, self) \
|
||||||
.obj_create(bundle,
|
.obj_create(bundle, **kwargs)
|
||||||
user=bundle.request.user, **kwargs)
|
|
||||||
|
|
||||||
def obj_update(self, bundle, **kwargs):
|
def obj_update(self, bundle, **kwargs):
|
||||||
bundle.data['user'] = bundle.request.user
|
|
||||||
|
instance = Snipt.objects.get(pk=bundle.data['id'])
|
||||||
|
|
||||||
|
if (instance.user.profile.is_a_team):
|
||||||
|
user = instance.user
|
||||||
|
else:
|
||||||
|
user = bundle.request.user
|
||||||
|
|
||||||
bundle.data['created'] = None
|
bundle.data['created'] = None
|
||||||
|
bundle.data['last_user_saved'] = bundle.request.user
|
||||||
bundle.data['modified'] = None
|
bundle.data['modified'] = None
|
||||||
|
bundle.data['user'] = user
|
||||||
|
|
||||||
if type(bundle.data['tags']) in (str, unicode):
|
if type(bundle.data['tags']) in (str, unicode):
|
||||||
bundle.data['tags_list'] = bundle.data['tags']
|
bundle.data['tags_list'] = bundle.data['tags']
|
||||||
|
@ -433,8 +454,7 @@ class PrivateSniptResource(ModelResource):
|
||||||
bundle = self._clean_publish_date(bundle)
|
bundle = self._clean_publish_date(bundle)
|
||||||
|
|
||||||
return super(PrivateSniptResource, self) \
|
return super(PrivateSniptResource, self) \
|
||||||
.obj_update(bundle,
|
.obj_update(bundle, **kwargs)
|
||||||
user=bundle.request.user, **kwargs)
|
|
||||||
|
|
||||||
def _clean_publish_date(self, bundle):
|
def _clean_publish_date(self, bundle):
|
||||||
if bundle.data['blog_post'] and 'publish_date' not in bundle.data:
|
if bundle.data['blog_post'] and 'publish_date' not in bundle.data:
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('snipts', '0002_sniptlogentry'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='snipt',
|
||||||
|
name='last_user_saved',
|
||||||
|
field=models.ForeignKey(related_name='last_user_saved', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -16,12 +16,17 @@ from pygments.util import ClassNotFound
|
||||||
from snipts.utils import slugify_uniquely
|
from snipts.utils import slugify_uniquely
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
from taggit.utils import edit_string_for_tags
|
from taggit.utils import edit_string_for_tags
|
||||||
|
from teams.models import Team
|
||||||
|
|
||||||
|
|
||||||
class Snipt(models.Model):
|
class Snipt(models.Model):
|
||||||
"""An individual Snipt."""
|
"""An individual Snipt."""
|
||||||
|
|
||||||
user = models.ForeignKey(User, blank=True, null=True)
|
user = models.ForeignKey(User, blank=True, null=True)
|
||||||
|
last_user_saved = models.ForeignKey(User,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
related_name='last_user_saved')
|
||||||
|
|
||||||
title = models.CharField(max_length=255, blank=True, null=True,
|
title = models.CharField(max_length=255, blank=True, null=True,
|
||||||
default='Untitled')
|
default='Untitled')
|
||||||
|
@ -174,7 +179,7 @@ class Snipt(models.Model):
|
||||||
diff = self._unidiff_output(self.original_code or '', self.code)
|
diff = self._unidiff_output(self.original_code or '', self.code)
|
||||||
|
|
||||||
if (diff != ''):
|
if (diff != ''):
|
||||||
log_entry = SniptLogEntry(user=self.user,
|
log_entry = SniptLogEntry(user=self.last_user_saved,
|
||||||
snipt=self,
|
snipt=self,
|
||||||
code=self.code,
|
code=self.code,
|
||||||
diff=diff)
|
diff=diff)
|
||||||
|
@ -298,6 +303,14 @@ class Snipt(models.Model):
|
||||||
else:
|
else:
|
||||||
return get_lexer_by_name(self.lexer).name
|
return get_lexer_by_name(self.lexer).name
|
||||||
|
|
||||||
|
def is_authorized_user(self, user):
|
||||||
|
if self.user == user:
|
||||||
|
return True
|
||||||
|
if self.user.profile.is_a_team:
|
||||||
|
team = Team.objects.get(user=self.user, disabled=False)
|
||||||
|
return team.user_is_member(user)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class SniptLogEntry(models.Model):
|
class SniptLogEntry(models.Model):
|
||||||
"""An individual log entry for a Snipt changeset."""
|
"""An individual log entry for a Snipt changeset."""
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block add-snipt %}
|
{% block add-snipt %}
|
||||||
<li class="add-snipt">
|
{% include 'add-snipt.html' %}
|
||||||
<button class="btn btn-info btn-large" id="add-snipt">
|
|
||||||
Add {% if request.user.username == 'blog' %}Post{% else %}Snipt{% endif %}
|
|
||||||
<i class="icon-search icon-plus icon-white"></i>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page-title %}/ {% if snipt.title %}{{ snipt.title }}{% else %}Untitled{% endif %} / {{ user.username }} - {{ block.super }}{% endblock %}
|
{% block page-title %}/ {% if snipt.title %}{{ snipt.title }}{% else %}Untitled{% endif %} / {{ user.username }} - {{ block.super }}{% endblock %}
|
||||||
|
@ -50,7 +45,7 @@
|
||||||
<section class="snipts" id="snipts">
|
<section class="snipts" id="snipts">
|
||||||
{% if not request.user.is_authenticated %}
|
{% if not request.user.is_authenticated %}
|
||||||
{% include 'ad-leaderboard-pro.html' %}
|
{% include 'ad-leaderboard-pro.html' %}
|
||||||
{% elif not request.user.profile.teams_beta_seen %}
|
{% elif not request.user.profile.teams_beta_seen and not request.user.team and not request.user.profile.has_teams %}
|
||||||
{% include 'ad-leaderboard-teams.html' %}
|
{% include 'ad-leaderboard-teams.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% with 'true' as detail %}
|
{% with 'true' as detail %}
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
{% extends "snipts/list.html" %}
|
{% extends "snipts/list.html" %}
|
||||||
|
|
||||||
{% block add-snipt %}
|
{% block add-snipt %}
|
||||||
<li class="add-snipt">
|
{% include 'add-snipt.html' %}
|
||||||
<button class="btn btn-info btn-large" id="add-snipt">
|
|
||||||
Add {% if request.user.username == 'blog' %}Post{% else %}Snipt{% endif %}
|
|
||||||
<i class="icon-search icon-plus icon-white"></i>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb %}
|
{% block breadcrumb %}
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
{% extends "snipts/list.html" %}
|
{% extends "snipts/list.html" %}
|
||||||
|
|
||||||
{% block add-snipt %}
|
{% block add-snipt %}
|
||||||
<li class="add-snipt">
|
{% include 'add-snipt.html' %}
|
||||||
<button class="btn btn-info btn-large" id="add-snipt">
|
|
||||||
Add {% if request.user.username == 'blog' %}Post{% else %}Snipt{% endif %}
|
|
||||||
<i class="icon-search icon-plus icon-white"></i>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb %}
|
{% block breadcrumb %}
|
||||||
|
|
|
@ -12,13 +12,18 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if 'team-cancelled' in request.GET %}
|
||||||
|
<div class="alert alert-success" style="margin: 30px;">
|
||||||
|
Your team plan has been succesfully cancelled.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<section class="snipts" id="snipts" ng-controller="SniptListController"
|
<section class="snipts" id="snipts" ng-controller="SniptListController"
|
||||||
{% if request.user.profile.list_view == 'C' %}
|
{% if request.user.profile.list_view == 'C' %}
|
||||||
ng-cloak ng-show="$root.account.id"
|
ng-cloak ng-show="$root.account.id"
|
||||||
{% endif %}>
|
{% endif %}>
|
||||||
{% if not request.user.is_authenticated %}
|
{% if not request.user.is_authenticated %}
|
||||||
{% include 'ad-leaderboard-pro.html' %}
|
{% include 'ad-leaderboard-pro.html' %}
|
||||||
{% elif not request.user.profile.teams_beta_seen %}
|
{% elif not request.user.profile.teams_beta_seen and not request.user.team and not request.user.profile.has_teams %}
|
||||||
{% include 'ad-leaderboard-teams.html' %}
|
{% include 'ad-leaderboard-teams.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% autopaginate snipts 10 %}
|
{% autopaginate snipts 10 %}
|
||||||
|
|
|
@ -37,6 +37,23 @@
|
||||||
</div>
|
</div>
|
||||||
<aside>
|
<aside>
|
||||||
<div class="in">
|
<div class="in">
|
||||||
|
<% if (snipt.new_from_js) { %>
|
||||||
|
{% endverbatim %}
|
||||||
|
{% if request.user.profile.has_teams %}
|
||||||
|
<div class="user">
|
||||||
|
<label class="user">
|
||||||
|
<span>User / team</span>
|
||||||
|
<select name="user" id="id_user">
|
||||||
|
<option value="{{ request.user.username }}">{{ request.user.username }}</option>
|
||||||
|
{% for team in request.user.profile.teams %}
|
||||||
|
<option value="{{ team.slug }}">{{ team.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% verbatim %}
|
||||||
|
<% } %>
|
||||||
<div class="type-lexer">
|
<div class="type-lexer">
|
||||||
<label class="lexer type-lexer">
|
<label class="lexer type-lexer">
|
||||||
<span>Type</span>
|
<span>Type</span>
|
||||||
|
|
|
@ -35,11 +35,9 @@
|
||||||
</div>
|
</div>
|
||||||
<aside>
|
<aside>
|
||||||
<ul class="options">
|
<ul class="options">
|
||||||
<% if (snipt.user.username === window.user) { %>
|
<li>
|
||||||
<li>
|
<a class="edit" href="#">Edit</a>
|
||||||
<a class="edit" href="#">Edit</a>
|
</li>
|
||||||
</li>
|
|
||||||
<% } %>
|
|
||||||
<li>
|
<li>
|
||||||
<a class="embed" href="#">Embed</a>
|
<a class="embed" href="#">Embed</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -47,16 +45,18 @@
|
||||||
<a class="copy" href="#">Copy</a>
|
<a class="copy" href="#">Copy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<section class="meta tags">
|
<% if (typeof(snipt.tags) === 'object') { %>
|
||||||
<h2><%= snipt.tags.length %> tag<% if ((snipt.tags.length > 1) || (snipt.tags.length === 0)) { print('s'); } %></h2>
|
<section class="meta tags">
|
||||||
<ul>
|
<h2><%= snipt.tags.length %> tag<% if ((snipt.tags.length > 1) || (snipt.tags.length === 0)) { print('s'); } %></h2>
|
||||||
<% for (var i=0; i < snipt.tags.length; i++) { %>
|
<ul>
|
||||||
<li <% if (i > 2 && !window.detail) { %>class="hidden"<% } %>>
|
<% for (var i=0; i < snipt.tags.length; i++) { %>
|
||||||
<a href="<%= snipt.tags[i].absolute_url %>"><%= snipt.tags[i].name %></a>
|
<li <% if (i > 2 && !window.detail) { %>class="hidden"<% } %>>
|
||||||
</li>
|
<a href="<%= snipt.tags[i].absolute_url %>"><%= snipt.tags[i].name %></a>
|
||||||
<% } %>
|
</li>
|
||||||
</ul>
|
<% } %>
|
||||||
</section>
|
</ul>
|
||||||
|
</section>
|
||||||
|
<% } %>
|
||||||
<section class="meta stats">
|
<section class="meta stats">
|
||||||
<ul>
|
<ul>
|
||||||
<li><%= snipt.views %> views</li>
|
<li><%= snipt.views %> views</li>
|
||||||
|
@ -72,16 +72,15 @@
|
||||||
<a href="<%= snipt.user.absolute_url %>">
|
<a href="<%= snipt.user.absolute_url %>">
|
||||||
<%= snipt.user.username %>
|
<%= snipt.user.username %>
|
||||||
</a>
|
</a>
|
||||||
<% if (window.user_is_pro) { %>
|
<% if (window.teams.indexOf(snipt.user.username) !== -1) { %>
|
||||||
|
<span class="pro"><a href="/for-teams/">Team</a></span>
|
||||||
|
<% } else if (window.user_has_pro) { %>
|
||||||
<span class="pro"><a href="/pro/">Pro</a></span>
|
<span class="pro"><a href="/pro/">Pro</a></span>
|
||||||
<% } %>
|
<% } %>
|
||||||
</li>
|
</li>
|
||||||
<% if (!snipt.new_from_js) { %>
|
<% if (!snipt.new_from_js) { %>
|
||||||
<li class="created" title="<%= snipt.created %>"><%= snipt.created_formatted %></li>
|
<li class="created" title="<%= snipt.created %>"><%= snipt.created_formatted %></li>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (snipt.public && !window.detail) { %>
|
|
||||||
<li class="comments"><a href="<%= snipt.absolute_url %>#disqus_thread" data-disqus-identifier="<%= snipt.id %>"></a></li>
|
|
||||||
<% } %>
|
|
||||||
<li class="raw">
|
<li class="raw">
|
||||||
<a href="<%= snipt.raw_url %>">Raw</a> /
|
<a href="<%= snipt.raw_url %>">Raw</a> /
|
||||||
<a href="<%= snipt.raw_url %>?nice">Raw Nice</a>
|
<a href="<%= snipt.raw_url %>?nice">Raw Nice</a>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{% if snipt.line_count > 8 and not detail and 'snipt-expand' not in snipt.tags_list %}
|
{% if snipt.line_count > 8 and not detail and 'snipt-expand' not in snipt.tags_list %}
|
||||||
expandable
|
expandable
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if snipt.user == request.user %}
|
{% if snipt|is_authorized_user:request.user %}
|
||||||
editable
|
editable
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_favorited %}
|
{% if is_favorited %}
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
{% block aside %}
|
{% block aside %}
|
||||||
<aside ng-show="!account || account.list_view == 'N'">
|
<aside ng-show="!account || account.list_view == 'N'">
|
||||||
<ul class="options">
|
<ul class="options">
|
||||||
{% if snipt.user == request.user %}
|
{% if snipt|is_authorized_user:request.user %}
|
||||||
{% if snipt.line_count <= 300 or detail %}
|
{% if snipt.line_count <= 300 or detail %}
|
||||||
<li>
|
<li>
|
||||||
<a class="edit" href="#">Edit</a>
|
<a class="edit" href="#">Edit</a>
|
||||||
|
@ -132,7 +132,7 @@
|
||||||
<a class="copy" href="#">Copy</a>
|
<a class="copy" href="#">Copy</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if snipt.user != request.user and request.user.is_authenticated %}
|
{% if snipt.user != request.user and request.user.is_authenticated and not snipt.user.team %}
|
||||||
<li>
|
<li>
|
||||||
{% if is_favorited %}
|
{% if is_favorited %}
|
||||||
<a class="favorite favorited" href="#">Favorited</a>
|
<a class="favorite favorited" href="#">Favorited</a>
|
||||||
|
@ -178,7 +178,9 @@
|
||||||
<li class="author">
|
<li class="author">
|
||||||
<span class="avatar" style="background-image: url('https://secure.gravatar.com/avatar/{{ snipt.user.email|md5 }}?s=15&d=https://snipt.s3.amazonaws.com/img/author-icon.png');"></span>
|
<span class="avatar" style="background-image: url('https://secure.gravatar.com/avatar/{{ snipt.user.email|md5 }}?s=15&d=https://snipt.s3.amazonaws.com/img/author-icon.png');"></span>
|
||||||
<a href="{{ snipt.user.get_absolute_url }}">{{ snipt.user.username }}</a>
|
<a href="{{ snipt.user.get_absolute_url }}">{{ snipt.user.username }}</a>
|
||||||
{% if snipt.user.profile.is_pro %}
|
{% if snipt.user.profile.is_a_team %}
|
||||||
|
<span class="pro"><a href="/for-teams/">Team</a></span>
|
||||||
|
{% elif snipt.user.profile.has_pro %}
|
||||||
<span class="pro"><a href="/pro/">Pro</a></span>
|
<span class="pro"><a href="/pro/">Pro</a></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if snipt.user.profile.gittip_username %}
|
{% if snipt.user.profile.gittip_username %}
|
||||||
|
@ -234,7 +236,7 @@
|
||||||
<div class="hide public">{{ snipt.public }}</div>
|
<div class="hide public">{{ snipt.public }}</div>
|
||||||
<div class="hide blog-post">{{ snipt.blog_post }}</div>
|
<div class="hide blog-post">{{ snipt.blog_post }}</div>
|
||||||
<div class="hide publish-date">{{ snipt.publish_date|date:"M d, Y \a\t h:i A" }}</div>
|
<div class="hide publish-date">{{ snipt.publish_date|date:"M d, Y \a\t h:i A" }}</div>
|
||||||
{% if snipt.user == request.user %}
|
{% if snipt|is_authorized_user:request.user %}
|
||||||
<div class="hide resource-uri">/api/private/snipt/{{ snipt.pk }}/</div>
|
<div class="hide resource-uri">/api/private/snipt/{{ snipt.pk }}/</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="hide resource-uri">/api/public/snipt/{{ snipt.pk }}/</div>
|
<div class="hide resource-uri">/api/public/snipt/{{ snipt.pk }}/</div>
|
||||||
|
|
|
@ -61,3 +61,8 @@ def generate_line_numbers(context, line_numbers):
|
||||||
@register.filter
|
@register.filter
|
||||||
def md5(string):
|
def md5(string):
|
||||||
return hashlib.md5(string.lower()).hexdigest()
|
return hashlib.md5(string.lower()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def is_authorized_user(snipt, user):
|
||||||
|
return snipt.is_authorized_user(user)
|
||||||
|
|
|
@ -12,7 +12,10 @@ def slugify_uniquely(value, model, slugfield="slug"):
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if suffix:
|
if suffix:
|
||||||
potential = "-".join([base, str(suffix)])
|
if value:
|
||||||
|
potential = "-".join([base, str(suffix)])
|
||||||
|
else:
|
||||||
|
potential = str(suffix)
|
||||||
if not model.objects.filter(**{slugfield: potential}).count():
|
if not model.objects.filter(**{slugfield: potential}).count():
|
||||||
return potential
|
return potential
|
||||||
suffix = str(uuid.uuid4()).split('-')[0]
|
suffix = str(uuid.uuid4()).split('-')[0]
|
||||||
|
|
|
@ -14,6 +14,7 @@ from haystack.query import EmptySearchQuerySet, SearchQuerySet
|
||||||
from pygments.lexers import get_lexer_by_name
|
from pygments.lexers import get_lexer_by_name
|
||||||
from snipts.models import Favorite, Snipt
|
from snipts.models import Favorite, Snipt
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
from teams.models import Team
|
||||||
|
|
||||||
RESULTS_PER_PAGE = getattr(settings, 'HAYSTACK_SEARCH_RESULTS_PER_PAGE', 20)
|
RESULTS_PER_PAGE = getattr(settings, 'HAYSTACK_SEARCH_RESULTS_PER_PAGE', 20)
|
||||||
|
|
||||||
|
@ -196,7 +197,10 @@ def list_user(request, username_or_custom_slug, tag_slug=None):
|
||||||
snipts = Snipt.objects
|
snipts = Snipt.objects
|
||||||
|
|
||||||
if user == request.user or \
|
if user == request.user or \
|
||||||
(request.GET.get('api_key') == user.api_key.key):
|
(request.GET.get('api_key') == user.api_key.key) or \
|
||||||
|
(user.profile.is_a_team and
|
||||||
|
user.team.user_is_member(request.user)):
|
||||||
|
|
||||||
public = False
|
public = False
|
||||||
|
|
||||||
favorites = Favorite.objects.filter(user=user).values('snipt')
|
favorites = Favorite.objects.filter(user=user).values('snipt')
|
||||||
|
@ -283,17 +287,33 @@ def search(request, template='search/search.html', load_all=True,
|
||||||
query = ''
|
query = ''
|
||||||
results = EmptySearchQuerySet()
|
results = EmptySearchQuerySet()
|
||||||
|
|
||||||
# We have a query.
|
|
||||||
if request.GET.get('q'):
|
if request.GET.get('q'):
|
||||||
|
|
||||||
if request.user.is_authenticated() and '--mine' in \
|
searchqueryset = SearchQuerySet() \
|
||||||
request.GET.get('q'):
|
.filter(Q(public=True) | Q(author=request.user)) \
|
||||||
|
.order_by('-pub_date')
|
||||||
|
|
||||||
|
if request.user.is_authenticated() and \
|
||||||
|
'mine-only' in request.GET:
|
||||||
searchqueryset = SearchQuerySet().filter(author=request.user) \
|
searchqueryset = SearchQuerySet().filter(author=request.user) \
|
||||||
.order_by('-pub_date')
|
.order_by('-pub_date')
|
||||||
else:
|
|
||||||
searchqueryset = SearchQuerySet() \
|
elif request.user.is_authenticated() and \
|
||||||
.filter(Q(public=True) | Q(author=request.user)) \
|
('author' in request.GET and
|
||||||
.order_by('-pub_date')
|
request.GET.get('author')):
|
||||||
|
|
||||||
|
author = request.GET.get('author')
|
||||||
|
|
||||||
|
if author == request.user.username:
|
||||||
|
searchqueryset = SearchQuerySet().filter(author=request.user) \
|
||||||
|
.order_by('-pub_date')
|
||||||
|
|
||||||
|
else:
|
||||||
|
team = get_object_or_None(Team, slug=author)
|
||||||
|
|
||||||
|
if team and team.user_is_member(request.user):
|
||||||
|
searchqueryset = SearchQuerySet().filter(author=team) \
|
||||||
|
.order_by('-pub_date')
|
||||||
|
|
||||||
form = ModelSearchForm(request.GET,
|
form = ModelSearchForm(request.GET,
|
||||||
searchqueryset=searchqueryset,
|
searchqueryset=searchqueryset,
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from teams.models import Team
|
||||||
|
|
||||||
|
|
||||||
|
class TeamAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'owner', 'created', 'modified')
|
||||||
|
ordering = ('-created',)
|
||||||
|
|
||||||
|
admin.site.register(Team, TeamAdmin)
|
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Team',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('slug', models.SlugField(max_length=255, blank=True)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modified', models.DateTimeField(auto_now=True)),
|
||||||
|
('members', models.ManyToManyField(related_name='member', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('owner', models.ForeignKey(related_name='owner', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(default='nick@snipt.net', max_length=255),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0002_team_email'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(null=True, blank=True, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0003_auto_20150818_0057'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='members',
|
||||||
|
field=models.ManyToManyField(related_name='member', to=settings.AUTH_USER_MODEL, blank=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0004_auto_20150930_1526'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='stripe_id',
|
||||||
|
field=models.CharField(max_length=100, null=True, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=30),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0006_team_plan'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='disabled',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0007_team_disabled'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='plan',
|
||||||
|
field=models.CharField(default=b'snipt-teams-25-monthly', max_length=100, null=True, blank=True, 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')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,72 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from snipts.utils import slugify_uniquely
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
members = models.ManyToManyField(User, related_name='member', blank=True)
|
||||||
|
name = models.CharField(max_length=30)
|
||||||
|
owner = models.ForeignKey(User, related_name='owner')
|
||||||
|
slug = models.SlugField(max_length=255, blank=True)
|
||||||
|
stripe_id = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
user = models.OneToOneField(User, blank=True, null=True)
|
||||||
|
plan = models.CharField(max_length=100, default='snipt-teams-25-monthly',
|
||||||
|
choices=PLANS, blank=True, null=True)
|
||||||
|
disabled = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
created = models.DateTimeField(auto_now_add=True, editable=False)
|
||||||
|
modified = models.DateTimeField(auto_now=True, editable=False)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify_uniquely(self.name, User, 'username')
|
||||||
|
return super(Team, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def member_count(self):
|
||||||
|
return self.members.all().count() + 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def member_limit(self):
|
||||||
|
|
||||||
|
if self.disabled:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
def user_is_member(self, user):
|
||||||
|
if self.disabled:
|
||||||
|
return False
|
||||||
|
if self.owner == user or user in self.members.all():
|
||||||
|
return True
|
||||||
|
return False
|
|
@ -13,12 +13,19 @@
|
||||||
<form class="form-horizontal static-box" id="pro-signup" method="post" action="/pro/complete/">
|
<form class="form-horizontal static-box" id="pro-signup" method="post" action="/pro/complete/">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
You rock.
|
Team created successfully.
|
||||||
<p class="sub" style="padding: 0 120px;">
|
|
||||||
We'll email you to get more information about your team and to help you get your first team created.
|
|
||||||
If you ever need anything at all, <a href="mailto:support@snipt.net">email support</a>, or use the chat in the bottom right.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.user.username }}">View your team profile</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.user.username }}/members">Add some members</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.user.username }}/billing">View billing info</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -0,0 +1,159 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block page-title %}Snipt for Teams{% endblock %}
|
||||||
|
|
||||||
|
{% block body-class %}{{ block.super }} static signup pro pro-signup{% endblock %}
|
||||||
|
|
||||||
|
{% block extra-scripts %}
|
||||||
|
<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% if debug %}
|
||||||
|
Stripe.setPublishableKey('pk_test_cgknmaWRMQeJt2adEdvH3T9l');
|
||||||
|
{% else %}
|
||||||
|
Stripe.setPublishableKey('pk_live_gUO2nCl7dhx6j0posz6gnbhA');
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumb %}
|
||||||
|
<li><a href="/for-teams/">Snipt for Teams</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if request.GET.declined %}
|
||||||
|
<div class="alert alert-error" style="margin: 30px;">
|
||||||
|
<strong>{{ request.GET.declined }}</strong> You have not been charged. Please try again.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<form class="form-horizontal static-box" id="pro-signup" method="post" action="/for-teams/complete/">
|
||||||
|
<fieldset>
|
||||||
|
<div class="info">
|
||||||
|
Snipt for Teams
|
||||||
|
<ul class="features">
|
||||||
|
<li>Team profile at snipt.net/{team-name}.</li>
|
||||||
|
<li>Members can create public or private snipts on a team.</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>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 14-day free trial.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% if not request.user.is_authenticated %}
|
||||||
|
<div class="login-first">
|
||||||
|
<span>
|
||||||
|
To create a team, <a href="/signup/?next=/for-teams/">sign up</a> or <a href="/login/?next=/for-teams/">log in</a>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="payment-form">
|
||||||
|
<div class="payment-loading -teams"><span>Please wait…</span></div>
|
||||||
|
<div class="payment-errors alert alert-error"></div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="team-name">Team name:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input maxlength="30" required type="text" class="input-xlarge" name="team-name" id="team-name" />
|
||||||
|
<p class="sub" style="margin-top: 3px; color: #999999;">
|
||||||
|
Maximum of 30 characters.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="email">Team email:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input required type="email" class="input-xlarge" name="email" id="email" />
|
||||||
|
<p class="sub" style="margin-top: 3px; color: #999999;">
|
||||||
|
For billing and your team's Gravatar. Will remain private.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="plan">Payment plan:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<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-100-monthly">100 users - $149/month</option>
|
||||||
|
<option value="snipt-teams-250-monthly">250 users - $299/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>
|
||||||
|
</select>
|
||||||
|
<p class="sub" style="margin-top: 3px; color: #999999;">
|
||||||
|
Free 14-day trial (your card won't be charged until then, and you can cancel at any time).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="number">Card number:</label>
|
||||||
|
<div class="controls cards">
|
||||||
|
<input type="text" class="input-xlarge" id="number" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-visa.png" alt="Visa" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-mastercard.png" alt="MasterCard" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-discover.png" alt="Discover" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-american-express.png" alt="American Express" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="exp-month">Expiration date:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<select id="exp-month" class="span2 exp-month">
|
||||||
|
<option value="">----</option>
|
||||||
|
<option value="01">01 - January</option>
|
||||||
|
<option value="02">02 - February</option>
|
||||||
|
<option value="03">03 - March</option>
|
||||||
|
<option value="04">04 - April</option>
|
||||||
|
<option value="05">05 - May</option>
|
||||||
|
<option value="06">06 - June</option>
|
||||||
|
<option value="07">07 - July</option>
|
||||||
|
<option value="08">08 - August</option>
|
||||||
|
<option value="09">09 - September</option>
|
||||||
|
<option value="10">10 - October</option>
|
||||||
|
<option value="11">11 - November</option>
|
||||||
|
<option value="12">12 - December</option>
|
||||||
|
</select>
|
||||||
|
<select id="exp-year" class="span2">
|
||||||
|
<option value="">----</option>
|
||||||
|
<option value="2015">2015</option>
|
||||||
|
<option value="2016">2016</option>
|
||||||
|
<option value="2017">2017</option>
|
||||||
|
<option value="2018">2018</option>
|
||||||
|
<option value="2019">2019</option>
|
||||||
|
<option value="2020">2020</option>
|
||||||
|
<option value="2021">2021</option>
|
||||||
|
<option value="2022">2022</option>
|
||||||
|
<option value="2023">2023</option>
|
||||||
|
<option value="2024">2024</option>
|
||||||
|
<option value="2025">2025</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="cvc">Security code (CVC):</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-min span1" id="cvc">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-success">Create team »</button>
|
||||||
|
<div class="security">
|
||||||
|
<a href="https://stripe.com/help/security">Secure</a> by default. Every Snipt page is secure.
|
||||||
|
</div>
|
||||||
|
<div class="stripe" style="margin-left: 125px;">
|
||||||
|
Your credit card is stored securely with <a href="https://stripe.com">Stripe</a> and we use <a href="https://stripe.com/docs/stripe.js">Stripe.js</a> for maximum security.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block analytics %}
|
||||||
|
{% if not debug %}
|
||||||
|
window.ll('tagScreen', 'Team beta signup view');
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,64 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load team_tags %}
|
||||||
|
|
||||||
|
{% 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 }}/">Profile</a>
|
||||||
|
</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">
|
||||||
|
{{ name }}
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Price">
|
||||||
|
${{ amount|currency_convert }}.00 USD / {{ interval }}
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Card">
|
||||||
|
xxxx-xxxx-xxxx-{{ last4 }}
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Status">
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Team since">
|
||||||
|
{{ team.created|date:'M d, Y' }}
|
||||||
|
</div>
|
||||||
|
<div class="def" data-title="Next bill date" ng-show="user.stripeAccount.status != 'inactive'">
|
||||||
|
{{ nextBill|to_date|date:'M d, Y' }}
|
||||||
|
</div>
|
||||||
|
<form class="alert alert-info group" style="margin: 15px; padding-right: 8px;" id="cancel-team-subscription" action="/{{ team.slug }}/billing/cancel/" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-danger pull-right">
|
||||||
|
Cancel subscription
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block analytics %}
|
||||||
|
{% if not debug %}
|
||||||
|
window.ll('tagScreen', 'Team billing view');
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,83 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load snipt_tags %}
|
||||||
|
|
||||||
|
{% block page-title %}Team Members{% 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 }}/members/">Members</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div ng-controller="TeamController" class="team-controller">
|
||||||
|
<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 }}/">Profile</a>
|
||||||
|
</li>
|
||||||
|
<li class="active">
|
||||||
|
<a href="/{{ team.slug }}/members/">Members</a>
|
||||||
|
</li>
|
||||||
|
{% if team.owner == request.user %}
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.slug }}/billing/">Billing</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
<section class="content">
|
||||||
|
{% if 'limit-reached' in request.GET %}
|
||||||
|
<p class="alert alert-error group">
|
||||||
|
You have no seats available to add this member.
|
||||||
|
To upgrade your plan, contact <a href="mailto:support@snipt.net">support@snipt.net</a>.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="def" data-title="Owner">
|
||||||
|
{{ team.owner }}
|
||||||
|
</div>
|
||||||
|
{% if team.owner == request.user %}
|
||||||
|
<div class="def add-member" data-title="Add member">
|
||||||
|
<input
|
||||||
|
ng-model="search"
|
||||||
|
placeholder="Search users..."
|
||||||
|
type="search"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<ul ng-cloak ng-if="users.length">
|
||||||
|
<li ng-repeat="user in users" class="user">
|
||||||
|
<img src="https://secure.gravatar.com/avatar/{[{ user.email_md5 }]}?s=26" />
|
||||||
|
<a href="/{[{ user.username }]}/"><span>{[{ user.username }]}</span></a>
|
||||||
|
<a class="btn btn-small" href="/{{ team.slug }}/members/add/{[{ user.username }]}/">Add »</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="def" data-title="Members ({{ team.members.all|length }} of {{ team.member_limit }})">
|
||||||
|
<ul class="member-list">
|
||||||
|
{% for member in team.members.all %}
|
||||||
|
<li class="user">
|
||||||
|
<img src="https://secure.gravatar.com/avatar/{{ member.email|md5 }}?s=26" />
|
||||||
|
<a href="/{{ member.username }}/"><span>{{ member.username }}</span></a>
|
||||||
|
{% if team.owner == request.user %}
|
||||||
|
<a class="btn btn-small" href="/{{ team.slug }}/members/remove/{{ member.username }}/">Remove »</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block analytics %}
|
||||||
|
{% if not debug %}
|
||||||
|
window.ll('tagScreen', 'Team members view');
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def user_is_member(team, user):
|
||||||
|
return team.user_is_member(user)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def currency_convert(amount):
|
||||||
|
return amount / 100
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def to_date(timestamp):
|
||||||
|
return datetime.datetime.fromtimestamp(float(timestamp))
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django.conf.urls import *
|
||||||
|
from teams import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = \
|
||||||
|
patterns('',
|
||||||
|
url(r'^for-teams/$', views.for_teams),
|
||||||
|
url(r'^for-teams/complete/$', views.for_teams_complete),
|
||||||
|
url(r'^(?P<username>[^/]+)/members/remove/(?P<member>[^/]+)/$',
|
||||||
|
views.remove_team_member,
|
||||||
|
name='remove-team-member'),
|
||||||
|
url(r'^(?P<username>[^/]+)/members/add/(?P<member>[^/]+)/$',
|
||||||
|
views.add_team_member,
|
||||||
|
name='add-team-member'),
|
||||||
|
url(r'^(?P<username>[^/]+)/members/$',
|
||||||
|
views.team_members,
|
||||||
|
name='team-members'),
|
||||||
|
url(r'^(?P<username>[^/]+)/billing/$',
|
||||||
|
views.team_billing,
|
||||||
|
name='team-billing'),
|
||||||
|
url(r'^(?P<username>[^/]+)/billing/cancel/$',
|
||||||
|
views.cancel_team_subscription,
|
||||||
|
name='team-cancel-subscription'))
|
|
@ -0,0 +1,178 @@
|
||||||
|
import os
|
||||||
|
import stripe
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from annoying.decorators import render_to
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.http import Http404, HttpResponseRedirect, HttpResponseBadRequest
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from teams.models import Team
|
||||||
|
|
||||||
|
|
||||||
|
@render_to('teams/for-teams.html')
|
||||||
|
def for_teams(request):
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
profile = request.user.profile
|
||||||
|
profile.teams_beta_seen = True
|
||||||
|
profile.save()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@render_to('teams/for-teams-complete.html')
|
||||||
|
def for_teams_complete(request):
|
||||||
|
if request.method == 'POST' and request.user.is_authenticated():
|
||||||
|
|
||||||
|
token = request.POST['token']
|
||||||
|
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
|
||||||
|
settings.STRIPE_SECRET_KEY)
|
||||||
|
|
||||||
|
plan = request.POST['plan']
|
||||||
|
|
||||||
|
try:
|
||||||
|
customer = stripe.Customer.create(card=token,
|
||||||
|
plan=plan,
|
||||||
|
email=request.user.email)
|
||||||
|
except stripe.CardError, e:
|
||||||
|
error_message = e.json_body['error']['message']
|
||||||
|
return HttpResponseRedirect('/for-teams/?declined=%s' %
|
||||||
|
error_message or
|
||||||
|
'Your card was declined.')
|
||||||
|
|
||||||
|
team = Team(name=request.POST['team-name'],
|
||||||
|
email=request.POST['email'],
|
||||||
|
plan=plan,
|
||||||
|
owner=request.user)
|
||||||
|
team.stripe_id = customer.id
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
user = User.objects.create_user(team.slug,
|
||||||
|
team.email,
|
||||||
|
str(uuid.uuid4()))
|
||||||
|
|
||||||
|
team.user = user
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
send_mail('[Snipt] New team signup: {}'.format(team.name),
|
||||||
|
"""
|
||||||
|
Team: https://snipt.net/{}
|
||||||
|
Email: {}
|
||||||
|
Plan: {}
|
||||||
|
""".format(team.slug, team.email, team.plan),
|
||||||
|
'support@snipt.net',
|
||||||
|
['nick@snipt.net'],
|
||||||
|
fail_silently=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'team': team
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@render_to('teams/team-billing.html')
|
||||||
|
def team_billing(request, username):
|
||||||
|
team = get_object_or_404(Team, slug=username, disabled=False)
|
||||||
|
if team.owner != request.user:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
|
||||||
|
settings.STRIPE_SECRET_KEY)
|
||||||
|
customer = stripe.Customer.retrieve(team.stripe_id)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'last4': customer.active_card.last4,
|
||||||
|
'created': customer.created,
|
||||||
|
'email': customer.email,
|
||||||
|
'team': team
|
||||||
|
}
|
||||||
|
|
||||||
|
if customer.subscription:
|
||||||
|
data['amount'] = customer.subscription.plan.amount
|
||||||
|
data['interval'] = customer.subscription.plan.interval
|
||||||
|
data['name'] = customer.subscription.plan.name
|
||||||
|
data['status'] = customer.subscription.status
|
||||||
|
data['nextBill'] = customer.subscription.current_period_end
|
||||||
|
else:
|
||||||
|
data['status'] = 'inactive'
|
||||||
|
|
||||||
|
return data
|
||||||
|
return {
|
||||||
|
'team': team
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@render_to('teams/team-members.html')
|
||||||
|
def team_members(request, username):
|
||||||
|
team = get_object_or_404(Team, slug=username, disabled=False)
|
||||||
|
if not team.user_is_member(request.user):
|
||||||
|
raise Http404
|
||||||
|
return {
|
||||||
|
'team': team
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_team_member(request, username, member):
|
||||||
|
team = get_object_or_404(Team, slug=username, disabled=False)
|
||||||
|
user = get_object_or_404(User, username=member)
|
||||||
|
|
||||||
|
if (team.owner != request.user):
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
if ((team.members.all().count() + 1) > team.member_limit):
|
||||||
|
return HttpResponseRedirect('/' + team.slug +
|
||||||
|
'/members/?limit-reached')
|
||||||
|
else:
|
||||||
|
team.members.add(user)
|
||||||
|
return HttpResponseRedirect('/' + team.slug + '/members/')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def remove_team_member(request, username, member):
|
||||||
|
team = get_object_or_404(Team, slug=username, disabled=False)
|
||||||
|
user = get_object_or_404(User, username=member)
|
||||||
|
|
||||||
|
if (team.owner != request.user):
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
team.members.remove(user)
|
||||||
|
|
||||||
|
return HttpResponseRedirect('/' + team.slug + '/members/')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def cancel_team_subscription(request, username):
|
||||||
|
|
||||||
|
if request.method != 'POST':
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, slug=username, disabled=False)
|
||||||
|
if team.owner != request.user:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
|
||||||
|
settings.STRIPE_SECRET_KEY)
|
||||||
|
customer = stripe.Customer.retrieve(team.stripe_id)
|
||||||
|
customer.delete()
|
||||||
|
|
||||||
|
team.disabled = True
|
||||||
|
team.stripe_id = None
|
||||||
|
team.plan = None
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
send_mail('[Snipt] Team cancelled plan: {}'.format(team.name),
|
||||||
|
"""
|
||||||
|
Team: https://snipt.net/{}
|
||||||
|
Email: {}
|
||||||
|
""".format(team.slug, team.email),
|
||||||
|
'support@snipt.net',
|
||||||
|
['nick@snipt.net'],
|
||||||
|
fail_silently=False)
|
||||||
|
|
||||||
|
return HttpResponseRedirect('/' + team.slug + '/?team-cancelled=true')
|
|
@ -1,4 +1,4 @@
|
||||||
<a href="/signup" class="snipt-promo">
|
<a href="/signup" class="snipt-promo">
|
||||||
<button class="btn btn-success btn-large pull-right">Sign up »</button>
|
<button class="btn btn-success btn-large pull-right">Sign up »</button>
|
||||||
Sign up for Snipt!<br /><span style="font-size: 16px;">Post public snipts for free. Private snipts just $5/mo.</span>
|
Sign up for Snipt!<br /><span style="font-size: 16px;">Post public snipts for free.</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<li class="add-snipt">
|
||||||
|
<button class="btn btn-info btn-large" id="add-snipt">
|
||||||
|
Add {% if request.user.username == 'blog' %}Post{% else %}Snipt{% endif %}
|
||||||
|
<i class="icon-search icon-plus icon-white"></i>
|
||||||
|
</button>
|
||||||
|
{% if request.user.profile.has_teams %}
|
||||||
|
<ul class="add-snipt-teams">
|
||||||
|
<li>
|
||||||
|
<a href data-intended-user="{{ request.user.username }}">
|
||||||
|
<i class="icon-user icon-white"></i>
|
||||||
|
<span class="as">as</span>
|
||||||
|
<span>{{ request.user.username }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% for team in request.user.profile.teams|dictsort:'name' %}
|
||||||
|
<li>
|
||||||
|
<a href data-intended-user="{{ team.user.username }}">
|
||||||
|
<i class="icon-user icon-white"></i>
|
||||||
|
<span class="as">under</span>
|
||||||
|
<span>{{ team.name }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
|
@ -68,14 +68,14 @@
|
||||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class="{% block body-class %}{% endblock %} {% if request.user.profile.is_pro %}is-pro{% endif %}" ng-controller="AppController">
|
<body class="{% block body-class %}{% endblock %} {% if request.user.profile.has_pro %}is-pro{% endif %}" ng-controller="AppController">
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<header class="main">
|
<header class="main">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div class="shadey"></div>
|
<div class="shadey"></div>
|
||||||
<h1 class="main-logo"><a href="{% if request.user.is_authenticated %}/{{ request.user.username }}/{% else %}/{% endif %}">snip<span>t</span></a></h1>
|
<h1 class="main-logo"><a href="{% if request.user.is_authenticated %}/{{ request.user.username }}/{% else %}/{% endif %}">snip<span>t</span></a></h1>
|
||||||
<form class="search" action="/search/" method="get" ng-controller="HeaderSearchController">
|
<form class="search" action="/search/" method="get">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<input ng-model="search.query" type="text" class="search-query" name="q"
|
<input ng-model="search.query" type="text" class="search-query" name="q"
|
||||||
|
@ -106,9 +106,33 @@
|
||||||
</li>
|
</li>
|
||||||
{% block add-snipt %}{% endblock %}
|
{% block add-snipt %}{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
{% if not request.user.is_authenticated %}
|
||||||
<a href="/for-teams/" {% if '/for-teams/' in request.path %} class="active"{% endif %}>Teams</a>
|
<li>
|
||||||
</li>
|
<a href="/for-teams/" class="{% if '/for-teams/' in request.path %}active{% endif %}">Teams</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="teams-nav">
|
||||||
|
<a href="#" class="teams-nav {% if '/for-teams/' in request.path %}active{% endif %}">Teams</a>
|
||||||
|
<ul>
|
||||||
|
{% if request.user.profile.has_teams %}
|
||||||
|
{% for team in request.user.profile.teams|dictsort:'name' %}
|
||||||
|
<li>
|
||||||
|
<a href="/{{ team.user.username }}/">
|
||||||
|
<i class="icon-user icon-white"></i>
|
||||||
|
{{ team.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
<a href="/for-teams/">
|
||||||
|
<i class="icon-plus icon-white"></i>
|
||||||
|
Create new team
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
@ -119,7 +143,7 @@
|
||||||
<span class="username">{{ request.user.username }}</span>
|
<span class="username">{{ request.user.username }}</span>
|
||||||
<i class="icon-cog icon-white"></i>
|
<i class="icon-cog icon-white"></i>
|
||||||
<span class="type">
|
<span class="type">
|
||||||
{% if request.user.profile.is_pro %}
|
{% if request.user.profile.has_pro %}
|
||||||
<span class="is-pro">Pro</span>
|
<span class="is-pro">Pro</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
Basic member
|
Basic member
|
||||||
|
@ -146,7 +170,7 @@
|
||||||
Account
|
Account
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if not request.user.profile.is_pro %}
|
{% if not request.user.profile.has_pro %}
|
||||||
<li>
|
<li>
|
||||||
<a href="/pro/">
|
<a href="/pro/">
|
||||||
<i class="icon-star-empty icon-white"></i>
|
<i class="icon-star-empty icon-white"></i>
|
||||||
|
@ -198,7 +222,7 @@
|
||||||
{% block aside %}
|
{% block aside %}
|
||||||
<aside class="main">
|
<aside class="main">
|
||||||
{% block ad %}
|
{% block ad %}
|
||||||
{% if not request.user.profile.is_pro %}
|
{% if not request.user.profile.has_pro %}
|
||||||
{% include 'ad-sidebar.html' %}
|
{% include 'ad-sidebar.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -212,7 +236,7 @@
|
||||||
<li class="twitter">
|
<li class="twitter">
|
||||||
<a href="https://twitter.com/#!/snipt"><span>@snipt</span></a>
|
<a href="https://twitter.com/#!/snipt"><span>@snipt</span></a>
|
||||||
</li>
|
</li>
|
||||||
{% if not request.user.profile.is_pro %}
|
{% if not request.user.profile.has_pro %}
|
||||||
<li class="pro">
|
<li class="pro">
|
||||||
<a href="/pro/"><span>Go Pro</span></a>
|
<a href="/pro/"><span>Go Pro</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -340,14 +364,6 @@
|
||||||
<td>p</td>
|
<td>p</td>
|
||||||
<td>Previous page</td>
|
<td>Previous page</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>g</td>
|
|
||||||
<td>Scroll to top of page</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>G</td>
|
|
||||||
<td>Scroll to bottom of page</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -364,6 +380,12 @@
|
||||||
window.user_ip = '{{ request.META.REMOTE_ADDR }}';
|
window.user_ip = '{{ request.META.REMOTE_ADDR }}';
|
||||||
window.user_profile_id = {% firstof request.user.profile.id 'null' %};
|
window.user_profile_id = {% firstof request.user.profile.id 'null' %};
|
||||||
window.user_email = '{{ request.user.email }}';
|
window.user_email = '{{ request.user.email }}';
|
||||||
|
window.teams = [
|
||||||
|
{% for team in request.user.profile.teams %}
|
||||||
|
'{{ team.slug }}',
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
window.intended_user = '{{ request.user.username }}';
|
||||||
|
|
||||||
{% if public %}
|
{% if public %}
|
||||||
window.pub = {{ public|lower }};
|
window.pub = {{ public|lower }};
|
||||||
|
@ -373,10 +395,10 @@
|
||||||
|
|
||||||
window.api_key = '{{ request.user.api_key.key }}';
|
window.api_key = '{{ request.user.api_key.key }}';
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% if request.user.profile.is_pro %}
|
{% if request.user.profile.has_pro %}
|
||||||
window.user_is_pro = true;
|
window.user_has_pro = true;
|
||||||
{% else %}
|
{% else %}
|
||||||
window.user_is_pro = false;
|
window.user_has_pro = false;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
window.default_editor = '{{ request.user.profile.get_default_editor_display|lower }}';
|
window.default_editor = '{{ request.user.profile.get_default_editor_display|lower }}';
|
||||||
window.editor_theme = '{{ request.user.profile.editor_theme }}';
|
window.editor_theme = '{{ request.user.profile.editor_theme }}';
|
||||||
|
@ -398,11 +420,11 @@
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/libs/codemirror.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/libs/codemirror.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/libs/highlight.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/libs/highlight.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/src/application.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/src/application.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}js/src/team.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/src/modules/site.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/src/modules/site.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/src/modules/snipt.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/src/modules/snipt.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/src/account.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/src/account.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/src/snipts.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/src/snipts.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/src/search.js"></script>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}js/snipt-all.min.js?74"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}js/snipt-all.min.js?74"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -433,7 +455,7 @@
|
||||||
custom_data: {
|
custom_data: {
|
||||||
'snipts count': {% snipts_count_for_user %},
|
'snipts count': {% snipts_count_for_user %},
|
||||||
'profile link': 'https://snipt.net/{{ request.user.username }}/',
|
'profile link': 'https://snipt.net/{{ request.user.username }}/',
|
||||||
'is pro': window.user_is_pro,
|
'is pro': window.user_has_pro,
|
||||||
'blog domain': '{{ request.user.profile.blog_domain }}',
|
'blog domain': '{{ request.user.profile.blog_domain }}',
|
||||||
'pro date': {% firstof request.user.profile.pro_date|date:"U" 'null' %},
|
'pro date': {% firstof request.user.profile.pro_date|date:"U" 'null' %},
|
||||||
'username': '{{ request.user.username }}'
|
'username': '{{ request.user.username }}'
|
||||||
|
@ -470,7 +492,7 @@
|
||||||
userId: {{ request.user.id }},
|
userId: {{ request.user.id }},
|
||||||
createdAt: '{{ request.user.date_joined }}',
|
createdAt: '{{ request.user.date_joined }}',
|
||||||
sniptsCount: {% snipts_count_for_user %},
|
sniptsCount: {% snipts_count_for_user %},
|
||||||
isPro: window.user_is_pro,
|
isPro: window.user_has_pro,
|
||||||
blogDomain: '{{ request.user.profile.blog_domain }}',
|
blogDomain: '{{ request.user.profile.blog_domain }}',
|
||||||
proDate: '{% firstof request.user.profile.pro_date 'null' %}'
|
proDate: '{% firstof request.user.profile.pro_date 'null' %}'
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block page-title %}Request beta access to Snipt for Teams{% endblock %}
|
|
||||||
|
|
||||||
{% block body-class %}{{ block.super }} static signup pro pro-signup{% endblock %}
|
|
||||||
|
|
||||||
{% block breadcrumb %}
|
|
||||||
<li><a href="/for-teams/">Snipt for Teams</a></li>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% if request.user.profile.teams_beta_applied %}
|
|
||||||
<div class="alert alert-success" style="margin: 30px;">
|
|
||||||
You've successfully applied for the beta program - we'll email you as soon as we're ready!
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<form class="form-horizontal static-box" id="teams-signup" method="post" action="/for-teams/complete/">
|
|
||||||
<fieldset>
|
|
||||||
<div class="info">
|
|
||||||
Snipt for Teams
|
|
||||||
<ul class="features">
|
|
||||||
<li>Team profile at snipt.net/{team-name}.</li>
|
|
||||||
<li>Members can create public or private snipts on a team.</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>All team members are automatically granted personal <span class="pro">Pro</span> accounts.</li>
|
|
||||||
<li>Plans starting at $49/month.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% if not request.user.is_authenticated %}
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="name">Your name:</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input required type="text" class="input-xlarge" name="username" id="username" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="name">Your email:</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input required type="email" class="input-xlarge" name="email" id="email" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="name">Team name:</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input required type="text" class="input-xlarge" name="name" id="name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="members">How many users?</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input required type="number" class="input-medium" name="members" id="members" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label style="float: none; width: auto; text-align: left; padding-left: 180px; margin-bottom: 10px;" class="control-label" for="members">Tell us a bit about your organization and how<br />you'd like to use Snipt for your team:</label>
|
|
||||||
<div class="controls">
|
|
||||||
<textarea required name="info" name="info" id="info" class="input-xlarge" style="height: 100px;"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="margin-left: 180px;" class="g-recaptcha" data-sitekey="6LerYA0TAAAAAFJaMf7JMnlQR2wzqd_3dMRvLd-4"></div>
|
|
||||||
<div class="form-actions">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-success">Request more info »</button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block analytics %}
|
|
||||||
{% if not debug %}
|
|
||||||
window.ll('tagScreen', 'Team beta signup view');
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -40,86 +40,101 @@
|
||||||
Group discounts available. Email <a href="mailto:support@snipt.net">support@snipt.net</a> for details.
|
Group discounts available. Email <a href="mailto:support@snipt.net">support@snipt.net</a> for details.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="payment-loading"><span>Please wait…</span></div>
|
{% if not request.user.is_authenticated %}
|
||||||
<div class="payment-errors alert alert-error"></div>
|
<div class="login-first">
|
||||||
<div class="control-group">
|
<span>
|
||||||
<label class="control-label" for="name">Payment plan:</label>
|
To go <span class="pro">Pro</span>, <a href="/signup/?next=/pro/">sign up</a> or <a href="/login/?next=/pro/">log in</a>.
|
||||||
<div class="controls">
|
</span>
|
||||||
<select name="plan" type="text" class="input-medium" id="plan">
|
|
||||||
<option value="snipt-pro-monthly">$5/month</option>
|
|
||||||
<option value="snipt-pro-yearly">$60/year</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% elif request.user.profile.has_pro %}
|
||||||
<div class="control-group">
|
<div class="login-first">
|
||||||
<label class="control-label" for="name">Name on card:</label>
|
<span>
|
||||||
<div class="controls">
|
{% if request.user.profile.is_pro %}
|
||||||
<input type="text" class="input-xlarge" id="name" />
|
You're already a <span class="pro">Pro</span>.
|
||||||
|
<a style="display: block; margin-top: 15px;" href="/account/billing/">View details »</a>
|
||||||
|
{% else %}
|
||||||
|
<p style="line-height: 26px;">You're a member of a team, and all team members automatically have <span class="pro">Pro</span> access.</p>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
<div class="control-group">
|
<div class="payment-form">
|
||||||
<label class="control-label" for="number">Card number:</label>
|
<div class="payment-loading"><span>Please wait…</span></div>
|
||||||
<div class="controls cards">
|
<div class="payment-errors alert alert-error"></div>
|
||||||
<input type="text" class="input-xlarge" id="number" />
|
<div class="control-group">
|
||||||
<img src="{{ STATIC_URL }}img/card-visa.png" alt="Visa" />
|
<label class="control-label" for="name">Payment plan:</label>
|
||||||
<img src="{{ STATIC_URL }}img/card-mastercard.png" alt="MasterCard" />
|
<div class="controls">
|
||||||
<img src="{{ STATIC_URL }}img/card-discover.png" alt="Discover" />
|
<select name="plan" type="text" class="input-medium" id="plan">
|
||||||
<img src="{{ STATIC_URL }}img/card-american-express.png" alt="American Express" />
|
<option value="snipt-pro-monthly">$5/month</option>
|
||||||
|
<option value="snipt-pro-yearly">$60/year</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="number">Card number:</label>
|
||||||
|
<div class="controls cards">
|
||||||
|
<input type="text" class="input-xlarge" id="number" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-visa.png" alt="Visa" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-mastercard.png" alt="MasterCard" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-discover.png" alt="Discover" />
|
||||||
|
<img src="{{ STATIC_URL }}img/card-american-express.png" alt="American Express" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="exp-month">Expiration date:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<select id="exp-month" class="span2 exp-month">
|
||||||
|
<option value="">----</option>
|
||||||
|
<option value="01">01 - January</option>
|
||||||
|
<option value="02">02 - February</option>
|
||||||
|
<option value="03">03 - March</option>
|
||||||
|
<option value="04">04 - April</option>
|
||||||
|
<option value="05">05 - May</option>
|
||||||
|
<option value="06">06 - June</option>
|
||||||
|
<option value="07">07 - July</option>
|
||||||
|
<option value="08">08 - August</option>
|
||||||
|
<option value="09">09 - September</option>
|
||||||
|
<option value="10">10 - October</option>
|
||||||
|
<option value="11">11 - November</option>
|
||||||
|
<option value="12">12 - December</option>
|
||||||
|
</select>
|
||||||
|
<select id="exp-year" class="span2">
|
||||||
|
<option value="">----</option>
|
||||||
|
<option value="2015">2015</option>
|
||||||
|
<option value="2016">2016</option>
|
||||||
|
<option value="2017">2017</option>
|
||||||
|
<option value="2018">2018</option>
|
||||||
|
<option value="2019">2019</option>
|
||||||
|
<option value="2020">2020</option>
|
||||||
|
<option value="2021">2021</option>
|
||||||
|
<option value="2022">2022</option>
|
||||||
|
<option value="2023">2023</option>
|
||||||
|
<option value="2024">2024</option>
|
||||||
|
<option value="2025">2025</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="cvc">Security code (CVC):</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-min span1" id="cvc">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-actions">
|
||||||
<div class="control-group">
|
{% csrf_token %}
|
||||||
<label class="control-label" for="exp-month">Expiration date:</label>
|
<button type="submit" class="btn btn-success">Subscribe »</button>
|
||||||
<div class="controls">
|
<div class="security">
|
||||||
<select id="exp-month" class="span2 exp-month">
|
<a href="https://stripe.com/help/security">Secure</a> by default. Every Snipt page is secure.
|
||||||
<option value="">----</option>
|
</div>
|
||||||
<option value="01">01 - January</option>
|
<div class="stripe">
|
||||||
<option value="02">02 - February</option>
|
Your credit card is stored securely with <a href="https://stripe.com">Stripe</a> and we use <a href="https://stripe.com/docs/stripe.js">Stripe.js</a> for maximum security.
|
||||||
<option value="03">03 - March</option>
|
</div>
|
||||||
<option value="04">04 - April</option>
|
|
||||||
<option value="05">05 - May</option>
|
|
||||||
<option value="06">06 - June</option>
|
|
||||||
<option value="07">07 - July</option>
|
|
||||||
<option value="08">08 - August</option>
|
|
||||||
<option value="09">09 - September</option>
|
|
||||||
<option value="10">10 - October</option>
|
|
||||||
<option value="11">11 - November</option>
|
|
||||||
<option value="12">12 - December</option>
|
|
||||||
</select>
|
|
||||||
<select id="exp-year" class="span2">
|
|
||||||
<option value="">----</option>
|
|
||||||
<option value="2014">2014</option>
|
|
||||||
<option value="2015">2015</option>
|
|
||||||
<option value="2016">2016</option>
|
|
||||||
<option value="2017">2017</option>
|
|
||||||
<option value="2018">2018</option>
|
|
||||||
<option value="2019">2019</option>
|
|
||||||
<option value="2020">2020</option>
|
|
||||||
<option value="2021">2021</option>
|
|
||||||
<option value="2022">2022</option>
|
|
||||||
<option value="2022">2023</option>
|
|
||||||
<option value="2022">2024</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-actions" style="color: #A2A2A2;">
|
||||||
<div class="control-group">
|
Prefer to pay with PayPal? Email <a href="mailto:support@snipt.net">support@snipt.net</a>.
|
||||||
<label class="control-label" for="cvc">Security code (CVC):</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input type="text" class="input-min span1" id="cvc">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="form-actions">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-success">Subscribe »</button>
|
|
||||||
<div class="security">
|
|
||||||
<a href="https://stripe.com/help/security">Secure</a> by default. Every Snipt page is secure.
|
|
||||||
</div>
|
|
||||||
<div class="stripe">
|
|
||||||
Your credit card is stored securely with <a href="https://stripe.com">Stripe</a> and we use <a href="https://stripe.com/docs/stripe.js">Stripe.js</a> for maximum security.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-actions" style="color: #A2A2A2;">
|
|
||||||
Prefer to pay with PayPal? Email <a href="mailto:support@snipt.net">support@snipt.net</a>.
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,33 +1,81 @@
|
||||||
{% load snipt_tags %}
|
{% load snipt_tags team_tags %}
|
||||||
|
|
||||||
<div class="profile group">
|
{% if user.profile.is_a_team %}
|
||||||
<a href="/{{ user.username }}/">
|
<div class="profile group">
|
||||||
<img src="https://secure.gravatar.com/avatar/{{ user.email|md5 }}?s=300" alt="{{ user.username }}" title="{{ user.username }}" />
|
<a href="/{{ user.username }}/">
|
||||||
</a>
|
<img src="https://secure.gravatar.com/avatar/{{ user.email|md5 }}?s=300" alt="{{ user.username }}" title="{{ user.username }}" />
|
||||||
<div class="meta">
|
</a>
|
||||||
<div class="username" title="{{ user.username }}">
|
<div class="meta">
|
||||||
<a href="">
|
<div class="username" title="{{ user.username }}">
|
||||||
{{ user.username }}
|
<a href="">
|
||||||
|
{{ user.username }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="member-since">Team since {{ user.date_joined|date:"Y" }}</div>
|
||||||
|
{% if user.profile.get_blog_posts %}
|
||||||
|
<div class="urls">
|
||||||
|
Snipt Blog:
|
||||||
|
<a href="{{ user.profile.get_user_profile_url }}">
|
||||||
|
{{ user.profile.get_user_profile_url }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="member-since">
|
||||||
|
<a href="/{{ user.username }}/members/">
|
||||||
|
{{ user.team.member_count }} member{{ user.team.member_count|pluralize }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% if user.username == 'nick' %}
|
</div>
|
||||||
<div class="member-since">Snipt Founder in {{ user.date_joined|date:"Y" }}</div>
|
</div>
|
||||||
{% else %}
|
{% if user.team|user_is_member:request.user %}
|
||||||
<div class="member-since">Member since {{ user.date_joined|date:"Y" }}</div>
|
<div class="profile team-settings group">
|
||||||
{% endif %}
|
<div class="team-search">
|
||||||
{% if user.profile.get_blog_posts %}
|
<form action="/search/" method="get">
|
||||||
<div class="urls">
|
<input type="text" class="text" value="" name="q" placeholder="Search team snipts...">
|
||||||
Snipt Blog:
|
<input type="hidden" class="text" value="{{ user.username }}" name="author">
|
||||||
<a href="{{ user.profile.get_user_profile_url }}">
|
</form>
|
||||||
{{ user.profile.get_user_profile_url }}
|
</div>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% if user.profile.is_pro %}
|
{% endif %}
|
||||||
<a class="pro" href="/pro/">Pro</a>
|
{% if user.team.owner == request.user %}
|
||||||
{% endif %}
|
<div class="profile team-settings group">
|
||||||
{% if user.profile.gittip_username %}
|
<div class="meta">
|
||||||
<a class="gittip" href="https://www.gittip.com/{{ user.profile.gittip_username }}/">Tip</a>
|
<span class="title">Team settings</span>
|
||||||
{% endif %}
|
<a href="/{{ user.username }}/billing/">Billing »</a>
|
||||||
</div>
|
<a href="/{{ user.username }}/members/">Members »</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="profile group">
|
||||||
|
<a href="/{{ user.username }}/">
|
||||||
|
<img src="https://secure.gravatar.com/avatar/{{ user.email|md5 }}?s=300" alt="{{ user.username }}" title="{{ user.username }}" />
|
||||||
|
</a>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="username" title="{{ user.username }}">
|
||||||
|
<a href="">
|
||||||
|
{{ user.username }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if user.username == 'nick' %}
|
||||||
|
<div class="member-since">Snipt Founder in {{ user.date_joined|date:"Y" }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="member-since">Member since {{ user.date_joined|date:"Y" }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.profile.get_blog_posts %}
|
||||||
|
<div class="urls">
|
||||||
|
Snipt Blog:
|
||||||
|
<a href="{{ user.profile.get_user_profile_url }}">
|
||||||
|
{{ user.profile.get_user_profile_url }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if user.profile.has_pro %}
|
||||||
|
<a class="pro" href="/pro/">Pro</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.profile.gittip_username %}
|
||||||
|
<a class="gittip" href="https://www.gittip.com/{{ user.profile.gittip_username }}/">Tip</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -22,15 +22,47 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="snipts" id="snipts"></section>
|
<section class="snipts" id="snipts"></section>
|
||||||
<div class="static-box {% if page.object_list|length > 0 %}has-snipts{% endif %}">
|
<div class="static-box {% if page.object_list|length > 0 %}has-snipts{% endif %}">
|
||||||
<form method="get" class="form-search" action="." ng-controller="SearchController">
|
<form method="get" class="form-search" action=".">
|
||||||
<input ng-model="search.query" type="text" class="search-query" name="q"
|
{% if not request.user.profile.has_teams %}
|
||||||
ng-init="search.query='{{ query|escapejs }}'"
|
<input type="text" class="search-query" name="q"
|
||||||
placeholder="Search snipts" id="id_q"
|
placeholder="Search snipts" id="id_q"
|
||||||
value="{{ query }}" />
|
value="{{ query }}" />
|
||||||
<label class="checkbox inline mine-only" ng-click="toggleMineOnly()">
|
{% if request.user.is_authenticated %}
|
||||||
<input {% if '--mine' in query %}checked="checked"{% endif %} ng-model="search.mineOnly" type="checkbox" id="inlineCheckbox1" value="option1"> Mine only
|
<label class="checkbox inline mine-only">
|
||||||
</label>
|
<input {% if 'mine-only' in request.GET %}checked{% endif %} type="checkbox" name="mine-only"> Mine only
|
||||||
<button type="submit" class="btn">Search</button>
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="with-teams-search">
|
||||||
|
<input type="text" class="search-query" name="q"
|
||||||
|
placeholder="Search snipts" id="id_q"
|
||||||
|
value="{{ query }}"
|
||||||
|
/>
|
||||||
|
<select name="author">
|
||||||
|
<option
|
||||||
|
{% if not request.GET.author or request.GET.author == '' %}selected{% endif %}
|
||||||
|
value=""
|
||||||
|
>
|
||||||
|
Mine and all public
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
{% if request.GET.author == request.user.username %}selected{% endif %}
|
||||||
|
value="{{ request.user.username }}"
|
||||||
|
>
|
||||||
|
Mine only
|
||||||
|
</option>
|
||||||
|
{% for team in request.user.profile.teams %}
|
||||||
|
<option
|
||||||
|
{% if request.GET.author == team.slug %}selected{% endif %}
|
||||||
|
value="{{ team.slug }}"
|
||||||
|
>
|
||||||
|
{{ team.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit" class="btn">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if query %}
|
{% if query %}
|
||||||
|
|
6
urls.py
6
urls.py
|
@ -12,7 +12,7 @@ from snipts.views import search
|
||||||
from tastypie.api import Api
|
from tastypie.api import Api
|
||||||
from utils.views import SniptRegistrationView
|
from utils.views import SniptRegistrationView
|
||||||
from views import (homepage, lexers, login_redirect, pro, sitemap, tags,
|
from views import (homepage, lexers, login_redirect, pro, sitemap, tags,
|
||||||
pro_complete, user_api_key, for_teams, for_teams_complete)
|
pro_complete, user_api_key)
|
||||||
|
|
||||||
public_api = Api(api_name='public')
|
public_api = Api(api_name='public')
|
||||||
public_api.register(PublicSniptResource())
|
public_api.register(PublicSniptResource())
|
||||||
|
@ -46,9 +46,6 @@ urlpatterns = \
|
||||||
url(r'^pro/$', pro),
|
url(r'^pro/$', pro),
|
||||||
url(r'^pro/complete/$', pro_complete),
|
url(r'^pro/complete/$', pro_complete),
|
||||||
|
|
||||||
url(r'^for-teams/$', for_teams),
|
|
||||||
url(r'^for-teams/complete/$', for_teams_complete),
|
|
||||||
|
|
||||||
url(r'^account/', include('accounts.urls')),
|
url(r'^account/', include('accounts.urls')),
|
||||||
|
|
||||||
url(r'^api/public/lexer/$', lexers),
|
url(r'^api/public/lexer/$', lexers),
|
||||||
|
@ -64,6 +61,7 @@ urlpatterns = \
|
||||||
name='registration_register'),
|
name='registration_register'),
|
||||||
url(r'', include('registration.backends.default.urls')),
|
url(r'', include('registration.backends.default.urls')),
|
||||||
|
|
||||||
|
url(r'^', include('teams.urls')),
|
||||||
url(r'^', include('snipts.urls')),
|
url(r'^', include('snipts.urls')),
|
||||||
|
|
||||||
url(r'^(?P<path>favicon\.ico)$', 'django.views.static.serve', {
|
url(r'^(?P<path>favicon\.ico)$', 'django.views.static.serve', {
|
||||||
|
|
82
views.py
82
views.py
|
@ -2,7 +2,6 @@ import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import stripe
|
import stripe
|
||||||
import requests
|
|
||||||
|
|
||||||
from accounts.models import UserProfile
|
from accounts.models import UserProfile
|
||||||
from annoying.decorators import ajax_request, render_to
|
from annoying.decorators import ajax_request, render_to
|
||||||
|
@ -20,74 +19,6 @@ from snipts.utils import get_lexers_list
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
|
|
||||||
@render_to('for-teams.html')
|
|
||||||
def for_teams(request):
|
|
||||||
if request.user.is_authenticated():
|
|
||||||
profile = request.user.profile
|
|
||||||
profile.teams_beta_seen = True
|
|
||||||
profile.save()
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@render_to('for-teams-complete.html')
|
|
||||||
def for_teams_complete(request):
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
|
|
||||||
if 'g-recaptcha-response' not in request.POST:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
'secret': settings.RECAPTCHA_SECRET,
|
|
||||||
'response': request.POST['g-recaptcha-response'],
|
|
||||||
'remoteip': request.META.get('REMOTE_ADDR')
|
|
||||||
}
|
|
||||||
r = requests.post('https://www.google.com/recaptcha/api/siteverify',
|
|
||||||
data=payload)
|
|
||||||
|
|
||||||
if not r.json()['success']:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
if request.user.is_authenticated():
|
|
||||||
name = request.POST['name']
|
|
||||||
members = request.POST['members']
|
|
||||||
info = request.POST['info']
|
|
||||||
send_mail('[Snipt] New Snipt for Teams beta request.', """
|
|
||||||
User: %s (%s)
|
|
||||||
Team name: %s
|
|
||||||
Team members: %s
|
|
||||||
Info:
|
|
||||||
|
|
||||||
%s
|
|
||||||
""" % (request.user.username, request.user.email, name, members,
|
|
||||||
info), 'support@snipt.net',
|
|
||||||
['nick@nicksergeant.com'], fail_silently=False)
|
|
||||||
|
|
||||||
profile = request.user.profile
|
|
||||||
profile.teams_beta_applied = True
|
|
||||||
profile.save()
|
|
||||||
else:
|
|
||||||
username = request.POST['username']
|
|
||||||
email = request.POST['email']
|
|
||||||
name = request.POST['name']
|
|
||||||
members = request.POST['members']
|
|
||||||
info = request.POST['info']
|
|
||||||
send_mail('[Snipt] New Snipt for Teams beta request.', """
|
|
||||||
User: %s (%s) (not authenticated)
|
|
||||||
Team name: %s
|
|
||||||
Team members: %s
|
|
||||||
Info:
|
|
||||||
|
|
||||||
%s
|
|
||||||
""" % (username, email, name, members, info), 'support@snipt.net',
|
|
||||||
['nick@nicksergeant.com'], fail_silently=False)
|
|
||||||
|
|
||||||
return {}
|
|
||||||
|
|
||||||
else:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
|
|
||||||
@render_to('homepage.html')
|
@render_to('homepage.html')
|
||||||
def homepage(request):
|
def homepage(request):
|
||||||
|
|
||||||
|
@ -152,11 +83,8 @@ def login_redirect(request):
|
||||||
return HttpResponseRedirect('/')
|
return HttpResponseRedirect('/')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@render_to('pro.html')
|
@render_to('pro.html')
|
||||||
def pro(request):
|
def pro(request):
|
||||||
if request.user.profile.is_pro:
|
|
||||||
return HttpResponseRedirect('/' + request.user.username + '/')
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,6 +118,16 @@ def pro_complete(request):
|
||||||
profile.stripe_id = customer.id
|
profile.stripe_id = customer.id
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
|
send_mail('[Snipt] New Pro signup: {}'.format(request.user.username),
|
||||||
|
"""
|
||||||
|
User: https://snipt.net/{}
|
||||||
|
Email: {}
|
||||||
|
Plan: {}
|
||||||
|
""".format(request.user.username, request.user.email, plan),
|
||||||
|
'support@snipt.net',
|
||||||
|
['nick@snipt.net'],
|
||||||
|
fail_silently=False)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue