aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Tom Willemsen2012-03-10 03:04:13 +0100
committerGravatar Tom Willemsen2012-03-10 03:04:13 +0100
commit9648ce73fda47739be5465d8142c38bda588c6f6 (patch)
tree866100c62ede3bc0dab50b0dc19de4a4191c5fd4
downloadryuslash.org-9648ce73fda47739be5465d8142c38bda588c6f6.tar.gz
ryuslash.org-9648ce73fda47739be5465d8142c38bda588c6f6.zip
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--__init__.py0
-rw-r--r--aggregator/__init__.py0
-rw-r--r--aggregator/fixtures/initial_data.json43
-rw-r--r--aggregator/management/__init__.py0
-rw-r--r--aggregator/management/commands/__init__.py0
-rw-r--r--aggregator/management/commands/load_feeds.py37
-rw-r--r--aggregator/migrations/0001_initial.py72
-rw-r--r--aggregator/migrations/__init__.py0
-rw-r--r--aggregator/models.py25
-rw-r--r--aggregator/views.py20
-rwxr-xr-xmanage.py14
-rw-r--r--settings.py149
-rw-r--r--templates/aggregator/posts.html45
-rw-r--r--urls.py11
15 files changed, 417 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/__init__.py
diff --git a/aggregator/__init__.py b/aggregator/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aggregator/__init__.py
diff --git a/aggregator/fixtures/initial_data.json b/aggregator/fixtures/initial_data.json
new file mode 100644
index 0000000..76c6471
--- /dev/null
+++ b/aggregator/fixtures/initial_data.json
@@ -0,0 +1,43 @@
+[
+ {
+ "model": "aggregator.Feed",
+ "pk": 1,
+ "fields": {
+ "base_url": "http://www.advogato.org/",
+ "feed_url": "person/ryuslash/rss.xml",
+ "uses_title": false,
+ "favicon_ext": "ico"
+ }
+ },
+ {
+ "model": "aggregator.Feed",
+ "pk": 2,
+ "fields": {
+ "base_url": "http://diasp.org/",
+ "feed_url": "public/ryuslash.atom",
+ "uses_title": false,
+ "favicon_ext": "png",
+ "br2nl": true
+ }
+ },
+ {
+ "model": "aggregator.Feed",
+ "pk": 3,
+ "fields": {
+ "base_url": "http://identi.ca/",
+ "feed_url": "api/statuses/user_timeline/107950.rss",
+ "uses_title": false,
+ "favicon_ext": "ico"
+ }
+ },
+ {
+ "model": "aggregator.Feed",
+ "pk": 4,
+ "fields": {
+ "base_url": "https://github.com/",
+ "feed_url": "ryuslash.atom",
+ "uses_title": true,
+ "favicon_ext": "png"
+ }
+ }
+]
diff --git a/aggregator/management/__init__.py b/aggregator/management/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aggregator/management/__init__.py
diff --git a/aggregator/management/commands/__init__.py b/aggregator/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aggregator/management/commands/__init__.py
diff --git a/aggregator/management/commands/load_feeds.py b/aggregator/management/commands/load_feeds.py
new file mode 100644
index 0000000..c143052
--- /dev/null
+++ b/aggregator/management/commands/load_feeds.py
@@ -0,0 +1,37 @@
+import feedparser
+import datetime
+
+from django.core.management.base import BaseCommand
+
+from aggregator.models import Feed, Post
+
+class Command(BaseCommand):
+ help = "hi"
+
+ def handle(self, *args, **kwargs):
+ feeds = Feed.objects.all()
+
+ for feed in feeds:
+ parsed = feedparser.parse(feed.get_feed_url())
+ feed.title = parsed.feed.title
+
+ for entry in parsed.entries:
+ if not Post.objects.filter(post_id=entry.id).exists():
+ updated = datetime.datetime(
+ entry.updated_parsed.tm_year,
+ entry.updated_parsed.tm_mon,
+ entry.updated_parsed.tm_mday,
+ entry.updated_parsed.tm_hour,
+ entry.updated_parsed.tm_min,
+ entry.updated_parsed.tm_sec)
+
+ post = Post(post_id=entry.id,
+ title=entry.title,
+ body=entry.summary,
+ remote_url=entry.link,
+ updated=updated,
+ feed=feed)
+ post.save()
+ feed.updated = datetime.datetime.now()
+
+ feed.save()
diff --git a/aggregator/migrations/0001_initial.py b/aggregator/migrations/0001_initial.py
new file mode 100644
index 0000000..73ca455
--- /dev/null
+++ b/aggregator/migrations/0001_initial.py
@@ -0,0 +1,72 @@
+# 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 'Feed'
+ db.create_table('aggregator_feed', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('base_url', self.gf('django.db.models.fields.URLField')(max_length=255)),
+ ('feed_url', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('favicon_ext', self.gf('django.db.models.fields.CharField')(max_length=4)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=500, blank=True)),
+ ('updated', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('uses_title', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('br2nl', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ))
+ db.send_create_signal('aggregator', ['Feed'])
+
+ # Adding model 'Post'
+ db.create_table('aggregator_post', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('post_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=500)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=500)),
+ ('body', self.gf('django.db.models.fields.TextField')()),
+ ('remote_url', self.gf('django.db.models.fields.URLField')(max_length=255)),
+ ('updated', self.gf('django.db.models.fields.DateTimeField')()),
+ ('added', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('feed', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['aggregator.Feed'])),
+ ))
+ db.send_create_signal('aggregator', ['Post'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Feed'
+ db.delete_table('aggregator_feed')
+
+ # Deleting model 'Post'
+ db.delete_table('aggregator_post')
+
+
+ models = {
+ 'aggregator.feed': {
+ 'Meta': {'object_name': 'Feed'},
+ 'base_url': ('django.db.models.fields.URLField', [], {'max_length': '255'}),
+ 'br2nl': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'favicon_ext': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'feed_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '500', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'uses_title': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'aggregator.post': {
+ 'Meta': {'object_name': 'Post'},
+ 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'body': ('django.db.models.fields.TextField', [], {}),
+ 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['aggregator.Feed']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '500'}),
+ 'remote_url': ('django.db.models.fields.URLField', [], {'max_length': '255'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ }
+ }
+
+ complete_apps = ['aggregator']
diff --git a/aggregator/migrations/__init__.py b/aggregator/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aggregator/migrations/__init__.py
diff --git a/aggregator/models.py b/aggregator/models.py
new file mode 100644
index 0000000..302f774
--- /dev/null
+++ b/aggregator/models.py
@@ -0,0 +1,25 @@
+from django.db import models
+
+class Feed(models.Model):
+ base_url = models.URLField(max_length=255)
+ feed_url = models.CharField(max_length=255)
+ favicon_ext = models.CharField(max_length=4)
+ title = models.CharField(max_length=500, blank=True)
+ updated = models.DateTimeField(null=True, blank=True)
+ uses_title = models.BooleanField(default=False)
+ br2nl = models.BooleanField(default=False)
+
+ def get_feed_url(self):
+ return self.base_url + self.feed_url
+
+ def get_favicon_url(self):
+ return self.base_url + 'favicon.' + self.favicon_ext
+
+class Post(models.Model):
+ post_id = models.CharField(max_length=500, unique=True)
+ title = models.CharField(max_length=500)
+ body = models.TextField()
+ remote_url = models.URLField(max_length=255)
+ updated = models.DateTimeField()
+ added = models.DateTimeField(auto_now_add=True)
+ feed = models.ForeignKey(Feed)
diff --git a/aggregator/views.py b/aggregator/views.py
new file mode 100644
index 0000000..c946c31
--- /dev/null
+++ b/aggregator/views.py
@@ -0,0 +1,20 @@
+from django.core.paginator import Paginator, InvalidPage, EmptyPage
+from django.http import Http404
+from django.shortcuts import render_to_response
+
+from .models import Post
+
+def posts(request, page=1):
+ queryset = Post.objects.order_by('-updated')
+ paginator = Paginator(queryset, 20)
+
+ if page == None:
+ page = 1
+
+ try:
+ object_list = paginator.page(page)
+ except (EmptyPage, InvalidPage):
+ raise Http404
+
+ return render_to_response('aggregator/posts.html',
+ { 'list': object_list })
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..1473748
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python2
+from django.core.management import execute_manager
+import imp
+try:
+ imp.find_module('settings') # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
+ sys.exit(1)
+
+import settings
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/settings.py b/settings.py
new file mode 100644
index 0000000..2a0cf36
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,149 @@
+# Django settings for ryuslash_org project.
+import os
+import sys
+
+DEPLOY_PATH = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, DEPLOY_PATH)
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'test.sqlite', # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/Brussels'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'f3ga_iu$h^-+$xg)r2rnbiocn4qx+c(c3!9_k=!a9#)g9o5*#='
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'ryuslash_org.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ '%s/templates' % DEPLOY_PATH,
+)
+
+INSTALLED_APPS = ('django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django.contrib.admin',
+ 'django.contrib.admindocs',
+ 'aggregator',
+ 'south')
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+ 'loggers': {
+ 'django.request': {
+ 'handlers': ['mail_admins'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ }
+}
diff --git a/templates/aggregator/posts.html b/templates/aggregator/posts.html
new file mode 100644
index 0000000..32f1ed7
--- /dev/null
+++ b/templates/aggregator/posts.html
@@ -0,0 +1,45 @@
+<html>
+ <head>
+ <title>ryuslash.org</title>
+ </head>
+ <body>
+
+ {% for post in list.object_list %}
+ <p>
+ <div>
+ <a href="{{ post.remote_url }}">
+ <img src="{{ post.feed.get_favicon_url }}" />
+ {{ post.updated }}
+ </a>
+ </div>
+ {% if post.feed.uses_title %}
+ <div>{{ post.title }}</div>
+ {% endif %}
+
+ {% autoescape off %}
+ {% if post.feed.br2nl %}
+ {{ post.body|linebreaks }}
+ {% else %}
+ {{ post.body }}
+ {% endif %}
+ {% endautoescape %}
+ </p>
+ {% endfor %}
+
+ <div class="pagination">
+ <span class="step-links">
+ {% if list.has_previous %}
+ <a href="/{{ list.previous_page_number }}/">previous</a>
+ {% endif %}
+
+ <span class="current">
+ Page {{ list.number }} of {{ list.paginator.num_pages }}.
+ </span>
+
+ {% if list.has_next %}
+ <a href="/{{ list.next_page_number }}/">next</a>
+ {% endif %}
+ </span>
+ </div>
+ </body>
+</html>
diff --git a/urls.py b/urls.py
new file mode 100644
index 0000000..fc93441
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ url(r'^((?P<page>\d+)/)?$', 'aggregator.views.posts'),
+ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ url(r'^admin/', include(admin.site.urls)),
+)