changeset 1001:c6c3ba5cf6eb

V2 news stories use forums for comments.
author Brian Neal <bgneal@gmail.com>
date Thu, 26 Nov 2015 00:27:42 -0600
parents abd4c02aefdb
children 28d68f97cb26
files forums/tools.py news/admin.py news/fixtures/news_categories.json news/migrations/0006_category_forum_slug.py news/migrations/0007_auto_20151125_2300.py news/models.py news/templatetags/news_tags.py news/utils.py news/views.py sg101/templates/news/story.html sg101/templates/news/story_summary.html
diffstat 11 files changed, 265 insertions(+), 137 deletions(-) [+]
line wrap: on
line diff
--- a/forums/tools.py	Tue Nov 24 22:55:18 2015 -0600
+++ b/forums/tools.py	Thu Nov 26 00:27:42 2015 -0600
@@ -88,6 +88,7 @@
     'sticky' - if True, the post will be stickied
     'locked' - if True, the post will be locked
 
+    The new topic is returned.
     """
     try:
         forum = Forum.objects.get(slug=forum_slug)
@@ -110,6 +111,7 @@
 
     notify_new_topic(topic)
     notify_new_post(post)
+    return topic
 
 
 def auto_favorite(post):
--- a/news/admin.py	Tue Nov 24 22:55:18 2015 -0600
+++ b/news/admin.py	Thu Nov 26 00:27:42 2015 -0600
@@ -4,6 +4,7 @@
 from django.contrib import admin
 from django.conf import settings
 
+from forums.tools import create_topic
 from news.models import PendingStory
 from news.models import Story
 from news.models import Category
@@ -11,9 +12,14 @@
 import ftfy
 
 
+COMMENT_THREAD_BODY = ("This topic was automatically created to discuss the "
+        "news story [{title}]({url}).")
+
+
 class CategoryAdmin(admin.ModelAdmin):
     prepopulated_fields = {'slug': ("title", )}
-    list_display = ['title', 'slug']
+    list_display = ['title', 'slug', 'forum_slug']
+    list_editable = ['forum_slug']
 
 
 class PendingStoryAdmin(admin.ModelAdmin):
@@ -21,7 +27,7 @@
     list_filter = ['date_submitted']
     search_fields = ['title', 'short_text', 'long_text']
     date_hierarchy = 'date_submitted'
-    actions = ['approve_story']
+    actions = ['approve_stories']
     readonly_fields = ['update_date', 'version']
     raw_id_fields = ['submitter']
 
@@ -48,31 +54,43 @@
             }),
     ]
 
-    def approve_story(self, request, qs):
+    def approve_stories(self, request, qs):
         for pending_story in qs:
-            story = Story(
-                    title=pending_story.title,
-                    submitter=pending_story.submitter,
-                    category=pending_story.category,
-                    short_text=pending_story.short_text,
-                    long_text=pending_story.long_text,
-                    date_submitted=pending_story.date_submitted,
-                    allow_comments=pending_story.allow_comments,
-                    tags=pending_story.tags,
-                    front_page_expiration=pending_story.front_page_expiration,
-                    priority=pending_story.priority,
-                    meta_description=pending_story.meta_description,
-                    short_markup=pending_story.short_markup,
-                    long_markup=pending_story.long_markup,
-                    admin_content=pending_story.admin_content)
-            story.save()
-            pending_story.delete()
+            self._approve_story(pending_story)
 
         count = len(qs)
         msg = "1 story" if count == 1 else "%d stories" % count
         self.message_user(request, "%s approved." % msg)
 
-    approve_story.short_description = "Approve selected pending stories"
+    approve_stories.short_description = "Approve selected pending stories"
+
+    def _approve_story(self, pending_story):
+        story = Story(
+                title=pending_story.title,
+                submitter=pending_story.submitter,
+                category=pending_story.category,
+                short_text=pending_story.short_text,
+                long_text=pending_story.long_text,
+                date_submitted=pending_story.date_submitted,
+                allow_comments=pending_story.allow_comments,
+                tags=pending_story.tags,
+                front_page_expiration=pending_story.front_page_expiration,
+                priority=pending_story.priority,
+                meta_description=pending_story.meta_description,
+                short_markup=pending_story.short_markup,
+                long_markup=pending_story.long_markup,
+                admin_content=pending_story.admin_content)
+        story.save()
+        pending_story.delete()
+
+        # Create comment thread if configured to do so.
+        forum_slug = story.category.forum_slug
+        if story.allow_comments and forum_slug:
+            post_body = COMMENT_THREAD_BODY.format(title=story.title,
+                                                   url=story.get_absolute_url())
+            topic = create_topic(forum_slug, story.submitter, story.title, post_body)
+            story.forums_topic = topic
+            story.save()
 
     class Media:
         js = ['js/news_admin.js'] + settings.GPP_THIRD_PARTY_JS['tiny_mce']
--- a/news/fixtures/news_categories.json	Tue Nov 24 22:55:18 2015 -0600
+++ b/news/fixtures/news_categories.json	Thu Nov 26 00:27:42 2015 -0600
@@ -1,101 +1,112 @@
 [
-    {
-        "pk": 2, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Articles.png", 
-            "slug": "articles", 
-            "title": "Articles"
-        }
-    }, 
-    {
-        "pk": 3, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Bands.png", 
-            "slug": "bands", 
-            "title": "Bands"
-        }
-    }, 
-    {
-        "pk": 11, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Featured_Video.png", 
-            "slug": "featured-videos", 
-            "title": "Featured Videos"
-        }
-    }, 
-    {
-        "pk": 4, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Gear.png", 
-            "slug": "gear", 
-            "title": "Gear"
-        }
-    }, 
-    {
-        "pk": 5, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Interviews.png", 
-            "slug": "interviews", 
-            "title": "Interviews"
-        }
-    }, 
-    {
-        "pk": 6, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Reviews.png", 
-            "slug": "reviews", 
-            "title": "Reviews"
-        }
-    }, 
-    {
-        "pk": 7, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Show_Announcements.png", 
-            "slug": "show-announcements", 
-            "title": "Show Announcements"
-        }
-    }, 
-    {
-        "pk": 8, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Show_Reports.png", 
-            "slug": "show-reports", 
-            "title": "Show Reports"
-        }
-    }, 
-    {
-        "pk": 1, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Site_News.png", 
-            "slug": "site-news", 
-            "title": "Site News"
-        }
-    }, 
-    {
-        "pk": 9, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Surf_Scene_News.png", 
-            "slug": "surf-scene-news", 
-            "title": "Surf Scene News"
-        }
-    }, 
-    {
-        "pk": 10, 
-        "model": "news.category", 
-        "fields": {
-            "icon": "news/categories/Guitar_Tabs.png", 
-            "slug": "tablature", 
-            "title": "Tablature"
-        }
-    }
-]
\ No newline at end of file
+{
+    "fields": {
+        "forum_slug": "surfguitar101-website",
+        "icon": "news/categories/Site_News.png",
+        "slug": "site-news",
+        "title": "Site News"
+    },
+    "model": "news.category",
+    "pk": 1
+},
+{
+    "fields": {
+        "forum_slug": "surf-music",
+        "icon": "news/categories/Articles.png",
+        "slug": "articles",
+        "title": "Articles"
+    },
+    "model": "news.category",
+    "pk": 2
+},
+{
+    "fields": {
+        "forum_slug": "surf-music",
+        "icon": "news/categories/Bands.png",
+        "slug": "bands",
+        "title": "Bands"
+    },
+    "model": "news.category",
+    "pk": 3
+},
+{
+    "fields": {
+        "forum_slug": "gear",
+        "icon": "news/categories/Gear.png",
+        "slug": "gear",
+        "title": "Gear"
+    },
+    "model": "news.category",
+    "pk": 4
+},
+{
+    "fields": {
+        "forum_slug": "surf-music",
+        "icon": "news/categories/Interviews.png",
+        "slug": "interviews",
+        "title": "Interviews"
+    },
+    "model": "news.category",
+    "pk": 5
+},
+{
+    "fields": {
+        "forum_slug": "music-reviews",
+        "icon": "news/categories/Reviews.png",
+        "slug": "reviews",
+        "title": "Reviews"
+    },
+    "model": "news.category",
+    "pk": 6
+},
+{
+    "fields": {
+        "forum_slug": "gigs",
+        "icon": "news/categories/Show_Announcements.png",
+        "slug": "show-announcements",
+        "title": "Show Announcements"
+    },
+    "model": "news.category",
+    "pk": 7
+},
+{
+    "fields": {
+        "forum_slug": "surf-music",
+        "icon": "news/categories/Show_Reports.png",
+        "slug": "show-reports",
+        "title": "Show Reports"
+    },
+    "model": "news.category",
+    "pk": 8
+},
+{
+    "fields": {
+        "forum_slug": "surf-music",
+        "icon": "news/categories/Surf_Scene_News.png",
+        "slug": "surf-scene-news",
+        "title": "Surf Scene News"
+    },
+    "model": "news.category",
+    "pk": 9
+},
+{
+    "fields": {
+        "forum_slug": "surf-musician",
+        "icon": "news/categories/Guitar_Tabs.png",
+        "slug": "tablature",
+        "title": "Tablature"
+    },
+    "model": "news.category",
+    "pk": 10
+},
+{
+    "fields": {
+        "forum_slug": "surf-videos",
+        "icon": "news/categories/Featured_Video.png",
+        "slug": "featured-videos",
+        "title": "Featured Videos"
+    },
+    "model": "news.category",
+    "pk": 11
+}
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/news/migrations/0006_category_forum_slug.py	Thu Nov 26 00:27:42 2015 -0600
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('news', '0005_auto_20151121_1445'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='category',
+            name='forum_slug',
+            field=models.CharField(default=b'', help_text=b'Identifies the forum to create comment threads in for stories in this category', max_length=80, blank=True),
+            preserve_default=True,
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/news/migrations/0007_auto_20151125_2300.py	Thu Nov 26 00:27:42 2015 -0600
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('forums', '__first__'),
+        ('news', '0006_category_forum_slug'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='story',
+            name='forums_topic',
+            field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, blank=True, to='forums.Topic', help_text=b'Forum topic used for comments', db_index=False),
+            preserve_default=True,
+        ),
+        migrations.AlterField(
+            model_name='category',
+            name='forum_slug',
+            field=models.CharField(default=b'', help_text=b'Identifies the forum to create comment threads in for stories in this category. If blank, no comment threads will be created for stories in this category.', max_length=80, blank=True),
+            preserve_default=True,
+        ),
+    ]
--- a/news/models.py	Tue Nov 24 22:55:18 2015 -0600
+++ b/news/models.py	Thu Nov 26 00:27:42 2015 -0600
@@ -19,6 +19,13 @@
     title = models.CharField(max_length=64)
     slug = models.SlugField(max_length=64)
     icon = models.ImageField(upload_to='news/categories/', blank=True)
+    forum_slug = models.CharField(
+            max_length=80,
+            default='',
+            blank=True,
+            help_text=("Identifies the forum to create comment threads in for "
+                       "stories in this category. If blank, no comment threads "
+                       "will be created for stories in this category."))
 
     def __unicode__(self):
         return self.title
@@ -85,6 +92,10 @@
 
 class Story(StoryBase):
     """Model for news stories"""
+    forums_topic = models.OneToOneField('forums.Topic', blank=True, null=True,
+                                        on_delete=models.SET_NULL,
+                                        db_index=False,
+                                        help_text="Forum topic used for comments")
 
     @models.permalink
     def get_absolute_url(self):
@@ -99,10 +110,17 @@
         verbose_name_plural = 'news stories'
 
     def can_comment_on(self):
+        # Only used for version 0 stories
         now = datetime.datetime.now()
         delta = now - self.date_submitted
         return self.allow_comments and delta.days < 30
 
+    def forums_comment_count(self):
+        """Returns number of comments for V2 news stories."""
+        if self.forums_topic:
+            return max(0, self.forums_topic.post_count - 1)
+        return 0
+
     def search_title(self):
         return self.title
 
--- a/news/templatetags/news_tags.py	Tue Nov 24 22:55:18 2015 -0600
+++ b/news/templatetags/news_tags.py	Thu Nov 26 00:27:42 2015 -0600
@@ -17,9 +17,10 @@
 def current_news():
     # Defer the tags field because we are going to get all the
     # tags out in 1 query later...
-    stories = Story.objects.defer('tags').exclude(
-            front_page_expiration__lt=datetime.date.today()).order_by(
-                    '-priority', '-date_submitted')[:10]
+    stories = Story.objects.defer('tags')\
+            .exclude(front_page_expiration__lt=datetime.date.today())\
+            .select_related('forums_topic')\
+            .order_by('-priority', '-date_submitted')[:10]
 
     attach_extra_attrs(stories)
 
--- a/news/utils.py	Tue Nov 24 22:55:18 2015 -0600
+++ b/news/utils.py	Thu Nov 26 00:27:42 2015 -0600
@@ -28,17 +28,25 @@
 
     for story in stories_dict.values():
         story.tag_list = []
-        story.comment_count = 0
+        if story.version == 0:
+            story.comment_count = 0
+        elif story.forums_topic:
+            # for convenience/consistency with old models...
+            story.comment_count = story.forums_comment_count()
 
     # attach tags
     for item in tagged_items:
         stories_dict[item.object_id].tag_list.append(item.tag.name)
 
-    # Now get all the comment counts out in one fell swoop
+    # Now get all the comment counts out in one fell swoop. This is only needed
+    # for older news stories...
 
-    story_ids = Comment.objects.filter(content_type=ct,
-            object_id__in=story_ids).values_list('object_id', flat=True)
+    story_ids = [pk for pk in story_ids if stories_dict[pk].version == 0]
 
-    # compute comment_count
-    for story_id in story_ids:
-        stories_dict[story_id].comment_count += 1
+    if story_ids:
+        story_ids = Comment.objects.filter(content_type=ct,
+                object_id__in=story_ids).values_list('object_id', flat=True)
+
+        # compute comment_count
+        for story_id in story_ids:
+            stories_dict[story_id].comment_count += 1
--- a/news/views.py	Tue Nov 24 22:55:18 2015 -0600
+++ b/news/views.py	Thu Nov 26 00:27:42 2015 -0600
@@ -42,7 +42,8 @@
 def index(request):
     # Defer the tags field because we are going to get all the
     # tags out in 1 query later...
-    stories = Story.objects.all().defer('tags').select_related()
+    stories = Story.objects.all().defer('tags').select_related(
+            'submitter', 'category', 'forums_topic')
     paginator = create_paginator(stories)
 
     page = get_page(request.GET)
@@ -75,7 +76,8 @@
 
 def archive(request, year, month):
     stories = Story.objects.defer('tags').filter(date_submitted__year=year,
-            date_submitted__month=month).select_related()
+            date_submitted__month=month).select_related(
+                'submitter', 'category', 'forums_topic')
     paginator = create_paginator(stories)
     page = get_page(request.GET)
     try:
@@ -110,7 +112,8 @@
 
 def category(request, slug):
     category = get_object_or_404(Category, slug=slug)
-    stories = Story.objects.defer('tags').filter(category=category).select_related()
+    stories = Story.objects.defer('tags').filter(category=category).select_related(
+                    'submitter', 'category', 'forums_topic')
     paginator = create_paginator(stories)
     page = get_page(request.GET)
     try:
@@ -129,7 +132,8 @@
 #######################################################################
 
 def story(request, story_id):
-    story = get_object_or_404(Story, pk=story_id)
+    story = get_object_or_404(Story.objects.select_related(
+                'submitter', 'category', 'forums_topic'), pk=story_id)
     return render_to_response('news/story.html', {
         'story': story,
         },
@@ -174,7 +178,8 @@
 def tag(request, tag_name):
     tag = get_object_or_404(Tag, name=tag_name)
     stories = TaggedItem.objects.get_by_model(
-            Story.objects.defer('tags').select_related(), tag)
+            Story.objects.defer('tags').select_related(
+                'submitter', 'category', 'forums_topic'), tag)
     paginator = create_paginator(stories)
     page = get_page(request.GET)
     try:
--- a/sg101/templates/news/story.html	Tue Nov 24 22:55:18 2015 -0600
+++ b/sg101/templates/news/story.html	Thu Nov 26 00:27:42 2015 -0600
@@ -55,6 +55,8 @@
    {% endif %}
    {% social_sharing story.title story.get_absolute_url %}
 </div>
+
+{% if story.version == 0 %}
 {% get_comment_count for story as comment_count %}
 <p>This story has <span id="comment-count">{{ comment_count }}</span> comment{{ comment_count|pluralize }}.</p>
 <hr />
@@ -66,5 +68,12 @@
 <p>Comments are closed for this story. If you'd like to share your thoughts on this story 
 with the site staff, you can <a href="{% url 'contact-form' %}">contact us directly</a>.</p>
 {% endif %}
+{% else %}
+   {% if story.forums_topic %}
+      <a href="{{ story.forums_topic.get_absolute_url }}">{{ story.forums_comment_count }} comment{{ story.forums_comment_count|pluralize }}</a>
+   {% else %}
+      <p><em>Comments are disabled.</em></p>
+   {% endif %}
+{% endif %}
 </div>
 {% endblock %}
--- a/sg101/templates/news/story_summary.html	Tue Nov 24 22:55:18 2015 -0600
+++ b/sg101/templates/news/story_summary.html	Thu Nov 26 00:27:42 2015 -0600
@@ -30,7 +30,15 @@
 <p>
 Category: <a href="{% url 'news-category' slug=story.category.slug %}">{{ story.category.title }}</a>
 <img src="{{ STATIC_URL }}icons/comments.png" alt="Comments" title="Comments" />
-<a href="{{ story.get_absolute_url }}">{{ story.comment_count }} comment{{ story.comment_count|pluralize }}</a>
+{% if story.version == 0 %}
+   <a href="{{ story.get_absolute_url }}">{{ story.comment_count }} comment{{ story.comment_count|pluralize }}</a>
+{% else %}
+   {% if story.forums_topic %}
+      <a href="{{ story.forums_topic.get_absolute_url }}">{{ story.comment_count }} comment{{ story.comment_count|pluralize }}</a>
+   {% else %}
+   <em>Comments are disabled</em>
+   {% endif %}
+{% endif %}
 <a href="{{ story.get_absolute_url }}"><img src="{{ STATIC_URL }}icons/link.png" alt="Permalink" title="Permalink" /></a>
 {% if user.is_authenticated %}
 <a href="{% url 'news.views.email_story' story.id %}"><img src="{{ STATIC_URL }}icons/email_go.png"