From f354fe977d25b1669a32f2d0124be6e0337c65ba Mon Sep 17 00:00:00 2001 From: Charles Connell Date: Mon, 23 Dec 2013 15:12:17 -0500 Subject: [PATCH] Tweet about new notes periodically --- .../0005_auto__add_field_note_tweeted.py | 104 ++++++++++++++++++ karmaworld/apps/notes/models.py | 1 + karmaworld/apps/notes/tasks.py | 66 +++++++++++ karmaworld/apps/users/admin.py | 7 ++ karmaworld/apps/users/models.py | 2 - karmaworld/secret/twitter.py.example | 8 ++ karmaworld/settings/prod.py | 13 ++- reqs/common.txt | 2 + 8 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 karmaworld/apps/notes/migrations/0005_auto__add_field_note_tweeted.py create mode 100644 karmaworld/apps/users/admin.py create mode 100644 karmaworld/secret/twitter.py.example diff --git a/karmaworld/apps/notes/migrations/0005_auto__add_field_note_tweeted.py b/karmaworld/apps/notes/migrations/0005_auto__add_field_note_tweeted.py new file mode 100644 index 0000000..9a331de --- /dev/null +++ b/karmaworld/apps/notes/migrations/0005_auto__add_field_note_tweeted.py @@ -0,0 +1,104 @@ +# -*- 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 'Note.tweeted' + db.add_column('notes_note', 'tweeted', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Note.tweeted' + db.delete_column('notes_note', 'tweeted') + + + models = { + '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'}) + }, + 'courses.course': { + 'Meta': {'ordering': "['-file_count', 'school', 'name']", 'unique_together': "(('school', 'name', 'instructor_name'),)", 'object_name': 'Course'}, + 'academic_year': ('django.db.models.fields.IntegerField', [], {'default': '2013', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'desc': ('django.db.models.fields.TextField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}), + 'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instructor_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'instructor_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'school': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.School']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}) + }, + 'courses.school': { + 'Meta': {'ordering': "['-file_count', '-priority', 'name']", 'object_name': 'School'}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'facebook_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'priority': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'blank': 'True'}), + 'usde_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'notes.note': { + 'Meta': {'ordering': "['-uploaded_at']", 'object_name': 'Note'}, + 'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.Course']"}), + 'desc': ('django.db.models.fields.TextField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}), + 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'embed_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'file_type': ('django.db.models.fields.CharField', [], {'default': "'???'", 'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'fp_file': ('django_filepicker.models.FPFileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'is_flagged': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'note_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'tweeted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.KarmaUser']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'year': ('django.db.models.fields.IntegerField', [], {'default': '2013', 'null': 'True', 'blank': 'True'}) + }, + 'taggit.tag': { + 'Meta': {'ordering': "['namespace', 'name']", 'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'namespace': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'taggit.taggeditem': { + 'Meta': {'object_name': 'TaggedItem'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"}) + }, + 'users.karmauser': { + 'Meta': {'object_name': 'KarmaUser'}, + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['notes'] \ No newline at end of file diff --git a/karmaworld/apps/notes/models.py b/karmaworld/apps/notes/models.py index ab5f46d..ed39e78 100644 --- a/karmaworld/apps/notes/models.py +++ b/karmaworld/apps/notes/models.py @@ -169,6 +169,7 @@ class Note(Document): is_flagged = models.BooleanField(default=False) is_moderated = models.BooleanField(default=False) + tweeted = models.BooleanField(default=False) thanks = models.PositiveIntegerField(default=0) diff --git a/karmaworld/apps/notes/tasks.py b/karmaworld/apps/notes/tasks.py index e69de29..b627ccf 100644 --- a/karmaworld/apps/notes/tasks.py +++ b/karmaworld/apps/notes/tasks.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding:utf8 -*- +# Copyright (C) 2013 FinalsClub Foundation + +from celery import task +from karmaworld.apps.notes.models import Note +from karmaworld.secret.twitter import * +import twitter +import logging +from pyshorteners.shorteners import Shortener + +logger = logging.getLogger(__name__) + +@task(name="tweet_note") +def tweet_note(): + """Tweet about a new note.""" + + api = twitter.Api(consumer_key=CONSUMER_KEY, + consumer_secret=CONSUMER_SECRET, + access_token_key=ACCESS_TOKEN_KEY, + access_token_secret=ACCESS_TOKEN_SECRET) + + newest_notes = Note.objects.all()[:100] + for n in newest_notes: + if not n.tweeted: + update = tweet_string(n) + logger.info("Tweeting:") + logger.info(update) + + # Mark this tweeted before we actually tweet it + # to be extra safe against double tweets + n.tweeted = True + n.save() + + api.PostUpdate(tweet_string(n)) + + break + + +def tweet_string(note): + # This url will use 13 characters + shortener = Shortener('GoogleShortener') + url = "https://www.karmanotes.org" + \ + note.get_absolute_url() + short_url = shortener.short(url) + + # space character + + # 16 characters + school = note.course.school.slug + short_school = school[:school.find('-')][:16] + + # space character + + # 50 characters + short_course = note.course.name[:50] + + # space and colon characters + + # 57 characters + short_note = note.name[:57] + + return short_url + " #" + short_school + " " + \ + short_course + ": " + \ + short_note + diff --git a/karmaworld/apps/users/admin.py b/karmaworld/apps/users/admin.py new file mode 100644 index 0000000..0cdb5fc --- /dev/null +++ b/karmaworld/apps/users/admin.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding:utf8 -*- +# Copyright (C) 2013 FinalsClub Foundation +from django.contrib import admin +from karmaworld.apps.users.models import KarmaUser + +admin.site.register(KarmaUser) \ No newline at end of file diff --git a/karmaworld/apps/users/models.py b/karmaworld/apps/users/models.py index b2997b0..4539aa3 100644 --- a/karmaworld/apps/users/models.py +++ b/karmaworld/apps/users/models.py @@ -11,5 +11,3 @@ class KarmaUser(models.Model): def __unicode__(self): return u'{e}'.format(e=self.email) -admin.site.register(KarmaUser) - diff --git a/karmaworld/secret/twitter.py.example b/karmaworld/secret/twitter.py.example new file mode 100644 index 0000000..bf7347f --- /dev/null +++ b/karmaworld/secret/twitter.py.example @@ -0,0 +1,8 @@ + + +CONSUMER_KEY = '' +CONSUMER_SECRET = '' +ACCESS_TOKEN_KEY = '' +ACCESS_TOKEN_SECRET = '' + + diff --git a/karmaworld/settings/prod.py b/karmaworld/settings/prod.py index 538cf96..869be9f 100644 --- a/karmaworld/settings/prod.py +++ b/karmaworld/settings/prod.py @@ -5,7 +5,7 @@ from os import environ - +from datetime import timedelta from S3 import CallingFormat from common import * @@ -97,6 +97,17 @@ BROKER_URL = environ.get('RABBITMQ_URL') or environ.get('CLOUDAMQP_URL') # See: http://docs.celeryproject.org/en/latest/configuration.html#celery-result-backend CELERY_RESULT_BACKEND = 'amqp' + +# Periodic tasks +CELERYBEAT_SCHEDULE = { + 'tweet-about-notes': { + 'task': 'tweet_note', + 'schedule': timedelta(minutes-60), + }, +} + +CELERY_TIMEZONE = 'UTC' + ########## END CELERY CONFIGURATION diff --git a/reqs/common.txt b/reqs/common.txt index 3558467..850d8b9 100644 --- a/reqs/common.txt +++ b/reqs/common.txt @@ -16,3 +16,5 @@ fabric-virtualenv requests beautifulsoup4 pyopenssl +python-twitter +pyshorteners -- 2.25.1