Initial commit

This commit is contained in:
Mark Steadman 2014-05-03 22:37:15 +01:00
commit 5deab02e61
23 changed files with 995 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pyc

4
README Normal file
View file

@ -0,0 +1,4 @@
Bambu Buffer
============
Post to Buffer and manage profile settings through a Django-powered site.

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Bambu Buffer
Post to Buffer and manage profile settings through a Django-powered site.

109
bambu_buffer/__init__.py Normal file
View file

@ -0,0 +1,109 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.db.models import Model
from bambu_buffer.exceptions import *
from bambu_buffer.models import BufferToken, BufferProfile, BufferedItem
from bambu_buffer.settings import POST_URL, TIMEOUT, AUTOPOST_MODELS
from bambu_buffer.sites import BufferSite
from datetime import datetime, date
from threading import Thread
import requests
__version__ = '2.0'
site = BufferSite()
class BufferThread(Thread):
def __init__(self, token, data, *args, **kwargs):
self.data = data
self.token = token
super(BufferThread, self).__init__(*args, **kwargs)
def run(self):
from bambu_buffer import log
response = requests.post(
'%s?access_token=%s' % (POST_URL, self.token),
self.data,
timeout = TIMEOUT
)
if response.status_code != 200:
log.error(response.json())
def post(item, author, **kwargs):
try:
token = author.buffer_tokens.get()
except BufferToken.DoesNotExist:
return
if 'url' in kwargs:
url = kwargs.get('url')
elif isinstance(item, Model):
url = u'http://%s%s' % (
Site.objects.get_current().domain, item.get_absolute_url()
)
content_type = ContentType.objects.get_for_model(item)
if BufferedItem.objects.filter(
object_id = item.pk,
content_type = content_type
).exists():
print '%s %d has already been sent to Buffer' % (
unicode(item._meta.verbose_name).capitalize(),
item.pk
)
return
BufferedItem.objects.create(
content_type = content_type,
object_id = item.pk
)
else:
url = None
data = {
'text': u'%s%s' % (
unicode(kwargs.get('text') or item),
url and (u' %s' % url) or u''
),
'profile_ids[]': kwargs.get('profile_ids') or BufferProfile.objects.filter(
service__token = token,
selected = True
).values_list('remote_id', flat = True),
'media[description]': kwargs.get('description')
}
if 'picture' in kwargs:
data['media[picture]'] = kwargs['picture']
if not 'thumbnail' in kwargs:
raise TypeError(
'For image-based updates, the thumbnail parameter is required.'
)
if 'thumbnail' in kwargs:
data['media[thumbnail]'] = kwargs['thumbnail']
if 'shorten' in kwargs:
data['shorten'] = kwargs['shorten'] and 'true' or 'false'
if 'now' in kwargs:
data['now'] = kwargs['now'] and 'true' or 'false'
if 'top' in kwargs:
data['top'] = kwargs['top'] and 'true' or 'false'
if 'scheduled_at' in kwargs:
if isinstance(kwargs['scheduled_at'], (datetime, date)):
data['scheduled_at'] = kwargs['scheduled_at'].isoformat()
else:
try:
data['scheduled_at'] = int(kwargs['scheduled_at'])
except:
raise TypeError(
'scheduled_at must be an integer or DateTime'
)
BufferThread(token.token, data).start()
site.hookup_signals(AUTOPOST_MODELS)

View file

@ -0,0 +1,5 @@
class BufferException(Exception):
pass
class NoBufferTokenException(Exception):
pass

39
bambu_buffer/log.py Normal file
View file

@ -0,0 +1,39 @@
from django.contrib import messages
from django.utils.translation import ugettext as _
from bambu_buffer.settings import SUCCESS_MESSAGE, ERROR_MESSAGE
from bambu_buffer.exceptions import BufferException
import logging
logger = logging.getLogger('bambu_buffer')
def error(data, request = None, raise_error = False):
error = data.get('error_description',
data.get('message',
'error' in data and data.get('error').capitalize().replace('_', ' ') or ''
)
) or u'Unknown error'
if 'message' in data:
data['error_message'] = data.pop('message')
logger.error(error, extra = data)
if request and ERROR_MESSAGE:
messages.error(request,
_(ERROR_MESSAGE) % error
)
if raise_error:
raise BufferException(error)
def success(data, request = None):
message = None
if 'access_token' in data:
message = u'Buffer access token created'
if message:
logger.info(message)
if request and SUCCESS_MESSAGE:
messages.success(request,
_(SUCCESS_MESSAGE)
)

View file

View file

@ -0,0 +1,40 @@
from django.db import transaction
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
from os import sys
class Command(BaseCommand):
help = 'Fake Buffer records for items in settings.BUFFER_AUTOPOST_MODELS'
@transaction.commit_on_success
def handle(self, *args, **options):
from django.contrib.contenttypes.models import ContentType
from bambu_buffer import site
from bambu_buffer.models import BufferedItem
for model, info in site._registry.items():
query = dict(
[
(key, callable(value) and value() or value)
for (key, value) in info['conditions'].items()
]
)
count = 0
for pk in model.objects.filter(**query).values_list('pk', flat = True):
item, created = BufferedItem.objects.get_or_create(
content_type = ContentType.objects.get_for_model(model),
object_id = pk
)
if not created:
count += 1
sys.stdout.write(
'Added fake Buffer item for %d %s\n' % (
count,
unicode(
count == 1 and model._meta.verbose_name or model._meta.verbose_name_plural
)
)
)

View file

@ -0,0 +1,118 @@
# -*- 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 model 'BufferToken'
db.create_table('buffer_token', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='buffer_tokens', unique=True, to=orm['auth.User'])),
('token', self.gf('django.db.models.fields.CharField')(max_length=36)),
))
db.send_create_signal(u'buffer', ['BufferToken'])
# Adding model 'BufferService'
db.create_table('buffer_service', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('token', self.gf('django.db.models.fields.related.ForeignKey')(related_name='services', to=orm['buffer.BufferToken'])),
('name', self.gf('django.db.models.fields.CharField')(max_length=30)),
('remote_id', self.gf('django.db.models.fields.CharField')(max_length=36)),
('username', self.gf('django.db.models.fields.CharField')(max_length=30)),
))
db.send_create_signal(u'buffer', ['BufferService'])
# Adding model 'BufferProfile'
db.create_table('buffer_profile', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('service', self.gf('django.db.models.fields.related.ForeignKey')(related_name='profiles', to=orm['buffer.BufferService'])),
('avatar', self.gf('django.db.models.fields.URLField')(max_length=255)),
('created_at', self.gf('django.db.models.fields.DateTimeField')()),
('default', self.gf('django.db.models.fields.BooleanField')(default=True)),
('formatted_username', self.gf('django.db.models.fields.CharField')(max_length=100)),
('remote_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=36)),
('schedules', self.gf('django.db.models.fields.TextField')()),
))
db.send_create_signal(u'buffer', ['BufferProfile'])
def backwards(self, orm):
# Deleting model 'BufferToken'
db.delete_table('buffer_token')
# Deleting model 'BufferService'
db.delete_table('buffer_service')
# Deleting model 'BufferProfile'
db.delete_table('buffer_profile')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'buffer.bufferprofile': {
'Meta': {'object_name': 'BufferProfile', 'db_table': "'buffer_profile'"},
'avatar': ('django.db.models.fields.URLField', [], {'max_length': '255'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {}),
'default': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'formatted_username': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'remote_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
'schedules': ('django.db.models.fields.TextField', [], {}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'profiles'", 'to': u"orm['buffer.BufferService']"})
},
u'buffer.bufferservice': {
'Meta': {'object_name': 'BufferService', 'db_table': "'buffer_service'"},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'remote_id': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
'token': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': u"orm['buffer.BufferToken']"}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30'})
},
u'buffer.buffertoken': {
'Meta': {'object_name': 'BufferToken', 'db_table': "'buffer_token'"},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buffer_tokens'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
u'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'}),
u'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 = ['buffer']

View file

@ -0,0 +1,87 @@
# -*- 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 'BufferProfile.selected'
db.add_column('buffer_profile', 'selected',
self.gf('django.db.models.fields.BooleanField')(default=1),
keep_default=False)
def backwards(self, orm):
# Deleting field 'BufferProfile.selected'
db.delete_column('buffer_profile', 'selected')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'buffer.bufferprofile': {
'Meta': {'object_name': 'BufferProfile', 'db_table': "'buffer_profile'"},
'avatar': ('django.db.models.fields.URLField', [], {'max_length': '255'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {}),
'default': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'formatted_username': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'remote_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
'schedules': ('django.db.models.fields.TextField', [], {}),
'selected': ('django.db.models.fields.BooleanField', [], {}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'profiles'", 'to': u"orm['buffer.BufferService']"})
},
u'buffer.bufferservice': {
'Meta': {'object_name': 'BufferService', 'db_table': "'buffer_service'"},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'remote_id': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
'token': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': u"orm['buffer.BufferToken']"}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30'})
},
u'buffer.buffertoken': {
'Meta': {'object_name': 'BufferToken', 'db_table': "'buffer_token'"},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buffer_tokens'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
u'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'}),
u'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 = ['buffer']

View file

@ -0,0 +1,102 @@
# -*- 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 model 'BufferedItem'
db.create_table(u'buffer_buffereditem', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()),
))
db.send_create_signal(u'buffer', ['BufferedItem'])
# Adding unique constraint on 'BufferedItem', fields ['content_type', 'object_id']
db.create_unique(u'buffer_buffereditem', ['content_type_id', 'object_id'])
def backwards(self, orm):
# Removing unique constraint on 'BufferedItem', fields ['content_type', 'object_id']
db.delete_unique(u'buffer_buffereditem', ['content_type_id', 'object_id'])
# Deleting model 'BufferedItem'
db.delete_table(u'buffer_buffereditem')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'buffer.buffereditem': {
'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'BufferedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
},
u'buffer.bufferprofile': {
'Meta': {'object_name': 'BufferProfile', 'db_table': "'buffer_profile'"},
'avatar': ('django.db.models.fields.URLField', [], {'max_length': '255'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {}),
'default': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'formatted_username': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'remote_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
'schedules': ('django.db.models.fields.TextField', [], {}),
'selected': ('django.db.models.fields.BooleanField', [], {}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'profiles'", 'to': u"orm['buffer.BufferService']"})
},
u'buffer.bufferservice': {
'Meta': {'object_name': 'BufferService', 'db_table': "'buffer_service'"},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'remote_id': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
'token': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': u"orm['buffer.BufferToken']"}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30'})
},
u'buffer.buffertoken': {
'Meta': {'object_name': 'BufferToken', 'db_table': "'buffer_token'"},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buffer_tokens'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
u'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'}),
u'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 = ['buffer']

View file

116
bambu_buffer/models.py Normal file
View file

@ -0,0 +1,116 @@
from django.db import models
from django.utils.timezone import pytz
from bambu_buffer.settings import PROFILES_URL, TIMEOUT
from bambu_buffer import log, helpers
from datetime import datetime, timedelta
import requests, json
class BufferToken(models.Model):
user = models.ForeignKey('auth.User', related_name = 'buffer_tokens', unique = True)
token = models.CharField(max_length = 36)
def __unicode__(self):
return self.token
def save(self, *args, **kwargs):
new = not self.pk
super(BufferToken, self).save(*args, **kwargs)
if new:
self.refresh_services()
def refresh_services(self):
response = requests.get(
'%s?access_token=%s' % (
PROFILES_URL,
self.token
),
timeout = TIMEOUT
)
if response.status_code == 200:
self.services.all().delete()
services = {}
for profile in response.json():
remote_id = profile['formatted_service']
if remote_id in services:
service = services[remote_id]
else:
service = self.services.create(
remote_id = profile[u'service_id'],
name = remote_id,
username = profile['service_username']
)
services[service.remote_id] = service
epoch = datetime(1970, 1, 1, 0, 0, 0,
tzinfo = pytz.timezone(
profile['timezone']
)
)
service.profiles.create(
avatar = profile.get('avatar_https',
profile.get('avatar')
),
created_at = epoch + timedelta(
seconds = profile['created_at']
),
default = profile['default'],
selected = profile['default'],
formatted_username = profile['formatted_username'],
remote_id = profile['id'],
schedules = json.dumps(
profile['schedules']
)
)
else:
log.error(response.json(), request)
class Meta:
db_table = 'buffer_token'
class BufferService(models.Model):
token = models.ForeignKey(BufferToken, related_name = 'services')
name = models.CharField(max_length = 30)
remote_id = models.CharField(max_length = 36)
username = models.CharField(max_length = 30)
def __unicode__(self):
return self.name
@property
def icon(self):
return self.name.lower().split(' ')[0].replace('+', '-plus')
class Meta:
db_table = 'buffer_service'
class BufferProfile(models.Model):
service = models.ForeignKey(BufferService, related_name = 'profiles')
avatar = models.URLField(max_length = 255)
created_at = models.DateTimeField()
default = models.BooleanField(default = True)
formatted_username = models.CharField(max_length = 100)
remote_id = models.CharField(max_length = 36, unique = True)
schedules = models.TextField()
selected = models.BooleanField()
def __unicode__(self):
return self.formatted_username
@property
def icon(self):
return self.service.icon
class Meta:
db_table = 'buffer_profile'
class BufferedItem(models.Model):
content_type = models.ForeignKey('contenttypes.ContentType')
object_id = models.PositiveIntegerField()
class Meta:
unique_together = ('content_type', 'object_id')
db_table = 'buffer_buffereditem'

44
bambu_buffer/settings.py Normal file
View file

@ -0,0 +1,44 @@
from django.conf import settings as s
from django.utils.timezone import now
CLIENT_ID = s.BUFFER_CLIENT_ID
CLIENT_SECRET = s.BUFFER_CLIENT_SECRET
AUTH_REDIRECT = getattr(s, 'BUFFER_AUTH_REDIRECT', '/')
SUCCESS_MESSAGE = getattr(s, 'BUFFER_SUCCESS_MESSAGE',
u'Your Buffer account is now conntected.'
)
ERROR_MESSAGE = getattr(s, 'BUFFER_ERROR_MESSAGE',
u'Sorry, your account could not be connected to Buffer: %s.'
)
UPDATED_MESSAGE = getattr(s, 'BUFFER_UPDATED_MESSAGE',
u'Your Buffer settings have been updated.'
)
REFRESHED_MESSAGES = getattr(s, 'BUFFER_REFRESHED_MESSAGES',
u'Your Buffer profiles have been refreshed.'
)
AUTOPOST_MODELS = getattr(s, 'BUFFER_AUTOPOST_MODELS',
(
(
'blog.Post',
'author', {
'published': True,
'date__lte': now
},
{
'top': True
}
),
)
)
TIMEOUT = getattr(s, 'BUFFER_TIMEOUT', 5)
AUTHORISE_URL = 'https://bufferapp.com/oauth2/authorize'
TOKEN_URL = 'https://api.bufferapp.com/1/oauth2/token.json'
PROFILES_URL = 'https://api.bufferapp.com/1/profiles.json'
POST_URL = 'https://api.bufferapp.com/1/updates/create.json'
RESPONSE_TYPE = 'code'
AUTHORISATION_CODE = 'authorization_code'

101
bambu_buffer/sites.py Normal file
View file

@ -0,0 +1,101 @@
from logging import getLogger
from django.db.models.loading import get_model
from django.db.models.signals import post_save
def post_save_receiver(sender, instance, **kwargs):
from bambu_buffer import post, site
model = site.get_info(type(instance))
if not model or not any(model):
print '%s not registered' % (
unicode(instance._meta.verbose_name).capitalize()
)
return
if any(model['conditions']):
query = dict(
[
(key, callable(value) and value() or value)
for (key, value) in model['conditions'].items()
]
)
if not type(instance).objects.filter(
pk = instance.pk,
**query
).exists():
print '%s does not match Buffer criteria' % unicode(
unicode(instance._meta.verbose_name).capitalize()
)
return
post(
instance,
getattr(instance,
model['author_field']
),
**dict(
[
(key, callable(value) and value() or value)
for (key, value) in model['post_kwargs'].items()
]
)
)
class BufferSite(object):
def __init__(self, *args, **kwargs):
self._registry = {}
def register(self, model, author_field, conditions = {}, post_kwargs = {}):
self._registry[model] = {
'author_field': author_field,
'conditions': conditions,
'post_kwargs': post_kwargs
}
def get_info(self, model):
return self._registry.get(model)
def hookup_signals(self, models):
logger = getLogger('bambu_buffer')
for m in [list(m) for m in models]:
if not any(m):
continue
name = m.pop(0)
if any(m):
author_field = m.pop(0)
else:
author_field = 'author'
if any(m):
conditions = m.pop(0)
else:
conditions = {}
if any(m):
post_kwargs = m.pop(0)
else:
post_kwargs = {}
try:
model = get_model(*name.split('.'))
except:
logger.warn('Model %s not found' % name)
continue
field = model._meta.get_field_by_name(author_field)
if not any(field) or field[0] is None:
raise Exception(
'Field %s not found in model %s' % (author_field, name)
)
self.register(model,
author_field = author_field,
conditions = conditions,
post_kwargs = post_kwargs
)
post_save.connect(post_save_receiver, sender = model)

View file

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

View file

@ -0,0 +1,35 @@
{% extends 'buffer/base.html' %}
{% load i18n icons %}
{% block page_header %}
<h1>{% trans 'Your Buffer profiles' %}</h1>
<p>{% trans 'Select the Buffer profiles you want to automatically post to.' %}</p>
{% endblock page_header %}
{% block form_content %}
<form method="post">
<ul class="media-list">
{% for profile in profiles %}
<li class="media">
<div class="pull-left">
<img class="media-object" src="{{ profile.avatar }}" alt="{{ profile }}" width="50" height="50" />
</div>
<div class="media-body">
<div class="checkbox">
<label>
<input name="profiles" type="checkbox" value="{{ profile.pk }}"{% if profile.selected %} checked{% endif %} />
<span class="h4">{{ profile }}</span><br />
{% icon profile.icon %} <span class="p">{{ profile.service }}</span>
</label>
</div>
</div>
</li>
{% endfor %}
</ul>
{% csrf_token %}
<button class="btn btn-primary">{% trans 'Save settings' %}</button>
<a class="btn btn-default" href="{% url 'buffer_refresh' %}">{% icon 'refresh' %} {% trans 'Reload my Buffer profiles' %}</a>
</form>
{% endblock form_content %}

12
bambu_buffer/tests.py Normal file
View file

@ -0,0 +1,12 @@
from django.test import TestCase
from django.test.client import RequestFactory
from bambu_buffer.views import auth
class AuthorisationTestCase(TestCase):
def setUp(self):
self.client = RequestFactory()
def test_authorisation(self):
request = self.client.get('/buffer/auth/')
response = auth(request)
response

8
bambu_buffer/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.conf.urls import patterns, url
urlpatterns = patterns('bambu_buffer.views',
url('^$', 'profiles', name = 'buffer_profiles'),
url('^refresh/$', 'refresh', name = 'buffer_refresh'),
url('^auth/$', 'auth', name = 'buffer_auth'),
url('^callback/$', 'callback', name = 'buffer_callback')
)

135
bambu_buffer/views.py Normal file
View file

@ -0,0 +1,135 @@
from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.db import transaction
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse
from django.utils.http import urlencode
from bambu_buffer import settings, log
from bambu_buffer.models import BufferToken, BufferProfile
import requests
@login_required
def auth(request):
next = request.GET.get('next', settings.AUTH_REDIRECT)
request.session['bambu_buffer.next'] = next
return HttpResponseRedirect(
'%s?%s' % (
settings.AUTHORISE_URL,
urlencode(
{
'client_id': settings.CLIENT_ID,
'redirect_uri': 'http%s://%s%s' % (
request.is_secure() and 's' or '',
Site.objects.get_current(),
reverse('buffer_callback')
),
'response_type': settings.RESPONSE_TYPE
}
)
)
)
@login_required
def callback(request):
response = requests.post(
settings.TOKEN_URL,
data = {
'client_id': settings.CLIENT_ID,
'client_secret': settings.CLIENT_SECRET,
'redirect_uri': 'http%s://%s%s' % (
request.is_secure() and 's' or '',
Site.objects.get_current(),
reverse('buffer_callback')
),
'code': request.GET.get('code'),
'grant_type': settings.AUTHORISATION_CODE
},
timeout = settings.TIMEOUT
)
next = request.session.get('bambu_buffer.next',
settings.AUTH_REDIRECT
)
if response.status_code == 200:
data = response.json()
token = data.get('access_token')
with transaction.commit_on_success():
request.user.buffer_tokens.all().delete()
request.user.buffer_tokens.create(
token = token
)
log.success(data, request)
return HttpResponseRedirect(
reverse('buffer_profiles')
)
else:
log.error(response.json(), request)
return HttpResponseRedirect(next)
@login_required
def profiles(request):
try:
token = request.user.buffer_tokens.get()
except BufferToken.DoesNotExist:
return HttpResponseRedirect(
reverse('buffer_auth')
)
if request.method == 'POST':
selected = request.POST.getlist('profiles')
for pk in selected:
BufferProfile.objects.filter(
service__token = token,
pk = pk
).update(
selected = True
)
BufferProfile.objects.filter(
service__token = token
).exclude(
pk__in = selected
).update(
selected = False
)
if settings.UPDATED_MESSAGE:
messages.success(request, settings.UPDATED_MESSAGE)
return HttpResponseRedirect(
reverse('buffer_profiles')
)
return TemplateResponse(
request,
'buffer/profiles.html',
{
'profiles': BufferProfile.objects.filter(
service__token = token
).select_related()
}
)
@login_required
def refresh(request):
try:
token = request.user.buffer_tokens.get()
except BufferToken.DoesNotExist:
return HttpResponseRedirect(
reverse('buffer_auth')
)
token.refresh_services()
if settings.REFRESHED_MESSAGES:
messages.success(request, settings.REFRESHED_MESSAGES)
return HttpResponseRedirect(
reverse('buffer_profiles')
)

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
Django>=1.6
requests>=2.0

33
setup.py Normal file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env python
from setuptools import setup
from os import path
setup(
name = 'bambu-buffer',
version = '2.0',
description = 'Post to Buffer and manage profile settings through a Django-powered site',
author = 'Steadman',
author_email = 'mark@steadman.io',
url = 'https://github.com/iamsteadman/bambu-buffer',
long_description = open(path.join(path.dirname(__file__), 'README')).read(),
packages = [
'bambu_buffer',
'bambu_buffer.migrations',
'bambu_buffer.management',
'bambu_buffer.management.commands'
],
package_data = {
'bambu_buffer': [
'templates/buffer/*.html'
]
},
install_requires = [
'Django>=1.6',
'requests>=2.0'
],
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django'
]
)