From f30c00af6db5e5a4eab516058829d46c2d379000 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 22 Mar 2012 00:00:15 +0100 Subject: Fallback to published or now When trying to set the `updated' property of a `Post', when no `updated_parsed' is available, try `published_parsed' or use `datetime.now()' if that is also not available. --- aggregator/management/commands/loadfeeds.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/aggregator/management/commands/loadfeeds.py b/aggregator/management/commands/loadfeeds.py index 04316b3..bedf65d 100644 --- a/aggregator/management/commands/loadfeeds.py +++ b/aggregator/management/commands/loadfeeds.py @@ -20,14 +20,12 @@ class Command(BaseCommand): for entry in parsed.entries: if not Post.objects.filter(post_id=entry.id).exists(): + dt = entry.updated_parsed \ + or entry.published_parsed \ + or datetime.datetime.now() 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) - + dt.tm_year, dt.tm_mon, dt.tm_mday, + dt.tm_hour, dt.tm_min, dt.tm_sec) post = Post(post_id=entry.id, title=entry.title, remote_url=entry.link, -- cgit v1.2.3-54-g00ecf From 921d06a8e01e2613abdd07ce4d1a88d4957fe0d1 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 22 Mar 2012 00:04:01 +0100 Subject: Add Category model Each feed can belong to multiple categories. A category is only a slug. --- aggregator/fixtures/initial_data.json | 35 +++++++++--- aggregator/migrations/0003_auto__add_category.py | 70 ++++++++++++++++++++++++ aggregator/models.py | 10 ++++ 3 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 aggregator/migrations/0003_auto__add_category.py diff --git a/aggregator/fixtures/initial_data.json b/aggregator/fixtures/initial_data.json index 5926e9c..e91883d 100644 --- a/aggregator/fixtures/initial_data.json +++ b/aggregator/fixtures/initial_data.json @@ -1,4 +1,18 @@ [ + { + "model": "aggregator.Category", + "pk": 1, + "fields": { + "name": "misc" + } + }, + { + "model": "aggregator.Category", + "pk": 2, + "fields": { + "name": "code" + } + }, { "model": "aggregator.Feed", "pk": 1, @@ -7,7 +21,8 @@ "base_url": "http://www.advogato.org/", "feed_url": "person/ryuslash/rss.xml", "profile_url": "person/ryuslash", - "favicon_ext": "ico" + "favicon_ext": "ico", + "categories": [ 1 ] } }, { @@ -19,7 +34,8 @@ "feed_url": "public/ryuslash.atom", "profile_url": "public/ryuslash", "br2nl": true, - "with_markdown": true + "with_markdown": true, + "categories": [ 1 ] } }, { @@ -30,7 +46,8 @@ "base_url": "http://identi.ca/", "feed_url": "api/statuses/user_timeline/107950.rss", "profile_url": "ryuslash", - "favicon_ext": "ico" + "favicon_ext": "ico", + "categories": [ 1 ] } }, { @@ -40,7 +57,8 @@ "name": "Github", "base_url": "https://github.com/", "feed_url": "ryuslash.atom", - "profile_url": "ryuslash" + "profile_url": "ryuslash", + "categories": [ 2 ] } }, { @@ -50,7 +68,8 @@ "name": "Gitorious", "base_url": "https://gitorious.org/", "feed_url": "~ryuslash/feed.atom", - "profile_url": "~ryuslash" + "profile_url": "~ryuslash", + "categories": [ 2 ] } }, { @@ -60,7 +79,8 @@ "name": "Ikiwiki", "base_url": "http://ryuslash.org/wiki/", "feed_url": "index.rss", - "uses_title": true + "uses_title": true, + "categories": [ 1 ] } }, { @@ -72,7 +92,8 @@ "feed_url": "user/ryuslash/.rss", "profile_url": "usr/ryuslash/", "uses_title": true, - "favicon_ext": "ico" + "favicon_ext": "ico", + "categories": [ 1 ] } } ] diff --git a/aggregator/migrations/0003_auto__add_category.py b/aggregator/migrations/0003_auto__add_category.py new file mode 100644 index 0000000..9997320 --- /dev/null +++ b/aggregator/migrations/0003_auto__add_category.py @@ -0,0 +1,70 @@ +# 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 'Category' + db.create_table('aggregator_category', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), + )) + db.send_create_signal('aggregator', ['Category']) + + # Adding M2M table for field categories on 'Feed' + db.create_table('aggregator_feed_categories', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('feed', models.ForeignKey(orm['aggregator.feed'], null=False)), + ('category', models.ForeignKey(orm['aggregator.category'], null=False)) + )) + db.create_unique('aggregator_feed_categories', ['feed_id', 'category_id']) + + + def backwards(self, orm): + + # Deleting model 'Category' + db.delete_table('aggregator_category') + + # Removing M2M table for field categories on 'Feed' + db.delete_table('aggregator_feed_categories') + + + models = { + 'aggregator.category': { + 'Meta': {'object_name': 'Category'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'aggregator.feed': { + 'Meta': {'ordering': "['-updated']", 'object_name': 'Feed'}, + 'base_url': ('django.db.models.fields.URLField', [], {'max_length': '255'}), + 'br2nl': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['aggregator.Category']", 'symmetrical': 'False'}), + 'favicon_ext': ('django.db.models.fields.CharField', [], {'default': "'png'", 'max_length': '4'}), + 'feed_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'profile_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + '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'}), + 'with_markdown': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'aggregator.post': { + 'Meta': {'ordering': "['-updated']", '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': '255'}), + '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/models.py b/aggregator/models.py index b5c4f3c..4936821 100644 --- a/aggregator/models.py +++ b/aggregator/models.py @@ -1,5 +1,11 @@ from django.db import models +class Category(models.Model): + name = models.SlugField() + + def __unicode__(self): + return self.name.capitalize() + class Feed(models.Model): name = models.CharField(max_length=200) base_url = models.URLField(max_length=255) @@ -11,6 +17,7 @@ class Feed(models.Model): uses_title = models.BooleanField(default=False) br2nl = models.BooleanField(default=False) with_markdown = models.BooleanField(default=False) + categories = models.ManyToManyField(Category) def get_profile_url(self): return self.base_url + self.profile_url @@ -21,6 +28,9 @@ class Feed(models.Model): def get_favicon_url(self): return self.base_url + 'favicon.' + self.favicon_ext + def __unicode__(self): + return self.name + class Meta: ordering = [ '-updated' ] -- cgit v1.2.3-54-g00ecf From 43d5b96188f6ffcc0196a29a11dec10776c30af5 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 27 Mar 2012 22:40:47 +0200 Subject: Rename categories, makes more sense this way --- aggregator/fixtures/initial_data.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aggregator/fixtures/initial_data.json b/aggregator/fixtures/initial_data.json index e91883d..a5d8676 100644 --- a/aggregator/fixtures/initial_data.json +++ b/aggregator/fixtures/initial_data.json @@ -3,14 +3,14 @@ "model": "aggregator.Category", "pk": 1, "fields": { - "name": "misc" + "name": "posts" } }, { "model": "aggregator.Category", "pk": 2, "fields": { - "name": "code" + "name": "activity" } }, { -- cgit v1.2.3-54-g00ecf From 74c070279fb1c240779f9562ab6171ba32e3d744 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 27 Mar 2012 22:44:39 +0200 Subject: Add categories to main page --- aggregator/views.py | 20 ++++++++++++-------- static/main.css | 15 +++++++++++++++ templates/aggregator/base.html | 8 +++++++- templates/aggregator/posts.html | 6 ++++-- urls.py | 3 ++- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/aggregator/views.py b/aggregator/views.py index b0fdf3b..aaea0f9 100644 --- a/aggregator/views.py +++ b/aggregator/views.py @@ -1,12 +1,14 @@ from django.core.paginator import Paginator, InvalidPage, EmptyPage from django.http import Http404 -from django.shortcuts import render_to_response +from django.shortcuts import render +from django.views.generic.base import TemplateView -from .models import Post, Feed +from .models import Post, Feed, Category -def posts(request, page=1): - queryset = Post.objects.all() - feeds = Feed.objects.all() +def posts(request, cat, page=1): + category = cat or 'posts' + queryset = Post.objects.filter(feed__categories__name=category) + feeds = Feed.objects.filter(categories__name=category) paginator = Paginator(queryset, 20) if page == None: @@ -17,6 +19,8 @@ def posts(request, page=1): except (EmptyPage, InvalidPage): raise Http404 - return render_to_response('aggregator/posts.html', - { 'list': object_list, - 'feeds': feeds }) + return render(request, 'aggregator/posts.html', + { 'list': object_list, + 'feeds': feeds, + 'category': category, + 'categories': Category.objects.order_by('name') }) diff --git a/static/main.css b/static/main.css index 8a7f2b5..24b7f7f 100644 --- a/static/main.css +++ b/static/main.css @@ -18,6 +18,21 @@ body { font-family: "DejaVu Sans", sans-serif; } +.category { + display: block; + float: right; + padding: 0 5px; + border-left: 1px #202020 solid; + margin-left: 5px; +} + +.category:hover { + border-left: 1px #ffffff solid; + background-color: #dddddd; + color: #404040; + text-decoration: none; +} + .clear { clear: both; } diff --git a/templates/aggregator/base.html b/templates/aggregator/base.html index 6a37f5a..0826711 100644 --- a/templates/aggregator/base.html +++ b/templates/aggregator/base.html @@ -14,7 +14,13 @@

ryuslash

-
Will this ever really be my website?
+
+ Will this ever really be my website? + {% for category in categories %} + {{ category.name }} + {% endfor %} +
{% block menu %}{% endblock %} diff --git a/templates/aggregator/posts.html b/templates/aggregator/posts.html index 2a5b8a1..efd1cb1 100644 --- a/templates/aggregator/posts.html +++ b/templates/aggregator/posts.html @@ -24,13 +24,15 @@
{% if list.has_previous %} {% endif %} {% if list.has_next %} {% endif %} diff --git a/urls.py b/urls.py index 039dff2..a8edc88 100644 --- a/urls.py +++ b/urls.py @@ -6,7 +6,8 @@ from aggregator.models import Post from aggregator.feeds import LatestPostsFeed, LatestCommentsFeed urlpatterns = patterns('', - url(r'^((?P\d+)/)?$', 'aggregator.views.posts'), + url(r'^((?P[a-z_-]+)/)?((?P\d+)/)?$', + 'aggregator.views.posts'), url(r'^post/((?P\d+)/)?$', DetailView.as_view(model=Post)), url(r'^feed/posts/$', LatestPostsFeed()), url(r'^feed/comments/$', LatestCommentsFeed()), -- cgit v1.2.3-54-g00ecf From 0e4044a8c4422460676d9baa9bdc36f3cf16e3a8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 27 Mar 2012 22:45:30 +0200 Subject: Add virtualenv requirements --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a7f9cbf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Django==1.3 +South==0.7.4 +Feedparser==5.1.1 +Markdown==2.1.1 -- cgit v1.2.3-54-g00ecf From b6900ac5ad618b0b401e31e79acdc5b1f9a7a6d8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 27 Mar 2012 22:59:18 +0200 Subject: Fix post page It was being overshadowed by the post list's regex. --- urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urls.py b/urls.py index a8edc88..d5e81b0 100644 --- a/urls.py +++ b/urls.py @@ -6,9 +6,9 @@ from aggregator.models import Post from aggregator.feeds import LatestPostsFeed, LatestCommentsFeed urlpatterns = patterns('', + url(r'^post/((?P\d+)/)?$', DetailView.as_view(model=Post)), url(r'^((?P[a-z_-]+)/)?((?P\d+)/)?$', 'aggregator.views.posts'), - url(r'^post/((?P\d+)/)?$', DetailView.as_view(model=Post)), url(r'^feed/posts/$', LatestPostsFeed()), url(r'^feed/comments/$', LatestCommentsFeed()), url(r'^comments/', include('django.contrib.comments.urls'))) -- cgit v1.2.3-54-g00ecf From 3bf5e5208ece3d71debc98552bf0dd71c65a9626 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 27 Mar 2012 23:03:24 +0200 Subject: The post page *needs* a post_id --- urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urls.py b/urls.py index d5e81b0..d3248a9 100644 --- a/urls.py +++ b/urls.py @@ -6,7 +6,7 @@ from aggregator.models import Post from aggregator.feeds import LatestPostsFeed, LatestCommentsFeed urlpatterns = patterns('', - url(r'^post/((?P\d+)/)?$', DetailView.as_view(model=Post)), + url(r'^post/(?P\d+)/$', DetailView.as_view(model=Post)), url(r'^((?P[a-z_-]+)/)?((?P\d+)/)?$', 'aggregator.views.posts'), url(r'^feed/posts/$', LatestPostsFeed()), -- cgit v1.2.3-54-g00ecf From d959e7dc954553913f66fe133a80e3e9f7d63bd8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 27 Mar 2012 23:12:00 +0200 Subject: If no parsed date could be found, now() should be used --- aggregator/management/commands/loadfeeds.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/aggregator/management/commands/loadfeeds.py b/aggregator/management/commands/loadfeeds.py index bedf65d..2bee069 100644 --- a/aggregator/management/commands/loadfeeds.py +++ b/aggregator/management/commands/loadfeeds.py @@ -21,11 +21,15 @@ class Command(BaseCommand): for entry in parsed.entries: if not Post.objects.filter(post_id=entry.id).exists(): dt = entry.updated_parsed \ - or entry.published_parsed \ - or datetime.datetime.now() - updated = datetime.datetime( - dt.tm_year, dt.tm_mon, dt.tm_mday, - dt.tm_hour, dt.tm_min, dt.tm_sec) + or entry.published_parsed + + if dt: + updated = datetime.datetime( + dt.tm_year, dt.tm_mon, dt.tm_mday, + dt.tm_hour, dt.tm_min, dt.tm_sec) + else: + updated = datetime.datetime.now() + post = Post(post_id=entry.id, title=entry.title, remote_url=entry.link, @@ -36,7 +40,7 @@ class Command(BaseCommand): content = entry.content[0]['value'] else: content = entry.summary - + if feed.with_markdown: post.body = markdown.markdown(content) else: -- cgit v1.2.3-54-g00ecf