Reorganise the file structure into a project tree

This commit is contained in:
2011-03-11 12:58:50 +00:00
parent 58b1691638
commit 3686aa7523
226 changed files with 7 additions and 5 deletions

0
app/reddit/__init__.py Normal file
View File

18
app/reddit/admin.py Normal file
View File

@@ -0,0 +1,18 @@
from django.contrib import admin
from reddit.models import RedditAccount
from reddit.forms import RedditAccountForm
from datetime import date
class RedditAccountAdmin(admin.ModelAdmin):
list_display = ('username', 'user', 'date_created', 'link_karma', 'comment_karma', 'last_update', 'validated', 'is_valid')
search_fields = ['username']
fields = ('user', 'username', 'validated')
def save_model(self, request, obj, form, change):
if not obj.pk:
obj.api_update()
obj.save()
admin.site.register(RedditAccount, RedditAccountAdmin)

141
app/reddit/api.py Normal file
View File

@@ -0,0 +1,141 @@
import simplejson as json
import urllib2
import urllib
from datetime import datetime
import unicodedata
class NotLoggedIn(Exception):
pass
class LoginError(Exception):
pass
class Comment(dict):
""" Abstraction for comment data provided by JSON
Comments can be identifed by Kind = 1 """
def __init__(self, data):
dict.__init__(self)
self['kind'] = 1
self['id'] = data['id']
self['post'] = data['link_id'][3:]
self['body'] = data['body']
self['ups'] = data['likes']
self['downs'] = data['downs']
self['subreddit_id'] = data['subreddit_id']
self['subreddit'] = data['subreddit']
self['author'] = data['author']
self['permalink'] = u'http://reddit.com/comments/%s/c/%s' % (self['post'], self['id'])
def __getattr__(self, name):
return dict.__getitem__(self, name)
def __unicode__(self):
return u'/r/%s - %s' % (self['subreddit'], ['self.author'])
def __str__(self):
return self.__unicode__()
class Message(dict):
""" Abstract for a Reddit Message """
def __init__(self, msg=None):
if msg:
for k in msg.keys():
self[k] = msg[k]
def __getattr__(self, name):
return dict.__getitem__(self, name)
def __unicode__(self):
return u"%s: %s" % (self.author, self.subject)
def __str__(self):
return self.__unicode__()
class Inbox():
"""
Reddit Inbox class, accesses a user's inbox and provides a iterable
list of messages
>>> inbox = Inbox(username='testuser', password='testpassword')
>>> len(inbox)
5
"""
REDDIT = "http://www.reddit.com"
REDDIT_API_LOGIN = "%s/api/login" % REDDIT
REDDIT_API_INBOX = "%s/message/inbox/.json?mark=false" % REDDIT
REDDIT_API_COMPOSE = "%s/api/compose/" % REDDIT
def __init__(self, username=None, password=None):
if username and password:
self.login(username, password)
def login(self, username, password):
data = { 'user': username,
'passwd': password,
'api_type': 'json' }
url = "%s/%s" % (self.REDDIT_API_LOGIN, username)
jsondoc = json.load(self._url_request(url, data))
if jsondoc and 'json' in jsondoc:
if 'data' in jsondoc['json']:
self.login_cookie = jsondoc['json']['data']['cookie']
self.modhash = jsondoc['json']['data']['modhash']
if self.login_cookie:
return True
elif 'errors' in jsondoc['json']:
raise LoginError(jsondoc['json']['errors'])
return False
@property
def _inbox_data(self):
if not self.login_cookie:
raise NotLoggedIn
if not hasattr(self, '__inbox_cache'):
inbox = json.load(self._opener.open(self.REDDIT_API_INBOX))['data']
self.__inbox_cache = []
for msg in inbox['children']:
self.__inbox_cache.append(Message(msg['data']))
return self.__inbox_cache
def _url_request(self, url, data):
if not hasattr(self, '_opener'):
self._opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
urllib2.install_opener(self._opener)
req = urllib2.Request(url, urllib.urlencode(data))
return self._opener.open(req)
def __len__(self):
return len(self._inbox_data)
def __getitem__(self, name):
return self._inbox_data[name]
def __iter__(self):
return self._inbox_data.__iter__()
def send(self, to, subject, text):
if not hasattr(self, 'login_cookie') or not self.login_cookie:
raise NotLoggedIn
data = { 'to': to,
'subject': subject.encode('utf-8'),
'text': text.encode('utf-8'),
'uh': self.modhash,
'thing_id': '' }
url = "%s" % (self.REDDIT_API_COMPOSE)
jsondoc = json.load(self._url_request(url, data))

18
app/reddit/forms.py Normal file
View File

@@ -0,0 +1,18 @@
from django import forms
from django.contrib.auth.models import User
from reddit.models import RedditAccount
class RedditAccountForm(forms.Form):
""" Basic Reddit account input form """
username = forms.CharField(label = u'Reddit Username', max_length=64)
def clean(self):
try:
eaccount = RedditAccount.objects.get(username=self.cleaned_data['username'])
except RedditAccount.DoesNotExist:
return self.cleaned_data
else:
raise forms.ValidationError("This User ID is already registered")

View File

@@ -0,0 +1,83 @@
# encoding: 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 'RedditAccount'
db.create_table('reddit_redditaccount', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
('username', self.gf('django.db.models.fields.CharField')(max_length=32)),
('reddit_id', self.gf('django.db.models.fields.CharField')(max_length=32)),
('link_karma', self.gf('django.db.models.fields.IntegerField')()),
('comment_karma', self.gf('django.db.models.fields.IntegerField')()),
('validated', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
('date_created', self.gf('django.db.models.fields.DateTimeField')()),
('last_update', self.gf('django.db.models.fields.DateTimeField')()),
))
db.send_create_signal('reddit', ['RedditAccount'])
def backwards(self, orm):
# Deleting model 'RedditAccount'
db.delete_table('reddit_redditaccount')
models = {
'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': {'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', 'blank': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'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': {'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'})
},
'reddit.redditaccount': {
'Meta': {'object_name': 'RedditAccount'},
'comment_karma': ('django.db.models.fields.IntegerField', [], {}),
'date_created': ('django.db.models.fields.DateTimeField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_update': ('django.db.models.fields.DateTimeField', [], {}),
'link_karma': ('django.db.models.fields.IntegerField', [], {}),
'reddit_id': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'validated': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})
}
}
complete_apps = ['reddit']

View File

90
app/reddit/models.py Normal file
View File

@@ -0,0 +1,90 @@
from django.db import models
from django.contrib.auth.models import User
import simplejson as json
import urllib
from datetime import datetime, date
from reddit.api import Comment
class RedditAccount(models.Model):
"""
Represents a User ID on Reddit
This model can be populated by API update:
>>> from reddit.models import RedditAccount
>>> mod = RedditAccount()
>>> mod.username = 'nik_doof'
>>> mod.api_update()
>>> mod.reddit_id
u'1axok'
"""
user = models.ForeignKey(User, blank=True, null=True)
username = models.CharField("Reddit Username", max_length=32, blank=False)
reddit_id = models.CharField("Reddit ID", max_length=32)
link_karma = models.IntegerField("Link Karma")
comment_karma = models.IntegerField("Comment Karma")
validated = models.BooleanField("Validated")
date_created = models.DateTimeField("Date Created")
last_update = models.DateTimeField("Last Update from API")
def api_update(self):
try:
jsondoc = json.load(urllib.urlopen("http://reddit.com/user/%s/about.json" % self.username))
except:
raise self.DoesNotExist
data = jsondoc['data']
self.link_karma = data['link_karma']
self.comment_karma = data['comment_karma']
self.reddit_id = data['id']
self.date_created = datetime.fromtimestamp(data['created_utc'])
self.last_update = datetime.now()
def recent_posts(self):
""" Returns the first page of posts visible on the user's profile page """
try:
jsondoc = json.load(urllib.urlopen("http://reddit.com/user/%s.json" % self.username))
except:
raise self.DoesNotExist
posts = []
for item in jsondoc['data']['children']:
if item['kind'] == 't1':
posts.append(Comment(item['data']))
elif item['kind'] == 't3':
posts.append(item['data'])
return posts
@property
def is_valid(self):
if not self.date_created:
return False
# Account 3 months old?
if (date.today() - self.date_created.date()).days >= 90:
return True
# Account created after 9/2/10 and before 13/2/10
if self.date_created.date() >= date(2010, 2, 9) and self.date_created.date() <= date(2010, 2, 13):
return True
return False
class Meta:
app_label = 'reddit'
ordering = ['username']
verbose_name = 'Reddit Account'
verbose_name_plural = 'Reddit Accounts'
def __unicode__(self):
return self.username

48
app/reddit/tasks.py Normal file
View File

@@ -0,0 +1,48 @@
from urllib2 import HTTPError, URLError
from celery.task import Task
from celery.decorators import task
from reddit.models import RedditAccount
from reddit.api import Inbox, LoginError
from django.conf import settings
class send_reddit_message(Task):
default_retry_delay = 5 * 60 # retry in 5 minutes
ignore_result = True
def run(self, to, subject, message, **kwargs):
logger = self.get_logger(**kwargs)
logger.info("Sending Reddit message to %s" % to)
try:
ib = Inbox(username=settings.REDDIT_USER, password=settings.REDDIT_PASSWORD)
ib.send(to, subject, message)
except (HTTPError, URLError), exc:
logger.error("Error sending message, queueing for retry")
send_reddit_message.retry(args=[to, subject, message], kwargs=kwargs, exc=exc)
pass
except LoginError, exc:
logger.error("Error logging into Reddit")
@task(ignore_result=True)
def process_validations():
logger = process_validations.get_logger()
try:
inbox = Inbox(settings.REDDIT_USER, settings.REDDIT_PASSWORD)
for msg in inbox:
if not msg.was_comment:
try:
acc = RedditAccount.objects.get(username__iexact=msg.author)
if not acc.validated and msg.subject == "Validation: %s" % acc.user.username:
logger.info("Validated %s" % acc.user.username)
acc.validated = True
acc.save()
except RedditAccount.DoesNotExist:
continue
except (HTTPError, URLError), exc:
logger.error("Error with Reddit, aborting.")
return
except LoginError, exc:
logger.error("Error logging into Reddit")
return

View File

@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Add Reddit Account{% endblock %}
{% block content %}
<p>This will bind a Reddit account to your Auth Gateway login, this is usually required for application to the
corporation</p>
<p>Please note, you will be forever tied to this account and posts and comments made on this account will be checked
on from time to time</p>
<form action="{% url reddit.views.reddit_add %}" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<br />
<input type="submit" value="Add Account" />
</form>
{% endblock %}

10
app/reddit/urls.py Normal file
View File

@@ -0,0 +1,10 @@
from django.conf.urls.defaults import *
from reddit import views
urlpatterns = patterns('',
(r'^profile/add/reddit', views.reddit_add),
(r'^profile/del/reddit/$', views.reddit_del),
(r'^profile/del/reddit/(?P<redditid>\d+)/$', views.reddit_del),
)

49
app/reddit/views.py Normal file
View File

@@ -0,0 +1,49 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.contrib import messages
from reddit.forms import RedditAccountForm
from reddit.models import RedditAccount
@login_required
def reddit_add(request):
""" Add a Reddit account to a user's account """
if request.method == 'POST':
form = RedditAccountForm(request.POST)
if form.is_valid():
acc = RedditAccount()
acc.user = request.user
acc.username = form.cleaned_data['username']
try:
acc.api_update()
except RedditAccount.DoesNotExist:
messages.add_message(request, messages.ERROR, "Error, user %s does not exist on Reddit" % acc.username )
return render_to_response('reddit/add_reddit_account.html', locals(), context_instance=RequestContext(request))
acc.save()
messages.add_message(request, messages.INFO, "Reddit account %s successfully added." % acc.username)
return redirect('sso.views.profile') # Redirect after POST
else:
defaults = { 'username': request.user.username, }
form = RedditAccountForm(defaults) # An unbound form
return render_to_response('reddit/add_reddit_account.html', locals(), context_instance=RequestContext(request))
@login_required
def reddit_del(request, redditid=0):
""" Delete a Reddit account from a user's account """
if redditid > 0 :
try:
acc = RedditAccount.objects.get(id=redditid)
except RedditAccount.DoesNotExist:
return redirect('sso.views.profile')
if acc.user == request.user:
acc.delete()
messages.add_message(request, messages.INFO, "Reddit account successfully deleted.")
return redirect('sso.views.profile')