Support for Google Ads in Pro blogs. Sanitize all Pro fields.

master
Nick Sergeant 2013-02-09 00:06:22 -05:00
parent 673f608727
commit 3cb6411a2c
8 changed files with 297 additions and 11 deletions

View File

@ -1,7 +1,114 @@
from django.forms import ModelForm
from django import forms
from accounts.models import UserProfile
class AccountForm(ModelForm):
import re
class AccountForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('user', 'is_pro', 'stripe_id',)
def clean_blog_title(self):
data = self.cleaned_data['blog_title']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_blog_theme(self):
data = self.cleaned_data['blog_theme']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_blog_domain(self):
data = self.cleaned_data['blog_domain']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_default_editor(self):
data = self.cleaned_data['default_editor']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_editor_theme(self):
data = self.cleaned_data['editor_theme']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_gittip_username(self):
data = self.cleaned_data['gittip_username']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_disqus_shortname(self):
data = self.cleaned_data['disqus_shortname']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_google_analytics_tracking_id(self):
data = self.cleaned_data['google_analytics_tracking_id']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_gauges_site_id(self):
data = self.cleaned_data['gauges_site_id']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_google_ad_client(self):
data = self.cleaned_data['google_ad_client']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_google_ad_slot(self):
data = self.cleaned_data['google_ad_slot']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_google_ad_width(self):
data = self.cleaned_data['google_ad_width']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data
def clean_google_ad_height(self):
data = self.cleaned_data['google_ad_height']
if not re.match('^[A-Za-z0-9\._-]*$', data):
raise forms.ValidationError('Only letters, numbers, underscores, dashes, and periods are valid.')
return data

View File

@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'UserProfile.google_ad_client'
db.add_column('accounts_userprofile', 'google_ad_client',
self.gf('django.db.models.fields.CharField')(max_length=250, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.google_ad_slot'
db.add_column('accounts_userprofile', 'google_ad_slot',
self.gf('django.db.models.fields.CharField')(max_length=250, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.google_ad_width'
db.add_column('accounts_userprofile', 'google_ad_width',
self.gf('django.db.models.fields.CharField')(max_length=250, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.google_ad_height'
db.add_column('accounts_userprofile', 'google_ad_height',
self.gf('django.db.models.fields.CharField')(max_length=250, null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'UserProfile.google_ad_client'
db.delete_column('accounts_userprofile', 'google_ad_client')
# Deleting field 'UserProfile.google_ad_slot'
db.delete_column('accounts_userprofile', 'google_ad_slot')
# Deleting field 'UserProfile.google_ad_width'
db.delete_column('accounts_userprofile', 'google_ad_width')
# Deleting field 'UserProfile.google_ad_height'
db.delete_column('accounts_userprofile', 'google_ad_height')
models = {
'accounts.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'blog_domain': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'blog_theme': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}),
'blog_title': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'default_editor': ('django.db.models.fields.CharField', [], {'default': "'C'", 'max_length': '250'}),
'disqus_shortname': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'editor_theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '250'}),
'gauges_site_id': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'gittip_username': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'google_ad_client': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'google_ad_height': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'google_ad_slot': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'google_ad_width': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'google_analytics_tracking_id': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_pro': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'stripe_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['accounts']

View File

@ -33,19 +33,30 @@ class UserProfile(models.Model):
('xq-dark', 'XQ Dark'),
)
# User
user = models.ForeignKey(User, unique=True)
is_pro = models.BooleanField(default=False)
stripe_id = models.CharField(max_length=100, null=True, blank=True)
# Blog
blog_title = models.CharField(max_length=250, null=True, blank=True)
blog_theme = models.CharField(max_length=1, null=False, blank=False, default='D', 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)
# Services and Analytics
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 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_height = models.CharField(max_length=250, null=True, blank=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

View File

@ -16,6 +16,11 @@
<p class="alert alert-{{ message.tags }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% if form.errors %}
<p class="alert alert-error">
Only letters, numbers, underscores, dashes, and periods are valid in Pro settings fields.
</p>
{% endif %}
<ul>
<li>Username: {{ request.user.username }}</li>
<li><a href="/api/">API</a> key: {{ request.user.api_key.key }}</li>
@ -25,65 +30,95 @@
{% if request.user.profile.is_pro %}
<form class="form-horizontal" action="/account/" method="post">
<legend>Edit Pro settings</legend>
<div class="control-group">
<div class="control-group {% if form.errors.blog_title %}error{% endif %}">
<label class="control-label" for="id_blog_title">Blog title:</label>
<div class="controls">
{{ form.blog_title }}
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.blog_theme %}error{% endif %}">
<label class="control-label" for="id_blog_theme">Blog theme:</label>
<div class="controls">
{{ form.blog_theme }}
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.blog_domain %}error{% endif %}">
<label class="control-label" for="id_blog_domain">Blog domain:</label>
<div class="controls">
{{ form.blog_domain }}
<span class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME / A-record to point to 96.126.110.160</span>
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.default_editor %}error{% endif %}">
<label class="control-label" for="id_default_editor">Default editor:</label>
<div class="controls">
{{ form.default_editor }}
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.editor_theme %}error{% endif %}">
<label class="control-label" for="id_editor_theme">Default editor theme:</label>
<div class="controls">
{{ form.editor_theme }}
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.gittip_username %}error{% endif %}">
<label class="control-label" for="id_gittip_username">Gittip username:</label>
<div class="controls">
{{ form.gittip_username }}
<span class="help-block">Your <a href="https://www.gittip.com/">Gittip</a> username, if you have one.</span>
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.disqus_shortname %}error{% endif %}">
<label class="control-label" for="id_disqus_shortname">Disqus shortname:</label>
<div class="controls">
{{ form.disqus_shortname }}
<span class="help-block">If you have your own <a href="http://disqus.com/">Disqus</a> account that you'd like to use for your blog comments, enter your shortname here.</span>
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.google_analytics_tracking_id %}error{% endif %}">
<label class="control-label" for="id_google_analytics_tracking_id">Google Analytics tracking ID:</label>
<div class="controls">
{{ form.google_analytics_tracking_id }}
<span class="help-block">If you'd like to track visits to your blog site with <a href="http://analytics.google.com">Google Analytics</a>, enter your tracking ID here.</span>
</div>
</div>
<div class="control-group">
<div class="control-group {% if form.errors.gauges_site_id %}error{% endif %}">
<label class="control-label" for="id_gauges_site_id">Gauges site ID:</label>
<div class="controls">
{{ form.gauges_site_id }}
<span class="help-block">If you'd like to track visits to your blog site with <a href="http://get.gaug.es/">Gauges</a>, enter your site ID here.</span>
</div>
</div>
<div class="control-group {% if form.errors.google_ad_client %}error{% endif %}">
<label class="control-label" for="id_google_ad_client">Google Ads: "google_ad_client"</label>
<div class="controls">
{{ form.google_ad_client }}
<span class="help-block">If you'd like to run Google Ads in your blog's sidebar, enter your client ID here, as well as the following three fields.</span>
</div>
</div>
<div class="control-group {% if form.errors.google_ad_slot %}error{% endif %}">
<label class="control-label" for="id_google_ad_slot">Google Ads: "google_ad_slot"</label>
<div class="controls">
{{ form.google_ad_slot }}
</div>
</div>
<div class="control-group {% if form.errors.google_ad_width %}error{% endif %}">
<label class="control-label" for="id_google_ad_width">Google Ads: "google_ad_width"</label>
<div class="controls">
{{ form.google_ad_width }}
</div>
</div>
<div class="control-group {% if form.errors.google_ad_height %}error{% endif %}">
<label class="control-label" for="id_google_ad_height">Google Ads: "google_ad_height"</label>
<div class="controls">
{{ form.google_ad_height }}
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-success">Save</button>
</div>

View File

@ -32,6 +32,11 @@ def account(request):
'disqus_shortname': profile.disqus_shortname,
'google_analytics_tracking_id': profile.google_analytics_tracking_id,
'gauges_site_id': profile.gauges_site_id,
'google_ad_client': profile.google_ad_client,
'google_ad_slot': profile.google_ad_slot,
'google_ad_width': profile.google_ad_width,
'google_ad_height': profile.google_ad_height,
})
return {

View File

@ -68,6 +68,17 @@
{{ sidebar.stylized|safe }}
</section>
{% endif %}
{% if blog_user.profile.is_pro and blog_user.profile.google_ad_client %}
<div class="google-ads">
<script type="text/javascript">
google_ad_client = '{{ blog_user.profile.google_ad_client }}';
google_ad_slot = '{{ blog_user.profile.google_ad_slot }}';
google_ad_width = {{ blog_user.profile.google_ad_width }};
google_ad_height = {{ blog_user.profile.google_ad_height }};
</script>
<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
</div>
{% endif %}
{% if not blog_user.profile.is_pro %}
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
<ul class="powered">

View File

@ -89,6 +89,17 @@
{% if sidebar %}
{{ sidebar.stylized|safe }}
{% endif %}
{% if blog_user.profile.google_ad_client %}
<div class="google-ads">
<script type="text/javascript">
google_ad_client = '{{ blog_user.profile.google_ad_client }}';
google_ad_slot = '{{ blog_user.profile.google_ad_slot }}';
google_ad_width = {{ blog_user.profile.google_ad_width }};
google_ad_height = {{ blog_user.profile.google_ad_height }};
</script>
<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
</div>
{% endif %}
{% if normal_snipts %}
<section class="module snipts">
<h1>Recent snipts</h1>

View File

@ -35,6 +35,7 @@
<li>Use your own <a href="http://disqus.com">Disqus</a> account for comments on your Snipt blog.</li>
<li>Use your own <a href="http://analytics.google.com">Google Analytics</a> account for tracking visits to your Snipt blog.</li>
<li>Use your own <a href="http://get.gaug.es/">Gauges</a> account for tracking visits to your Snipt blog.</li>
<li>Display your own <a href="https://www.google.com/adsense/">Google Ads</a> in the sidebar of your Snipt blog.</li>
</ul>
{% if request.user.profile.is_pro %}
<h3>You're already a Pro. You know that ;)</h3>