summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan McGee <dan@archlinux.org>2011-04-07 14:39:14 -0500
committerDan McGee <dan@archlinux.org>2011-04-07 17:01:16 -0500
commit9f4902f9c921b82f924fe0af106fa5480ca10ca9 (patch)
tree489df7db006f43f6ab74bb191fe220012073c539
parent0504fbeb92e83e92e1f1bc6e003bdb3f93c4318f (diff)
downloadarchweb-9f4902f9c921b82f924fe0af106fa5480ca10ca9.tar.gz
archweb-9f4902f9c921b82f924fe0af106fa5480ca10ca9.zip
Ensure feed GUIDs are unchanging and unique
Implement 'tag:' style URIs for the GUID field on our RSS feeds. This ensures new package updates show up as new, and we aren't jumping back and forth between generated GUIDs having 'http://' and 'https://' prefixes. Much of the work here is to attempt to keep old news GUIDs constant so we don't once again make everything show up as new in newsreaders. Signed-off-by: Dan McGee <dan@archlinux.org>
-rw-r--r--feeds.py14
-rw-r--r--news/migrations/0007_add_guid.py65
-rw-r--r--news/migrations/0008_set_prior_guids.py83
-rw-r--r--news/models.py24
-rw-r--r--templates/feeds/news_description.html2
-rw-r--r--templates/feeds/news_title.html2
-rw-r--r--templates/feeds/packages_description.html2
-rw-r--r--templates/feeds/packages_title.html2
8 files changed, 186 insertions, 8 deletions
diff --git a/feeds.py b/feeds.py
index cdba9913..d2043271 100644
--- a/feeds.py
+++ b/feeds.py
@@ -1,6 +1,7 @@
import datetime
from decimal import Decimal, ROUND_HALF_DOWN
+from django.contrib.sites.models import Site
from django.contrib.syndication.views import Feed
from django.core.cache import cache
from django.db.models import Q
@@ -97,9 +98,18 @@ class PackageFeed(Feed):
s += '.'
return s
+ subtitle = description
+
def items(self, obj):
return obj['qs']
+ def item_guid(self, item):
+ # http://diveintomark.org/archives/2004/05/28/howto-atom-id
+ date = item.last_update
+ return 'tag:%s,%s:%s%s' % (Site.objects.get_current().domain,
+ date.strftime('%Y-%m-%d'), item.get_absolute_url(),
+ date.strftime('%Y%m%d%H%M'))
+
def item_pubdate(self, item):
return item.last_update
@@ -135,6 +145,7 @@ class NewsFeed(Feed):
title = 'Arch Linux: Recent news updates'
link = '/news/'
description = 'The latest and greatest news from the Arch Linux distribution.'
+ subtitle = description
title_template = 'feeds/news_title.html'
description_template = 'feeds/news_description.html'
@@ -146,6 +157,9 @@ class NewsFeed(Feed):
return News.objects.select_related('author').order_by(
'-postdate', '-id')[:10]
+ def item_guid(self, item):
+ return item.guid
+
def item_pubdate(self, item):
return item.postdate
diff --git a/news/migrations/0007_add_guid.py b/news/migrations/0007_add_guid.py
new file mode 100644
index 00000000..5fa8193e
--- /dev/null
+++ b/news/migrations/0007_add_guid.py
@@ -0,0 +1,65 @@
+# 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):
+ db.add_column('news', 'guid', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
+
+ def backwards(self, orm):
+ db.delete_column('news', 'guid')
+
+ 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': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
+ '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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ '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'})
+ },
+ 'news.news': {
+ 'Meta': {'ordering': "['-postdate']", 'object_name': 'News', 'db_table': "'news'"},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'news_author'", 'to': "orm['auth.User']"}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'guid': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'postdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['news']
diff --git a/news/migrations/0008_set_prior_guids.py b/news/migrations/0008_set_prior_guids.py
new file mode 100644
index 00000000..704b11c9
--- /dev/null
+++ b/news/migrations/0008_set_prior_guids.py
@@ -0,0 +1,83 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.conf import settings
+from django.db import models
+
+class Migration(DataMigration):
+ '''The point of this migration is to not mark every news item as 'new' in
+ people's feed readers, and store the GUID perminantly with the news item.
+ All previously published news items will get their former auto-assigned
+ GUID; new ones will get a generated tag: URI and this won't apply to
+ them.'''
+
+ def forwards(self, orm):
+ all_news = orm.News.objects.all().defer('content')
+ site = orm['sites.site'].objects.get(pk=settings.SITE_ID).domain
+ for news in all_news:
+ new_guid = 'http://%s/news/%s/' % (site, news.slug)
+ # looks totally silly, but prevents full updates of all fields,
+ # including content and last_modified which we want to leave alone
+ orm.News.objects.filter(pk=news.pk).update(guid=new_guid)
+
+ def backwards(self, orm):
+ pass
+
+ 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': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
+ '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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ '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'})
+ },
+ 'news.news': {
+ 'Meta': {'ordering': "['-postdate']", 'object_name': 'News', 'db_table': "'news'"},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'news_author'", 'to': "orm['auth.User']"}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'guid': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'postdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['sites', 'news']
diff --git a/news/models.py b/news/models.py
index c2d644b7..c4fb136a 100644
--- a/news/models.py
+++ b/news/models.py
@@ -1,13 +1,16 @@
+from datetime import datetime
+
from django.db import models
from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
class News(models.Model):
slug = models.SlugField(max_length=255, unique=True)
author = models.ForeignKey(User, related_name='news_author')
- postdate = models.DateTimeField("post date", auto_now_add=True, db_index=True)
- last_modified = models.DateTimeField(editable=False,
- auto_now=True, db_index=True)
+ postdate = models.DateTimeField("post date", db_index=True)
+ last_modified = models.DateTimeField(editable=False, db_index=True)
title = models.CharField(max_length=255)
+ guid = models.CharField(max_length=255, editable=False)
content = models.TextField()
def get_absolute_url(self):
@@ -22,10 +25,23 @@ class News(models.Model):
get_latest_by = 'postdate'
ordering = ['-postdate']
+def set_news_fields(sender, **kwargs):
+ news = kwargs['instance']
+ now = datetime.now()
+ news.last_modified = now
+ if not news.postdate:
+ news.postdate = now
+ # http://diveintomark.org/archives/2004/05/28/howto-atom-id
+ news.guid = 'tag:%s,%s:%s' % (Site.objects.get_current(),
+ now.strftime('%Y-%m-%d'), news.get_absolute_url())
+
# connect signals needed to keep cache in line with reality
from main.utils import refresh_news_latest
-from django.db.models.signals import post_save
+from django.db.models.signals import pre_save, post_save
+
post_save.connect(refresh_news_latest, sender=News,
dispatch_uid="news.models")
+pre_save.connect(set_news_fields, sender=News,
+ dispatch_uid="news.models")
# vim: set ts=4 sw=4 et:
diff --git a/templates/feeds/news_description.html b/templates/feeds/news_description.html
index a1e6446f..e75d0af7 100644
--- a/templates/feeds/news_description.html
+++ b/templates/feeds/news_description.html
@@ -1,3 +1,3 @@
{% load markup %}
<p>{{obj.author.get_full_name}} wrote:</p>
-{{ obj.content|markdown }}
+{{ obj.content|markdown }} \ No newline at end of file
diff --git a/templates/feeds/news_title.html b/templates/feeds/news_title.html
index d355de5b..7899fce3 100644
--- a/templates/feeds/news_title.html
+++ b/templates/feeds/news_title.html
@@ -1 +1 @@
-{{ obj.title }}
+{{ obj.title }} \ No newline at end of file
diff --git a/templates/feeds/packages_description.html b/templates/feeds/packages_description.html
index 6b9c47b3..cfc42616 100644
--- a/templates/feeds/packages_description.html
+++ b/templates/feeds/packages_description.html
@@ -1 +1 @@
-{{ obj.pkgdesc }}
+{{ obj.pkgdesc }} \ No newline at end of file
diff --git a/templates/feeds/packages_title.html b/templates/feeds/packages_title.html
index 5c54ba65..f92ac684 100644
--- a/templates/feeds/packages_title.html
+++ b/templates/feeds/packages_title.html
@@ -1 +1 @@
-{{ obj.pkgname }} {{ obj.full_version }} {{ obj.arch.name }}
+{{ obj.pkgname }} {{ obj.full_version }} {{ obj.arch.name }} \ No newline at end of file