Commits

Gregory Petukhov committed 39a970b

Refactoring

Comments (0)

Files changed (22)

 Feedzilla Django Application
 ============================
 
+Changelog
+=========
+
+0.20::
+----
+
+* lxml instead BeautifulSoup
+* New dependencies: lxml, grab
+* Templates for ATOM/RSS feed moved to templates/feedzilla/feed directory
+
+
+About feedzilla
+===============
+
 This is Django application that adds to your site ability to aggregate
 ATOM/RSS feeds and display them in single stream. In other words you can
 use feedzilla to build planet site.
 * Use ``pip`` or ``easy_install`` to install *feedzilla* package.
 * Install dependencies (see below).
 * Add feedzilla to INSTALLED_APPS.
-* Run ``manage.py syncdb`` or ``manage.py sync`` if you use South.
+* Run ``manage.py syncdb`` or ``manage.py syncdb --migrate`` if you use South.
 * Include ``url('', include('feedzilla.urls'))`` in url config.
 * Setup Site instance via Django admin interface.
 * Setup feedzilla settings via settings.py. See available settings below.
 * django-common
 * django-tagging
 * feedparser
+* lxml
+* grab
 
 Settings
 ========

feedzilla/context_processor.py

+from django.conf import settings
+
+def feedzilla_settings(request):
+    result = {}
+    for key in dir(settings):
+        print 'key', key
+        if key.startswith('FEEDZILLA_'):
+            result[key] = getattr(settings, key)
+    print result
+    return result

feedzilla/models.py

 # License: BSD
 from urlparse import urlsplit
 import re
+from grab.tools.lxml_tools import clean_html
 
-from django.db.models import permalink
 from django.db import models
 from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext_lazy as _
 from django.conf import settings
 
-from feedzilla.util.clean import safe_html
 from tagging.fields import TagField
 
 class Feed(models.Model):
     title = models.CharField(_('title'), max_length=255)
-    feed_url = models.URLField(_('feed url'), unique=True, verify_exists=False)
-    site_url = models.URLField(_('site url'), verify_exists=False)
+    feed_url = models.CharField(_('feed url'), max_length=255, unique=True)
+    site_url = models.CharField(_('site url'), max_length=255)
     active = models.BooleanField(_('active'), blank=True, default=True)
     etag = models.CharField(u'ETag', max_length=255, blank=True, default='')
     last_checked = models.DateTimeField(_('last checked'), blank=True, null=True)
         return self.link
 
     def summary_uncached(self):
-        return safe_html(self.content[:settings.FEEDZILLA_SUMMARY_SIZE])
+        return clean_html(self.content[:settings.FEEDZILLA_SUMMARY_SIZE])
 
 
 class FilterTag(models.Model):

feedzilla/settings.py

 FEEDZILLA_POST_PROCESSORS = (
     'feedzilla.processors.ContentFilterProcessor',
 )
+FEEDZILLA_EXPAND_FULL = False

feedzilla/static/feedzilla/css/custom.css

+.tag-cloud {
+}
+
+    .tag-cloud .size-1 {
+        font-size: 0.9em;
+    }
+
+    .tag-cloud .size-2 {
+        font-size: 1.2em;
+    }
+
+    .tag-cloud .size-3 {
+        font-size: 1.4em;
+    }
+
+    .tag-cloud .size-4 {
+        font-size: 1.6em;
+    }
+
+.feedzilla-counters .counter {
+    float: left;
+    padding-right: 1em;
+}
+
+ul.errorlist {
+    margin-left: 0;
+}
+
+.errorlist li {
+    color: red;
+    list-style-type: none;
+}
+
+.blog-post-item .meta-display {
+    margin-top: 10px;
+    text-align: right;
+}
+
+.blog-post-item .meta .date {
+    margin-left: 20px;
+}

feedzilla/syndication.py

 
 
 class PostFeed(Feed):
+    title_template = 'feedzilla/feed/post_title.html'
+    description_template = 'feedzilla/feed/post_description.html'
+
     title = settings.FEEDZILLA_SITE_TITLE
     description = settings.FEEDZILLA_SITE_DESCRIPTION
     link = '/'

feedzilla/templates/base.html

     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
     <meta name="keywords" content="{% block meta_keywords %}{% endblock %}" />
     <meta name="description" content="{% block meta_description %}{% endblock %}" />
-    <link rel="alternate" type="application/atom+xml" href="{% url feedzilla_feed "posts" %}" title="Yet another feedzilla site" />
+    <link rel="alternate" type="application/atom+xml" href="{% url "feedzilla_feed" "posts" %}" title="Yet another feedzilla site" />
 
     <link type="text/css" rel="stylesheet" href="{{ MEDIA_URL }}feedzilla/css/reset.css" />
     <link type="text/css" rel="stylesheet" href="{{ MEDIA_URL }}feedzilla/css/style.css" />
                 </div>
                 <div class="right">
                     <div class="site-updates">
-                        <a href="{% url feedzilla_feed "posts" %}" class="feed-link">
+                        <a href="{% url "feedzilla_feed" "posts" %}" class="feed-link">
                             <img src="{{ MEDIA_URL }}feedzilla/img/feed-icon-big.png" alt="{% trans "ATOM feed" %}" />
                         </a>
                     </div>
-                    <form class="search-form" action="{% url feedzilla_search %}" method="get">
+                    <form class="search-form" action="{% url "feedzilla_search" %}" method="get">
                         <input id="id_query" type="text" maxlength="64" name="query" value="{% block feedzilla_query_value %}{% endblock %}" />
                         <input type="submit" class="submit" value="{% trans "Search" %}" />
                     </form>
         <div class="site-navigation">
             <ul>
                 <li><a href="/">{% trans "News stream" %}</a></li>
-                <li><a href="{% url feedzilla_source_list %}">{% trans "Sources" %}</a></li>
+                <li><a href="{% url "feedzilla_source_list" %}">{% trans "Sources" %}</a></li>
             </ul>
         </div>
         <div class="site-head-bottom">

feedzilla/templates/feeds/post_description.html

+{{ obj.content|safe }}

feedzilla/templates/feeds/post_title.html

+{{ obj.feed.title|safe }} — {{ obj.title|safe }}

feedzilla/templates/feedzilla/_post_template.html

 {% load tagging_tags %}
 {% load i18n %}
 
-<div class="post">
-    <h3 class="author">
-        <img class="favicon" src="http://www.google.com/s2/favicons?domain={{ post.feed.site_hostname }}" />
-        <a href="{{ post.feed.site_url }}">{{ post.feed.author_or_title }}</a>
-    </h3>
-    <h4 class="title">
-        <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
-    </h4>
+<div class="blog-post-item">
+    <a name="post-{{ post.pk }}"></a>
+    <h3>{{ post.title }}</h3>
     <div class="content">
+        {{ post.summary_uncached|safe }}
+    </div>
+    <div class="content-full" style="display: none">
         {{ post.content|safe }}
     </div>
-    <div class="details">
-        <ul>
-            <li class="share">
-                <!-- AddThis Button BEGIN -->
-                <div class="addthis_toolbox addthis_default_style" addthis:url="{{ post.get_absolute_url }}" addthis:title="{{ post }}">
-                    <a class="addthis_button_preferred_1"></a>
-                    <a class="addthis_button_preferred_2"></a>
-                    <a class="addthis_button_preferred_3"></a>
-                    <a class="addthis_button_preferred_4"></a>
-                    <a class="addthis_button_compact"></a>
-                </div>
-                <!-- AddThis Button END -->
-            </li>
-            <li><span title="{% trans "Date of publication" %}">{{ post.created|date:"d M H:i" }}</span></li>
-            {% if post.tags %}<li><span class="tags" title="{% trans "Labels of post" %}">
+    <div class="meta-display">
+        {% if FEEDZILLA_EXPAND_FULL %}<a class="btn btn-mini" href="#" onclick="var p = $(this).parent().parent(); p.find('.content').hide(); p.find('.content-full').show(); return false;">{% trans "Expand" %}</a> {% trans "or" %} {% endif %}<a class="btn btn-mini btn-success" href="{{ post.get_absolute_url }}">{% trans "Read original" %}</a>
+    </div>
+    <div class="meta">
+        <span class="author">
+            <i class="icon icon-user"></i>
+            <a href="{{ post.feed.site_url }}">{{ post.feed.author_or_title }}</a>
+        </span>
+        <span class="date">
+            <i class="icon icon-time"></i>
+            {{ post.created|date:"d M H:i" }}
+        </span>
+        {% if post.tags %}
+        <div class="row2">
+            <i class="icon icon-tag"></i>
+            <span class="tags">
                 {% tags_for_object post as tags %}
-                {% for tag in tags %}<a href="{% url feedzilla_tag tag.name %}">{{ tag }}</a>{% endfor %}
-            </li>{% endif %}
-        </ul>
+                {% for tag in tags %}<a href="{% url "feedzilla_tag" tag.name %}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}
+            </span>
+        </div>
+        {% endif %}
     </div>
 </div>
+

feedzilla/templates/feedzilla/_sidebar.html

 <div class="box">
     <div class="add-blog">
         <img src="{{ MEDIA_URL }}feedzilla/img/add.png" />
-        <a href="{% url feedzilla_submit_blog %}">{% trans "Submit a blog" %}</a>
+        <a href="{% url "feedzilla_submit_blog" %}">{% trans "Submit a blog" %}</a>
     </div>
 </div>
 

feedzilla/templates/feedzilla/_tag_cloud.html

 {% load feedzilla_tags %}
 <div class="tag-cloud">
 {% for tag in tags %}
-<a class="tag size-{{ tag.font_size }}" href="{% url feedzilla_tag tag %}">{{ tag|escape|feedzilla_strong_spaces }}</a>
+<a class="tag size-{{ tag.font_size }}" href="{% url "feedzilla_tag" tag %}">{{ tag|escape|feedzilla_strong_spaces }}</a>
 {% endfor %}
 </div>

feedzilla/templates/feedzilla/base.html

 {% extends 'base.html' %}
 
+{% block extra_css %}
+<link href="{{ STATIC_URL }}feedzilla/css/custom.css" type="text/css" rel="stylesheet" media="screen,projection" />
+{% endblock %}
+
 {% block extra_js %}
     {% block feedzilla_extra_js %}{% endblock %}
 {% endblock %}

feedzilla/templates/feedzilla/feed/post_description.html

+{{ obj.content|safe }}

feedzilla/templates/feedzilla/feed/post_title.html

+{{ obj.feed.title|safe }} — {{ obj.title|safe }}

feedzilla/templates/feedzilla/index.html

 {% extends 'feedzilla/base.html' %}
 {% load feedzilla_tags %}
+{% load i18n %}
+
+{% block feedzilla_title %}{{ FEEDZILLA_SITE_TITLE }}{% if page.number > 1 %}, {% trans "page" %} {{ page.number }}{% endif %}{% endblock %}
+
+{% block crumbs %}{% endblock %}
 
 {% block feedzilla_content %}
+<h1>{{ FEEDZILLA_SITE_TITLE }}</h1>
+<hr>
 {% for post in page.object_list %}
 {% include 'feedzilla/_post_template.html' %}
+<hr>
 {% endfor %}
 {% include "pagination.html" %}
 {% endblock %}
+

feedzilla/templates/feedzilla/source_list.html

 {% load i18n %}
 
 {% block feedzilla_content %}
-<h1>{% block feedzilla_title %}{% trans "Sources" %}{% endblock %}</h1>
-<div class="divider2"></div>
-<div class="contentarea">
-    <ul>
-        {% for feed in feed %}
-        <li style="line-height: 1.6em">
+<h1>{% block feedzilla_title %}{{ FEEDZILLA_SITE_TITLE }}{% endblock %}</h1>
+
+<table class="table table-striped">
+    <thead>
+        <tr>
+            <th>{% trans "Feed Name" %}</th>
+            <th>{% trans "Post Count" %}</th>
+            <th>{% trans "Feed URL" %}</th>
+        </tr>
+    </thead>
+    {% for feed in feed %}
+    <tr>
+        <td>
             <a href="{{ feed.site_url }}">{{ feed.title }}</a>
-            &nbsp;
-            ({{ feed.post_count }})
-            &nbsp;
-            <a href="{{ feed.feed_url }}">
-                <img style="position: relative; top: 3px" src="{{ MEDIA_URL }}feedzilla/img/feed-icon-small.png" />
-            </a>
-        </li>
-        {% endfor %}
-    </ul>
-</div>
+        </td>
+        <td>
+        {{ feed.post_count }}
+        </td>
+        <td>
+        <a href="{{ feed.feed_url }}">
+            <img style="position: relative; top: 3px" src="{{ MEDIA_URL }}feedzilla/img/feed-icon-small.png" />
+        </a>
+        </td>
+    </tr>
+    {% endfor %}
+</table>
 {% endblock %}
+

feedzilla/templates/feedzilla/tag.html

 
 {% block feedzilla_content %}
 <h1>{% block title %}{% blocktrans %}Posts with &laquo;{{ tag }}&raquo; label{% endblocktrans %}{% endblock title %}</h1>
+<hr>
 {% if page.object_list %}
     {% for post in page.object_list %}
     {% include 'feedzilla/_post_template.html' %}
+    <hr>
     {% endfor %}
     {% include "pagination.html" %}
 {% else %}

feedzilla/urls.py

 urlpatterns += patterns('django.contrib.syndication.views',
     # WTF???
     url(r'^ru/projects/feed$', PostFeed(), name='feedzilla_feed'),
-    # valid old line
+    # depprecated
     url(r'^feeds/posts$', PostFeed(), name='feedzilla_feed'),
+    # new
+    url(r'^feed/post$', PostFeed(), name='feedzilla_feed'),
 )

feedzilla/util/clean.py

-# -*- coding: utf-8
-# Copyright: 2011, Grigoriy Petukhov
-# Author: Grigoriy Petukhov (http://lorien.name)
-# License: BSD
-
-import htmldata
-from BeautifulSoup import BeautifulSoup
-
-def normalize_html(data):
-    """
-    Make valid HTML.
-    """
-
-    return unicode(BeautifulSoup(data))
-
-
-def safe_html(data):
-    """
-    Remove all tag attributes from html except a.href and img.src
-    """
-    
-    data = normalize_html(data)
-    tree = htmldata.tagextract(data)
-    for elem in tree:
-        if isinstance(elem, tuple):
-            for attr in elem[1].keys():
-                if 'a' == elem[0] and 'href' == attr:
-                    continue
-                if 'img/' == elem[0] and 'src' == attr:
-                    continue
-                del elem[1][attr]
-    data = htmldata.tagjoin(tree)
-    # Temporary hack
-    # htmldata doing something shitty with html:
-    # tagjoin return invalid DIV
-    # Data for testing: http://py-algorithm.blogspot.com/2011/04/blog-post_3267.html
-    data = normalize_html(data)
-    return data

feedzilla/util/parse.py

 from datetime import datetime
 import feedparser
 import logging
-
-import clean
+from grab.tools.lxml_tools import clean_html
 
 log = logging.getLogger('feedzilla.util.parse')
 
 
             summary = content[:summary_size]
 
-            summary = clean.safe_html(summary)
-            content = clean.safe_html(content)
+            summary = clean_html(summary)
+            content = clean_html(content)
 
             created = parse_modified_date(entry, resp['feed'])
             if not created:
 
 setup(
     name = 'feedzilla',
-    version = '0.1.21',
-    description = 'Django application for atom/rss feeds aggregation i.e. planet engine',
+    version = '0.2',
+    description = 'Django application for ATOM/RSS feeds aggregation i.e. planet engine',
+    long_description = open('README.rst').read(),
     url = 'http://bitbucket.org/lorien/feedzilla',
     author = 'Grigoriy Petukhov',
     author_email = 'lorien@lorien.name',