Browse Source

So much PEP8.

master
Nick Sergeant 6 years ago
parent
commit
6894f44349
39 changed files with 517 additions and 494 deletions
  1. +3
    -1
      accounts/admin.py
  2. +31
    -20
      accounts/models.py
  3. +8
    -6
      accounts/urls.py
  4. +8
    -4
      accounts/views.py
  5. +4
    -2
      admin.py
  6. +11
    -4
      blogs/middleware.py
  7. +1
    -3
      blogs/urls.py
  8. +44
    -31
      blogs/views.py
  9. +0
    -0
      jobs/__init__.py
  10. +0
    -9
      jobs/admin.py
  11. +0
    -0
      jobs/management/__init__.py
  12. +0
    -0
      jobs/management/commands/__init__.py
  13. +0
    -48
      jobs/management/commands/importjobs.py
  14. +0
    -25
      jobs/migrations/0001_initial.py
  15. +0
    -0
      jobs/migrations/__init__.py
  16. +0
    -16
      jobs/models.py
  17. +0
    -70
      jobs/templates/jobs/jobs.html
  18. +0
    -16
      jobs/tests.py
  19. +0
    -22
      jobs/views.py
  20. +15
    -9
      settings.py
  21. +1
    -1
      settings_local.py-template
  22. +4
    -1
      snipts/admin.py
  23. +77
    -41
      snipts/api.py
  24. +1
    -0
      snipts/forms.py
  25. +99
    -52
      snipts/models.py
  26. +2
    -1
      snipts/search_indexes.py
  27. +5
    -0
      snipts/templatetags/snipt_tags.py
  28. +42
    -20
      snipts/tests.py
  29. +43
    -20
      snipts/urls.py
  30. +2
    -0
      snipts/utils.py
  31. +35
    -15
      snipts/views.py
  32. +0
    -1
      templates/analytics.html
  33. +39
    -34
      urls.py
  34. +1
    -1
      utils/backends.py
  35. +8
    -6
      utils/forms.py
  36. +8
    -2
      utils/templatetags/intercom.py
  37. +1
    -1
      utils/templatetags/verbatim.py
  38. +1
    -1
      utils/views.py
  39. +23
    -11
      views.py

+ 3
- 1
accounts/admin.py View File

@@ -2,8 +2,10 @@ from django.contrib import admin

from accounts.models import UserProfile


class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'is_pro', 'stripe_id', 'gittip_username', 'teams_beta_seen')
list_display = ('user', 'is_pro', 'stripe_id', 'gittip_username',
'teams_beta_seen')
list_filter = ['teams_beta_seen', 'teams_beta_applied']
search_fields = ('user__username', 'gittip_username',)



+ 31
- 20
accounts/models.py View File

@@ -1,9 +1,9 @@
from django.contrib.auth.models import User
from datetime import date
from datetime import datetime
from django.db import models
from snipts.models import Snipt


class UserProfile(models.Model):

LIST_VIEW_CHOICES = (
@@ -42,38 +42,45 @@ class UserProfile(models.Model):
)

# User
user = models.OneToOneField(User)
is_pro = models.BooleanField(default=False)
user = models.OneToOneField(User)
is_pro = models.BooleanField(default=False)
teams_beta_seen = models.BooleanField(default=False)
teams_beta_applied = models.BooleanField(default=False)
pro_date = models.DateTimeField(blank=True, null=True)
stripe_id = models.CharField(max_length=100, null=True, blank=True)
has_gravatar = models.BooleanField(default=False)
list_view = models.CharField(max_length=1, null=False, blank=False,default='N', choices=LIST_VIEW_CHOICES)
pro_date = models.DateTimeField(blank=True, null=True)
stripe_id = models.CharField(max_length=100, null=True, blank=True)
has_gravatar = models.BooleanField(default=False)
list_view = models.CharField(max_length=1, null=False, blank=False,
default='N', choices=LIST_VIEW_CHOICES)

# Blog
blog_title = models.CharField(max_length=250, null=True, blank=True)
blog_theme = models.CharField(max_length=1, null=False, blank=False, default='A', choices=THEME_CHOICES)
blog_domain = models.CharField(max_length=250, null=True, blank=True)
blog_title = models.CharField(max_length=250, null=True, blank=True)
blog_theme = models.CharField(max_length=1, null=False, blank=False,
default='A', choices=THEME_CHOICES)
blog_domain = models.CharField(max_length=250, null=True, blank=True)

# Editor
default_editor = models.CharField(max_length=250, null=False, blank=False, default='C', choices=EDITOR_CHOICES)
editor_theme = models.CharField(max_length=250, null=False, blank=False, default='default', choices=EDITOR_THEME_CHOICES)
default_editor = models.CharField(max_length=250, null=False, blank=False,
default='C', choices=EDITOR_CHOICES)
editor_theme = models.CharField(max_length=250, null=False, blank=False,
default='default',
choices=EDITOR_THEME_CHOICES)

# Services and Analytics
gittip_username = models.CharField(max_length=250, null=True, blank=True)
gittip_username = models.CharField(max_length=250, null=True, blank=True)
disqus_shortname = models.CharField(max_length=250, null=True, blank=True)
google_analytics_tracking_id = models.CharField(max_length=250, null=True, blank=True)
gauges_site_id = models.CharField(max_length=250, null=True, blank=True)
google_analytics_tracking_id = models.CharField(max_length=250, null=True,
blank=True)
gauges_site_id = models.CharField(max_length=250, null=True, blank=True)

# Google Ads
google_ad_client = models.CharField(max_length=250, null=True, blank=True)
google_ad_slot = models.CharField(max_length=250, null=True, blank=True)
google_ad_width = models.CharField(max_length=250, null=True, blank=True)
google_ad_slot = models.CharField(max_length=250, null=True, blank=True)
google_ad_width = models.CharField(max_length=250, null=True, blank=True)
google_ad_height = models.CharField(max_length=250, null=True, blank=True)

def get_blog_posts(self):
return Snipt.objects.filter(user=self.user, blog_post=True, public=True)
return Snipt.objects.filter(user=self.user, blog_post=True,
public=True)

def get_primary_blog_domain(self):
if not self.blog_domain:
@@ -98,10 +105,14 @@ class UserProfile(models.Model):
return url

def has_public_snipts(self):
return True if Snipt.objects.filter(user=self, public=True).count() > 0 else False
return True \
if Snipt.objects.filter(user=self,
public=True).count() > 0 \
else False

def get_account_age(self):
delta = datetime.now().replace(tzinfo=None) - self.user.date_joined.replace(tzinfo=None)
delta = datetime.now().replace(tzinfo=None) - \
self.user.date_joined.replace(tzinfo=None)
return delta.days

User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

+ 8
- 6
accounts/urls.py View File

@@ -2,9 +2,11 @@ from django.conf.urls import *

from accounts import views

urlpatterns = patterns('',
url(r'^stats/$', views.stats, name='account-stats'),
url(r'^cancel-subscription/$', views.cancel_subscription, name='cancel-subscription'),
url(r'^stripe-account-details/$', views.stripe_account_details, name='stripe-account-details'),
url(r'^', views.account, name='account-detail'),
)
urlpatterns = \
patterns('',
url(r'^stats/$', views.stats, name='account-stats'),
url(r'^cancel-subscription/$', views.cancel_subscription,
name='cancel-subscription'),
url(r'^stripe-account-details/$', views.stripe_account_details,
name='stripe-account-details'),
url(r'^', views.account, name='account-detail'))

+ 8
- 4
accounts/views.py View File

@@ -3,7 +3,8 @@ from django.contrib.auth.decorators import login_required
from annoying.decorators import ajax_request, render_to
from snipts.models import Snipt

import os, stripe
import os
import stripe


@login_required
@@ -19,7 +20,8 @@ def cancel_subscription(request):
if request.user.profile.stripe_id is None:
return {}
else:
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY', settings.STRIPE_SECRET_KEY)
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
customer = stripe.Customer.retrieve(request.user.profile.stripe_id)
customer.delete()

@@ -28,7 +30,8 @@ def cancel_subscription(request):
profile.stripe_id = None
profile.save()

return { 'deleted': True }
return {'deleted': True}


@login_required
@ajax_request
@@ -37,7 +40,8 @@ def stripe_account_details(request):
if request.user.profile.stripe_id is None:
return {}
else:
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY', settings.STRIPE_SECRET_KEY)
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
customer = stripe.Customer.retrieve(request.user.profile.stripe_id)

data = {


+ 4
- 2
admin.py View File

@@ -2,9 +2,11 @@ from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.contrib import admin


class UserAdmin(UserAdmin):
list_display = ['username', 'email', 'first_name', 'last_name', 'last_login',
'date_joined', 'is_active', 'is_staff', 'api_key']
list_display = ['username', 'email', 'first_name', 'last_name',
'last_login', 'date_joined', 'is_active', 'is_staff',
'api_key']
list_filter = ['is_staff', 'is_superuser', 'is_active']
ordering = ['-date_joined']



+ 11
- 4
blogs/middleware.py View File

@@ -12,19 +12,26 @@ class BlogMiddleware:
host = request.META.get('HTTP_HOST', '')
host_s = host.replace('www.', '').split('.')

if host != 'snipt.net' and host != 'snipt.localhost' and host != 'local.snipt.net':
if host != 'snipt.net' and \
host != 'snipt.localhost' and \
host != 'local.snipt.net':
if len(host_s) > 2:
if host_s[1] == 'snipt':

blog_user = ''.join(host_s[:-2])

if '-' in blog_user:
request.blog_user = get_object_or_None(User, username__iexact=blog_user)
request.blog_user = \
get_object_or_None(User,
username__iexact=blog_user)

if request.blog_user is None:
request.blog_user = get_object_or_404(User, username__iexact=blog_user.replace('-', '_'))
request.blog_user = \
get_object_or_404(User,
username__iexact=blog_user.replace('-', '_'))
else:
request.blog_user = get_object_or_404(User, username__iexact=blog_user)
request.blog_user = \
get_object_or_404(User, username__iexact=blog_user)

if request.blog_user is None:
pro_users = User.objects.filter(userprofile__is_pro=True)


+ 1
- 3
blogs/urls.py View File

@@ -1,5 +1,3 @@
from django.conf.urls import *

urlpatterns = patterns('',
url(r'^$', views.blog, name='blog'),
)
urlpatterns = patterns('', url(r'^$', views.blog, name='blog'))

+ 44
- 31
blogs/views.py View File

@@ -11,6 +11,7 @@ THEME_CHOICES = {
'A': 'blogs/themes/pro-adams/',
}


def blog_list(request, username_or_custom_slug=None):

if username_or_custom_slug:
@@ -19,17 +20,25 @@ def blog_list(request, username_or_custom_slug=None):
snipts = Snipt.objects.filter(user=request.blog_user,
blog_post=True,
public=True,
publish_date__lte=datetime.datetime.now()
).order_by('-publish_date').exclude(title__iexact='Homepage').exclude(title__iexact='Work')

normal_snipts = Snipt.objects.filter(blog_post=False, user=request.blog_user, public=True).order_by('-created')
publish_date__lte=datetime.datetime.now()) \
.order_by('-publish_date') \
.exclude(title__iexact='Homepage') \
.exclude(title__iexact='Work')

normal_snipts = Snipt.objects.filter(blog_post=False,
user=request.blog_user,
public=True) \
.order_by('-created')
normal_snipts = normal_snipts.exclude(title__in=[''])
normal_snipts = normal_snipts.exclude(tags__name__in=['tmp'])
normal_snipts = normal_snipts[:3]

sidebar = get_object_or_None(Snipt, user=request.blog_user, title='Sidebar', blog_post=True)
header = get_object_or_None(Snipt, user=request.blog_user, title='Header', blog_post=True)
custom_css = get_object_or_None(Snipt, user=request.blog_user, title='CSS', lexer='css', blog_post=True)
sidebar = get_object_or_None(Snipt, user=request.blog_user,
title='Sidebar', blog_post=True)
header = get_object_or_None(Snipt, user=request.blog_user, title='Header',
blog_post=True)
custom_css = get_object_or_None(Snipt, user=request.blog_user, title='CSS',
lexer='css', blog_post=True)

context = {
'blog_user': request.blog_user,
@@ -55,26 +64,33 @@ def blog_list(request, username_or_custom_slug=None):
context,
context_instance=RequestContext(request))


def blog_post(request, username_or_custom_slug):

snipt = get_object_or_404(Snipt, user=request.blog_user,
blog_post=True,
public=True,
publish_date__lte=datetime.datetime.now(),
slug=username_or_custom_slug,
)
blog_post=True,
public=True,
publish_date__lte=datetime.datetime.now(),
slug=username_or_custom_slug)

snipts = Snipt.objects.filter(user=request.blog_user,
blog_post=True,
public=True,
publish_date__lte=datetime.datetime.now()
).order_by('-publish_date').exclude(title__iexact='Homepage').exclude(title__iexact='Work')

sidebar = get_object_or_None(Snipt, user=request.blog_user, title='Sidebar', blog_post=True)
header = get_object_or_None(Snipt, user=request.blog_user, title='Header', blog_post=True)
custom_css = get_object_or_None(Snipt, user=request.blog_user, title='CSS', lexer='css', blog_post=True)

normal_snipts = Snipt.objects.filter(blog_post=False, user=request.blog_user, public=True).order_by('-created')
publish_date__lte=datetime.datetime.now()) \
.order_by('-publish_date') \
.exclude(title__iexact='Homepage') \
.exclude(title__iexact='Work')

sidebar = get_object_or_None(Snipt, user=request.blog_user,
title='Sidebar', blog_post=True)
header = get_object_or_None(Snipt, user=request.blog_user, title='Header',
blog_post=True)
custom_css = get_object_or_None(Snipt, user=request.blog_user, title='CSS',
lexer='css', blog_post=True)

normal_snipts = Snipt.objects.filter(blog_post=False,
user=request.blog_user,
public=True).order_by('-created')
normal_snipts = normal_snipts.exclude(title__in=[''])
normal_snipts = normal_snipts.exclude(tags__name__in=['tmp'])
normal_snipts = normal_snipts[:3]
@@ -100,16 +116,13 @@ def blog_post(request, username_or_custom_slug):

template = '{}/post.html'.format(template)

return render_to_response(
template,
context,
context_instance=RequestContext(request)
)
return render_to_response(template,
context,
context_instance=RequestContext(request))


def rss(request, context):
return render_to_response(
'blogs/themes/default/rss.xml',
context,
context_instance=RequestContext(request),
content_type="application/rss+xml"
)
return render_to_response('blogs/themes/default/rss.xml',
context,
context_instance=RequestContext(request),
content_type="application/rss+xml")

+ 0
- 0
jobs/__init__.py View File


+ 0
- 9
jobs/admin.py View File

@@ -1,9 +0,0 @@
from django.contrib import admin
from jobs.models import Job


class JobAdmin(admin.ModelAdmin):
list_display = ('title', 'created', 'company', 'url',)
ordering = ('-created',)

admin.site.register(Job, JobAdmin)

+ 0
- 0
jobs/management/__init__.py View File


+ 0
- 0
jobs/management/commands/__init__.py View File


+ 0
- 48
jobs/management/commands/importjobs.py View File

@@ -1,48 +0,0 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from jobs.models import Job

import json
import requests


class Command(BaseCommand):
help = 'Import jobs'
listings = []

def get_for_page(self, page):
r = requests.get('{}&page={}'.format(settings.JOBS_BASE_URL, page))
obj = r.json()
self.listings.extend(obj['listings']['listing'])

if page < obj['listings']['pages']:
return self.get_for_page(page + 1)

return self.listings

def handle(self, *args, **options):
listings = self.get_for_page(1)

jobs = Job.objects.all()
for job in jobs:
job.delete()

for listing in listings:

try:
location = listing['company']['location']['city']
except:
location = ''

try:
company = str(listing['company']['name'])
except:
company = ''

newjob = Job(title=listing['title'],
company=company,
location=location,
url=listing['url'],
data=json.dumps(listing),
created=listing['post_date'])
newjob.save()

+ 0
- 25
jobs/migrations/0001_initial.py View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
]

operations = [
migrations.CreateModel(
name='Job',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=255)),
('company', models.CharField(max_length=255)),
('location', models.CharField(max_length=255)),
('url', models.CharField(max_length=255)),
('data', models.TextField(null=True, blank=True)),
('created', models.DateTimeField()),
],
),
]

+ 0
- 0
jobs/migrations/__init__.py View File


+ 0
- 16
jobs/models.py View File

@@ -1,16 +0,0 @@
from django.db import models


class Job(models.Model):
"""An individual Job."""

title = models.CharField(max_length=255)
company = models.CharField(max_length=255)
location = models.CharField(max_length=255)
url = models.CharField(max_length=255)
data = models.TextField(blank=True, null=True)

created = models.DateTimeField()

def __unicode__(self):
return self.title

+ 0
- 70
jobs/templates/jobs/jobs.html View File

@@ -1,70 +0,0 @@
{% extends "base.html" %}

{% load humanize %}
{% load pagination_tags %}

{% block page-title %}Snipt Jobs{% endblock %}

{% block body-class %}{{ block.super }} static jobs search{% endblock %}

{% block js %}
window.jobs = [
{% for job in jobs %}
{{ job.data|safe }}{% if not forloop.last %},{% endif %}
{% endfor %}
];
{% endblock %}

{% block breadcrumb %}
<li><a href="/jobs/">Snipt Jobs</a></li>
{% endblock %}

{% block content %}
<div ng-controller="JobSearchController" ng-cloak class="ng-cloak">
<div class="static-box {% if page.object_list|length > 0 %}has-snipts{% endif %}">
<form method="get" class="form-search" action=".">
<input ng-model="query" type="text" class="search-query" name="q" placeholder="Filter jobs" />
</form>
</div>
<div class="loading-jobs" ng-show="!jobs">
Loading jobs&hellip;
</div>
<div class="pagination ng-cloak" ng-cloak ng-show="jobs">
<button class="btn prev" style="margin: 0 10px;" href ng-disabled="currentPage == 0" ng-click="currentPage=currentPage - 1">Previous</button>
<span class="page">{[{ currentPage + 1 }]} / {[{ numberOfPages() }]}</span>
<button class="btn next" style="margin: 0 10px;" ng-disabled="currentPage >= filteredJobs.length / pageSize - 1" ng-click="currentPage=currentPage + 1">Next</button>
</div>
<section class="jobs">
<ul>
<li ng-repeat="job in filteredJobs|startFrom:currentPage * pageSize|limitTo:pageSize">
<a href="{[{ job.url }]}?aff=dbe7e" class="group job-link">
<img ng-show="job.company.logo" ng-src="{[{ job.company.logo }]}" />
<span class="left">
<span class="job">{[{ job.title }]}</span>
<span class="company">{[{ job.company.name }]}</span>
</span>
<span class="right">
<span class="location">{[{ job.company.location.city }]}</span>
<span class="date">{[{ job.created }]}</span>
</span>
</a>
</li>
</ul>
</section>
<div class="pagination ng-cloak" ng-cloak ng-show="jobs">
<button class="btn prev" style="margin: 0 10px;" href ng-disabled="currentPage == 0" ng-click="currentPage=currentPage - 1">Previous</button>
<span class="page">{[{ currentPage + 1 }]} / {[{ numberOfPages() }]}</span>
<button class="btn next" style="margin: 0 10px;" ng-disabled="currentPage >= filteredJobs.length / pageSize - 1" ng-click="currentPage=currentPage + 1">Next</button>
</div>
</div>
{% endblock %}

{% block ad %}{% endblock %}
{% block aside-top %}
<div class="post-job">
<a id="post-job" href="http://www.authenticjobs.com/post/?aff=dbe7e" class="btn btn-large btn-success">Post a job</a>
<p>
We use <a class="job-link" href="http://www.authenticjobs.com?aff=dbe7e">Authentic Jobs</a> for our job board provider. Once you post your ad, it will appear on Snipt within 30 minutes.
</p>
</div>
{% endblock %}

+ 0
- 16
jobs/tests.py View File

@@ -1,16 +0,0 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".

Replace this with more appropriate tests for your application.
"""

from django.test import TestCase


class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

+ 0
- 22
jobs/views.py View File

@@ -1,22 +0,0 @@
from annoying.decorators import ajax_request, render_to
from jobs.models import Job

import json


@render_to('jobs/jobs.html')
def jobs(request):
return {}


@ajax_request
def jobs_json(request):

jobs_json = []

jobs = Job.objects.all().order_by('-created')

for job in jobs:
jobs_json.append(json.loads(job.data))

return jobs_json

+ 15
- 9
settings.py View File

@@ -1,13 +1,15 @@
import dj_database_url, os
import dj_database_url
import os

from urlparse import urlparse


if 'DATABASE_URL' in os.environ:

DATABASES = { 'default': dj_database_url.config() }
DATABASES = {'default': dj_database_url.config()}

es = urlparse(os.environ.get('SEARCHBOX_SSL_URL') or 'http://127.0.0.1:9200/')
es = urlparse(os.environ.get('SEARCHBOX_SSL_URL') or
'http://127.0.0.1:9200/')
port = es.port or 80

HAYSTACK_CONNECTIONS = {
@@ -19,9 +21,11 @@ if 'DATABASE_URL' in os.environ:
}

if es.username:
HAYSTACK_CONNECTIONS['default']['KWARGS'] = {"http_auth": es.username + ':' + es.password}
HAYSTACK_CONNECTIONS['default']['KWARGS'] = {
"http_auth": es.username + ':' + es.password
}

ABSOLUTE_URL_OVERRIDES = { 'auth.user': lambda u: "/%s/" % u.username, }
ABSOLUTE_URL_OVERRIDES = {'auth.user': lambda u: "/%s/" % u.username}
ACCOUNT_ACTIVATION_DAYS = 0
ADMINS = (('Nick Sergeant', 'nick@snipt.net'),)
ALLOWED_HOSTS = ['*']
@@ -46,7 +50,7 @@ MEDIA_URL = '/media/uploads/'
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
POSTMARK_API_KEY = os.environ.get('POSTMARK_API_KEY', '')
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
RAVEN_CONFIG = { 'dsn': os.environ.get('RAVEN_CONFIG_DSN', '') }
RAVEN_CONFIG = {'dsn': os.environ.get('RAVEN_CONFIG_DSN', '')}
REGISTRATION_EMAIL_HTML = False
ROOT_URLCONF = 'urls'
SECRET_KEY = os.environ.get('SECRET_KEY', 'changeme')
@@ -57,11 +61,14 @@ SESSION_COOKIE_AGE = 15801100
SESSION_COOKIE_SECURE = True if 'USE_SSL' in os.environ else False
SITE_ID = 1
STATICFILES_DIRS = (os.path.join(BASE_PATH, 'media'),)
STATICFILES_FINDERS = ('django.contrib.staticfiles.finders.FileSystemFinder','django.contrib.staticfiles.finders.AppDirectoriesFinder',)
STATICFILES_FINDERS = ('django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder')
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
STATIC_ROOT = os.path.join(BASE_PATH, 'static')
STATIC_URL = '/static/'
TASTYPIE_CANNED_ERROR = "There was an error with your request. The site developers have a record of this error, please email api@snipt.net and we'll help you out."
TASTYPIE_CANNED_ERROR = """There was an error with your request. The site
developers have a record of this error, please email api@snipt.net and
we'll help you out."""
TEMPLATE_DIRS = (os.path.join(PROJECT_PATH, 'templates'),)
TEMPLATE_DEBUG = DEBUG
TIME_ZONE = 'America/New_York'
@@ -93,7 +100,6 @@ INSTALLED_APPS = (
'typogrify',
'accounts',
'blogs',
'jobs',
'snipts',
'utils',
)


+ 1
- 1
settings_local.py-template View File

@@ -2,7 +2,7 @@ CSRF_COOKIE_SECURE = False
DEBUG = True
INTERCOM_SECRET_KEY = ''
POSTMARK_API_KEY = ''
RAVEN_CONFIG = { 'dsn': '' }
RAVEN_CONFIG = {'dsn': ''}
SECRET_KEY = 'changeme'
SESSION_COOKIE_SECURE = False
SSLIFY_DISABLE = False


+ 4
- 1
snipts/admin.py View File

@@ -5,7 +5,9 @@ from snipts.models import Favorite, Snipt

class SniptAdmin(admin.ModelAdmin):
readonly_fields = ('user',)
list_display = ('title', 'slug', 'views', 'favs', 'user', 'lexer', 'public', 'blog_post', 'created', 'modified', 'publish_date',)
list_display = ('title', 'slug', 'views', 'favs', 'user', 'lexer',
'public', 'blog_post', 'created', 'modified',
'publish_date')
list_filter = ('blog_post',)
search_fields = ('title', 'slug', 'user__username', 'lexer', 'id', 'key',)
ordering = ('-created',)
@@ -13,6 +15,7 @@ class SniptAdmin(admin.ModelAdmin):

admin.site.register(Snipt, SniptAdmin)


class FavoriteAdmin(admin.ModelAdmin):
readonly_fields = ('snipt', 'user',)
list_display = ('snipt', 'user', 'created',)


+ 77
- 41
snipts/api.py View File

@@ -12,12 +12,14 @@ from haystack.query import SearchQuerySet
from accounts.models import UserProfile
from tastypie.cache import SimpleCache
from tastypie.fields import ListField
from django.http import HttpResponse
from taggit.models import Tag
from django.db import models
from tastypie import fields

import datetime, hashlib, time, re
import datetime
import hashlib
import re
import time

import parsedatetime as pdt

@@ -49,6 +51,7 @@ class PrivateFavoriteAuthorization(Authorization):
def delete_detail(self, object_list, bundle):
return bundle.obj.user == bundle.request.user


class PrivateSniptAuthorization(Authorization):
def read_list(self, object_list, bundle):
return object_list.filter(user=bundle.request.user)
@@ -74,6 +77,7 @@ class PrivateSniptAuthorization(Authorization):
def delete_detail(self, object_list, bundle):
return bundle.obj.user == bundle.request.user


class PrivateUserProfileAuthorization(Authorization):
def read_list(self, object_list, bundle):
raise Unauthorized()
@@ -99,6 +103,7 @@ class PrivateUserProfileAuthorization(Authorization):
def delete_detail(self, object_list, bundle):
raise Unauthorized()


class PrivateUserAuthorization(Authorization):
def read_list(self, object_list, bundle):
raise Unauthorized()
@@ -130,22 +135,27 @@ class FavoriteValidation(Validation):
errors = {}
snipt = bundle.data['snipt']

if Favorite.objects.filter(user=bundle.request.user, snipt=snipt).count():
if Favorite.objects.filter(user=bundle.request.user,
snipt=snipt).count():
errors['duplicate'] = 'User has already favorited this snipt.'

return errors


class SniptValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}

if 'pk' not in bundle.data and \
request.user.profile.get_account_age() > 7 and \
request.user.profile.is_pro == False:
errors['expired'] = "Your trial has expired. You'll need to go Pro (https://snipt.net/pro/) in order to create new snipts."
request.user.profile.get_account_age() > 7 and \
request.user.profile.is_pro is False:
errors['expired'] = """Your trial has expired. You'll need
to go Pro (https://snipt.net/pro/)
in order to create new snipts."""

return errors


class UserProfileValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
@@ -153,7 +163,9 @@ class UserProfileValidation(Validation):
for field in bundle.data:
if bundle.data[field]:
if not re.match('^[ A-Za-z0-9\/\@\._-]*$', bundle.data[field]):
errors[field] = 'Only spaces, letters, numbers, underscores, dashes, periods, forward slashes, and "at sign" are valid.'
errors[field] = """Only spaces, letters, numbers,
underscores, dashes, periods, forward
slashes, and "at sign" are valid."""

return errors

@@ -162,26 +174,31 @@ class PublicUserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
fields = ['id', 'username',]
fields = ['id', 'username']
include_absolute_url = True
allowed_methods = ['get']
filtering = { 'username': 'exact', }
filtering = {'username': 'exact'}
max_limit = 200
cache = SimpleCache()

def dehydrate(self, bundle):
bundle.data['snipts'] = '/api/public/snipt/?user=%d' % bundle.obj.id
bundle.data['email_md5'] = hashlib.md5(bundle.obj.email.lower()).hexdigest()
bundle.data['snipts_count'] = Snipt.objects.filter(user=bundle.obj.id, public=True).count()
bundle.data['email_md5'] = hashlib \
.md5(bundle.obj.email.lower()) \
.hexdigest()
bundle.data['snipts_count'] = Snipt.objects.filter(user=bundle.obj.id,
public=True).count()
return bundle


class PublicTagResource(ModelResource):
class Meta:
queryset = Tag.objects.filter()
queryset = queryset.annotate(count=models.Count('taggit_taggeditem_items__id'))
queryset = queryset.annotate(
count=models.Count('taggit_taggeditem_items__id'))
queryset = queryset.order_by('-count', 'name')
resource_name = 'tag'
fields = ['id', 'name',]
fields = ['id', 'name']
allowed_methods = ['get']
max_limit = 200
cache = SimpleCache()
@@ -202,19 +219,22 @@ class PublicTagResource(ModelResource):
bundle.data['snipts'] = '/api/public/snipt/?tag=%d' % bundle.obj.id
return bundle


class PublicSniptResource(ModelResource):
user = fields.ForeignKey(PublicUserResource, 'user', full=True)
tags = fields.ToManyField(PublicTagResource, 'tags', related_name='tag', full=True)
tags = fields.ToManyField(PublicTagResource, 'tags', related_name='tag',
full=True)

class Meta:
queryset = Snipt.objects.filter(public=True).order_by('-created')
resource_name = 'snipt'
fields = ['id', 'title', 'slug', 'lexer', 'code', 'description', 'line_count',
'stylized', 'created', 'modified', 'publish_date', 'blog_post', 'meta',]
fields = ['id', 'title', 'slug', 'lexer', 'code', 'description',
'line_count', 'stylized', 'created', 'modified',
'publish_date', 'blog_post', 'meta']
include_absolute_url = True
allowed_methods = ['get']
filtering = { 'user': 'exact', 'blog_post': 'exact' }
ordering = ['created', 'modified',]
filtering = {'user': 'exact', 'blog_post': 'exact'}
ordering = ['created', 'modified']
max_limit = 200
cache = SimpleCache()

@@ -222,7 +242,8 @@ class PublicSniptResource(ModelResource):
bundle.data['embed_url'] = bundle.obj.get_embed_url()
bundle.data['raw_url'] = bundle.obj.get_raw_url()
bundle.data['full_absolute_url'] = bundle.obj.get_full_absolute_url()
bundle.data['description_rendered'] = linebreaksbr(urlize(bundle.obj.description))
bundle.data['description_rendered'] = \
linebreaksbr(urlize(bundle.obj.description))

if 'omit_code' in bundle.request.GET:
del bundle.data['code']
@@ -272,8 +293,10 @@ class PrivateUserProfileResource(ModelResource):
bundle.data['is_pro'] = bundle.obj.user.profile.is_pro
return bundle


class PrivateUserResource(ModelResource):
profile = fields.ForeignKey(PrivateUserProfileResource, 'profile', full=False)
profile = fields.ForeignKey(PrivateUserProfileResource, 'profile',
full=False)

class Meta:
queryset = User.objects.all()
@@ -289,18 +312,25 @@ class PrivateUserResource(ModelResource):
cache = SimpleCache()

def dehydrate(self, bundle):
bundle.data['email_md5'] = hashlib.md5(bundle.obj.email.lower()).hexdigest()
bundle.data['email_md5'] = hashlib \
.md5(bundle.obj.email.lower()) \
.hexdigest()
bundle.data['is_pro'] = bundle.obj.profile.is_pro
bundle.data['stats'] = {
'public_snipts': Snipt.objects.filter(user=bundle.obj.id, public=True).count(),
'private_snipts': Snipt.objects.filter(user=bundle.obj.id, public=False).count(),
'public_snipts': Snipt.objects.filter(user=bundle.obj.id,
public=True).count(),
'private_snipts': Snipt.objects.filter(user=bundle.obj.id,
public=False).count(),
'total_snipts': Snipt.objects.filter(user=bundle.obj.id).count(),
'total_views': Snipt.objects.filter(user=bundle.obj.id).aggregate(models.Sum('views'))['views__sum']
'total_views': Snipt.objects.filter(user=bundle.obj.id).aggregate(
models.Sum('views'))['views__sum']
}
bundle.data['lexers'] = [snipt['lexer'] for snipt in \
Snipt.objects.filter(user=bundle.obj).values('lexer').distinct()]
bundle.data['lexers'] = [
snipt['lexer'] for snipt in Snipt.objects.filter(user=bundle.obj)
.values('lexer').distinct()]
return bundle


class PrivateSniptResource(ModelResource):
user = fields.ForeignKey(PrivateUserResource, 'user', full=True)
tags_list = ListField()
@@ -308,15 +338,16 @@ class PrivateSniptResource(ModelResource):
class Meta:
queryset = Snipt.objects.all().order_by('-created')
resource_name = 'snipt'
fields = ['id', 'title', 'slug', 'lexer', 'code', 'description', 'line_count', 'stylized',
'key', 'public', 'blog_post', 'created', 'modified', 'publish_date', 'meta',]
fields = ['id', 'title', 'slug', 'lexer', 'code', 'description',
'line_count', 'stylized', 'key', 'public', 'blog_post',
'created', 'modified', 'publish_date', 'meta']
include_absolute_url = True
detail_allowed_methods = ['get', 'patch', 'put', 'delete']
list_allowed_methods = ['get', 'post']
authentication = ApiKeyAuthentication()
authorization = PrivateSniptAuthorization()
validation = SniptValidation()
ordering = ['created', 'modified',]
ordering = ['created', 'modified']
always_return_data = True
max_limit = 200
cache = SimpleCache()
@@ -326,12 +357,14 @@ class PrivateSniptResource(ModelResource):
bundle.data['raw_url'] = bundle.obj.get_raw_url()
bundle.data['tags_list'] = edit_string_for_tags(bundle.obj.tags.all())
bundle.data['full_absolute_url'] = bundle.obj.get_full_absolute_url()
bundle.data['description_rendered'] = linebreaksbr(urlize(bundle.obj.description))
bundle.data['description_rendered'] = \
linebreaksbr(urlize(bundle.obj.description))
bundle.data['views'] = bundle.obj.views
bundle.data['favs'] = bundle.obj.favs()

if bundle.data['publish_date']:
bundle.data['publish_date'] = date(bundle.data['publish_date'], 'M d, Y \\a\\t h:i A')
bundle.data['publish_date'] = \
date(bundle.data['publish_date'], 'M d, Y \\a\\t h:i A')

return bundle

@@ -342,8 +375,9 @@ class PrivateSniptResource(ModelResource):
if 'blog_post' in bundle.data:
bundle = self._clean_publish_date(bundle)

return super(PrivateSniptResource, self).obj_create(bundle,
user=bundle.request.user, **kwargs)
return super(PrivateSniptResource, self) \
.obj_create(bundle,
user=bundle.request.user, **kwargs)

def obj_update(self, bundle, **kwargs):
bundle.data['user'] = bundle.request.user
@@ -359,8 +393,9 @@ class PrivateSniptResource(ModelResource):
if 'blog_post' in bundle.data:
bundle = self._clean_publish_date(bundle)

return super(PrivateSniptResource, self).obj_update(bundle,
user=bundle.request.user, **kwargs)
return super(PrivateSniptResource, self) \
.obj_update(bundle,
user=bundle.request.user, **kwargs)

def _clean_publish_date(self, bundle):
if bundle.data['blog_post'] and 'publish_date' not in bundle.data:
@@ -407,6 +442,7 @@ class PrivateSniptResource(ModelResource):
else:
bundle.obj.tags.set()


class PrivateFavoriteResource(ModelResource):
user = fields.ForeignKey(PrivateUserResource, 'user', full=True)
snipt = fields.ForeignKey(PrivateSniptResource, 'snipt', full=False)
@@ -414,24 +450,24 @@ class PrivateFavoriteResource(ModelResource):
class Meta:
queryset = Favorite.objects.all().order_by('-created')
resource_name = 'favorite'
fields = ['id',]
fields = ['id']
validation = FavoriteValidation()
detail_allowed_methods = ['get', 'post', 'delete']
list_allowed_methods = ['get', 'post',]
list_allowed_methods = ['get', 'post']
authentication = ApiKeyAuthentication()
authorization = PrivateFavoriteAuthorization()
ordering = ['created',]
ordering = ['created']
always_return_data = True
max_limit = 200
cache = SimpleCache()

def dehydrate(self, bundle):
bundle.data['snipt'] = '/api/public/snipt/{}/'.format(
bundle.obj.snipt.pk)
bundle.obj.snipt.pk)
return bundle

def obj_create(self, bundle, **kwargs):
bundle.data['user'] = bundle.request.user
bundle.data['snipt'] = Snipt.objects.get(pk=bundle.data['snipt'])
return super(PrivateFavoriteResource, self).obj_create(bundle,
user=bundle.request.user, **kwargs)
return super(PrivateFavoriteResource, self) \
.obj_create(bundle, user=bundle.request.user, **kwargs)

+ 1
- 0
snipts/forms.py View File

@@ -1,6 +1,7 @@
from django.forms import ModelForm
from snipts.models import Snipt


class SniptForm(ModelForm):
class Meta:
model = Snipt

+ 99
- 52
snipts/models.py View File

@@ -15,36 +15,40 @@ from pygments.formatters import HtmlFormatter

from snipts.utils import slugify_uniquely

import datetime, hashlib, random, re
import datetime
import hashlib
import random
import re


class Snipt(models.Model):
"""An individual Snipt."""

user = models.ForeignKey(User, blank=True, null=True)
user = models.ForeignKey(User, blank=True, null=True)

title = models.CharField(max_length=255, blank=True, null=True, default='Untitled')
slug = models.SlugField(max_length=255, blank=True)
custom_slug = models.SlugField(max_length=255, blank=True)
tags = TaggableManager()
title = models.CharField(max_length=255, blank=True, null=True,
default='Untitled')
slug = models.SlugField(max_length=255, blank=True)
custom_slug = models.SlugField(max_length=255, blank=True)
tags = TaggableManager()

lexer = models.CharField(max_length=50)
code = models.TextField()
meta = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
stylized = models.TextField(blank=True, null=True)
lexer = models.CharField(max_length=50)
code = models.TextField()
meta = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
stylized = models.TextField(blank=True, null=True)
stylized_min = models.TextField(blank=True, null=True)
embedded = models.TextField(blank=True, null=True)
line_count = models.IntegerField(blank=True, null=True, default=None)
embedded = models.TextField(blank=True, null=True)
line_count = models.IntegerField(blank=True, null=True, default=None)

key = models.CharField(max_length=100, blank=True, null=True)
public = models.BooleanField(default=False)
blog_post = models.BooleanField(default=False)
key = models.CharField(max_length=100, blank=True, null=True)
public = models.BooleanField(default=False)
blog_post = models.BooleanField(default=False)

views = models.IntegerField(default=0)
created = models.DateTimeField(auto_now_add=True, editable=False)
modified = models.DateTimeField(auto_now=True, editable=False)
views = models.IntegerField(default=0)
created = models.DateTimeField(auto_now_add=True, editable=False)
modified = models.DateTimeField(auto_now=True, editable=False)
publish_date = models.DateTimeField(blank=True, null=True)

def save(self, *args, **kwargs):
@@ -53,7 +57,9 @@ class Snipt(models.Model):
self.slug = slugify_uniquely(self.title, Snipt)

if not self.key:
self.key = hashlib.md5(self.slug + str(datetime.datetime.now()) + str(random.random())).hexdigest()
self.key = hashlib.md5(self.slug +
str(datetime.datetime.now()) +
str(random.random())).hexdigest()

if self.lexer == 'markdown':
self.stylized = markdown(self.code, 'default')
@@ -61,21 +67,46 @@ class Snipt(models.Model):
# Snipt embeds
for match in re.findall('\[\[(\w{32})\]\]', self.stylized):
self.stylized = self.stylized.replace('[[' + str(match) + ']]',
'<script type="text/javascript" src="https://snipt.net/embed/{}/?snipt"></script><div id="snipt-embed-{}"></div>'.format(match, match))
"""
<script type="text/javascript"
src="https://snipt.net/embed/{}/?snipt">
</script>
<div id="snipt-embed-{}"></div>""".format(match, match))

# YouTube embeds
for match in re.findall('\[\[youtube-(\w{11})\-(\d+)x(\d+)\]\]', self.stylized):
self.stylized = self.stylized.replace('[[youtube-{}-{}x{}]]'.format(str(match[0]), str(match[1]), str(match[2])),
'<iframe width="{}" height="{}" src="https://www.youtube.com/embed/{}" frameborder="0" allowfullscreen></iframe>'.format(match[1], match[2], match[0]))
for match in re.findall('\[\[youtube-(\w{11})\-(\d+)x(\d+)\]\]',
self.stylized):
self.stylized = self.stylized \
.replace('[[youtube-{}-{}x{}]]'.format(
str(match[0]),
str(match[1]),
str(match[2])),
"""<iframe width="{}" height="{}"
src="https://www.youtube.com/embed/{}"
frameborder="0" allowfullscreen></iframe>"""
.format(match[1], match[2], match[0]))

# Vimeo embeds
for match in re.findall('\[\[vimeo-(\d+)\-(\d+)x(\d+)\]\]', self.stylized):
self.stylized = self.stylized.replace('[[vimeo-{}-{}x{}]]'.format(str(match[0]), str(match[1]), str(match[2])),
'<iframe src="https://player.vimeo.com/video/{}" width="{}" height="{}" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>'.format(match[0], match[1], match[2]))
for match in re.findall('\[\[vimeo-(\d+)\-(\d+)x(\d+)\]\]',
self.stylized):
self.stylized = self.stylized \
.replace('[[vimeo-{}-{}x{}]]'.format(
str(match[0]),
str(match[1]),
str(match[2])),
"""<iframe src="https://player.vimeo.com/video/{}"
width="{}" height="{}" frameborder="0"
webkitAllowFullScreen mozallowfullscreen
allowFullScreen></iframe>"""
.format(match[0], match[1], match[2]))

# Tweet embeds
for match in re.findall('\[\[tweet-(\d+)\]\]', self.stylized):
self.stylized = self.stylized.replace('[[tweet-{}]]'.format(str(match)), '<div class="embedded-tweet" data-tweet-id="{}"></div>'.format(str(match)))
self.stylized = self.stylized \
.replace(
'[[tweet-{}]]'.format(str(match)),
'<div class="embedded-tweet" data-tweet-id="{}"></div>'
.format(str(match)))

# Parse Snipt usernames
for match in re.findall('@(\w+) ', self.stylized):
@@ -85,17 +116,19 @@ class Snipt(models.Model):

if user:
url = user.profile.get_user_profile_url()
self.stylized = self.stylized.replace('@{} '.format(str(match)), '<a href="{}">@{}</a> '.format(url, match))
self.stylized = self.stylized \
.replace('@{} '.format(str(match)),
'<a href="{}">@{}</a> '.format(url, match))

else:
self.stylized = highlight(self.code,
get_lexer_by_name(self.lexer, encoding='UTF-8'),
get_lexer_by_name(self.lexer,
encoding='UTF-8'),
HtmlFormatter(linenos='table',
anchorlinenos=True,
lineanchors='L',
linespans='L',
)
)
anchorlinenos=True,
lineanchors='L',
linespans='L',
))
self.line_count = len(self.code.split('\n'))

if self.lexer == 'markdown':
@@ -104,7 +137,8 @@ class Snipt(models.Model):
lexer_for_embedded = self.lexer

embedded = highlight(self.code,
get_lexer_by_name(lexer_for_embedded, encoding='UTF-8'),
get_lexer_by_name(lexer_for_embedded,
encoding='UTF-8'),
HtmlFormatter(
style='native',
noclasses=True,
@@ -120,8 +154,8 @@ class Snipt(models.Model):
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
"""))
embedded = (embedded.replace("\\\"","\\\\\"")
.replace('\'','\\\'')
embedded = (embedded.replace("\\\"", "\\\\\"")
.replace('\'', '\\\'')
.replace("\\", "\\\\")
.replace('background: #202020', ''))
self.embedded = embedded
@@ -139,18 +173,23 @@ class Snipt(models.Model):
if self.lexer == 'markdown':
self.stylized_min = markdown(self.code[:1000], 'default')
else:
self.stylized_min = highlight(self.code[:1000],
get_lexer_by_name(self.lexer, encoding='UTF-8'),
HtmlFormatter(linenos='table', linenospecial=1, lineanchors='line'))
self.stylized_min = highlight(
self.code[:1000],
get_lexer_by_name(self.lexer, encoding='UTF-8'),
HtmlFormatter(linenos='table',
linenospecial=1,
lineanchors='line'))
return self.stylized_min

def get_absolute_url(self):

if self.blog_post:
if self.user.profile.blog_domain:
return u'http://{}/{}/'.format(self.user.profile.blog_domain.split(' ')[0], self.slug)
return u'http://{}/{}/'.format(
self.user.profile.blog_domain.split(' ')[0], self.slug)
else:
return u'https://{}.snipt.net/{}/'.format(self.user.username.replace('_', '-'), self.slug)
return u'https://{}.snipt.net/{}/'.format(
self.user.username.replace('_', '-'), self.slug)

if self.custom_slug:
return u'/{}/'.format(self.custom_slug)
@@ -158,15 +197,18 @@ class Snipt(models.Model):
if self.public:
return u'/{}/{}/'.format(self.user.username, self.slug)
else:
return u'/{}/{}/?key={}'.format(self.user.username, self.slug, self.key)
return u'/{}/{}/?key={}'.format(
self.user.username, self.slug, self.key)

def get_full_absolute_url(self):

if self.blog_post:
if self.user.profile.blog_domain:
return u'http://{}/{}/'.format(self.user.profile.blog_domain.split(' ')[0], self.slug)
return u'http://{}/{}/'.format(
self.user.profile.blog_domain.split(' ')[0], self.slug)
else:
return u'https://{}.snipt.net/{}/'.format(self.user.username, self.slug)
return u'https://{}.snipt.net/{}/'.format(
self.user.username, self.slug)

if settings.DEBUG:
root = 'http://local.snipt.net'
@@ -176,7 +218,10 @@ class Snipt(models.Model):
if self.public:
return u'{}/{}/{}/'.format(root, self.user.username, self.slug)
else:
return u'{}/{}/{}/?key={}'.format(root, self.user.username, self.slug, self.key)
return u'{}/{}/{}/?key={}'.format(root,
self.user.username,
self.slug,
self.key)

def get_download_url(self):

@@ -233,12 +278,14 @@ class Snipt(models.Model):
else:
return get_lexer_by_name(self.lexer).name


class Favorite(models.Model):
snipt = models.ForeignKey(Snipt)
user = models.ForeignKey(User)
user = models.ForeignKey(User)

created = models.DateTimeField(auto_now_add=True, editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
modified = models.DateTimeField(auto_now=True, editable=False)
def __unicode__(self):
return u'{} favorited by {}'.format(self.snipt.title, self.user.username)
return u'{} favorited by {}'.format(self.snipt.title,
self.user.username)

+ 2
- 1
snipts/search_indexes.py View File

@@ -15,4 +15,5 @@ class SniptIndex(indexes.SearchIndex, indexes.Indexable):

def index_queryset(self, **kwargs):
"""Used when the entire index for model is updated."""
return self.get_model().objects.filter(created__lte=datetime.datetime.now())
return self.get_model().objects.filter(
created__lte=datetime.datetime.now())

+ 5
- 0
snipts/templatetags/snipt_tags.py View File

@@ -11,6 +11,7 @@ import hashlib

register = template.Library()


@tag(register, [Constant('as'), Variable()])
def snipt_is_favorited_by_user(context, asvar):

@@ -30,6 +31,7 @@ def snipt_is_favorited_by_user(context, asvar):

return ''


@tag(register, [])
def snipts_count_for_user(context):

@@ -42,11 +44,13 @@ def snipts_count_for_user(context):

return snipts


@tag(register, [Constant('as'), Variable()])
def get_lexers(context, asvar):
context[asvar] = get_lexers_list()
return ''


@tag(register, [Constant('for'), Variable()])
def generate_line_numbers(context, line_numbers):
html = ''
@@ -56,6 +60,7 @@ def generate_line_numbers(context, line_numbers):

return html


@register.filter
def md5(string):
return hashlib.md5(string.lower()).hexdigest()

+ 42
- 20
snipts/tests.py View File

@@ -11,11 +11,19 @@ class SniptResourceTest(ResourceTestCase):
super(SniptResourceTest, self).setUp()

# Johnny
self.johnny = User.objects.create_user('johnny', 'johnny@snipt.net', 'password')
self.johnny = User.objects.create_user('johnny', 'johnny@snipt.net',
'password')
ApiKey.objects.get_or_create(user=self.johnny)
self.johnny_auth = self.create_apikey(self.johnny, self.johnny.api_key.key)
self.johnny_private = Snipt(title='Private snipt for Johnny', lexer='text', public=False, user=self.johnny)
self.johnny_public = Snipt(title='Public snipt for Johnny', lexer='text', public=True, user=self.johnny)
self.johnny_auth = self.create_apikey(self.johnny,
self.johnny.api_key.key)
self.johnny_private = Snipt(title='Private snipt for Johnny',
lexer='text',
public=False,
user=self.johnny)
self.johnny_public = Snipt(title='Public snipt for Johnny',
lexer='text',
public=True,
user=self.johnny)
self.johnny_private.save()
self.johnny_public.save()

@@ -23,16 +31,21 @@ class SniptResourceTest(ResourceTestCase):
self.bob = User.objects.create_user('bob', 'bob@snipt.net', 'password')
ApiKey.objects.get_or_create(user=self.bob)
self.bob_auth = self.create_apikey(self.bob, self.bob.api_key.key)
self.bob_private = Snipt(title='Private snipt for Bob', lexer='text', public=False, user=self.bob)
self.bob_public = Snipt(title='Public snipt for Bob', lexer='text', public=True, user=self.bob)
self.bob_private = Snipt(title='Private snipt for Bob',
lexer='text',
public=False,
user=self.bob)
self.bob_public = Snipt(title='Public snipt for Bob',
lexer='text',
public=True,
user=self.bob)
self.bob_private.save()
self.bob_public.save()


# Private
def test_get_private_list(self):

resp = self.api_client.get('/api/private/snipt/', format='json', authentication=self.johnny_auth)
resp = self.api_client.get('/api/private/snipt/', format='json',
authentication=self.johnny_auth)

self.assertHttpOK(resp)
self.assertValidJSONResponse(resp)
@@ -40,18 +53,27 @@ class SniptResourceTest(ResourceTestCase):

def test_get_private_detail(self):

resp = self.api_client.get('/api/private/snipt/{}/'.format(self.johnny_private.pk), format='json', authentication=self.johnny_auth)
resp = self.api_client.get(
'/api/private/snipt/{}/'.format(self.johnny_private.pk),
format='json',
authentication=self.johnny_auth)

self.assertHttpOK(resp)
self.assertValidJSONResponse(resp)
self.assertEqual(self.deserialize(resp)['key'], self.johnny_private.key)
self.assertEqual(self.deserialize(resp)['key'],
self.johnny_private.key)

# Unauthenticated request.
resp = self.api_client.get('/api/private/snipt/{}/'.format(self.johnny_private.pk), format='json')
resp = self.api_client.get(
'/api/private/snipt/{}/'.format(self.johnny_private.pk),
format='json')
self.assertHttpUnauthorized(resp)

# Unauthorized request.
resp = self.api_client.get('/api/private/snipt/{}/'.format(self.johnny_private.pk), format='json', authentication=self.bob_auth)
resp = self.api_client.get(
'/api/private/snipt/{}/'.format(self.johnny_private.pk),
format='json',
authentication=self.bob_auth)
self.assertHttpUnauthorized(resp)

def test_post_private_list(self):
@@ -63,21 +85,21 @@ class SniptResourceTest(ResourceTestCase):
}

resp = self.api_client.post('/api/private/snipt/',
data=new_snipt,
format='json',
authentication=self.johnny_auth)
data=new_snipt,
format='json',
authentication=self.johnny_auth)
self.assertHttpCreated(resp)
self.assertEqual(Snipt.objects.count(), 5)

resp = self.api_client.get('/api/private/snipt/', format='json', authentication=self.johnny_auth)
resp = self.api_client.get('/api/private/snipt/',
format='json',
authentication=self.johnny_auth)
self.assertEqual(len(self.deserialize(resp)['objects']), 3)

resp = self.api_client.get('/api/public/snipt/', format='json')
self.assertEqual(len(self.deserialize(resp)['objects']), 2)


# Public
def test_get_public_list(self):

self.assertEqual(Snipt.objects.count(), 4)


+ 43
- 20
snipts/urls.py View File

@@ -3,23 +3,46 @@ from django.conf.urls import *
from snipts import views


urlpatterns = patterns('',

# Redirects
url(r'^s/(?P<snipt_key>[^/]+)/(?P<lexer>[^\?]+)?$', views.redirect_snipt, name='redirect-snipt'),
url(r'^(?P<username>[^/]+)/feed/$', views.redirect_user_feed, name='redirect-feed'),
url(r'^public/tag/(?P<tag_slug>[^/]+)/feed/$', views.redirect_public_tag_feed, name='redirect-public-tag-feed'),
url(r'^(?P<username>[^/]+)/tag/(?P<tag_slug>[^/]+)/feed/$', views.redirect_user_tag_feed, name='redirect-user-tag-feed'),

url(r'^public/$', views.list_public, name='list-public'),
url(r'^public/tag/(?P<tag_slug>[^/]+)/$', views.list_public, name='list-public-tag'),
url(r'^download/(?P<snipt_key>[^/]+).*$', views.download, name='download'),
url(r'^embed/(?P<snipt_key>[^/]+)/$', views.embed, name='embed'),
url(r'^raw/(?P<snipt_key>[^/]+)/(?P<lexer>[^\?]+)?$', views.raw, name='raw'),
url(r'^(?P<username_or_custom_slug>[^/]+)/$', views.list_user, name='list-user'),
url(r'^(?P<username_or_custom_slug>[^/]+)/tag/(?P<tag_slug>[^/]+)/$', views.list_user, name='list-user-tag'),
url(r'^(?P<username>[^/]+)/favorites/$', views.favorites, name='favorites'),
url(r'^(?P<username>[^/]+)/blog-posts/$', views.blog_posts, name='blog-posts'),
url(r'^(?P<username>[^/]+)/(?P<snipt_slug>[^/]+)/$', views.detail, name='detail'),

)
urlpatterns = \
patterns('',
url(r'^s/(?P<snipt_key>[^/]+)/(?P<lexer>[^\?]+)?$',
views.redirect_snipt, name='redirect-snipt'),
url(r'^(?P<username>[^/]+)/feed/$',
views.redirect_user_feed,
name='redirect-feed'),
url(r'^public/tag/(?P<tag_slug>[^/]+)/feed/$',
views.redirect_public_tag_feed,
name='redirect-public-tag-feed'),
url(r'^(?P<username>[^/]+)/tag/(?P<tag_slug>[^/]+)/feed/$',
views.redirect_user_tag_feed,
name='redirect-user-tag-feed'),
url(r'^public/$',
views.list_public,
name='list-public'),
url(r'^public/tag/(?P<tag_slug>[^/]+)/$',
views.list_public,
name='list-public-tag'),
url(r'^download/(?P<snipt_key>[^/]+).*$',
views.download,
name='download'),
url(r'^embed/(?P<snipt_key>[^/]+)/$',
views.embed,
name='embed'),
url(r'^raw/(?P<snipt_key>[^/]+)/(?P<lexer>[^\?]+)?$',
views.raw,
name='raw'),
url(r'^(?P<username_or_custom_slug>[^/]+)/$',
views.list_user,
name='list-user'),
url(r'^(?P<username_or_custom_slug>[^/]+)/tag/(?P<tag_slug>[^/]+)/$',
views.list_user,
name='list-user-tag'),
url(r'^(?P<username>[^/]+)/favorites/$',
views.favorites,
name='favorites'),
url(r'^(?P<username>[^/]+)/blog-posts/$',
views.blog_posts,
name='blog-posts'),
url(r'^(?P<username>[^/]+)/(?P<snipt_slug>[^/]+)/$',
views.detail,
name='detail'))

+ 2
- 0
snipts/utils.py View File

@@ -17,6 +17,7 @@ def slugify_uniquely(value, model, slugfield="slug"):
return potential
suffix = str(uuid.uuid4()).split('-')[0]


def activate_user(user, request, **kwargs):
user.is_active = True
user.save()
@@ -25,6 +26,7 @@ def activate_user(user, request, **kwargs):
password=request.POST['password1'])
login(request, user)


def get_lexers_list():
lexers = list(get_all_lexers())



+ 35
- 15
snipts/views.py View File

@@ -62,10 +62,12 @@ def detail(request, username, snipt_slug):
'user': user,
}


def download(request, snipt_key):
snipt = get_object_or_404(Snipt, key=snipt_key)
return HttpResponse(snipt.code, content_type='application/x-download')


def embed(request, snipt_key):
snipt = get_object_or_404(Snipt, key=snipt_key)

@@ -75,6 +77,7 @@ def embed(request, snipt_key):
context_instance=RequestContext(request),
content_type='application/javascript')


@render_to('snipts/list-user.html')
def blog_posts(request, username):

@@ -92,7 +95,8 @@ def blog_posts(request, username):
public_user = True
user = get_object_or_404(User, username=username)
snipts = Snipt.objects.filter(blog_post=True, user=user, public=True)
tags = Tag.objects.filter(snipt__user=user, snipt__public=True).distinct()
tags = Tag.objects.filter(snipt__user=user,
snipt__public=True).distinct()

tags = tags.order_by('name')
snipts = snipts.order_by('-created')
@@ -112,6 +116,7 @@ def blog_posts(request, username):

return context


@render_to('snipts/list-user.html')
def favorites(request, username):

@@ -148,6 +153,7 @@ def favorites(request, username):

return context


@render_to('snipts/list-public.html')
def list_public(request, tag_slug=None):

@@ -175,6 +181,7 @@ def list_public(request, tag_slug=None):

return context


@render_to('snipts/list-user.html')
def list_user(request, username_or_custom_slug, tag_slug=None):

@@ -190,7 +197,8 @@ def list_user(request, username_or_custom_slug, tag_slug=None):
tags = Tag.objects
snipts = Snipt.objects

if user == request.user or (request.GET.get('api_key') == user.api_key.key):
if user == request.user or \
(request.GET.get('api_key') == user.api_key.key):
public = False

favorites = Favorite.objects.filter(user=user).values('snipt')
@@ -232,6 +240,7 @@ def list_user(request, username_or_custom_slug, tag_slug=None):

return context


def raw(request, snipt_key, lexer=None):
snipt = get_object_or_404(Snipt, key=snipt_key)

@@ -250,23 +259,23 @@ def raw(request, snipt_key, lexer=None):
snipt.lexer = lexer
snipt.save()

content_type='text/plain'
content_type = 'text/plain'

if 'nice' in request.GET:
content_type='text/html'
content_type = 'text/html'

return render_to_response('snipts/raw.html',
{'snipt': snipt},
context_instance=RequestContext(request),
content_type=content_type)


def rss(request, context):
return render_to_response(
'rss.xml',
context,
context_instance=RequestContext(request),
content_type="application/rss+xml"
)
return render_to_response('rss.xml',
context,
context_instance=RequestContext(request),
content_type="application/rss+xml")


def search(request, template='search/search.html', load_all=True,
form_class=ModelSearchForm, searchqueryset=None,
@@ -279,12 +288,18 @@ def search(request, template='search/search.html', load_all=True,
# We have a query.
if request.GET.get('q'):

if request.user.is_authenticated() and '--mine' in request.GET.get('q'):
searchqueryset = SearchQuerySet().filter(author=request.user).order_by('-pub_date')
if request.user.is_authenticated() and '--mine' in \
request.GET.get('q'):
searchqueryset = SearchQuerySet().filter(author=request.user) \
.order_by('-pub_date')
else:
searchqueryset = SearchQuerySet().filter(Q(public=True) | Q(author=request.user)).order_by('-pub_date')
searchqueryset = SearchQuerySet() \
.filter(Q(public=True) | Q(author=request.user)) \
.order_by('-pub_date')

form = ModelSearchForm(request.GET, searchqueryset=searchqueryset, load_all=load_all)
form = ModelSearchForm(request.GET,
searchqueryset=searchqueryset,
load_all=load_all)

if form.is_valid():
query = form.cleaned_data['q']
@@ -314,19 +329,24 @@ def search(request, template='search/search.html', load_all=True,
if extra_context:
context.update(extra_context)

return render_to_response(template, context, context_instance=context_class(request))
return render_to_response(template,
context,
context_instance=context_class(request))


def redirect_snipt(request, snipt_key, lexer=None):
snipt = get_object_or_404(Snipt, key=snipt_key)
return HttpResponseRedirect(snipt.get_absolute_url())


def redirect_public_tag_feed(request, tag_slug):
return HttpResponseRedirect('/public/tag/{}/?rss'.format(tag_slug))


def redirect_user_feed(request, username):
user = get_object_or_404(User, username=username)
return HttpResponseRedirect(user.get_absolute_url() + '?rss')


def redirect_user_tag_feed(request, username, tag_slug):
return HttpResponseRedirect(u'/{}/tag/{}/?rss'.format(username, tag_slug))

+ 0
- 1
templates/analytics.html View File

@@ -66,7 +66,6 @@
{% endif %}
{% else %}
<script>
document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')
window.ll = function() {};
</script>
{% endif %}

+ 39
- 34
urls.py View File

@@ -1,6 +1,4 @@
from django.conf import settings
from django.conf.urls import include, patterns, url
from django.conf.urls.static import static
from django.views.generic import TemplateView
from django.http import HttpResponseRedirect
from django.contrib import admin
@@ -11,11 +9,9 @@ from snipts.api import (PublicSniptResource,
from snipts.views import search
from tastypie.api import Api
from utils.views import SniptRegistrationView
from jobs.views import jobs, jobs_json
from views import (homepage, lexers, login_redirect, pro, sitemap, tags,
pro_complete, user_api_key, for_teams, for_teams_complete)

import admin as custom_admin
import os


@@ -32,47 +28,56 @@ private_api.register(PrivateUserResource())
private_api.register(PrivateFavoriteResource())
private_api.register(PrivateUserProfileResource())

urlpatterns = patterns('',
urlpatterns = \
patterns('',

url(r'^$', homepage),
url(r'^login-redirect/$', login_redirect),
url(r'^$', homepage),
url(r'^login-redirect/$', login_redirect),

url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', include(admin.site.urls)),

url(r'^404/$', TemplateView.as_view(template_name='404.html')),
url(r'^500/$', TemplateView.as_view(template_name='500.html')),
url(r'^404/$', TemplateView.as_view(template_name='404.html')),
url(r'^500/$', TemplateView.as_view(template_name='500.html')),

url(r'^robots.txt$', TemplateView.as_view(template_name='robots.txt')),
url(r'^humans.txt$', TemplateView.as_view(template_name='humans.txt')),
url(r'^sitemap.xml$', sitemap),
url(r'^tags/$', tags),
url(r'^robots.txt$',
TemplateView.as_view(template_name='robots.txt')),
url(r'^humans.txt$',
TemplateView.as_view(template_name='humans.txt')),
url(r'^sitemap.xml$', sitemap),
url(r'^tags/$', tags),

url(r'^pro/$', pro),
url(r'^pro/complete/$', pro_complete),
url(r'^pro/$', pro),
url(r'^pro/complete/$', pro_complete),

url(r'^for-teams/$', for_teams),
url(r'^for-teams/complete/$', for_teams_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),

url(r'^api/private/key/$', user_api_key),
url(r'^api/', include(public_api.urls)),
url(r'^api/', include(private_api.urls)),
url(r'^api/private/key/$', user_api_key),
url(r'^api/', include(public_api.urls)),
url(r'^api/', include(private_api.urls)),

url(r'^search/$', search),
url(r'^search/$', search),

url(r'^register/$', lambda x: HttpResponseRedirect('/signup/')),
url(r'^signup/$', SniptRegistrationView.as_view(),
name='registration_register'),
url(r'', include('registration.backends.default.urls')),
url(r'^register/$', lambda x: HttpResponseRedirect('/signup/')),
url(r'^signup/$', SniptRegistrationView.as_view(),
name='registration_register'),
url(r'', include('registration.backends.default.urls')),

url(r'^', include('snipts.urls')),
url(r'^', include('snipts.urls')),

url(r'^(?P<path>favicon\.ico)$', 'django.views.static.serve', {'document_root': os.path.join(os.path.dirname(__file__), 'static/img')}),
)
url(r'^(?P<path>favicon\.ico)$', 'django.views.static.serve', {
'document_root': os.path.join(os.path.dirname(__file__),
'static/img')
}),
)

urlpatterns += patterns('',
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': os.path.join(os.path.dirname(__file__), 'media')}),
)
urlpatterns += \
patterns('',
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {
'document_root': os.path.join(os.path.dirname(__file__),
'media')
}))

+ 1
- 1
utils/backends.py View File

@@ -1,6 +1,6 @@
from django.conf import settings
from django.contrib.auth.models import User


class EmailOrUsernameModelBackend(object):
def authenticate(self, username=None, password=None):
if '@' in username:


+ 8
- 6
utils/forms.py View File

@@ -3,21 +3,22 @@ from registration.forms import RegistrationForm
from django.contrib.auth.models import User
from django import forms


class SniptRegistrationForm(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which enforces uniqueness of
email addresses and further restricts usernames.
"""
def clean_username(self):
"""
Validate that the username is alphanumeric and is not already
in use.
"""
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
existing = User.objects.filter(
username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError(_("A user with that username already exists."))
raise forms.ValidationError(
_("A user with that username already exists."))

elif '@' in self.cleaned_data['username']:
raise forms.ValidationError(_("Cannot have '@' in username."))
@@ -33,8 +34,9 @@ class SniptRegistrationForm(RegistrationForm):
"""
Validate that the supplied email address is unique for the
site.
"""
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
raise forms.ValidationError(
_("""This email address is already in use. Please supply a
different email address."""))
return self.cleaned_data['email']

+ 8
- 2
utils/templatetags/intercom.py View File

@@ -1,10 +1,16 @@
from django.conf import settings
from django import template

import hmac, hashlib, os
import hmac
import hashlib
import os

register = template.Library()


@register.filter
def intercom_sha_256(user_id):
return hmac.new(os.environ.get('INTERCOM_SECRET_KEY', settings.INTERCOM_SECRET_KEY), str(user_id), digestmod=hashlib.sha256).hexdigest()
return hmac.new(os.environ.get('INTERCOM_SECRET_KEY',
settings.INTERCOM_SECRET_KEY),
str(user_id),
digestmod=hashlib.sha256).hexdigest()

+ 1
- 1
utils/templatetags/verbatim.py View File

@@ -20,7 +20,7 @@ class VerbatimNode(template.Node):

def __init__(self, text):
self.text = text
def render(self, context):
return self.text



+ 1
- 1
utils/views.py View File

@@ -1,9 +1,9 @@
from registration.backends.default.views import RegistrationView
from utils.forms import SniptRegistrationForm


class SniptRegistrationView(RegistrationView):
"""
Custom registration view that uses our custom form.
"""
form_class = SniptRegistrationForm

+ 23
- 11
views.py View File

<
@@ -1,10 +1,9 @@
from accounts.models import UserProfile
from annoying.decorators import ajax_request
from annoying.decorators import ajax_request, render_to
from blogs.views import blog_list
from django.http import HttpResponseRedirect, HttpResponseBadRequest
from django.conf import settings
from django.contrib.auth.decorators import login_required
from annoying.decorators import ajax_request, render_to
from django.shortcuts import render_to_response
from django.template import RequestContext
from snipts.utils import get_lexers_list
@@ -19,6 +18,7 @@ import hashlib
import os
import stripe


@render_to('for-teams.html')
def for_teams(request):
if request.user.is_authenticated():
@@ -27,6 +27,7 @@ def for_teams(request):
profile.save()
return {}


@render_to('for-teams-complete.html')
def for_teams_complete(request):

@@ -43,7 +44,8 @@ def for_teams_complete(request):
Info:

%s
""" % (request.user.username, request.user.email, name, members, info), 'support@snipt.net',
""" % (request.user.username, request.user.email, name, members,
info), 'support@snipt.net',
['nick@nicksergeant.com'], fail_silently=False)

profile = request.user.profile
@@ -70,6 +72,7 @@ def for_teams_complete(request):
else:
return HttpResponseBadRequest()


@render_to('homepage.html')
def homepage(request):

@@ -99,6 +102,7 @@ def homepage(request):
'users_count': User.objects.all().count(),
}


@ajax_request
def lexers(request):
lexers = get_lexers_list()
@@ -125,12 +129,14 @@ def lexers(request):

return {'objects': objects}