Sync to trunk.
@ -26,9 +26,12 @@ def freeze():
|
||||
|
||||
binary_excludes = ['libGLcore*', 'libGL*', 'libnvidia*']
|
||||
|
||||
os.system('sudo cp /usr/bin/calibre-mount-helper /tmp/calibre-mount-helper')
|
||||
os.system('sudo chown kovid:users /tmp/calibre-mount-helper')
|
||||
|
||||
binary_includes = [
|
||||
'/usr/bin/pdftohtml',
|
||||
'/usr/bin/calibre-mount-helper',
|
||||
'/tmp/calibre-mount-helper',
|
||||
'/usr/lib/libunrar.so',
|
||||
'/usr/lib/libsqlite3.so.0',
|
||||
'/usr/lib/libsqlite3.so.0',
|
||||
|
@ -10,11 +10,18 @@ from distutils.core import Extension as _Extension
|
||||
from distutils.command.build_ext import build_ext as _build_ext
|
||||
from distutils.dep_util import newer_group
|
||||
from distutils import log
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
import sipconfig, os, sys, string, glob, shutil
|
||||
from PyQt4 import pyqtconfig
|
||||
iswindows = 'win32' in sys.platform
|
||||
QMAKE = os.path.expanduser('~/qt/bin/qmake') if 'darwin' in sys.platform else'qmake'
|
||||
isosx = 'darwin' in sys.platform
|
||||
QMAKE = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
|
||||
if find_executable('qmake-qt4'):
|
||||
QMAKE = find_executable('qmake-qt4')
|
||||
elif find_executable('qmake'):
|
||||
QMAKE = find_executable('qmake')
|
||||
QMAKE = os.environ.get('QMAKE', QMAKE)
|
||||
WINDOWS_PYTHON = ['C:/Python26/libs']
|
||||
OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk'
|
||||
|
||||
|
6
setup.py
@ -65,7 +65,7 @@ def setup_mount_helper():
|
||||
|
||||
if __name__ == '__main__':
|
||||
from setuptools import setup, find_packages
|
||||
from pyqtdistutils import PyQtExtension, build_ext, Extension
|
||||
from pyqtdistutils import PyQtExtension, build_ext, Extension, QMAKE
|
||||
from upload import sdist, pot, build, build_py, manual, \
|
||||
resources, clean, gui, translations, update, \
|
||||
tag_release, upload_demo, build_linux, build_windows, \
|
||||
@ -76,10 +76,8 @@ if __name__ == '__main__':
|
||||
entry_points['console_scripts'].append(
|
||||
'calibre_postinstall = calibre.linux:post_install')
|
||||
optional = []
|
||||
qmake = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
|
||||
qmake = os.environ.get('QMAKE', qmake)
|
||||
def qmake_query(arg=''):
|
||||
return subprocess.Popen([qmake, '-query', arg],
|
||||
return subprocess.Popen([QMAKE, '-query', arg],
|
||||
stdout=subprocess.PIPE).stdout.read()
|
||||
qt_inc = qt_lib = None
|
||||
qt_inc = qmake_query('QT_INSTALL_HEADERS').splitlines()[0]
|
||||
|
@ -35,14 +35,10 @@ normally have been passed to the output plugin.
|
||||
|
||||
After specifying the input \
|
||||
and output file you can customize the conversion by specifying various \
|
||||
options. the available options depend on the input and output file types. \
|
||||
options. The available options depend on the input and output file types. \
|
||||
To get help on them specify the input and output file and then use the -h \
|
||||
option.
|
||||
|
||||
You can also get detailed help on all the options any input/output pair \
|
||||
of formats supports by specifying the -h flag after the input and output \
|
||||
filenames.
|
||||
|
||||
For full documentation of the conversion system see
|
||||
''') + 'http://calibre.kovidgoyal.net/user_manual/conversion.html'
|
||||
|
||||
|
@ -17,7 +17,10 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Format:</string>
|
||||
<string>&Format:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_format</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -17,7 +17,10 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Line Un-Wrapping Factor:</string>
|
||||
<string>Line &Un-Wrapping Factor:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_unwrap_factor</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -50,7 +53,7 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="opt_no_images">
|
||||
<property name="text">
|
||||
<string>No Images</string>
|
||||
<string>No &Images</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -17,7 +17,10 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Paper Size:</string>
|
||||
<string>&Paper Size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_paper_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -27,7 +30,10 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Orientation:</string>
|
||||
<string>&Orientation:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_orientation</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -17,7 +17,10 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Line ending style:</string>
|
||||
<string>&Line ending style:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_newline</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -54,7 +54,7 @@ class ChangelogFormatter(blog.LogFormatter):
|
||||
for msg in entry[1]:
|
||||
txt.append(u' * ' + msg)
|
||||
|
||||
return u'\n'.join(txt)
|
||||
return (u'\n'.join(txt)).encode('ascii', 'replace')
|
||||
|
||||
def bzr_log_to_txt():
|
||||
path = BZR_PATH
|
||||
|
@ -5,8 +5,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: calibre 0.6.6\n"
|
||||
"POT-Creation-Date: 2009-08-10 19:10+MDT\n"
|
||||
"PO-Revision-Date: 2009-08-10 19:10+MDT\n"
|
||||
"POT-Creation-Date: 2009-08-11 11:45+MDT\n"
|
||||
"PO-Revision-Date: 2009-08-11 11:45+MDT\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -643,54 +643,52 @@ msgid ""
|
||||
"\n"
|
||||
"The output ebook format is guessed from the file extension of output_file. output_file can also be of the special format .EXT where EXT is the output file extension. In this case, the name of the output file is derived the name of the input file. Note that the filenames must not start with a hyphen. Finally, if output_file has no extension, then it is treated as a directory and an \"open ebook\" (OEB) consisting of HTML files is written to that directory. These files are the files that would normally have been passed to the output plugin.\n"
|
||||
"\n"
|
||||
"After specifying the input and output file you can customize the conversion by specifying various options. the available options depend on the input and output file types. To get help on them specify the input and output file and then use the -h option.\n"
|
||||
"\n"
|
||||
"You can also get detailed help on all the options any input/output pair of formats supports by specifying the -h flag after the input and output filenames.\n"
|
||||
"After specifying the input and output file you can customize the conversion by specifying various options. The available options depend on the input and output file types. To get help on them specify the input and output file and then use the -h option.\n"
|
||||
"\n"
|
||||
"For full documentation of the conversion system see\n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:101
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:97
|
||||
msgid "INPUT OPTIONS"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:102
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:98
|
||||
msgid "Options to control the processing of the input %s file"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:108
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:104
|
||||
msgid "OUTPUT OPTIONS"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:109
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:105
|
||||
msgid "Options to control the processing of the output %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:123
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:119
|
||||
msgid "Options to control the look and feel of the output"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:138
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:134
|
||||
msgid "Control auto-detection of document structure."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:148
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:144
|
||||
msgid "Control the automatic generation of a Table of Contents. By default, if the source file has a Table of Contents, it will be used in preference to the automatically generated one."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:158
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:154
|
||||
msgid "Options to set metadata in the output"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:161
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:157
|
||||
msgid "Options to help with debugging the conversion"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:185
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:181
|
||||
msgid "List builtin recipes"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:254
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:250
|
||||
msgid "Output saved to"
|
||||
msgstr ""
|
||||
|
||||
@ -4129,7 +4127,7 @@ msgid ""
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Set a regular expression pattern to use when trying to guess ebook metadata from filenames. </p>\n"
|
||||
"<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">A <a href=\"http://docs.python.org/lib/re-syntax.html\"><span style=\" text-decoration: underline; color:#0000ff;\">reference</span></a> on the syntax of regular expressions is available.</p>\n"
|
||||
"<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">A <a href=\"http://docs.python.org/lib/re-syntax.html\"><span style=\" text-decoration: underline; color:#0000ee;\">reference</span></a> on the syntax of regular expressions is available.</p>\n"
|
||||
"<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Use the <span style=\" font-weight:600;\">Test</span> functionality below to test your regular expression on a few sample filenames. The group names for the various metadata entries are documented in tooltips.</p></body></html>"
|
||||
msgstr ""
|
||||
|
||||
@ -5889,47 +5887,51 @@ msgid ""
|
||||
"%sUsage%s: %s\n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:81
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:87
|
||||
msgid "Created by "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:538
|
||||
msgid "Path to the database in which books are stored"
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:88
|
||||
msgid "Whenever you pass arguments to %prog that have spaces in them, enclose the arguments in quotation marks."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:540
|
||||
msgid "Pattern to guess metadata from filenames"
|
||||
msgid "Path to the database in which books are stored"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:542
|
||||
msgid "Access key for isbndb.com"
|
||||
msgid "Pattern to guess metadata from filenames"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:544
|
||||
msgid "Default timeout for network operations (seconds)"
|
||||
msgid "Access key for isbndb.com"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:546
|
||||
msgid "Path to directory in which your library of books is stored"
|
||||
msgid "Default timeout for network operations (seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:548
|
||||
msgid "The language in which to display the user interface"
|
||||
msgid "Path to directory in which your library of books is stored"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:550
|
||||
msgid "The language in which to display the user interface"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:552
|
||||
msgid "The default output format for ebook conversions."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:554
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:556
|
||||
msgid "Ordered list of formats to prefer for input."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:556
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:558
|
||||
msgid "Read metadata from files"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:558
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/config.py:560
|
||||
msgid "The priority of worker processes"
|
||||
msgstr ""
|
||||
|
||||
@ -6406,272 +6408,3 @@ msgstr ""
|
||||
msgid "Show detailed output information. Useful for debugging"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:12
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:38
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:45
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:103
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:141
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:13
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:39
|
||||
msgid "slug"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:16
|
||||
msgid "category"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:17
|
||||
msgid "categories"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:35
|
||||
msgid "Draft"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:36
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:41
|
||||
msgid "body"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:42
|
||||
msgid "tease"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:43
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:44
|
||||
msgid "allow comments"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:45
|
||||
msgid "publish"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:46
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:47
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:53
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:153
|
||||
msgid "post"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/blog/models.py:54
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:154
|
||||
msgid "posts"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/admin.py:31
|
||||
msgid "Fields updated automatically by Feedjack"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:17
|
||||
msgid "Date published."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:18
|
||||
msgid "Date the post was first obtained."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:22
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:38
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:98
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:126
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:173
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:23
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:26
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:105
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:142
|
||||
msgid "link"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:27
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:61
|
||||
msgid "links"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:39
|
||||
msgid "url"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:42
|
||||
msgid "Example"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:46
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:47
|
||||
msgid "welcome"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:48
|
||||
msgid "greets"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:50
|
||||
msgid "default site"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:51
|
||||
msgid "posts per page"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:52
|
||||
msgid "order posts by"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:54
|
||||
msgid "tagcloud level"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:55
|
||||
msgid "show tagcloud"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:57
|
||||
msgid "use internal cache"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:58
|
||||
msgid "cache duration"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:59
|
||||
msgid "Duration in seconds of the cached pages and data."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:63
|
||||
msgid "template"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:65
|
||||
msgid "This template must be a directory in your feedjack templates directory. Leave blank to use the default template."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:69
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:170
|
||||
msgid "site"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:70
|
||||
msgid "sites"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:96
|
||||
msgid "feed url"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:99
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:175
|
||||
msgid "shortname"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:100
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:178
|
||||
msgid "is active"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:101
|
||||
msgid "If disabled, this feed will not be further updated."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:104
|
||||
msgid "tagline"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:108
|
||||
msgid "etag"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:109
|
||||
msgid "last modified"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:110
|
||||
msgid "last checked"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:113
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:140
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:171
|
||||
msgid "feed"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:114
|
||||
msgid "feeds"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:129
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:130
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:149
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:143
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:144
|
||||
msgid "date modified"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:145
|
||||
msgid "guid"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:146
|
||||
msgid "author"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:147
|
||||
msgid "author email"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:148
|
||||
msgid "comments"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:150
|
||||
msgid "date created"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:174
|
||||
msgid "Keep blank to use the Feed's original name."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:177
|
||||
msgid "Keep blank to use the Feed's original shortname."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:179
|
||||
msgid "If disabled, this subscriber will not appear in the site or in the site's feed."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:183
|
||||
msgid "subscriber"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/www/apps/feedjack/models.py:184
|
||||
msgid "subscribers"
|
||||
msgstr ""
|
||||
|
||||
|
@ -78,13 +78,15 @@ class OptionParser(_OptionParser):
|
||||
def __init__(self,
|
||||
usage='%prog [options] filename',
|
||||
version='%%prog (%s %s)'%(__appname__, __version__),
|
||||
epilog=_('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL,
|
||||
epilog=None,
|
||||
gui_mode=False,
|
||||
conflict_handler='resolve',
|
||||
**kwds):
|
||||
usage = textwrap.dedent(usage)
|
||||
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
||||
'''enclose the arguments in quotation marks.'''
|
||||
if epilog is None:
|
||||
epilog = _('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL
|
||||
usage += '\n\n'+_('''Whenever you pass arguments to %prog that have spaces in them, '''
|
||||
'''enclose the arguments in quotation marks.''')
|
||||
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
||||
formatter=CustomHelpFormatter(),
|
||||
conflict_handler=conflict_handler, **kwds)
|
||||
|
@ -52,7 +52,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'diagonales', 'miradasalsur', 'newsweek_argentina', 'veintitres',
|
||||
'gva_be', 'hln', 'tijd', 'degentenaar', 'inquirer_net', 'uncrate',
|
||||
'fastcompany', 'accountancyage', 'laprensa_hn', 'latribuna',
|
||||
'eltiempo_hn', 'slate',
|
||||
'eltiempo_hn', 'slate', 'tnxm', 'bbcvietnamese', 'vnexpress',
|
||||
)]
|
||||
|
||||
|
||||
|
34
src/calibre/web/feeds/recipes/recipe_bbcvietnamese.py
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Huan Komrade T <huantnh at gmail.com>'
|
||||
'''
|
||||
bbc.co.uk
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class BBCVietnamese(BasicNewsRecipe):
|
||||
title = u'BBC Vietnamese'
|
||||
__author__ = 'Huan Komrade T'
|
||||
description = 'Vietnam news and current affairs from the British Broadcasting Corporation'
|
||||
no_stylesheets = True
|
||||
language = _('Vietnamese')
|
||||
encoding = 'utf-8'
|
||||
recursions = 0
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'footer'})]
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
||||
feeds = [
|
||||
('Index', 'http://www.bbc.co.uk/vietnamese/index.xml'),
|
||||
('Vietnam', 'http://www.bbc.co.uk/vietnamese/vietnam/index.xml'),
|
||||
('Business', 'http://www.bbc.co.uk/vietnamese/business/index.xml'),
|
||||
('Culture', 'http://www.bbc.co.uk/vietnamese/culture/index.xml'),
|
||||
('Football', 'http://www.bbc.co.uk/vietnamese/football/index.xml'),
|
||||
('Forum', 'http://www.bbc.co.uk/vietnamese/forum/index.xml'),
|
||||
('In Depth', 'http://www.bbc.co.uk/vietnamese/indepth/index.xml'),
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('http://www.bbc.co.uk/vietnamese/', 'http://www.bbc.co.uk/vietnamese/lg/')
|
@ -1,39 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Slashdot(BasicNewsRecipe):
|
||||
title = u'Slashdot.org'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = _('English')
|
||||
__author__ = 'floweros'
|
||||
no_stylesheets = True
|
||||
keep_only_tags = [dict(name='div',attrs={'id':'article'})]
|
||||
remove_tags = [
|
||||
dict(name='div',attrs={'id':'userlogin-title'}),
|
||||
dict(name='div',attrs={'id':'userlogin-content'}),
|
||||
dict(name='div',attrs={'id':'commentwrap'}),
|
||||
dict(name='span',attrs={'id':'more_comments_num_a'}),
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Slashdot',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdot?m=5072'),
|
||||
(u'/. IT',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotIT'),
|
||||
(u'/. Hardware',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotHardware'),
|
||||
(u'/. Linux',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotLinux'),
|
||||
(u'/. Your Rights Online',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotYourRightsOnline')
|
||||
]
|
||||
|
||||
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net> edited by Huan T'
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Slashdot(BasicNewsRecipe):
|
||||
title = u'Slashdot.org'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = _('English')
|
||||
__author__ = 'floweros edited by Huan T'
|
||||
no_stylesheets = True
|
||||
# keep_only_tags = [
|
||||
# dict(name='div',attrs={'class':'article'}),
|
||||
# dict(name='div',attrs={'class':'commentTop'}),
|
||||
# ]
|
||||
|
||||
feeds = [
|
||||
(u'Slashdot',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdot'),
|
||||
(u'/. IT',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotIT'),
|
||||
(u'/. Hardware',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotHardware'),
|
||||
(u'/. Linux',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotLinux'),
|
||||
(u'/. Your Rights Online',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotYourRightsOnline')
|
||||
]
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('feedburner_origlink', None)
|
||||
|
28
src/calibre/web/feeds/recipes/recipe_tnxm.py
Normal file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Wasabi <wasabi at tnxm.net>'
|
||||
'''
|
||||
tnxm.net
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class TNXM(BasicNewsRecipe):
|
||||
title = u'Thanh Nien Xa Me'
|
||||
__author__ = 'Wasabi'
|
||||
description = 'Vietnam news and current affairs from TNXM - the finest Vietnamese bulletin board.'
|
||||
no_stylesheets = True
|
||||
language = _('Vietnamese')
|
||||
encoding = 'utf-8'
|
||||
recursions = 0
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'footer'})]
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
||||
feeds = [
|
||||
('Index', 'http://tnxm.net/external.php?type=RSS'),
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('showthread.php?', 'printthread.php?pp=160&')
|
36
src/calibre/web/feeds/recipes/recipe_vnexpress.py
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Huan Komrade T <huantnh at gmail.com>'
|
||||
'''
|
||||
vnexpress.net
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class BBCVietnamese(BasicNewsRecipe):
|
||||
title = u'VnExpress'
|
||||
__author__ = 'Huan Komrade T'
|
||||
description = 'Vietnam news and current affairs from the Food Production Technology Corporation'
|
||||
no_stylesheets = True
|
||||
language = _('Vietnamese')
|
||||
encoding = 'utf-8'
|
||||
recursions = 0
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'footer'})]
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
||||
feeds = [
|
||||
('Index', 'http://vnexpress.net/rss/gl/trang-chu.rss'),
|
||||
('Vietnam', 'http://vnexpress.net/rss/gl/xa-hoi.rss'),
|
||||
('World News', 'http://vnexpress.net/rss/gl/the-gioi.rss'),
|
||||
('Business', 'http://vnexpress.net/rss/gl/kinh-doanh.rss'),
|
||||
('Culture', 'http://vnexpress.net/rss/gl/van-hoa.rss'),
|
||||
('Sports', 'http://vnexpress.net/rss/gl/the-thao.rss'),
|
||||
('Lifestyle', 'http://vnexpress.net/rss/gl/doi-song.rss'),
|
||||
('From The Readers', 'http://vnexpress.net/rss/gl/ban-doc-viet.rss'),
|
||||
('From The Readers - Sharing', 'http://vnexpress.net/rss/gl/ban-doc-viet-tam-su.rss'),
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?q=1'
|
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
|
@ -1,85 +0,0 @@
|
||||
changes:
|
||||
date: 2008-09-17
|
||||
change: Enabled the ability to override the default template names.
|
||||
|
||||
date: 2008-08-26
|
||||
change: Upgraded post_detail.html to now use new Django refactored comments. Sidenote: basic.remarks have been removed.
|
||||
|
||||
date: 2008-07-14
|
||||
change: Removed get_query_set from Blog manager to fix a problem where saving a post marked as Draft would not save.
|
||||
change: Added a get_previous_post and get_next_post method for front end template. These will not return Draft posts.
|
||||
|
||||
date: 2008-06-17
|
||||
change: BlogPostFeed is now BlogPostsFeed and there is a new BlogPostsByCategory.
|
||||
|
||||
date: 2008-05-18
|
||||
change: Converted everything to 4 space tabs and made a few other changes to comply with Python Style Guide.
|
||||
|
||||
date: 2008-04-23
|
||||
change: Added an inline admin interface helper for choosing inlines to go into posts.
|
||||
change: The inline app is now a dependancy of the blog.
|
||||
|
||||
date: 2008-04-22
|
||||
change: Removed the 'render_inlines' filter from the Blog template tags. The tag is now in an app called inlines which can be used with any django app.
|
||||
|
||||
date: 2008-02-27
|
||||
change: Added 'allow_comments' field to the Post model.
|
||||
change: Removed 'Closed' choice from status field of Post model
|
||||
|
||||
date: 2008-02-18
|
||||
fix: Fixed feed pointing to hardcoded url.
|
||||
|
||||
date: 2008-02-15
|
||||
change: Internationalized models
|
||||
|
||||
date: 2008-02-04
|
||||
change: Added 'get_links' template filter.
|
||||
change: Templates: added a {% block content_title %}
|
||||
|
||||
date: 2008-02-02
|
||||
change: Added a sitemap
|
||||
|
||||
date: 2008-01-30
|
||||
change: Renamed 'do_inlines' filter to 'render_inlines'
|
||||
|
||||
date: 2008-01-29
|
||||
change: BeautifulSoup is no longer a dependancy unless you want to use the do_inlines filter.
|
||||
|
||||
date: 2008-01-27
|
||||
fix: removed 'tagging.register(Post)' from model. It was causing too many unnecessary SQL JOINS.
|
||||
change: Changed the inlines tag to a filter. (Example: {{ object.text|do_inlines }})
|
||||
|
||||
date: 2008-01-22
|
||||
change: Registered the Post model with the tagging app
|
||||
|
||||
date: 2008-01-19
|
||||
change: Renamed the 'list' class to 'link_list'
|
||||
|
||||
date: 2008-01-09
|
||||
change: Changed urls.py so you can have /posts/page/2/ or /posts/?page=2
|
||||
|
||||
date: 2008-01-07
|
||||
change: Removed PublicPostManager in favor of ManagerWithPublished.
|
||||
change: Made wrappers for generic views.
|
||||
|
||||
date: 2008-01-06
|
||||
fix: In blog.py changed 'beautifulsoup' to 'BeautifulSoup'
|
||||
|
||||
date: 2007-12-31
|
||||
change: Changed some syntax in managers.py to hopefully fix a bug.
|
||||
change: Removed an inline template that didn't belong.
|
||||
|
||||
date: 2007-12-21
|
||||
change: Added markup tag that formats inlines.
|
||||
|
||||
date: 2007-12-12
|
||||
change: Cleaned up unit tests.
|
||||
|
||||
date: 2007-12-11
|
||||
change: Add documentation to templatetags and views.
|
||||
change: Smartened up the previous/next blog part of the post_detail.html template.
|
||||
|
||||
date: 2007-12-09
|
||||
change: Added feed templates and wrapped up feeds.py.
|
||||
change: Changed Post.live manager to Post.public
|
||||
change: Added a search view along with templates
|
@ -1,18 +0,0 @@
|
||||
===========================================
|
||||
Django Basic Blog
|
||||
http://code.google.com/p/django-basic-apps/
|
||||
===========================================
|
||||
|
||||
A simple blog application for Django projects.
|
||||
|
||||
To install this app, simply create a folder somewhere in
|
||||
your PYTHONPATH named 'basic' and place the 'blog'
|
||||
app inside. Then add 'basic.blog' to your projects
|
||||
INSTALLED_APPS list in your settings.py file.
|
||||
|
||||
=== Dependancies ===
|
||||
* Basic Inlines
|
||||
* [http://www.djangoproject.com/documentation/add_ons/#comments Django Comments]
|
||||
* [http://code.google.com/p/django-tagging Django Tagging]
|
||||
* [http://www.djangoproject.com/documentation/add_ons/#markup Markup]
|
||||
* [http://www.crummy.com/software/BeautifulSoup/ BeautifulSoup] - only if you want to use the [http://code.google.com/p/django-basic-blog/wiki/BlogInlinesProposal render_inlines] filter, otherwise it's not necessary.
|
@ -1,17 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from calibre.www.apps.blog.models import *
|
||||
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
|
||||
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'publish', 'status')
|
||||
list_filter = ('publish', 'categories', 'status')
|
||||
search_fields = ('title', 'body')
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
|
||||
admin.site.register(Post, PostAdmin)
|
@ -1,42 +0,0 @@
|
||||
from django.contrib.syndication.feeds import FeedDoesNotExist
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.syndication.feeds import Feed
|
||||
from django.core.urlresolvers import reverse
|
||||
from calibre.www.apps.blog.models import Post, Category
|
||||
|
||||
|
||||
class BlogPostsFeed(Feed):
|
||||
_site = Site.objects.get_current()
|
||||
title = '%s feed' % _site.name
|
||||
description = '%s posts feed.' % _site.name
|
||||
|
||||
def link(self):
|
||||
return reverse('blog_index')
|
||||
|
||||
def items(self):
|
||||
return Post.objects.published()[:10]
|
||||
|
||||
def item_pubdate(self, obj):
|
||||
return obj.publish
|
||||
|
||||
|
||||
class BlogPostsByCategory(Feed):
|
||||
_site = Site.objects.get_current()
|
||||
title = '%s posts category feed' % _site.name
|
||||
|
||||
def get_object(self, bits):
|
||||
if len(bits) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
return Category.objects.get(slug__exact=bits[0])
|
||||
|
||||
def link(self, obj):
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def description(self, obj):
|
||||
return "Posts recently categorized as %s" % obj.title
|
||||
|
||||
def items(self, obj):
|
||||
return obj.post_set.published()[:10]
|
@ -1,9 +0,0 @@
|
||||
from django.db.models import Manager
|
||||
import datetime
|
||||
|
||||
|
||||
class PublicManager(Manager):
|
||||
"""Returns published posts that are not in the future."""
|
||||
|
||||
def published(self):
|
||||
return self.get_query_set().filter(status__gte=2, publish__lte=datetime.datetime.now())
|
@ -1,80 +0,0 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import permalink
|
||||
from django.contrib.auth.models import User
|
||||
from tagging.fields import TagField
|
||||
from calibre.www.apps.blog.managers import PublicManager
|
||||
|
||||
import tagging
|
||||
|
||||
class Category(models.Model):
|
||||
"""Category model."""
|
||||
title = models.CharField(_('title'), max_length=100)
|
||||
slug = models.SlugField(_('slug'), unique=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('category')
|
||||
verbose_name_plural = _('categories')
|
||||
db_table = 'blog_categories'
|
||||
ordering = ('title',)
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.title
|
||||
|
||||
@permalink
|
||||
def get_absolute_url(self):
|
||||
return ('blog_category_detail', None, {'slug': self.slug})
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
"""Post model."""
|
||||
STATUS_CHOICES = (
|
||||
(1, _('Draft')),
|
||||
(2, _('Public')),
|
||||
)
|
||||
title = models.CharField(_('title'), max_length=200)
|
||||
slug = models.SlugField(_('slug'), unique_for_date='publish')
|
||||
author = models.ForeignKey(User, blank=True, null=True)
|
||||
body = models.TextField(_('body'))
|
||||
tease = models.TextField(_('tease'), blank=True)
|
||||
status = models.IntegerField(_('status'), choices=STATUS_CHOICES, default=2)
|
||||
allow_comments = models.BooleanField(_('allow comments'), default=True)
|
||||
publish = models.DateTimeField(_('publish'))
|
||||
created = models.DateTimeField(_('created'), auto_now_add=True)
|
||||
modified = models.DateTimeField(_('modified'), auto_now=True)
|
||||
categories = models.ManyToManyField(Category, blank=True)
|
||||
tags = TagField()
|
||||
objects = PublicManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('post')
|
||||
verbose_name_plural = _('posts')
|
||||
db_table = 'blog_posts'
|
||||
ordering = ('-publish',)
|
||||
get_latest_by = 'publish'
|
||||
|
||||
class Admin:
|
||||
list_display = ('title', 'publish', 'status')
|
||||
list_filter = ('publish', 'categories', 'status')
|
||||
search_fields = ('title', 'body')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.title
|
||||
|
||||
@permalink
|
||||
def get_absolute_url(self):
|
||||
return ('blog_detail', None, {
|
||||
'year': self.publish.year,
|
||||
'month': self.publish.strftime('%b').lower(),
|
||||
'day': self.publish.day,
|
||||
'slug': self.slug
|
||||
})
|
||||
|
||||
def get_previous_post(self):
|
||||
return self.get_previous_by_publish(status__gte=2)
|
||||
|
||||
def get_next_post(self):
|
||||
return self.get_next_by_publish(status__gte=2)
|
@ -1,13 +0,0 @@
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from calibre.www.apps.blog.models import Post
|
||||
|
||||
|
||||
class BlogSitemap(Sitemap):
|
||||
changefreq = "never"
|
||||
priority = 0.5
|
||||
|
||||
def items(self):
|
||||
return Post.objects.published()
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.publish
|
@ -1,56 +0,0 @@
|
||||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
{% load adminmedia inlines %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
function InlineInit() {
|
||||
var body_div = document.getElementById('id_body').parentNode;
|
||||
var content = ''
|
||||
content += '{% get_inline_types as inline_list %}'
|
||||
content += '<label>Body inlines:</label>'
|
||||
|
||||
content += '<strong>Inline type:</strong> '
|
||||
content += '<select id="id_inline_content_type" onchange="document.getElementById(\'lookup_id_inline\').href = \'../../../\'+this.value+\'/\';" style="margin-right:20px;">'
|
||||
content += ' <option>----------</option>'
|
||||
content += ' {% for inline in inline_list %}'
|
||||
content += ' <option value="{{ inline.content_type.app_label }}/{{ inline.content_type.model }}">{{ inline.content_type.app_label|capfirst }}: {{ inline.content_type.model|capfirst }}</option>'
|
||||
content += ' {% endfor %}'
|
||||
content += '</select> '
|
||||
|
||||
content += '<strong>Object:</strong> '
|
||||
content += '<input type="text" class="vIntegerField" id="id_inline" size="10" /> '
|
||||
content += '<a id="lookup_id_inline" href="#" class="related-lookup" onclick="if(document.getElementById(\'id_inline_content_type\').value != \'----------\') { return showRelatedObjectLookupPopup(this); }" style="margin-right:20px;"><img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Loopup" /></a> '
|
||||
|
||||
content += '<strong>Class:</strong> '
|
||||
content += '<select id="id_inline_class">'
|
||||
content += ' <option value="small_left">Small left</option>'
|
||||
content += ' <option value="small_right">Small right</option>'
|
||||
content += ' <option value="medium_left">Medium left</option>'
|
||||
content += ' <option value="medium_right">Medium right</option>'
|
||||
content += ' <option value="large_left">Large left</option>'
|
||||
content += ' <option value="large_right">Large right</option>'
|
||||
content += ' <option value="full">Full</option>'
|
||||
content += '</select>'
|
||||
|
||||
content += '<input type="button" value="Add" style="margin-left:10px;" onclick="return insertInline(document.getElementById(\'id_inline_content_type\').value, document.getElementById(\'id_inline\').value, document.getElementById(\'id_inline_class\').value)" />'
|
||||
content += '<p class="help">Insert inlines into your body by choosing an inline type, then an object, then a class.</p>'
|
||||
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute('style', 'margin-top:10px;');
|
||||
div.innerHTML = content;
|
||||
|
||||
body_div.insertBefore(div);
|
||||
}
|
||||
|
||||
function insertInline(type, id, classname) {
|
||||
if (type != '----------' && id != '') {
|
||||
inline = '<inline type="'+type.replace('/', '.')+'" id="'+id+'" class="'+classname+'" />';
|
||||
body = document.getElementById('id_body');
|
||||
body.value = body.value + inline + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
addEvent(window, 'load', InlineInit);
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
</head>
|
||||
<body id="{% block body_id %}{% endblock %}">
|
||||
<div id="body">
|
||||
{% block body %}
|
||||
<div>
|
||||
{% block content_title %}{% endblock %}
|
||||
</div>
|
||||
<div class="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
|
||||
{% block body_class %}blog{% endblock %}
|
@ -1,25 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Posts for {{ category.title }}{% endblock %}
|
||||
{% block body_class %}{{ block.super }} category_detail{% endblock %}
|
||||
{% block body_id %}category_{{ category.id }}{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Posts for {{ category.title }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% load markup %}
|
||||
<div class="post_list">
|
||||
{% for post in object_list %}
|
||||
<div>
|
||||
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
|
||||
<p class="date">{{ post.publish|date:"Y F d" }}</p>
|
||||
<p class="tease">{{ post.tease }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,20 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Post categories{% endblock %}
|
||||
{% block body_class %}{{ block.super }} category_list{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Post categories</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% load markup %}
|
||||
<ul class="link_list">
|
||||
{% for category in object_list %}
|
||||
<li><a href="{{ category.get_absolute_url }}">{{ category }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Post archive for {{ day|date:"d F Y" }}{% endblock %}
|
||||
{% block body_class %}{{ block.super }} post_archive_day{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Post archive for {{ day|date:"d F Y" }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="post_list">
|
||||
{% for post in object_list %}
|
||||
<div>
|
||||
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
|
||||
<p class="date">{{ post.publish|date:"Y F d" }}</p>
|
||||
<p class="tease">{{ post.tease }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Post archive for {{ month|date:"F Y" }}{% endblock %}
|
||||
{% block body_class %}{{ block.super }} post_archive_month{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Post archive for {{ month|date:"F Y" }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="post_list">
|
||||
{% for post in object_list %}
|
||||
<div>
|
||||
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
|
||||
<p class="date">{{ post.publish|date:"Y F d" }}</p>
|
||||
<p class="tease">{{ post.tease }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,21 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Post archive for {{ year }}{% endblock %}
|
||||
{% block body_class %}{{ block.super }} post_archive_year{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Post archive for {{ year }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% load markup %}
|
||||
|
||||
<ul class="link_list">
|
||||
{% for month in date_list %}
|
||||
<li><a href="{% url blog_index %}{{ year }}/{{ month|date:"b" }}/">{{ month|date:"F" }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,67 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}{{ object.title }}{% endblock %}
|
||||
{% block body_class %}{{ block.super }} post_detail{% endblock %}
|
||||
{% block body_id %}post_{{ object.id }}{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>{{ object.title }}</h2>
|
||||
<p class="other_posts">
|
||||
{% if object.get_previous_by_publish %}
|
||||
<a class="previous" href="{{ object.get_previous_post.get_absolute_url }}">« {{ object.get_previous_post }}</a>
|
||||
{% endif %}
|
||||
{% if object.get_next_by_publish %}
|
||||
| <a class="next" href="{{ object.get_next_post.get_absolute_url }}">{{ object.get_next_post }} »</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% load blog markup comments tagging_tags %}
|
||||
|
||||
<p class="date">{{ object.publish|date:"j F Y" }}</p>
|
||||
|
||||
<div class="body">
|
||||
{{ object.body|markdown:"safe" }}
|
||||
</div>
|
||||
|
||||
{% tags_for_object object as tag_list %}
|
||||
{% if tag_list %}
|
||||
<p class="inline_tag_list"><strong>Related tags:</strong>
|
||||
{% for tag in tag_list %}
|
||||
{{ tag }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% get_comment_list for object as comment_list %}
|
||||
{% if comment_list %}
|
||||
<div id="comments">
|
||||
<a name="comments"></a>
|
||||
<h3 class="comments_title">Comments</h3>
|
||||
{% for comment in comment_list %}
|
||||
{% if comment.is_public %}
|
||||
<div class="comment">
|
||||
<h5 class="name">
|
||||
<a name="c{{ comment.id }}" href="{{ comment.get_absolute_url }}" title="Permalink to {{ comment.person_name }}'s comment" class="count">{{ forloop.counter }}</a>
|
||||
{% if comment.user_url %}<a href="{{ comment.user_url }}">{{ comment.user_name }}</a>{% else %}{{ comment.user_name }}{% endif %} says...
|
||||
</h5>
|
||||
{{ comment.comment|urlizetrunc:"60"|markdown:"safe" }}
|
||||
<p class="date">Posted at {{ comment.submit_date|date:"P" }} on {{ comment.submit_date|date:"F j, Y" }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if object.allow_comments %}
|
||||
{% render_comment_form for object %}
|
||||
{% else %}
|
||||
<div id="comment_form">
|
||||
<h3>Comments are closed.</h3>
|
||||
<p>Comments have been close for this post.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,35 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Post archive{% endblock %}
|
||||
{% block body_class %}{{ block.super }} post_list{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Post archive</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="post_list">
|
||||
{% for post in object_list %}
|
||||
<div>
|
||||
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
|
||||
<p class="date">{{ post.publish|date:"Y F d" }}</p>
|
||||
<p class="tease">{{ post.tease }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if is_paginated %}
|
||||
<p class="pagination">
|
||||
{% if has_next %}
|
||||
<a class="older" href="?page={{ next }}">Older</a>
|
||||
{% endif %}
|
||||
{% if has_next and has_previous %} | {% endif %}
|
||||
{% if has_previous %}
|
||||
<a class="newer" href="?page={{ previous }}">Newer</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,37 +0,0 @@
|
||||
{% extends "blog/base_blog.html" %}
|
||||
|
||||
|
||||
{% block title %}Post search{% endblock %}
|
||||
{% block body_class %}{{ block.super }} post_search{% endblock %}
|
||||
|
||||
|
||||
{% block content_title %}
|
||||
<h2>Search</h2>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="get" id="post_search_form">
|
||||
<p>
|
||||
<input type="text" name="q" value="{{ search_term }}" id="search">
|
||||
<input type="submit" class="button" value="Search">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
{% if message %}
|
||||
<p class="message">{{ message }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if object_list %}
|
||||
<div class="post_list">
|
||||
{% for post in object_list %}
|
||||
<div>
|
||||
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
|
||||
<p class="date">{{ post.publish|date:"Y F d" }}</p>
|
||||
<p class="tease">{{ post.tease }}</p>
|
||||
<p class="comments">{% if comment_count %}{{ comment_count }} comment{{ comment_count|pluralize }}{% endif %}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1 +0,0 @@
|
||||
{{ obj.tease }}
|
@ -1 +0,0 @@
|
||||
{{ obj.title }}
|
@ -1,7 +0,0 @@
|
||||
{% if object %}
|
||||
{{ object }}
|
||||
{% else %}
|
||||
{% for object in object_list %}
|
||||
{{ object }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
@ -1,103 +0,0 @@
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
import re
|
||||
|
||||
Post = models.get_model('blog', 'post')
|
||||
Category = models.get_model('blog', 'category')
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class LatestPosts(template.Node):
|
||||
def __init__(self, limit, var_name):
|
||||
self.limit = limit
|
||||
self.var_name = var_name
|
||||
|
||||
def render(self, context):
|
||||
posts = Post.objects.published()[:int(self.limit)]
|
||||
if posts and (int(self.limit) == 1):
|
||||
context[self.var_name] = posts[0]
|
||||
else:
|
||||
context[self.var_name] = posts
|
||||
return ''
|
||||
|
||||
@register.tag
|
||||
def get_latest_posts(parser, token):
|
||||
"""
|
||||
Gets any number of latest posts and stores them in a varable.
|
||||
|
||||
Syntax::
|
||||
|
||||
{% get_latest_posts [limit] as [var_name] %}
|
||||
|
||||
Example usage::
|
||||
|
||||
{% get_latest_posts 10 as latest_post_list %}
|
||||
"""
|
||||
try:
|
||||
tag_name, arg = token.contents.split(None, 1)
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
|
||||
m = re.search(r'(.*?) as (\w+)', arg)
|
||||
if not m:
|
||||
raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
|
||||
format_string, var_name = m.groups()
|
||||
return LatestPosts(format_string, var_name)
|
||||
|
||||
|
||||
class BlogCategories(template.Node):
|
||||
def __init__(self, var_name):
|
||||
self.var_name = var_name
|
||||
|
||||
def render(self, context):
|
||||
categories = Category.objects.all()
|
||||
context[self.var_name] = categories
|
||||
return ''
|
||||
|
||||
@register.tag
|
||||
def get_blog_categories(parser, token):
|
||||
"""
|
||||
Gets all blog categories.
|
||||
|
||||
Syntax::
|
||||
|
||||
{% get_blog_categories as [var_name] %}
|
||||
|
||||
Example usage::
|
||||
|
||||
{% get_blog_categories as category_list %}
|
||||
"""
|
||||
try:
|
||||
tag_name, arg = token.contents.split(None, 1)
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
|
||||
m = re.search(r'as (\w+)', arg)
|
||||
if not m:
|
||||
raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
|
||||
var_name = m.groups()[0]
|
||||
return BlogCategories(var_name)
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_links(value):
|
||||
"""
|
||||
Extracts links from a ``Post`` body and returns a list.
|
||||
|
||||
Template Syntax::
|
||||
|
||||
{{ post.body|markdown:"safe"|get_links }}
|
||||
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
except ImportError:
|
||||
from beautifulsoup import BeautifulSoup
|
||||
soup = BeautifulSoup(value)
|
||||
return soup.findAll('a')
|
||||
except ImportError:
|
||||
if settings.DEBUG:
|
||||
raise template.TemplateSyntaxError, "Error in 'get_links' filter: BeautifulSoup isn't installed."
|
||||
return value
|
@ -1,66 +0,0 @@
|
||||
"""
|
||||
>>> from django.test import Client
|
||||
>>> from calibre.www.apps.blog.models import Post, Category
|
||||
>>> import datetime
|
||||
>>> from django.core.urlresolvers import reverse
|
||||
>>> client = Client()
|
||||
|
||||
>>> category = Category(title='Django', slug='django')
|
||||
>>> category.save()
|
||||
>>> category2 = Category(title='Rails', slug='rails')
|
||||
>>> category2.save()
|
||||
|
||||
>>> post = Post(title='DJ Ango', slug='dj-ango', body='Yo DJ! Turn that music up!', status=2, publish=datetime.datetime(2008,5,5,16,20))
|
||||
>>> post.save()
|
||||
|
||||
>>> post2 = Post(title='Where my grails at?', slug='where', body='I Can haz Holy plez?', status=2, publish=datetime.datetime(2008,4,2,11,11))
|
||||
>>> post2.save()
|
||||
|
||||
>>> post.categories.add(category)
|
||||
>>> post2.categories.add(category2)
|
||||
|
||||
>>> response = client.get(reverse('blog_index'))
|
||||
>>> response.context[-1]['object_list']
|
||||
[<Post: DJ Ango>, <Post: Where my grails at?>]
|
||||
>>> response.status_code
|
||||
200
|
||||
|
||||
>>> response = client.get(reverse('blog_category_list'))
|
||||
>>> response.context[-1]['object_list']
|
||||
[<Category: Django>, <Category: Rails>]
|
||||
>>> response.status_code
|
||||
200
|
||||
|
||||
>>> response = client.get(category.get_absolute_url())
|
||||
>>> response.context[-1]['object_list']
|
||||
[<Post: DJ Ango>]
|
||||
>>> response.status_code
|
||||
200
|
||||
|
||||
>>> response = client.get(post.get_absolute_url())
|
||||
>>> response.context[-1]['object']
|
||||
<Post: DJ Ango>
|
||||
>>> response.status_code
|
||||
200
|
||||
|
||||
>>> response = client.get(reverse('blog_search'), {'q': 'DJ'})
|
||||
>>> response.context[-1]['object_list']
|
||||
[<Post: DJ Ango>]
|
||||
>>> response.status_code
|
||||
200
|
||||
>>> response = client.get(reverse('blog_search'), {'q': 'Holy'})
|
||||
>>> response.context[-1]['object_list']
|
||||
[<Post: Where my grails at?>]
|
||||
>>> response.status_code
|
||||
200
|
||||
>>> response = client.get(reverse('blog_search'), {'q': ''})
|
||||
>>> response.context[-1]['message']
|
||||
'Search term was too vague. Please try again.'
|
||||
|
||||
>>> response = client.get(reverse('blog_detail', args=[2008, 'apr', 2, 'where']))
|
||||
>>> response.context[-1]['object']
|
||||
<Post: Where my grails at?>
|
||||
>>> response.status_code
|
||||
200
|
||||
"""
|
||||
|
@ -1,41 +0,0 @@
|
||||
from django.conf.urls.defaults import *
|
||||
from calibre.www.apps.blog import views as blog_views
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<year>\d{4})/(?P<month>\w{3})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
|
||||
view=blog_views.post_detail,
|
||||
name='blog_detail'),
|
||||
|
||||
url(r'^(?P<year>\d{4})/(?P<month>\w{3})/(?P<day>\d{1,2})/$',
|
||||
view=blog_views.post_archive_day,
|
||||
name='blog_archive_day'),
|
||||
|
||||
url(r'^(?P<year>\d{4})/(?P<month>\w{3})/$',
|
||||
view=blog_views.post_archive_month,
|
||||
name='blog_archive_month'),
|
||||
|
||||
url(r'^(?P<year>\d{4})/$',
|
||||
view=blog_views.post_archive_year,
|
||||
name='blog_archive_year'),
|
||||
|
||||
url(r'^categories/(?P<slug>[-\w]+)/$',
|
||||
view=blog_views.category_detail,
|
||||
name='blog_category_detail'),
|
||||
|
||||
url (r'^categories/$',
|
||||
view=blog_views.category_list,
|
||||
name='blog_category_list'),
|
||||
|
||||
url (r'^search/$',
|
||||
view=blog_views.search,
|
||||
name='blog_search'),
|
||||
|
||||
url(r'^page/(?P<page>\w)/$',
|
||||
view=blog_views.post_list,
|
||||
name='blog_index_paginated'),
|
||||
|
||||
url(r'^$',
|
||||
view=blog_views.post_list,
|
||||
name='blog_index'),
|
||||
)
|
@ -1,160 +0,0 @@
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.http import Http404
|
||||
from django.views.generic import date_based, list_detail
|
||||
from django.db.models import Q
|
||||
from calibre.www.apps.blog.models import *
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
|
||||
def post_list(request, page=0, **kwargs):
|
||||
return list_detail.object_list(
|
||||
request,
|
||||
queryset = Post.objects.published(),
|
||||
paginate_by = 20,
|
||||
page = page,
|
||||
**kwargs
|
||||
)
|
||||
post_list.__doc__ = list_detail.object_list.__doc__
|
||||
|
||||
|
||||
def post_archive_year(request, year, **kwargs):
|
||||
return date_based.archive_year(
|
||||
request,
|
||||
year = year,
|
||||
date_field = 'publish',
|
||||
queryset = Post.objects.published(),
|
||||
make_object_list = True,
|
||||
**kwargs
|
||||
)
|
||||
post_archive_year.__doc__ = date_based.archive_year.__doc__
|
||||
|
||||
|
||||
def post_archive_month(request, year, month, **kwargs):
|
||||
return date_based.archive_month(
|
||||
request,
|
||||
year = year,
|
||||
month = month,
|
||||
date_field = 'publish',
|
||||
queryset = Post.objects.published(),
|
||||
**kwargs
|
||||
)
|
||||
post_archive_month.__doc__ = date_based.archive_month.__doc__
|
||||
|
||||
|
||||
def post_archive_day(request, year, month, day, **kwargs):
|
||||
return date_based.archive_day(
|
||||
request,
|
||||
year = year,
|
||||
month = month,
|
||||
day = day,
|
||||
date_field = 'publish',
|
||||
queryset = Post.objects.published(),
|
||||
**kwargs
|
||||
)
|
||||
post_archive_day.__doc__ = date_based.archive_day.__doc__
|
||||
|
||||
|
||||
def post_detail(request, slug, year, month, day, **kwargs):
|
||||
return date_based.object_detail(
|
||||
request,
|
||||
year = year,
|
||||
month = month,
|
||||
day = day,
|
||||
date_field = 'publish',
|
||||
slug = slug,
|
||||
queryset = Post.objects.published(),
|
||||
**kwargs
|
||||
)
|
||||
post_detail.__doc__ = date_based.object_detail.__doc__
|
||||
|
||||
|
||||
def category_list(request, template_name = 'blog/category_list.html', **kwargs):
|
||||
"""
|
||||
Category list
|
||||
|
||||
Template: ``blog/category_list.html``
|
||||
Context:
|
||||
object_list
|
||||
List of categories.
|
||||
"""
|
||||
return list_detail.object_list(
|
||||
request,
|
||||
queryset = Category.objects.all(),
|
||||
template_name = template_name,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def category_detail(request, slug, template_name = 'blog/category_detail.html', **kwargs):
|
||||
"""
|
||||
Category detail
|
||||
|
||||
Template: ``blog/category_detail.html``
|
||||
Context:
|
||||
object_list
|
||||
List of posts specific to the given category.
|
||||
category
|
||||
Given category.
|
||||
"""
|
||||
category = get_object_or_404(Category, slug__iexact=slug)
|
||||
|
||||
return list_detail.object_list(
|
||||
request,
|
||||
queryset = category.post_set.published(),
|
||||
extra_context = {'category': category},
|
||||
template_name = template_name,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
# Stop Words courtesy of http://www.dcs.gla.ac.uk/idom/ir_resources/linguistic_utils/stop_words
|
||||
STOP_WORDS = r"""\b(a|about|above|across|after|afterwards|again|against|all|almost|alone|along|already|also|
|
||||
although|always|am|among|amongst|amoungst|amount|an|and|another|any|anyhow|anyone|anything|anyway|anywhere|are|
|
||||
around|as|at|back|be|became|because|become|becomes|becoming|been|before|beforehand|behind|being|below|beside|
|
||||
besides|between|beyond|bill|both|bottom|but|by|call|can|cannot|cant|co|computer|con|could|couldnt|cry|de|describe|
|
||||
detail|do|done|down|due|during|each|eg|eight|either|eleven|else|elsewhere|empty|enough|etc|even|ever|every|everyone|
|
||||
everything|everywhere|except|few|fifteen|fify|fill|find|fire|first|five|for|former|formerly|forty|found|four|from|
|
||||
front|full|further|get|give|go|had|has|hasnt|have|he|hence|her|here|hereafter|hereby|herein|hereupon|hers|herself|
|
||||
him|himself|his|how|however|hundred|i|ie|if|in|inc|indeed|interest|into|is|it|its|itself|keep|last|latter|latterly|
|
||||
least|less|ltd|made|many|may|me|meanwhile|might|mill|mine|more|moreover|most|mostly|move|much|must|my|myself|name|
|
||||
namely|neither|never|nevertheless|next|nine|no|nobody|none|noone|nor|not|nothing|now|nowhere|of|off|often|on|once|
|
||||
one|only|onto|or|other|others|otherwise|our|ours|ourselves|out|over|own|part|per|perhaps|please|put|rather|re|same|
|
||||
see|seem|seemed|seeming|seems|serious|several|she|should|show|side|since|sincere|six|sixty|so|some|somehow|someone|
|
||||
something|sometime|sometimes|somewhere|still|such|system|take|ten|than|that|the|their|them|themselves|then|thence|
|
||||
there|thereafter|thereby|therefore|therein|thereupon|these|they|thick|thin|third|this|those|though|three|through|
|
||||
throughout|thru|thus|to|together|too|top|toward|towards|twelve|twenty|two|un|under|until|up|upon|us|very|via|was|
|
||||
we|well|were|what|whatever|when|whence|whenever|where|whereafter|whereas|whereby|wherein|whereupon|wherever|whether|
|
||||
which|while|whither|who|whoever|whole|whom|whose|why|will|with|within|without|would|yet|you|your|yours|yourself|
|
||||
yourselves)\b"""
|
||||
|
||||
|
||||
def search(request, template_name='blog/post_search.html'):
|
||||
"""
|
||||
Search for blog posts.
|
||||
|
||||
This template will allow you to setup a simple search form that will try to return results based on
|
||||
given search strings. The queries will be put through a stop words filter to remove words like
|
||||
'the', 'a', or 'have' to help imporve the result set.
|
||||
|
||||
Template: ``blog/post_search.html``
|
||||
Context:
|
||||
object_list
|
||||
List of blog posts that match given search term(s).
|
||||
search_term
|
||||
Given search term.
|
||||
"""
|
||||
context = {}
|
||||
if request.GET:
|
||||
stop_word_list = re.compile(STOP_WORDS, re.IGNORECASE)
|
||||
search_term = '%s' % request.GET['q']
|
||||
cleaned_search_term = stop_word_list.sub('', search_term)
|
||||
cleaned_search_term = cleaned_search_term.strip()
|
||||
if len(cleaned_search_term) != 0:
|
||||
post_list = Post.objects.published().filter(Q(body__icontains=cleaned_search_term) | Q(tags__icontains=cleaned_search_term) | Q(categories__title__icontains=cleaned_search_term))
|
||||
context = {'object_list': post_list, 'search_term':search_term}
|
||||
else:
|
||||
message = 'Search term was too vague. Please try again.'
|
||||
context = {'message':message}
|
||||
return render_to_response(template_name, context, context_instance=RequestContext(request))
|
@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
__init__.py
|
||||
"""
|
||||
|
@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
admin.py
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from calibre.www.apps.feedjack import models
|
||||
|
||||
|
||||
class LinkAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class SiteAdmin(admin.ModelAdmin):
|
||||
list_display = ('url', 'name')
|
||||
filter_vertical = ('links',)
|
||||
|
||||
|
||||
|
||||
class FeedAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'feed_url', 'title', 'last_modified', \
|
||||
'is_active')
|
||||
fieldsets = (
|
||||
(None,
|
||||
{'fields':('feed_url', 'name', 'shortname', 'is_active')}),
|
||||
(_('Fields updated automatically by Feedjack'),
|
||||
{'classes':('collapse',),
|
||||
'fields':('title', 'tagline', 'link', 'etag', 'last_modified',
|
||||
'last_checked'),
|
||||
})
|
||||
)
|
||||
search_fields = ['feed_url', 'name', 'title']
|
||||
|
||||
|
||||
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'link', 'author', 'date_modified')
|
||||
search_fields = ['link', 'title']
|
||||
date_hierarchy = 'date_modified'
|
||||
filter_vertical = ('tags',)
|
||||
|
||||
|
||||
|
||||
class SubscriberAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'site', 'feed')
|
||||
list_filter = ('site',)
|
||||
|
||||
|
||||
admin.site.register(models.Link, LinkAdmin)
|
||||
admin.site.register(models.Site, SiteAdmin)
|
||||
admin.site.register(models.Feed, FeedAdmin)
|
||||
admin.site.register(models.Post, PostAdmin)
|
||||
admin.site.register(models.Subscriber, SubscriberAdmin)
|
||||
|
||||
#~
|
@ -1,83 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
fjcache.py
|
||||
"""
|
||||
|
||||
import md5
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
T_HOST = 1
|
||||
T_ITEM = 2
|
||||
T_META = 3
|
||||
|
||||
|
||||
def str2md5(key):
|
||||
""" Returns the md5 hash of a string.
|
||||
"""
|
||||
ctx = md5.new()
|
||||
ctx.update(key.encode('utf-8'))
|
||||
return ctx.hexdigest()
|
||||
|
||||
def getkey(stype, site_id=None, key=None):
|
||||
""" Returns the cache key depending on it's type.
|
||||
"""
|
||||
base = '%s.feedjack' % (settings.CACHE_MIDDLEWARE_KEY_PREFIX)
|
||||
if stype == T_HOST:
|
||||
return '%s.hostcache' % base
|
||||
elif stype == T_ITEM:
|
||||
return '%s.%d.item.%s' % (base, site_id, str2md5(key))
|
||||
elif stype == T_META:
|
||||
return '%s.%d.meta' % (base, site_id)
|
||||
|
||||
|
||||
def hostcache_get():
|
||||
""" Retrieves the hostcache dictionary
|
||||
"""
|
||||
return cache.get(getkey(T_HOST))
|
||||
|
||||
def hostcache_set(value):
|
||||
""" Sets the hostcache dictionary
|
||||
"""
|
||||
cache.set(getkey(T_HOST), value)
|
||||
|
||||
def cache_get(site_id, key):
|
||||
""" Retrieves cache data from a site.
|
||||
"""
|
||||
return cache.get(getkey(T_ITEM, site_id, key))
|
||||
|
||||
def cache_set(site, key, data):
|
||||
""" Sets cache data for a site.
|
||||
|
||||
All keys related to a site are stored in a meta key. This key is per-site.
|
||||
"""
|
||||
tkey = getkey(T_ITEM, site.id, key)
|
||||
mkey = getkey(T_META, site.id)
|
||||
tmp = cache.get(mkey)
|
||||
longdur = 365*24*60*60
|
||||
if not tmp:
|
||||
tmp = [tkey]
|
||||
cache.set(mkey, [tkey], longdur)
|
||||
elif tkey not in tmp:
|
||||
tmp.append(tkey)
|
||||
cache.set(mkey, tmp, longdur)
|
||||
cache.set(tkey, data, site.cache_duration)
|
||||
|
||||
def cache_delsite(site_id):
|
||||
""" Removes all cache data from a site.
|
||||
"""
|
||||
mkey = getkey(T_META, site_id)
|
||||
tmp = cache.get(mkey)
|
||||
if not tmp:
|
||||
return
|
||||
for tkey in tmp:
|
||||
cache.delete(tkey)
|
||||
cache.delete(mkey)
|
||||
|
||||
|
@ -1,93 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
fjcloud.py
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
from calibre.www.apps.feedjack import fjlib, fjcache
|
||||
|
||||
def getsteps(levels, tagmax):
|
||||
""" Returns a list with the max number of posts per "tagcloud level"
|
||||
"""
|
||||
ntw = levels
|
||||
if ntw < 2:
|
||||
ntw = 2
|
||||
|
||||
steps = [(stp, 1 + (stp * int(math.ceil(tagmax * 1.0 / ntw - 1))))
|
||||
for stp in range(ntw)]
|
||||
# just to be sure~
|
||||
steps[-1] = (steps[-1][0], tagmax+1)
|
||||
return steps
|
||||
|
||||
def build(site, tagdata):
|
||||
""" Returns the tag cloud for a list of tags.
|
||||
"""
|
||||
|
||||
tagdata.sort()
|
||||
|
||||
# we get the most popular tag to calculate the tags' weigth
|
||||
tagmax = 0
|
||||
for tagname, tagcount in tagdata:
|
||||
if tagcount > tagmax:
|
||||
tagmax = tagcount
|
||||
steps = getsteps(site.tagcloud_levels, tagmax)
|
||||
|
||||
tags = []
|
||||
for tagname, tagcount in tagdata:
|
||||
weight = [twt[0] \
|
||||
for twt in steps if twt[1] >= tagcount and twt[1] > 0][0]+1
|
||||
tags.append({'tagname':tagname, 'count':tagcount, 'weight':weight})
|
||||
return tags
|
||||
|
||||
def cloudata(site):
|
||||
""" Returns a dictionary with all the tag clouds related to a site.
|
||||
"""
|
||||
|
||||
tagdata = fjlib.getquery("""
|
||||
SELECT feedjack_post.feed_id, feedjack_tag.name, COUNT(*)
|
||||
FROM feedjack_post, feedjack_subscriber, feedjack_tag,
|
||||
feedjack_post_tags
|
||||
WHERE feedjack_post.feed_id=feedjack_subscriber.feed_id AND
|
||||
feedjack_post_tags.tag_id=feedjack_tag.id AND
|
||||
feedjack_post_tags.post_id=feedjack_post.id AND
|
||||
feedjack_subscriber.site_id=%d
|
||||
GROUP BY feedjack_post.feed_id, feedjack_tag.name
|
||||
ORDER BY feedjack_post.feed_id, feedjack_tag.name""" % site.id)
|
||||
tagdict = {}
|
||||
globaldict = {}
|
||||
cloudict = {}
|
||||
for feed_id, tagname, tagcount in tagdata:
|
||||
if feed_id not in tagdict:
|
||||
tagdict[feed_id] = []
|
||||
tagdict[feed_id].append((tagname, tagcount))
|
||||
try:
|
||||
globaldict[tagname] += tagcount
|
||||
except KeyError:
|
||||
globaldict[tagname] = tagcount
|
||||
tagdict[0] = globaldict.items()
|
||||
for key, val in tagdict.items():
|
||||
cloudict[key] = build(site, val)
|
||||
return cloudict
|
||||
|
||||
def getcloud(site, feed_id=None):
|
||||
""" Returns the tag cloud for a site or a site's subscriber.
|
||||
"""
|
||||
|
||||
cloudict = fjcache.cache_get(site.id, 'tagclouds')
|
||||
if not cloudict:
|
||||
cloudict = cloudata(site)
|
||||
fjcache.cache_set(site, 'tagclouds', cloudict)
|
||||
|
||||
# A subscriber's tag cloud has been requested.
|
||||
if feed_id:
|
||||
feed_id = int(feed_id)
|
||||
if feed_id in cloudict:
|
||||
return cloudict[feed_id]
|
||||
return []
|
||||
# The site tagcloud has been requested.
|
||||
return cloudict[0]
|
||||
|
@ -1,280 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
fjlib.py
|
||||
"""
|
||||
|
||||
from django.db import connection
|
||||
from django.core.paginator import Paginator, InvalidPage
|
||||
from django.http import Http404
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
from calibre.www.apps.feedjack import models, fjcache
|
||||
|
||||
class PageNotAnInteger(ValueError):
|
||||
pass
|
||||
|
||||
# this is taken from django, it was removed in r8191
|
||||
class ObjectPaginator(Paginator):
|
||||
"""
|
||||
Legacy ObjectPaginator class, for backwards compatibility.
|
||||
|
||||
Note that each method on this class that takes page_number expects a
|
||||
zero-based page number, whereas the new API (Paginator/Page) uses one-based
|
||||
page numbers.
|
||||
"""
|
||||
def __init__(self, query_set, num_per_page, orphans=0):
|
||||
Paginator.__init__(self, query_set, num_per_page, orphans)
|
||||
import warnings
|
||||
warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning)
|
||||
|
||||
# Keep these attributes around for backwards compatibility.
|
||||
self.query_set = query_set
|
||||
self.num_per_page = num_per_page
|
||||
self._hits = self._pages = None
|
||||
|
||||
def validate_page_number(self, page_number):
|
||||
try:
|
||||
page_number = int(page_number) + 1
|
||||
except ValueError:
|
||||
raise PageNotAnInteger
|
||||
return self.validate_number(page_number)
|
||||
|
||||
def get_page(self, page_number):
|
||||
try:
|
||||
page_number = int(page_number) + 1
|
||||
except ValueError:
|
||||
raise PageNotAnInteger
|
||||
return self.page(page_number).object_list
|
||||
|
||||
def has_next_page(self, page_number):
|
||||
return page_number < self.pages - 1
|
||||
|
||||
def has_previous_page(self, page_number):
|
||||
return page_number > 0
|
||||
|
||||
def first_on_page(self, page_number):
|
||||
"""
|
||||
Returns the 1-based index of the first object on the given page,
|
||||
relative to total objects found (hits).
|
||||
"""
|
||||
page_number = self.validate_page_number(page_number)
|
||||
return (self.num_per_page * (page_number - 1)) + 1
|
||||
|
||||
def last_on_page(self, page_number):
|
||||
"""
|
||||
Returns the 1-based index of the last object on the given page,
|
||||
relative to total objects found (hits).
|
||||
"""
|
||||
page_number = self.validate_page_number(page_number)
|
||||
if page_number == self.num_pages:
|
||||
return self.count
|
||||
return page_number * self.num_per_page
|
||||
|
||||
# The old API called it "hits" instead of "count".
|
||||
hits = Paginator.count
|
||||
|
||||
# The old API called it "pages" instead of "num_pages".
|
||||
pages = Paginator.num_pages
|
||||
|
||||
|
||||
def sitefeeds(siteobj):
|
||||
""" Returns the active feeds of a site.
|
||||
"""
|
||||
return siteobj.subscriber_set.filter(is_active=True).select_related()
|
||||
#return [subscriber['feed'] \
|
||||
# for subscriber \
|
||||
# in siteobj.subscriber_set.filter(is_active=True).values('feed')]
|
||||
|
||||
def getquery(query):
|
||||
""" Performs a query and get the results.
|
||||
"""
|
||||
try:
|
||||
conn = connection.cursor()
|
||||
conn.execute(query)
|
||||
data = conn.fetchall()
|
||||
conn.close()
|
||||
except:
|
||||
data = []
|
||||
return data
|
||||
|
||||
def get_extra_content(site, sfeeds_ids, ctx):
|
||||
""" Returns extra data useful to the templates.
|
||||
"""
|
||||
|
||||
# get the subscribers' feeds
|
||||
if sfeeds_ids:
|
||||
basefeeds = models.Feed.objects.filter(id__in=sfeeds_ids)
|
||||
try:
|
||||
ctx['feeds'] = basefeeds.order_by('name').select_related()
|
||||
except:
|
||||
ctx['feeds'] = []
|
||||
|
||||
# get the last_checked time
|
||||
try:
|
||||
ctx['last_modified'] = basefeeds.filter(\
|
||||
last_checked__isnull=False).order_by(\
|
||||
'-last_checked').select_related()[0].last_checked.ctime()
|
||||
|
||||
except:
|
||||
ctx['last_modified'] = '??'
|
||||
else:
|
||||
ctx['feeds'] = []
|
||||
ctx['last_modified'] = '??'
|
||||
ctx['site'] = site
|
||||
|
||||
def get_posts_tags(object_list, sfeeds_obj, user_id, tag_name):
|
||||
""" Adds a qtags property in every post object in a page.
|
||||
|
||||
Use "qtags" instead of "tags" in templates to avoid unnecessary DB hits.
|
||||
"""
|
||||
tagd = {}
|
||||
user_obj = None
|
||||
tag_obj = None
|
||||
tags = models.Tag.objects.extra(\
|
||||
select={'post_id':'%s.%s' % (\
|
||||
connection.ops.quote_name('feedjack_post_tags'), \
|
||||
connection.ops.quote_name('post_id'))}, \
|
||||
tables=['feedjack_post_tags'], \
|
||||
where=[\
|
||||
'%s.%s=%s.%s' % (\
|
||||
connection.ops.quote_name('feedjack_tag'), \
|
||||
connection.ops.quote_name('id'), \
|
||||
connection.ops.quote_name('feedjack_post_tags'), \
|
||||
connection.ops.quote_name('tag_id')), \
|
||||
'%s.%s IN (%s)' % (\
|
||||
connection.ops.quote_name('feedjack_post_tags'), \
|
||||
connection.ops.quote_name('post_id'), \
|
||||
', '.join([str(post.id) for post in object_list]))])
|
||||
for tag in tags:
|
||||
if tag.post_id not in tagd:
|
||||
tagd[tag.post_id] = []
|
||||
tagd[tag.post_id].append(tag)
|
||||
if tag_name and tag.name == tag_name:
|
||||
tag_obj = tag
|
||||
subd = {}
|
||||
for sub in sfeeds_obj:
|
||||
subd[sub.feed.id] = sub
|
||||
for post in object_list:
|
||||
if post.id in tagd:
|
||||
post.qtags = tagd[post.id]
|
||||
else:
|
||||
post.qtags = []
|
||||
post.subscriber = subd[post.feed.id]
|
||||
if user_id and int(user_id) == post.feed.id:
|
||||
user_obj = post.subscriber
|
||||
return user_obj, tag_obj
|
||||
|
||||
def getcurrentsite(http_post, path_info, query_string):
|
||||
""" Returns the site id and the page cache key based on the request.
|
||||
"""
|
||||
url = u'http://%s/%s' % (smart_unicode(http_post.rstrip('/')), \
|
||||
smart_unicode(path_info.lstrip('/')))
|
||||
pagecachekey = '%s?%s' % (smart_unicode(path_info), \
|
||||
smart_unicode(query_string))
|
||||
hostdict = fjcache.hostcache_get()
|
||||
|
||||
if not hostdict:
|
||||
hostdict = {}
|
||||
if url not in hostdict:
|
||||
default, ret = None, None
|
||||
for site in models.Site.objects.all():
|
||||
if url.startswith(site.url):
|
||||
ret = site
|
||||
break
|
||||
if not default or site.default_site:
|
||||
default = site
|
||||
if not ret:
|
||||
if default:
|
||||
ret = default
|
||||
else:
|
||||
# Somebody is requesting something, but the user didn't create
|
||||
# a site yet. Creating a default one...
|
||||
ret = models.Site(name='Default Feedjack Site/Planet', \
|
||||
url='www.feedjack.org', \
|
||||
title='Feedjack Site Title', \
|
||||
description='Feedjack Site Description. ' \
|
||||
'Please change this in the admin interface.')
|
||||
ret.save()
|
||||
hostdict[url] = ret.id
|
||||
fjcache.hostcache_set(hostdict)
|
||||
|
||||
return hostdict[url], pagecachekey
|
||||
|
||||
def get_paginator(site, sfeeds_ids, page=0, tag=None, user=None):
|
||||
""" Returns a paginator object and a requested page from it.
|
||||
"""
|
||||
|
||||
if tag:
|
||||
try:
|
||||
localposts = models.Tag.objects.get(name=tag).post_set.filter(\
|
||||
feed__in=sfeeds_ids)
|
||||
except:
|
||||
raise Http404
|
||||
else:
|
||||
localposts = models.Post.objects.filter(feed__in=sfeeds_ids)
|
||||
|
||||
if user:
|
||||
try:
|
||||
localposts = localposts.filter(feed=user)
|
||||
except:
|
||||
raise Http404
|
||||
if site.order_posts_by == 2:
|
||||
localposts = localposts.order_by('-date_created', '-date_modified')
|
||||
else:
|
||||
localposts = localposts.order_by('-date_modified')
|
||||
|
||||
paginator = ObjectPaginator(localposts.select_related(), \
|
||||
site.posts_per_page)
|
||||
try:
|
||||
object_list = paginator.get_page(page)
|
||||
except InvalidPage:
|
||||
if page == 0:
|
||||
object_list = []
|
||||
else:
|
||||
raise Http404
|
||||
return (paginator, object_list)
|
||||
|
||||
def page_context(request, site, tag=None, user_id=None, sfeeds=None):
|
||||
""" Returns the context dictionary for a page view.
|
||||
"""
|
||||
sfeeds_obj, sfeeds_ids = sfeeds
|
||||
try:
|
||||
page = int(request.GET.get('page', 0))
|
||||
except ValueError:
|
||||
page = 0
|
||||
paginator, object_list = get_paginator(site, sfeeds_ids, \
|
||||
page=page, tag=tag, user=user_id)
|
||||
if object_list:
|
||||
# This will hit the DB once per page instead of once for every post in
|
||||
# a page. To take advantage of this the template designer must call
|
||||
# the qtags property in every item, instead of the default tags
|
||||
# property.
|
||||
user_obj, tag_obj = get_posts_tags(object_list, sfeeds_obj, \
|
||||
user_id, tag)
|
||||
else:
|
||||
user_obj, tag_obj = None, None
|
||||
ctx = {
|
||||
'object_list': object_list,
|
||||
'is_paginated': paginator.pages > 1,
|
||||
'results_per_page': site.posts_per_page,
|
||||
'has_next': paginator.has_next_page(page),
|
||||
'has_previous': paginator.has_previous_page(page),
|
||||
'page': page + 1,
|
||||
'next': page + 1,
|
||||
'previous': page - 1,
|
||||
'pages': paginator.pages,
|
||||
'hits' : paginator.hits,
|
||||
}
|
||||
get_extra_content(site, sfeeds_ids, ctx)
|
||||
from calibre.www.apps.feedjack import fjcloud
|
||||
ctx['tagcloud'] = fjcloud.getcloud(site, user_id)
|
||||
ctx['user_id'] = user_id
|
||||
ctx['user'] = user_obj
|
||||
ctx['tag'] = tag_obj
|
||||
ctx['subscribers'] = sfeeds_obj
|
||||
return ctx
|
||||
|
||||
|
@ -1,202 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable-msg=W0232, R0903, W0131
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
models.py
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
from calibre.www.apps.feedjack import fjcache
|
||||
|
||||
SITE_ORDERBY_CHOICES = (
|
||||
(1, _('Date published.')),
|
||||
(2, _('Date the post was first obtained.'))
|
||||
)
|
||||
|
||||
class Link(models.Model):
|
||||
name = models.CharField(_('name'), max_length=100, unique=True)
|
||||
link = models.URLField(_('link'), verify_exists=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('link')
|
||||
verbose_name_plural = _('links')
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s (%s)' % (self.name, self.link)
|
||||
|
||||
|
||||
|
||||
class Site(models.Model):
|
||||
name = models.CharField(_('name'), max_length=100)
|
||||
url = models.CharField(_('url'),
|
||||
max_length=100,
|
||||
unique=True,
|
||||
help_text=u'%s: %s, %s' % (smart_unicode(_('Example')),
|
||||
u'http://www.planetexample.com',
|
||||
u'http://www.planetexample.com:8000/foo'))
|
||||
title = models.CharField(_('title'), max_length=200)
|
||||
description = models.TextField(_('description'))
|
||||
welcome = models.TextField(_('welcome'), null=True, blank=True)
|
||||
greets = models.TextField(_('greets'), null=True, blank=True)
|
||||
|
||||
default_site = models.BooleanField(_('default site'), default=False)
|
||||
posts_per_page = models.IntegerField(_('posts per page'), default=20)
|
||||
order_posts_by = models.IntegerField(_('order posts by'), default=1,
|
||||
choices=SITE_ORDERBY_CHOICES)
|
||||
tagcloud_levels = models.IntegerField(_('tagcloud level'), default=5)
|
||||
show_tagcloud = models.BooleanField(_('show tagcloud'), default=True)
|
||||
|
||||
use_internal_cache = models.BooleanField(_('use internal cache'), default=True)
|
||||
cache_duration = models.IntegerField(_('cache duration'), default=60*60*24,
|
||||
help_text=_('Duration in seconds of the cached pages and data.') )
|
||||
|
||||
links = models.ManyToManyField(Link, verbose_name=_('links'),
|
||||
null=True, blank=True)
|
||||
template = models.CharField(_('template'), max_length=100, null=True,
|
||||
blank=True,
|
||||
help_text=_('This template must be a directory in your feedjack '
|
||||
'templates directory. Leave blank to use the default template.') )
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('site')
|
||||
verbose_name_plural = _('sites')
|
||||
ordering = ('name',)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def save(self):
|
||||
if not self.template:
|
||||
self.template = 'default'
|
||||
# there must be only ONE default site
|
||||
defs = Site.objects.filter(default_site=True)
|
||||
if not defs:
|
||||
self.default_site = True
|
||||
elif self.default_site:
|
||||
for tdef in defs:
|
||||
if tdef.id != self.id:
|
||||
tdef.default_site = False
|
||||
tdef.save()
|
||||
self.url = self.url.rstrip('/')
|
||||
fjcache.hostcache_set({})
|
||||
super(Site, self).save()
|
||||
|
||||
|
||||
|
||||
|
||||
class Feed(models.Model):
|
||||
feed_url = models.URLField(_('feed url'), unique=True)
|
||||
|
||||
name = models.CharField(_('name'), max_length=100)
|
||||
shortname = models.CharField(_('shortname'), max_length=50)
|
||||
is_active = models.BooleanField(_('is active'), default=True,
|
||||
help_text=_('If disabled, this feed will not be further updated.') )
|
||||
|
||||
title = models.CharField(_('title'), max_length=200, blank=True)
|
||||
tagline = models.TextField(_('tagline'), blank=True)
|
||||
link = models.URLField(_('link'), blank=True)
|
||||
|
||||
# http://feedparser.org/docs/http-etag.html
|
||||
etag = models.CharField(_('etag'), max_length=50, blank=True)
|
||||
last_modified = models.DateTimeField(_('last modified'), null=True, blank=True)
|
||||
last_checked = models.DateTimeField(_('last checked'), null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('feed')
|
||||
verbose_name_plural = _('feeds')
|
||||
ordering = ('name', 'feed_url',)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s (%s)' % (self.name, self.feed_url)
|
||||
|
||||
def save(self):
|
||||
super(Feed, self).save()
|
||||
|
||||
|
||||
|
||||
class Tag(models.Model):
|
||||
name = models.CharField(_('name'), max_length=50, unique=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('tag')
|
||||
verbose_name_plural = _('tags')
|
||||
ordering = ('name',)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def save(self):
|
||||
super(Tag, self).save()
|
||||
|
||||
class Post(models.Model):
|
||||
feed = models.ForeignKey(Feed, verbose_name=_('feed'), null=False, blank=False)
|
||||
title = models.CharField(_('title'), max_length=255)
|
||||
link = models.URLField(_('link'), )
|
||||
content = models.TextField(_('content'), blank=True)
|
||||
date_modified = models.DateTimeField(_('date modified'), null=True, blank=True)
|
||||
guid = models.CharField(_('guid'), max_length=200, db_index=True)
|
||||
author = models.CharField(_('author'), max_length=50, blank=True)
|
||||
author_email = models.EmailField(_('author email'), blank=True)
|
||||
comments = models.URLField(_('comments'), blank=True)
|
||||
tags = models.ManyToManyField(Tag, verbose_name=_('tags'))
|
||||
date_created = models.DateField(_('date created'), auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('post')
|
||||
verbose_name_plural = _('posts')
|
||||
ordering = ('-date_modified',)
|
||||
unique_together = (('feed', 'guid'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def save(self):
|
||||
super(Post, self).save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return self.link
|
||||
|
||||
|
||||
|
||||
class Subscriber(models.Model):
|
||||
site = models.ForeignKey(Site, verbose_name=_('site') )
|
||||
feed = models.ForeignKey(Feed, verbose_name=_('feed') )
|
||||
|
||||
name = models.CharField(_('name'), max_length=100, null=True, blank=True,
|
||||
help_text=_('Keep blank to use the Feed\'s original name.') )
|
||||
shortname = models.CharField(_('shortname'), max_length=50, null=True,
|
||||
blank=True,
|
||||
help_text=_('Keep blank to use the Feed\'s original shortname.') )
|
||||
is_active = models.BooleanField(_('is active'), default=True,
|
||||
help_text=_('If disabled, this subscriber will not appear in the site or '
|
||||
'in the site\'s feed.') )
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('subscriber')
|
||||
verbose_name_plural = _('subscribers')
|
||||
ordering = ('site', 'name', 'feed')
|
||||
unique_together = (('site', 'feed'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s in %s' % (self.feed, self.site)
|
||||
|
||||
def get_cloud(self):
|
||||
from calibre.www.apps.feedjack import fjcloud
|
||||
return fjcloud.getcloud(self.site, self.feed.id)
|
||||
|
||||
def save(self):
|
||||
if not self.name:
|
||||
self.name = self.feed.name
|
||||
if not self.shortname:
|
||||
self.shortname = self.feed.shortname
|
||||
super(Subscriber, self).save()
|
||||
|
||||
|
@ -1,506 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
update_feeds.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import optparse
|
||||
import datetime
|
||||
import socket
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import feedparser
|
||||
|
||||
try:
|
||||
import threadpool
|
||||
except ImportError:
|
||||
threadpool = None
|
||||
|
||||
VERSION = '0.9.16'
|
||||
URL = 'http://www.feedjack.org/'
|
||||
USER_AGENT = 'Feedjack %s - %s' % (VERSION, URL)
|
||||
SLOWFEED_WARNING = 10
|
||||
ENTRY_NEW, ENTRY_UPDATED, ENTRY_SAME, ENTRY_ERR = range(4)
|
||||
FEED_OK, FEED_SAME, FEED_ERRPARSE, FEED_ERRHTTP, FEED_ERREXC = range(5)
|
||||
|
||||
|
||||
def encode(tstr):
|
||||
""" Encodes a unicode string in utf-8
|
||||
"""
|
||||
if not tstr:
|
||||
return ''
|
||||
# this is _not_ pretty, but it works
|
||||
try:
|
||||
return tstr.encode('utf-8', "xmlcharrefreplace")
|
||||
except UnicodeDecodeError:
|
||||
# it's already UTF8.. sigh
|
||||
return tstr.decode('utf-8').encode('utf-8')
|
||||
|
||||
def prints(tstr):
|
||||
""" lovely unicode
|
||||
"""
|
||||
sys.stdout.write('%s\n' % (tstr.encode(sys.getdefaultencoding(),
|
||||
'replace')))
|
||||
sys.stdout.flush()
|
||||
|
||||
def mtime(ttime):
|
||||
""" datetime auxiliar function.
|
||||
"""
|
||||
return datetime.datetime.fromtimestamp(time.mktime(ttime))
|
||||
|
||||
class ProcessEntry:
|
||||
def __init__(self, feed, options, entry, postdict, fpf):
|
||||
self.feed = feed
|
||||
self.options = options
|
||||
self.entry = entry
|
||||
self.postdict = postdict
|
||||
self.fpf = fpf
|
||||
|
||||
def get_tags(self):
|
||||
""" Returns a list of tag objects from an entry.
|
||||
"""
|
||||
from calibre.www.apps.feedjack import models
|
||||
|
||||
fcat = []
|
||||
if self.entry.has_key('tags'):
|
||||
for tcat in self.entry.tags:
|
||||
if tcat.label != None:
|
||||
term = tcat.label
|
||||
else:
|
||||
term = tcat.term
|
||||
qcat = term.strip()
|
||||
if ',' in qcat or '/' in qcat:
|
||||
qcat = qcat.replace(',', '/').split('/')
|
||||
else:
|
||||
qcat = [qcat]
|
||||
for zcat in qcat:
|
||||
tagname = zcat.lower()
|
||||
while ' ' in tagname:
|
||||
tagname = tagname.replace(' ', ' ')
|
||||
tagname = tagname.strip()
|
||||
if not tagname or tagname == ' ':
|
||||
continue
|
||||
if not models.Tag.objects.filter(name=tagname):
|
||||
cobj = models.Tag(name=tagname)
|
||||
cobj.save()
|
||||
fcat.append(models.Tag.objects.get(name=tagname))
|
||||
return fcat
|
||||
|
||||
def get_entry_data(self):
|
||||
""" Retrieves data from a post and returns it in a tuple.
|
||||
"""
|
||||
try:
|
||||
link = self.entry.link
|
||||
except AttributeError:
|
||||
link = self.feed.link
|
||||
try:
|
||||
title = self.entry.title
|
||||
except AttributeError:
|
||||
title = link
|
||||
guid = self.entry.get('id', title)
|
||||
|
||||
if self.entry.has_key('author_detail'):
|
||||
author = self.entry.author_detail.get('name', '')
|
||||
author_email = self.entry.author_detail.get('email', '')
|
||||
else:
|
||||
author, author_email = '', ''
|
||||
|
||||
if not author:
|
||||
author = self.entry.get('author', self.entry.get('creator', ''))
|
||||
if not author_email:
|
||||
# this should be optional~
|
||||
author_email = 'nospam@nospam.com'
|
||||
|
||||
try:
|
||||
content = self.entry.content[0].value
|
||||
except:
|
||||
content = self.entry.get('summary',
|
||||
self.entry.get('description', ''))
|
||||
|
||||
if self.entry.has_key('modified_parsed'):
|
||||
date_modified = mtime(self.entry.modified_parsed)
|
||||
else:
|
||||
date_modified = None
|
||||
|
||||
fcat = self.get_tags()
|
||||
comments = self.entry.get('comments', '')
|
||||
|
||||
return (link, title, guid, author, author_email, content,
|
||||
date_modified, fcat, comments)
|
||||
|
||||
def process(self):
|
||||
""" Process a post in a feed and saves it in the DB if necessary.
|
||||
"""
|
||||
from calibre.www.apps.feedjack import models
|
||||
|
||||
(link, title, guid, author, author_email, content, date_modified,
|
||||
fcat, comments) = self.get_entry_data()
|
||||
|
||||
if False and self.options.verbose:
|
||||
prints(u'[%d] Entry\n' \
|
||||
u' title: %s\n' \
|
||||
u' link: %s\n' \
|
||||
u' guid: %s\n' \
|
||||
u' author: %s\n' \
|
||||
u' author_email: %s\n' \
|
||||
u' tags: %s' % (
|
||||
self.feed.id,
|
||||
title, link, guid, author, author_email,
|
||||
u' '.join(tcat.name for tcat in fcat)))
|
||||
|
||||
if guid in self.postdict:
|
||||
tobj = self.postdict[guid]
|
||||
if tobj.content != content or (date_modified and
|
||||
tobj.date_modified != date_modified):
|
||||
retval = ENTRY_UPDATED
|
||||
if self.options.verbose:
|
||||
prints('[%d] Updating existing post: %s' % (
|
||||
self.feed.id, link))
|
||||
if not date_modified:
|
||||
# damn non-standard feeds
|
||||
date_modified = tobj.date_modified
|
||||
tobj.title = title
|
||||
tobj.link = link
|
||||
tobj.content = content
|
||||
tobj.guid = guid
|
||||
tobj.date_modified = date_modified
|
||||
tobj.author = author
|
||||
tobj.author_email = author_email
|
||||
tobj.comments = comments
|
||||
tobj.tags.clear()
|
||||
[tobj.tags.add(tcat) for tcat in fcat]
|
||||
tobj.save()
|
||||
else:
|
||||
retval = ENTRY_SAME
|
||||
if self.options.verbose:
|
||||
prints('[%d] Post has not changed: %s' % (self.feed.id,
|
||||
link))
|
||||
else:
|
||||
retval = ENTRY_NEW
|
||||
if self.options.verbose:
|
||||
prints('[%d] Saving new post: %s' % (self.feed.id, link))
|
||||
if not date_modified and self.fpf:
|
||||
# if the feed has no date_modified info, we use the feed
|
||||
# mtime or the current time
|
||||
if self.fpf.feed.has_key('modified_parsed'):
|
||||
date_modified = mtime(self.fpf.feed.modified_parsed)
|
||||
elif self.fpf.has_key('modified'):
|
||||
date_modified = mtime(self.fpf.modified)
|
||||
if not date_modified:
|
||||
date_modified = datetime.datetime.now()
|
||||
tobj = models.Post(feed=self.feed, title=title, link=link,
|
||||
content=content, guid=guid, date_modified=date_modified,
|
||||
author=author, author_email=author_email,
|
||||
comments=comments)
|
||||
tobj.save()
|
||||
[tobj.tags.add(tcat) for tcat in fcat]
|
||||
return retval
|
||||
|
||||
|
||||
class ProcessFeed:
|
||||
def __init__(self, feed, options):
|
||||
self.feed = feed
|
||||
self.options = options
|
||||
self.fpf = None
|
||||
|
||||
def process_entry(self, entry, postdict):
|
||||
""" wrapper for ProcessEntry
|
||||
"""
|
||||
entry = ProcessEntry(self.feed, self.options, entry, postdict,
|
||||
self.fpf)
|
||||
ret_entry = entry.process()
|
||||
del entry
|
||||
return ret_entry
|
||||
|
||||
def process(self):
|
||||
""" Downloads and parses a feed.
|
||||
"""
|
||||
from calibre.www.apps.feedjack import models
|
||||
|
||||
ret_values = {
|
||||
ENTRY_NEW:0,
|
||||
ENTRY_UPDATED:0,
|
||||
ENTRY_SAME:0,
|
||||
ENTRY_ERR:0}
|
||||
|
||||
prints(u'[%d] Processing feed %s' % (self.feed.id,
|
||||
self.feed.feed_url))
|
||||
|
||||
# we check the etag and the modified time to save bandwith and
|
||||
# avoid bans
|
||||
try:
|
||||
self.fpf = feedparser.parse(self.feed.feed_url,
|
||||
agent=USER_AGENT,
|
||||
etag=self.feed.etag)
|
||||
except:
|
||||
prints('! ERROR: feed cannot be parsed')
|
||||
return FEED_ERRPARSE, ret_values
|
||||
|
||||
if hasattr(self.fpf, 'status'):
|
||||
if self.options.verbose:
|
||||
prints(u'[%d] HTTP status %d: %s' % (self.feed.id,
|
||||
self.fpf.status,
|
||||
self.feed.feed_url))
|
||||
if self.fpf.status == 304:
|
||||
# this means the feed has not changed
|
||||
if self.options.verbose:
|
||||
prints('[%d] Feed has not changed since ' \
|
||||
'last check: %s' % (self.feed.id,
|
||||
self.feed.feed_url))
|
||||
return FEED_SAME, ret_values
|
||||
|
||||
if self.fpf.status >= 400:
|
||||
# http error, ignore
|
||||
prints('[%d] !HTTP_ERROR! %d: %s' % (self.feed.id,
|
||||
self.fpf.status,
|
||||
self.feed.feed_url))
|
||||
return FEED_ERRHTTP, ret_values
|
||||
|
||||
if hasattr(self.fpf, 'bozo') and self.fpf.bozo:
|
||||
prints('[%d] !BOZO! Feed is not well formed: %s' % (
|
||||
self.feed.id, self.feed.feed_url))
|
||||
|
||||
# the feed has changed (or it is the first time we parse it)
|
||||
# saving the etag and last_modified fields
|
||||
self.feed.etag = self.fpf.get('etag', '')
|
||||
# some times this is None (it never should) *sigh*
|
||||
if self.feed.etag is None:
|
||||
self.feed.etag = ''
|
||||
|
||||
try:
|
||||
self.feed.last_modified = mtime(self.fpf.modified)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.feed.title = self.fpf.feed.get('title', '')[0:254]
|
||||
self.feed.tagline = self.fpf.feed.get('tagline', '')
|
||||
self.feed.link = self.fpf.feed.get('link', '')
|
||||
self.feed.last_checked = datetime.datetime.now()
|
||||
|
||||
if False and self.options.verbose:
|
||||
prints(u'[%d] Feed info for: %s\n' \
|
||||
u' title %s\n' \
|
||||
u' tagline %s\n' \
|
||||
u' link %s\n' \
|
||||
u' last_checked %s' % (
|
||||
self.feed.id, self.feed.feed_url, self.feed.title,
|
||||
self.feed.tagline, self.feed.link, self.feed.last_checked))
|
||||
|
||||
guids = []
|
||||
for entry in self.fpf.entries:
|
||||
if entry.get('id', ''):
|
||||
guids.append(entry.get('id', ''))
|
||||
elif entry.title:
|
||||
guids.append(entry.title)
|
||||
elif entry.link:
|
||||
guids.append(entry.link)
|
||||
self.feed.save()
|
||||
if guids:
|
||||
postdict = dict([(post.guid, post)
|
||||
for post in models.Post.objects.filter(
|
||||
feed=self.feed.id).filter(guid__in=guids)])
|
||||
else:
|
||||
postdict = {}
|
||||
|
||||
for entry in self.fpf.entries:
|
||||
try:
|
||||
ret_entry = self.process_entry(entry, postdict)
|
||||
except:
|
||||
(etype, eobj, etb) = sys.exc_info()
|
||||
print '[%d] ! -------------------------' % (self.feed.id,)
|
||||
print traceback.format_exception(etype, eobj, etb)
|
||||
traceback.print_exception(etype, eobj, etb)
|
||||
print '[%d] ! -------------------------' % (self.feed.id,)
|
||||
ret_entry = ENTRY_ERR
|
||||
ret_values[ret_entry] += 1
|
||||
|
||||
self.feed.save()
|
||||
|
||||
return FEED_OK, ret_values
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, options, num_threads):
|
||||
self.options = options
|
||||
self.entry_stats = {
|
||||
ENTRY_NEW:0,
|
||||
ENTRY_UPDATED:0,
|
||||
ENTRY_SAME:0,
|
||||
ENTRY_ERR:0}
|
||||
self.feed_stats = {
|
||||
FEED_OK:0,
|
||||
FEED_SAME:0,
|
||||
FEED_ERRPARSE:0,
|
||||
FEED_ERRHTTP:0,
|
||||
FEED_ERREXC:0}
|
||||
self.entry_trans = {
|
||||
ENTRY_NEW:'new',
|
||||
ENTRY_UPDATED:'updated',
|
||||
ENTRY_SAME:'same',
|
||||
ENTRY_ERR:'error'}
|
||||
self.feed_trans = {
|
||||
FEED_OK:'ok',
|
||||
FEED_SAME:'unchanged',
|
||||
FEED_ERRPARSE:'cant_parse',
|
||||
FEED_ERRHTTP:'http_error',
|
||||
FEED_ERREXC:'exception'}
|
||||
self.entry_keys = sorted(self.entry_trans.keys())
|
||||
self.feed_keys = sorted(self.feed_trans.keys())
|
||||
if threadpool:
|
||||
self.tpool = threadpool.ThreadPool(num_threads)
|
||||
else:
|
||||
self.tpool = None
|
||||
self.time_start = datetime.datetime.now()
|
||||
|
||||
|
||||
def add_job(self, feed):
|
||||
""" adds a feed processing job to the pool
|
||||
"""
|
||||
if self.tpool:
|
||||
req = threadpool.WorkRequest(self.process_feed_wrapper,
|
||||
(feed,))
|
||||
self.tpool.putRequest(req)
|
||||
else:
|
||||
# no threadpool module, just run the job
|
||||
self.process_feed_wrapper(feed)
|
||||
|
||||
def process_feed_wrapper(self, feed):
|
||||
""" wrapper for ProcessFeed
|
||||
"""
|
||||
start_time = datetime.datetime.now()
|
||||
try:
|
||||
pfeed = ProcessFeed(feed, self.options)
|
||||
ret_feed, ret_entries = pfeed.process()
|
||||
del pfeed
|
||||
except:
|
||||
(etype, eobj, etb) = sys.exc_info()
|
||||
print '[%d] ! -------------------------' % (feed.id,)
|
||||
print traceback.format_exception(etype, eobj, etb)
|
||||
traceback.print_exception(etype, eobj, etb)
|
||||
print '[%d] ! -------------------------' % (feed.id,)
|
||||
ret_feed = FEED_ERREXC
|
||||
ret_entries = {}
|
||||
|
||||
delta = datetime.datetime.now() - start_time
|
||||
if delta.seconds > SLOWFEED_WARNING:
|
||||
comment = u' (SLOW FEED!)'
|
||||
else:
|
||||
comment = u''
|
||||
prints(u'[%d] Processed %s in %s [%s] [%s]%s' % (
|
||||
feed.id, feed.feed_url, unicode(delta),
|
||||
self.feed_trans[ret_feed],
|
||||
u' '.join(u'%s=%d' % (self.entry_trans[key],
|
||||
ret_entries[key]) for key in self.entry_keys),
|
||||
comment))
|
||||
|
||||
self.feed_stats[ret_feed] += 1
|
||||
for key, val in ret_entries.items():
|
||||
self.entry_stats[key] += val
|
||||
|
||||
return ret_feed, ret_entries
|
||||
|
||||
def poll(self):
|
||||
""" polls the active threads
|
||||
"""
|
||||
if not self.tpool:
|
||||
# no thread pool, nothing to poll
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
time.sleep(0.2)
|
||||
self.tpool.poll()
|
||||
except KeyboardInterrupt:
|
||||
prints('! Cancelled by user')
|
||||
break
|
||||
except threadpool.NoResultsPending:
|
||||
prints(u'* DONE in %s\n* Feeds: %s\n* Entries: %s' % (
|
||||
unicode(datetime.datetime.now() - self.time_start),
|
||||
u' '.join(u'%s=%d' % (self.feed_trans[key],
|
||||
self.feed_stats[key])
|
||||
for key in self.feed_keys),
|
||||
u' '.join(u'%s=%d' % (self.entry_trans[key],
|
||||
self.entry_stats[key])
|
||||
for key in self.entry_keys)
|
||||
))
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
""" Main function. Nothing to see here. Move along.
|
||||
"""
|
||||
parser = optparse.OptionParser(usage='%prog [options]',
|
||||
version=USER_AGENT)
|
||||
parser.add_option('--settings',
|
||||
help='Python path to settings module. If this isn\'t provided, ' \
|
||||
'the DJANGO_SETTINGS_MODULE enviroment variable will be used.')
|
||||
parser.add_option('-f', '--feed', action='append', type='int',
|
||||
help='A feed id to be updated. This option can be given multiple ' \
|
||||
'times to update several feeds at the same time ' \
|
||||
'(-f 1 -f 4 -f 7).')
|
||||
parser.add_option('-s', '--site', type='int',
|
||||
help='A site id to update.')
|
||||
parser.add_option('-v', '--verbose', action='store_true',
|
||||
dest='verbose', default=False, help='Verbose output.')
|
||||
parser.add_option('-t', '--timeout', type='int', default=10,
|
||||
help='Wait timeout in seconds when connecting to feeds.')
|
||||
parser.add_option('-w', '--workerthreads', type='int', default=10,
|
||||
help='Worker threads that will fetch feeds in parallel.')
|
||||
options = parser.parse_args()[0]
|
||||
if options.settings:
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = options.settings
|
||||
|
||||
|
||||
from calibre.www.apps.feedjack import models, fjcache
|
||||
|
||||
# settting socket timeout (default= 10 seconds)
|
||||
socket.setdefaulttimeout(options.timeout)
|
||||
|
||||
# our job dispatcher
|
||||
disp = Dispatcher(options, options.workerthreads)
|
||||
|
||||
prints('* BEGIN: %s' % (unicode(datetime.datetime.now()),))
|
||||
|
||||
if options.feed:
|
||||
feeds = models.Feed.objects.filter(id__in=options.feed)
|
||||
known_ids = []
|
||||
for feed in feeds:
|
||||
known_ids.append(feed.id)
|
||||
disp.add_job(feed)
|
||||
for feed in options.feed:
|
||||
if feed not in known_ids:
|
||||
prints('! Unknown feed id: %d' % (feed,))
|
||||
elif options.site:
|
||||
try:
|
||||
site = models.Site.objects.get(pk=int(options.site))
|
||||
except models.Site.DoesNotExist:
|
||||
site = None
|
||||
prints('! Unknown site id: %d' % (options.site,))
|
||||
if site:
|
||||
feeds = [sub.feed for sub in site.subscriber_set.all()]
|
||||
for feed in feeds:
|
||||
disp.add_job(feed)
|
||||
else:
|
||||
for feed in models.Feed.objects.filter(is_active=True):
|
||||
disp.add_job(feed)
|
||||
|
||||
disp.poll()
|
||||
|
||||
# removing the cached data in all sites, this will only work with the
|
||||
# memcached, db and file backends
|
||||
[fjcache.cache_delsite(site.id) for site in models.Site.objects.all()]
|
||||
|
||||
if threadpool:
|
||||
tcom = u'%d threads' % (options.workerthreads,)
|
||||
else:
|
||||
tcom = u'no threadpool module available, no parallel fetching'
|
||||
|
||||
prints('* END: %s (%s)' % (unicode(datetime.datetime.now()), tcom))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
urls.py
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import patterns
|
||||
from django.views.generic.simple import redirect_to
|
||||
|
||||
from calibre.www.apps.feedjack import views
|
||||
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^rss20.xml$', redirect_to,
|
||||
{'url':'/feed/rss/'}),
|
||||
(r'^feed/$', redirect_to,
|
||||
{'url':'/feed/atom/'}),
|
||||
(r'^feed/rss/$', views.rssfeed),
|
||||
(r'^feed/atom/$', views.atomfeed),
|
||||
|
||||
(r'^feed/user/(?P<user>\d+)/tag/(?P<tag>.*)/$', redirect_to,
|
||||
{'url':'/feed/atom/user/%(user)s/tag/%(tag)s/'}),
|
||||
(r'^feed/user/(?P<user>\d+)/$', redirect_to,
|
||||
{'url':'/feed/atom/user/%(user)s/'}),
|
||||
(r'^feed/tag/(?P<tag>.*)/$', redirect_to,
|
||||
{'url':'/feed/atom/tag/%(tag)s/'}),
|
||||
|
||||
(r'^feed/atom/user/(?P<user>\d+)/tag/(?P<tag>.*)/$', views.atomfeed),
|
||||
(r'^feed/atom/user/(?P<user>\d+)/$', views.atomfeed),
|
||||
(r'^feed/atom/tag/(?P<tag>.*)/$', views.atomfeed),
|
||||
(r'^feed/rss/user/(?P<user>\d+)/tag/(?P<tag>.*)/$', views.rssfeed),
|
||||
(r'^feed/rss/user/(?P<user>\d+)/$', views.rssfeed),
|
||||
(r'^feed/rss/tag/(?P<tag>.*)/$', views.rssfeed),
|
||||
|
||||
(r'^user/(?P<user>\d+)/tag/(?P<tag>.*)/$', views.mainview),
|
||||
(r'^user/(?P<user>\d+)/$', views.mainview),
|
||||
(r'^tag/(?P<tag>.*)/$', views.mainview),
|
||||
|
||||
(r'^opml/$', views.opml),
|
||||
(r'^foaf/$', views.foaf),
|
||||
(r'^$', views.mainview),
|
||||
)
|
||||
|
||||
#~
|
@ -1,152 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
feedjack
|
||||
Gustavo Picón
|
||||
views.py
|
||||
"""
|
||||
|
||||
|
||||
from django.utils import feedgenerator
|
||||
from django.shortcuts import render_to_response
|
||||
from django.http import HttpResponse
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from django.template import RequestContext, loader
|
||||
|
||||
from calibre.www.apps.feedjack import models, fjlib, fjcache
|
||||
|
||||
def initview(request):
|
||||
""" Retrieves the basic data needed by all feeds (host, feeds, etc)
|
||||
|
||||
Returns a tuple of:
|
||||
1. A valid cached response or None
|
||||
2. The current site object
|
||||
3. The cache key
|
||||
4. The subscribers for the site (objects)
|
||||
5. The feeds for the site (ids)
|
||||
"""
|
||||
|
||||
site_id, cachekey = fjlib.getcurrentsite(request.META.get('HTTP_HOST',
|
||||
'planet.calibre-ebook.com'), \
|
||||
request.META.get('REQUEST_URI', request.META.get('PATH_INFO', '/')), \
|
||||
request.META['QUERY_STRING'])
|
||||
response = fjcache.cache_get(site_id, cachekey)
|
||||
if response:
|
||||
return response, None, cachekey, [], []
|
||||
|
||||
site = models.Site.objects.get(pk=site_id)
|
||||
sfeeds_obj = fjlib.sitefeeds(site)
|
||||
sfeeds_ids = [subscriber.feed.id for subscriber in sfeeds_obj]
|
||||
|
||||
return None, site, cachekey, sfeeds_obj, sfeeds_ids
|
||||
|
||||
def blogroll(request, btype):
|
||||
""" View that handles the generation of blogrolls.
|
||||
"""
|
||||
|
||||
response, site, cachekey, sfeeds_obj, sfeeds_ids = initview(request)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# for some reason this isn't working:
|
||||
#
|
||||
#response = render_to_response('feedjack/%s.xml' % btype, \
|
||||
# fjlib.get_extra_content(site, sfeeds_ids))
|
||||
#response.mimetype = 'text/xml; charset=utf-8'
|
||||
#
|
||||
# so we must use this:
|
||||
|
||||
template = loader.get_template('feedjack/%s.xml' % btype)
|
||||
ctx = {}
|
||||
fjlib.get_extra_content(site, sfeeds_ids, ctx)
|
||||
ctx = RequestContext(request, ctx)
|
||||
response = HttpResponse(template.render(ctx) , \
|
||||
mimetype='text/xml; charset=utf-8')
|
||||
|
||||
|
||||
patch_vary_headers(response, ['Host'])
|
||||
fjcache.cache_set(site, cachekey, response)
|
||||
return response
|
||||
|
||||
def foaf(request):
|
||||
""" View that handles the generation of the FOAF blogroll.
|
||||
"""
|
||||
|
||||
return blogroll(request, 'foaf')
|
||||
|
||||
def opml(request):
|
||||
""" View that handles the generation of the OPML blogroll.
|
||||
"""
|
||||
|
||||
return blogroll(request, 'opml')
|
||||
|
||||
|
||||
def buildfeed(request, feedclass, tag=None, user=None):
|
||||
""" View that handles the feeds.
|
||||
"""
|
||||
|
||||
response, site, cachekey, sfeeds_obj, sfeeds_ids = initview(request)
|
||||
if response:
|
||||
return response
|
||||
|
||||
object_list = fjlib.get_paginator(site, sfeeds_ids, page=0, tag=tag, \
|
||||
user=user)[1]
|
||||
|
||||
feed = feedclass(\
|
||||
title=site.title,
|
||||
link=site.url,
|
||||
description=site.description,
|
||||
feed_url='%s/%s' % (site.url, '/feed/rss/'))
|
||||
for post in object_list:
|
||||
feed.add_item( \
|
||||
title = '%s: %s' % (post.feed.name, post.title), \
|
||||
link = post.link, \
|
||||
description = post.content, \
|
||||
author_email = post.author_email, \
|
||||
author_name = post.author, \
|
||||
pubdate = post.date_modified, \
|
||||
unique_id = post.link, \
|
||||
categories = [tag.name for tag in post.tags.all()])
|
||||
response = HttpResponse(mimetype=feed.mime_type)
|
||||
|
||||
# per host caching
|
||||
patch_vary_headers(response, ['Host'])
|
||||
|
||||
feed.write(response, 'utf-8')
|
||||
if site.use_internal_cache:
|
||||
fjcache.cache_set(site, cachekey, response)
|
||||
return response
|
||||
|
||||
def rssfeed(request, tag=None, user=None):
|
||||
""" Generates the RSS2 feed.
|
||||
"""
|
||||
return buildfeed(request, feedgenerator.Rss201rev2Feed, tag, user)
|
||||
|
||||
def atomfeed(request, tag=None, user=None):
|
||||
""" Generates the Atom 1.0 feed.
|
||||
"""
|
||||
return buildfeed(request, feedgenerator.Atom1Feed, tag, user)
|
||||
|
||||
def mainview(request, tag=None, user=None):
|
||||
""" View that handles all page requests.
|
||||
"""
|
||||
|
||||
response, site, cachekey, sfeeds_obj, sfeeds_ids = initview(request)
|
||||
if response:
|
||||
return response
|
||||
|
||||
ctx = fjlib.page_context(request, site, tag, user, (sfeeds_obj, \
|
||||
sfeeds_ids))
|
||||
|
||||
response = render_to_response('feedjack/%s/post_list.html' % \
|
||||
(site.template), ctx, context_instance=RequestContext(request))
|
||||
|
||||
# per host caching, in case the cache middleware is enabled
|
||||
patch_vary_headers(response, ['Host'])
|
||||
|
||||
if site.use_internal_cache:
|
||||
fjcache.cache_set(site, cachekey, response)
|
||||
return response
|
||||
|
||||
#~
|
||||
|
@ -1,10 +0,0 @@
|
||||
changes:
|
||||
date: 2008-05-18
|
||||
change: Converted everything to 4 space tabs and made a few other changes to comply with Python Style Guide.
|
||||
|
||||
date: 2008-04-23
|
||||
change: Added a mode called InlineType and a template tag that returns this models objects.
|
||||
|
||||
date: 2008-04-22
|
||||
change: Added an 'extract_inlines' filter so you can loop over a list of inlines in a body of text.
|
||||
change: Creating new inlines app.
|
@ -1,27 +0,0 @@
|
||||
==============================================
|
||||
Django Basic Inlines
|
||||
http://code.google.com/p/django-basic-apps/
|
||||
==============================================
|
||||
|
||||
A simple book library application for Django projects.
|
||||
|
||||
To install this app, simply create a folder somewhere in
|
||||
your PYTHONPATH named 'basic' and place the 'inlines'
|
||||
app inside. Then add 'basic.inlines' to your projects
|
||||
INSTALLED_APPS list in your settings.py file.
|
||||
|
||||
Inlines is a template filter that can be used in
|
||||
conjunction with inline markup to insert content objects
|
||||
into other pieces of content. An example would be inserting
|
||||
a photo into a blog post body.
|
||||
|
||||
An example of the markup is:
|
||||
<inline type="media.photo" id="1" />
|
||||
|
||||
The type attribute is app_name.model_name and the id is
|
||||
the object id. Pretty simple.
|
||||
|
||||
In your template you would say:
|
||||
{% load inlines %}
|
||||
|
||||
{{ post.body|render_inlines }}
|
@ -1,5 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from calibre.www.apps.inlines.models import *
|
||||
|
||||
|
||||
admin.site.register(InlineType)
|
@ -1,17 +0,0 @@
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
class InlineType(models.Model):
|
||||
""" InlineType model """
|
||||
title = models.CharField(max_length=200)
|
||||
content_type = models.ForeignKey(ContentType)
|
||||
|
||||
class Meta:
|
||||
db_table = 'inline_types'
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
@ -1,92 +0,0 @@
|
||||
from django.template import TemplateSyntaxError
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import Http404
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
def inlines(value, return_list=False):
|
||||
try:
|
||||
from BeautifulSoup import BeautifulStoneSoup
|
||||
except ImportError:
|
||||
from beautifulsoup import BeautifulStoneSoup
|
||||
|
||||
content = BeautifulStoneSoup(value, selfClosingTags=['inline','img','br','input','meta','link','hr'])
|
||||
inline_list = []
|
||||
|
||||
if return_list:
|
||||
for inline in content.findAll('inline'):
|
||||
rendered_inline = render_inline(inline)
|
||||
inline_list.append(rendered_inline['context'])
|
||||
return inline_list
|
||||
else:
|
||||
for inline in content.findAll('inline'):
|
||||
rendered_inline = render_inline(inline)
|
||||
inline.replaceWith(render_to_string(rendered_inline['template'], rendered_inline['context']))
|
||||
return mark_safe(content)
|
||||
|
||||
|
||||
def render_inline(inline):
|
||||
"""
|
||||
Replace inline markup with template markup that matches the
|
||||
appropriate app and model.
|
||||
|
||||
"""
|
||||
|
||||
# Look for inline type, 'app.model'
|
||||
try:
|
||||
app_label, model_name = inline['type'].split('.')
|
||||
except:
|
||||
if settings.DEBUG:
|
||||
raise TemplateSyntaxError, "Couldn't find the attribute 'type' in the <inline> tag."
|
||||
else:
|
||||
return ''
|
||||
|
||||
# Look for content type
|
||||
try:
|
||||
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
|
||||
model = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
if settings.DEBUG:
|
||||
raise TemplateSyntaxError, "Inline ContentType not found."
|
||||
else:
|
||||
return ''
|
||||
|
||||
# Check for an inline class attribute
|
||||
try:
|
||||
inline_class = smart_unicode(inline['class'])
|
||||
except:
|
||||
inline_class = ''
|
||||
|
||||
try:
|
||||
try:
|
||||
id_list = [int(i) for i in inline['ids'].split(',')]
|
||||
obj_list = model.objects.in_bulk(id_list)
|
||||
obj_list = list(obj_list[int(i)] for i in id_list)
|
||||
context = { 'object_list': obj_list, 'class': inline_class }
|
||||
except ValueError:
|
||||
if settings.DEBUG:
|
||||
raise ValueError, "The <inline> ids attribute is missing or invalid."
|
||||
else:
|
||||
return ''
|
||||
except KeyError:
|
||||
try:
|
||||
obj = model.objects.get(pk=inline['id'])
|
||||
context = { 'content_type':"%s.%s" % (app_label, model_name), 'object': obj, 'class': inline_class, 'settings': settings }
|
||||
except model.DoesNotExist:
|
||||
if settings.DEBUG:
|
||||
raise model.DoesNotExist, "Object matching '%s' does not exist"
|
||||
else:
|
||||
return ''
|
||||
except:
|
||||
if settings.DEBUG:
|
||||
raise TemplateSyntaxError, "The <inline> id attribute is missing or invalid."
|
||||
else:
|
||||
return ''
|
||||
|
||||
template = ["inlines/%s_%s.html" % (app_label, model_name), "inlines/default.html"]
|
||||
rendered_inline = {'template':template, 'context':context}
|
||||
|
||||
return rendered_inline
|
@ -1,81 +0,0 @@
|
||||
from django import template
|
||||
from calibre.www.apps.inlines.parser import inlines
|
||||
from calibre.www.apps.inlines.models import InlineType
|
||||
import re
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def render_inlines(value):
|
||||
"""
|
||||
Renders inlines in a ``Post`` by passing them through inline templates.
|
||||
|
||||
Template Syntax::
|
||||
|
||||
{{ post.body|render_inlines|markdown:"safe" }}
|
||||
|
||||
Inline Syntax (singular)::
|
||||
|
||||
<inline type="<app_name>.<model_name>" id="<id>" class="med_left" />
|
||||
|
||||
Inline Syntax (plural)::
|
||||
|
||||
<inline type="<app_name>.<model_name>" ids="<id>, <id>, <id>" />
|
||||
|
||||
An inline template will be used to render the inline. Templates will be
|
||||
locaed in the following maner:
|
||||
|
||||
``inlines/<app_name>_<model_name>.html``
|
||||
|
||||
The template will be passed the following context:
|
||||
|
||||
``object``
|
||||
An object for the corresponding passed id.
|
||||
|
||||
or
|
||||
|
||||
``object_list``
|
||||
A list of objects for the corresponding ids.
|
||||
|
||||
It would be wise to anticipate both object_list and object unless
|
||||
you know for sure one or the other will only be present.
|
||||
"""
|
||||
return inlines(value)
|
||||
|
||||
@register.filter
|
||||
def extract_inlines(value):
|
||||
return inlines(value, True)
|
||||
|
||||
|
||||
class InlineTypes(template.Node):
|
||||
def __init__(self, var_name):
|
||||
self.var_name = var_name
|
||||
|
||||
def render(self, context):
|
||||
types = InlineType.objects.all()
|
||||
context[self.var_name] = types
|
||||
return ''
|
||||
|
||||
@register.tag(name='get_inline_types')
|
||||
def do_get_inline_types(parser, token):
|
||||
"""
|
||||
Gets all inline types.
|
||||
|
||||
Syntax::
|
||||
|
||||
{% get_inline_types as [var_name] %}
|
||||
|
||||
Example usage::
|
||||
|
||||
{% get_inline_types as inline_list %}
|
||||
"""
|
||||
try:
|
||||
tag_name, arg = token.contents.split(None, 1)
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
|
||||
m = re.search(r'as (\w+)', arg)
|
||||
if not m:
|
||||
raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
|
||||
var_name = m.groups()[0]
|
||||
return InlineTypes(var_name)
|
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
from django.core.management import execute_manager
|
||||
try:
|
||||
import 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(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_manager(settings)
|
@ -1,49 +0,0 @@
|
||||
# Django settings for planet project.
|
||||
|
||||
from calibre.www.settings import DEBUG, TEMPLATE_DEBUG, ADMINS, MANAGERS, \
|
||||
TEMPLATE_LOADERS, TEMPLATE_DIRS, MIDDLEWARE_CLASSES, MEDIA_ROOT, \
|
||||
MEDIA_URL, ADMIN_MEDIA_PREFIX, TEMPLATE_CONTEXT_PROCESSORS
|
||||
|
||||
FORCE_LOWERCASE_TAGS = False
|
||||
MAX_TAG_LENGTH = 50
|
||||
|
||||
if not DEBUG:
|
||||
MEDIA_URL = 'http://kovid.calibre-ebook.com/site_media/'
|
||||
ADMIN_MEDIA_PREFIX = 'http://kovid.calibre-ebook.com/admin_media/'
|
||||
MEDIA_ROOT = '/usr/local/calibre/src/calibre/www/static/'
|
||||
|
||||
|
||||
if DEBUG:
|
||||
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||
DATABASE_NAME = '/tmp/kovid.db' # Or path to database file if using sqlite3.
|
||||
else:
|
||||
DATABASE_ENGINE = 'mysql'
|
||||
DATABASE_NAME = 'calibre_kovid'
|
||||
DATABASE_USER = 'calibre_django'
|
||||
DATABASE_PASSWORD = open('/var/www/calibre-ebook.com/dbpass').read().strip()
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
if DEBUG:
|
||||
SECRET_KEY = '06mv&t$cobjkijgg#0ndwm5#&90_(tm=oqi1bv-x^vii$*33n5'
|
||||
else:
|
||||
SECRET_KEY = open('/var/www/kovid.calibre-ebook.com/django_secret_key').read().strip()
|
||||
|
||||
|
||||
ROOT_URLCONF = 'calibre.www.kovid.urls'
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.comments',
|
||||
'django.contrib.markup',
|
||||
'calibre.www.apps.inlines',
|
||||
'tagging',
|
||||
'calibre.www.apps.blog',
|
||||
)
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
from django.conf.urls.defaults import patterns, include, handler404, handler500
|
||||
from django.conf import settings
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
|
||||
(r'^admin/(.*)', admin.site.root),
|
||||
|
||||
(r'^comments/', include('django.contrib.comments.urls')),
|
||||
(r'', include('calibre.www.apps.blog.urls')),
|
||||
|
||||
|
||||
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += patterns('',
|
||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
|
||||
{'document_root': settings.MEDIA_ROOT}),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
Test
|
||||
=====
|
||||
|
||||
Calibre planet can be run either in development mode or deployment mode. For testing,
|
||||
it should be run in development mode as follows:
|
||||
|
||||
* Install django
|
||||
* ``cd test && ./test``
|
||||
* Planet is at `http://localhost:8000`
|
||||
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
from django.core.management import execute_manager
|
||||
try:
|
||||
import 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(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_manager(settings)
|
@ -1,42 +0,0 @@
|
||||
# Django settings for planet project.
|
||||
|
||||
from calibre.www.settings import DEBUG, TEMPLATE_DEBUG, ADMINS, MANAGERS, \
|
||||
TEMPLATE_LOADERS, TEMPLATE_DIRS, MIDDLEWARE_CLASSES, MEDIA_ROOT, \
|
||||
MEDIA_URL, ADMIN_MEDIA_PREFIX, TEMPLATE_CONTEXT_PROCESSORS
|
||||
|
||||
if not DEBUG:
|
||||
MEDIA_URL = 'http://planet.calibre-ebook.com/site_media/'
|
||||
ADMIN_MEDIA_PREFIX = 'http://planet.calibre-ebook.com/admin_media/'
|
||||
MEDIA_ROOT = '/usr/local/calibre/src/calibre/www/static/'
|
||||
|
||||
|
||||
if DEBUG:
|
||||
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||
DATABASE_NAME = '/tmp/planet.db' # Or path to database file if using sqlite3.
|
||||
else:
|
||||
DATABASE_ENGINE = 'mysql'
|
||||
DATABASE_NAME = 'calibre_planet'
|
||||
DATABASE_USER = 'calibre_django'
|
||||
DATABASE_PASSWORD = open('/var/www/calibre-ebook.com/dbpass').read().strip()
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
if DEBUG:
|
||||
SECRET_KEY = '06mv&t$cobjkijgg#0ndwm5#&90_(tm=oqi1bv-x^vii$*33n5'
|
||||
else:
|
||||
SECRET_KEY = open('/var/www/planet.calibre-ebook.com/django_secret_key').read().strip()
|
||||
|
||||
|
||||
ROOT_URLCONF = 'calibre.www.planet.urls'
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.admin',
|
||||
'calibre.www.apps.feedjack',
|
||||
)
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
cp planet.db /tmp
|
||||
cd ..
|
||||
python manage.py runserver
|
@ -1,24 +0,0 @@
|
||||
from django.conf.urls.defaults import patterns, include, handler404, handler500
|
||||
from django.conf import settings
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
|
||||
(r'^admin/(.*)', admin.site.root),
|
||||
|
||||
|
||||
(r'', include('calibre.www.apps.feedjack.urls')),
|
||||
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += patterns('',
|
||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
|
||||
{'document_root': settings.MEDIA_ROOT}),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ssh divok "cd /usr/local/calibre && bzr up"
|
||||
ssh divok /etc/init.d/apache2 graceful
|
@ -1,86 +0,0 @@
|
||||
# Django settings
|
||||
# Import base settings from here into the site specific settings files
|
||||
|
||||
import socket, os
|
||||
|
||||
DEBUG = socket.gethostname() != 'divok'
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
('Kovid Goyal', 'kovid@kovidgoyal.net'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
# Absolute path to the directory that holds media.
|
||||
# Example: "/home/media/media.lawrence.com/"
|
||||
if DEBUG:
|
||||
MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'static/')
|
||||
else:
|
||||
MEDIA_ROOT = ''
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash if there is a path component (optional in other cases).
|
||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||
if DEBUG:
|
||||
MEDIA_URL = 'http://127.0.0.1:8000/site_media/'
|
||||
else:
|
||||
MEDIA_URL = 'http://planet.calibre-ebook.com/site_media/'
|
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://foo.com/media/", "/media/".
|
||||
ADMIN_MEDIA_PREFIX = '/media/'
|
||||
|
||||
# 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.
|
||||
# If running in a Windows environment this must be set to the same as your
|
||||
# system time zone.
|
||||
TIME_ZONE = 'America/Los_Angeles'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.load_template_source',
|
||||
'django.template.loaders.app_directories.load_template_source',
|
||||
# 'django.template.loaders.eggs.load_template_source',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'planet.urls'
|
||||
|
||||
|
||||
if DEBUG:
|
||||
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.
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
)
|
||||
else:
|
||||
TEMPLATE_DIRS = (
|
||||
'/usr/local/calibre/src/calibre/www/templates',
|
||||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
"django.core.context_processors.auth",
|
||||
"django.core.context_processors.debug",
|
||||
"django.core.context_processors.i18n",
|
||||
"django.core.context_processors.media"
|
||||
)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 292 B |
Before Width: | Height: | Size: 399 B |
Before Width: | Height: | Size: 317 B |
Before Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 17 KiB |
@ -1,40 +0,0 @@
|
||||
body {
|
||||
font-family: sansserif;
|
||||
background-color: #f1fff1;
|
||||
}
|
||||
|
||||
img {
|
||||
border:0 none;
|
||||
}
|
||||
|
||||
#_header {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid black;
|
||||
margin-bottom: 20px;
|
||||
height: 100px;
|
||||
background-color: #A2B964;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#_header > h1 {
|
||||
margin: 0px; padding: 0px;
|
||||
font-size: 60px;
|
||||
font-family: sansserif;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#_logo {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#_footer {
|
||||
font-size: small;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
border-top: 1px solid black;
|
||||
font-style: italic;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
#_footer img { vertical-align: middle; }
|
@ -1,115 +0,0 @@
|
||||
/* codebox header */
|
||||
.wp_codebox_msgheader {
|
||||
width: 100%;
|
||||
border: 1px solid #DEDEB8;
|
||||
border-bottom: 0;
|
||||
font-weight: bold;
|
||||
background: #F3F8D7 url(../images/arrow-square.gif) no-repeat right 5px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader.active {
|
||||
background-position: right -51px;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .right {
|
||||
float: right;
|
||||
text-align: right;
|
||||
padding: 5px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .right a {
|
||||
font: 12px Arial, Tahoma !important;
|
||||
font: 11px Arial, Tahoma;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .left,.wp_codebox_msgheader .left2 {
|
||||
float: left;
|
||||
/* background-color:#FFFFFF;
|
||||
border:1px solid #DCDCDC;
|
||||
padding:8px 0px 2px 8px;*/
|
||||
font-family: tahoma, arial, verdana;
|
||||
/* display: block;
|
||||
width:50%;
|
||||
margin: 0 auto;*/
|
||||
padding: 5px 5px 5px 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .left {
|
||||
background: url(../images/view_code.png) no-repeat left;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .left2 {
|
||||
background: url(../images/down.gif) no-repeat left;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .left a { /* margin:0px 5px 0px 10px;*/
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .left2 a { /* margin:0px 5px 0px 5px;*/
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wp_codebox_msgheader .codebox_clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* codebox */
|
||||
.wp_codebox {
|
||||
color: #100;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid silver;
|
||||
margin: 0 0 1.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* IE FIX */
|
||||
.wp_codebox {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: expression(this.scrollWidth > this.offsetWidth ? 15 : 0)
|
||||
;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wp_codebox table {
|
||||
border-collapse: collapse;
|
||||
border: none;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.wp_codebox div,.wp_codebox td {
|
||||
vertical-align: top;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.wp_codebox td.line_numbers {
|
||||
text-align: right;
|
||||
background-color: #def;
|
||||
color: #666;
|
||||
overflow: visible;
|
||||
border-right: 1px solid #B0BEC7;
|
||||
table-layout: auto;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
/* potential overrides for other styles */
|
||||
.wp_codebox pre {
|
||||
border: none;
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
float: none;
|
||||
clear: none;
|
||||
overflow: visible;
|
||||
font-size: 12px;
|
||||
line-height: 1.333;
|
||||
}
|
||||
|
||||
.line_numbers pre {
|
||||
padding-left: 10px;
|
||||
}
|
@ -1,313 +0,0 @@
|
||||
/*
|
||||
* Feedjack LostWoods theme
|
||||
**************************
|
||||
* Simple and green (where's the brown? -brown doesn't count)
|
||||
*
|
||||
* Copyright Diego Escalante Urrelo <diegoe@gnome.org>
|
||||
*
|
||||
*/
|
||||
body {
|
||||
font-size: 0.8em;
|
||||
font-family: verdana;
|
||||
margin: 0;
|
||||
}
|
||||
div {
|
||||
/*border: 1px solid blue;
|
||||
padding: 5px;
|
||||
margin: 5px;*/
|
||||
}
|
||||
/*
|
||||
* Structure
|
||||
*/
|
||||
#logo {
|
||||
padding-top: 3px;
|
||||
padding-left: 1em;
|
||||
}
|
||||
#tags {
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
#paginate {
|
||||
margin-bottom: 15px;
|
||||
margin-left: 0px;
|
||||
margin-top: 5px;
|
||||
padding-left: 0;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
width: 60%;
|
||||
color: black;
|
||||
}
|
||||
#buttons {
|
||||
text-align: right;
|
||||
float: right;
|
||||
vertical-align: middle;
|
||||
color: #aaa;
|
||||
width: 40%;
|
||||
line-height: 1.7em;
|
||||
padding:0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
#usertags {
|
||||
clear: both;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
width: 75%;
|
||||
text-align: center;
|
||||
}
|
||||
#post_list {
|
||||
clear : both;
|
||||
width: 75%;
|
||||
}
|
||||
#sidebar {
|
||||
width: 20%;
|
||||
position: absolute;
|
||||
right:0;
|
||||
top: 170px;
|
||||
border-left: 10px outset #6E9C60;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
div.date {
|
||||
font-size: x-large;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
padding: 5px;
|
||||
color: black;
|
||||
border: 2px solid #6E9C60;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
margin-bottom: 10px;
|
||||
|
||||
}
|
||||
/*
|
||||
* Post structure
|
||||
*/
|
||||
div.post {
|
||||
overflow: auto;
|
||||
margin-bottom: 50px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 12px solid #6E9C60;
|
||||
border-right: 1px inset #6E9C60;
|
||||
padding-right: 10px;
|
||||
}
|
||||
div.avatar {
|
||||
float: right;
|
||||
width: 15%;
|
||||
text-align: center;
|
||||
}
|
||||
div.post-title {
|
||||
text-align: left;
|
||||
|
||||
font-size: 180%;
|
||||
font-family: trebuchet ms;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
width: 75%;
|
||||
}
|
||||
div.post-content {
|
||||
width: 72%;
|
||||
overflow: auto;
|
||||
text-align: justify;
|
||||
font-size: 90%;
|
||||
line-height: 1.8em;
|
||||
padding-left: 4em;
|
||||
padding-right: 4em;
|
||||
border-right: 1px dotted #ccc;
|
||||
}
|
||||
div.post-content li {
|
||||
line-height: 130%;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
div.post-content table{
|
||||
border: 0;
|
||||
margin-left: 50px;
|
||||
|
||||
}
|
||||
div.post-content td {
|
||||
border: 2px solid #ccc;
|
||||
}
|
||||
div.post-meta {
|
||||
color: #666;
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
div.tags {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Elements
|
||||
*/
|
||||
blockquote {
|
||||
color: #777;
|
||||
margin: 15px 30px 0px 10px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-left: 7px solid #ddd;
|
||||
|
||||
}
|
||||
a:link {
|
||||
color: #4C6B46;
|
||||
}
|
||||
a:hover {
|
||||
color: #33408A;
|
||||
}
|
||||
h1 a:link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
#buttons img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#head h1 {
|
||||
font-style: italic;
|
||||
font-size: xx-large;
|
||||
border-bottom: 3px solid #6E9C60;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
#head a:link, a:visited, #head a:active {
|
||||
color: inherit;
|
||||
}
|
||||
.love_feedjack {
|
||||
font-size: 145%;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
.cloud_1 {
|
||||
font-size: 50%;
|
||||
}
|
||||
.cloud_2 {
|
||||
font-size: 100%;
|
||||
}
|
||||
.cloud_3 {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.cloud_4 {
|
||||
font-size: 140%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.cloud_5 {
|
||||
font-size: 160%;
|
||||
font-weight: bold;
|
||||
}
|
||||
#paginate ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
#paginate li {
|
||||
display: inline;
|
||||
margin: 2px;
|
||||
padding: 10px;
|
||||
background-color: #fbfbfb;
|
||||
border: 2px solid #ddd;
|
||||
line-height: 1.7em;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#paginate li.tagname {
|
||||
font-weight: bold;
|
||||
font-size: 140%;
|
||||
}
|
||||
#paginate a:link {
|
||||
color: #4C6B46;
|
||||
}
|
||||
|
||||
#paginate a { text-decoration: none; }
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
#sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#sidebar li {
|
||||
display: block;
|
||||
clear: both;
|
||||
line-height: 25px;
|
||||
}
|
||||
#sidebar a.nombre {
|
||||
display: inline;
|
||||
color: #33408A;
|
||||
vertical-align: middle;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
#sidebar img.face {
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
}
|
||||
#sidebar h4 {
|
||||
font-style: italic;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#sidebar h4 + p {
|
||||
text-align: justify;
|
||||
}
|
||||
#tags ul, #usertags ul {
|
||||
background-color: #fbfbfb;
|
||||
padding: 5px;
|
||||
border: 2px solid #ddd;
|
||||
margin:0;
|
||||
text-align: center;
|
||||
}
|
||||
#tags li, #usertags li {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
line-height: 1.7em;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
#tags a:link, #usertags a:link {
|
||||
color: #4C6B46;
|
||||
}
|
||||
span.name {
|
||||
color: #333;
|
||||
}
|
||||
span.nick {
|
||||
color: #555;
|
||||
}
|
||||
span.url a {
|
||||
color: #bbb;
|
||||
}
|
||||
span.url a:hover {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.tags ul {
|
||||
background-color: #fbfbfb;
|
||||
padding: 5px;
|
||||
border: 2px solid #ddd;
|
||||
margin:0;
|
||||
text-align: center;
|
||||
}
|
||||
div.tags li {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
line-height: 1.7em;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
#planet_donate
|
||||
{
|
||||
text-align: center;
|
||||
border-top: 1px solid gray;
|
||||
border-bottom: 1px solid gray;
|
||||
padding-top: 5px; padding-bottom: 5px;
|
||||
}
|
||||
|
||||
#planet_ads {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Page not found{% endblock %}
|
||||
|
||||
{% block header_text %}Page not found{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>Sorry, but the requested page could not be found.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer_text %} {% endblock %}
|
@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Page unavailable</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Page unavailable</h1>
|
||||
|
||||
<p>Sorry, but the requested page is unavailable due to a
|
||||
server hiccup.</p>
|
||||
|
||||
<p>Our engineers have been notified, so check back later.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<!--
|
||||
Base template that defines navigation/sidebars etc.
|
||||
-->
|
||||
<head>
|
||||
<title>{% block title %}calibre - E-book management{% endblock %}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/styles/base.css" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
{% block extra_header %} {% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="_header">
|
||||
<h1>
|
||||
<img id="_logo" alt="calibre" src="{{ MEDIA_URL }}/img/logo.png" />
|
||||
{% block header_text %}e-book management{% endblock %}
|
||||
</h1>
|
||||
</div>
|
||||
<div id="_content">
|
||||
{% block content %}Hello world{% endblock %}
|
||||
</div>
|
||||
|
||||
<div id="_footer">
|
||||
{% block footer_text %}
|
||||
Created by Kovid Goyal.
|
||||
Powered by <a href="http://www.djangoproject.com">
|
||||
<img alt="Django" src="{{ MEDIA_URL }}/img/button-django.png"/>
|
||||
</a>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,190 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title%}Calibre Planet{% endblock %}
|
||||
{% block header_text %}Planet{% endblock %}
|
||||
|
||||
{% block extra_header %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/styles/planet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/styles/codebox.css" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="paginate">
|
||||
<ul>
|
||||
|
||||
{% if has_previous %}
|
||||
<li><a href="?page={{ previous }}"><<</a></li>
|
||||
{% endif %}
|
||||
<li>
|
||||
Page {{ page }} of {{ pages }} ({{ hits }} posts)
|
||||
</li>
|
||||
{% if has_next %}
|
||||
<li><a href="?page={{ next }}">>></a></li>
|
||||
{% endif %}
|
||||
{% if user %}
|
||||
<li class="username"><a href="{{ user.feed.link }}">{{ user.name }}</a>talks about »</li>
|
||||
{% endif %}
|
||||
{% if tag %}
|
||||
<li class="tagname">{{ tag.name }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div> <!-- end paginate -->
|
||||
|
||||
<div id="buttons">
|
||||
<a href="{{ site.url }}/feed/rss/" title="RSS 2.0 feed"><img src="{{ MEDIA_URL }}/img/button-rss.png"/></a> •
|
||||
<a href="{{ site.url }}/feed/atom/" title="Atom 1.0 feed"><img src="{{ MEDIA_URL }}/img/button-atom.png"/></a> •
|
||||
<a href="{{ site.url }}/opml/" title="OPML"><img src="{{ MEDIA_URL }}/img/button-opml.png"/></a> •
|
||||
<a href="{{ site.url }}/foaf/" title="FOAF"><img src="{{ MEDIA_URL }}/img/button-foaf.png"/></a>
|
||||
</div> <!-- end buttons -->
|
||||
|
||||
<div id="post_list">
|
||||
{% for item in object_list %}
|
||||
|
||||
{% ifchanged %}
|
||||
<div class="date">{{ item.date_modified|date:"F j, Y" }}</div>
|
||||
{% endifchanged %}
|
||||
|
||||
<div class="post">
|
||||
{% ifchanged %}
|
||||
<!-- {{ item.date_modified|date:"F j, Y" }} -->
|
||||
<div class="avatar">
|
||||
<a href="{{ item.feed.link }}">
|
||||
<img
|
||||
src="{{ MEDIA_URL }}/img/faces/{{ item.subscriber.shortname}}.png" alt="" />
|
||||
<div class="url">
|
||||
{{ item.feed.name|safe }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endifchanged %}
|
||||
|
||||
{% if item.title %}
|
||||
<div class="post-title">» {{ item.title }}</div>
|
||||
{% else %}
|
||||
<div class="post-title">» {{ item.subscriber.name }}</div>
|
||||
{% endif %}
|
||||
<div class="post-content">
|
||||
<p>{{ item.content|safe }}</p>
|
||||
<div class="post-meta">
|
||||
<a href="{{ item.link }}">
|
||||
{% if item.author %}by {{ item.author }} at{% endif %}
|
||||
{{ item.date_modified|date:"g:i A" }}</a>
|
||||
{% for tag in item.qtags %}
|
||||
{% if forloop.first %}under{% endif %}
|
||||
<a href="{{ site.url }}/tag/{{ tag.name }}">{{ tag.name }}</a>
|
||||
{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% if item.comments %}
|
||||
<a href="{{ item.comments }}">(Comments)</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<div id="sidebar">
|
||||
<h4>{{ site.name }}</h4>
|
||||
|
||||
<p style="text-align:left">
|
||||
Planet Calibre is a window into the world, work and lives of Calibre developers and contributors.
|
||||
</p>
|
||||
<p style="text-align:left">
|
||||
If you have a question or would like your blog added to the planet, please email
|
||||
<a href="mailto:kovid@kovidgoyal.net">Kovid Goyal</a>.
|
||||
</p>
|
||||
|
||||
<div id="planet_donate">
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
|
||||
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||
<input type="hidden" name="hosted_button_id" value="3028915" />
|
||||
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
|
||||
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
|
||||
<div>Donate to support the development of calibre.</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="tags">
|
||||
<ul id="cloud">
|
||||
{% for tag in tagcloud %}
|
||||
<li><a
|
||||
{% if user_id %}
|
||||
href="{{ site.url }}/user/{{ user_id }}/tag/{{ tag.tagname|urlencode }}/"
|
||||
{% else %}
|
||||
href="{{ site.url }}/tag/{{ tag.tagname|urlencode }}/"
|
||||
{% endif %}
|
||||
title="{{ tag.count }} posts"
|
||||
class="cloud_{{ tag.weight }}">{{ tag.tagname }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id='planet_ads' style="z-index:0">
|
||||
<script type="text/javascript"><!--
|
||||
google_ad_client = "pub-2595272032872519";
|
||||
/* Calibre Planet */
|
||||
google_ad_slot = "6940385240";
|
||||
google_ad_width = 120;
|
||||
google_ad_height = 600;
|
||||
//-->
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h4>Last update</h4>
|
||||
<b>{{ last_modified }}</b>
|
||||
|
||||
<h4>People</h4>
|
||||
|
||||
<ul class="suscriptores">
|
||||
{% for feed in subscribers %}
|
||||
<li>
|
||||
<a href="{{ feed.feed.feed_url }}"
|
||||
{% if feed.feed.last_modified %}
|
||||
title="feed (last modified: {{ feed.feed.last_modified }})"
|
||||
{% else %}
|
||||
title="feed"
|
||||
{% endif %}
|
||||
>
|
||||
<img src="{{ MEDIA_URL }}/img/feed.png" alt="feed"></a>
|
||||
<a class="nombre" href="{{ site.url }}/user/{{ feed.feed.id }}"
|
||||
title="{{ feed.feed.title }}">{{ feed.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="paginate">
|
||||
<ul>
|
||||
|
||||
{% if has_previous %}
|
||||
<li><a href="?page={{ previous }}"><<</a></li>
|
||||
{% endif %}
|
||||
<li>
|
||||
Page {{ page }} of {{ pages }} (
|
||||
{{ hits }} posts
|
||||
)
|
||||
</li>
|
||||
{% if has_next %}
|
||||
<li><a href="?page={{ next }}">>></a></li>
|
||||
{% endif %}
|
||||
{% if user %}
|
||||
<li class="username"><a href="{{ user.feed.link }}">{{ user.name }}</a></li>
|
||||
{% endif %}
|
||||
{% if tag %}
|
||||
<li class="tagname">{{ tag.name }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="height:50px"> </div>
|
||||
|
||||
|
||||
{% endblock %}
|
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<rdf:RDF
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
||||
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||
xmlns:rss="http://purl.org/rss/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
>
|
||||
<!-- based on http://www-128.ibm.com/developerworks/xml/library/x-pblog/ -->
|
||||
<foaf:Group>
|
||||
|
||||
<foaf:name>{{ site.title }}</foaf:name>
|
||||
<foaf:homepage>{{ site.url }}</foaf:homepage>
|
||||
<rdfs:seeAlso rdf:resource="{{ site.url }}/foaf/" />
|
||||
|
||||
{% for feed in feeds %}
|
||||
<foaf:member>
|
||||
<foaf:Person>
|
||||
<foaf:name>{{ feed.name }}</foaf:name>
|
||||
<foaf:weblog>
|
||||
<foaf:Document rdf:about="{{ feed.link }}">
|
||||
<dc:title>{{ feed.title }}</dc:title>
|
||||
<rdfs:seeAlso>
|
||||
<rss:channel rdf:about="{{ feed.feed_url }}" />
|
||||
</rdfs:seeAlso>
|
||||
</foaf:Document>
|
||||
</foaf:weblog>
|
||||
</foaf:Person>
|
||||
</foaf:member>
|
||||
{% endfor %}
|
||||
|
||||
</foaf:Group>
|
||||
</rdf:RDF>
|
||||
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf8"?>
|
||||
<opml version="1.1">
|
||||
<head>
|
||||
<title>{{ site.title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
{% for feed in feeds %}
|
||||
<outline type="rss" text="{{ feed.name }}" description="{{ feed.title }}" htmlUrl="{{ feed.link }}" xmlUrl="{{ feed.feed_url }}"/>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</opml>
|
@ -1,30 +0,0 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from tagging.managers import ModelTaggedItemManager, TagDescriptor
|
||||
|
||||
VERSION = (0, 3, 'pre')
|
||||
|
||||
class AlreadyRegistered(Exception):
|
||||
"""
|
||||
An attempt was made to register a model more than once.
|
||||
"""
|
||||
pass
|
||||
|
||||
registry = []
|
||||
|
||||
def register(model, tag_descriptor_attr='tags',
|
||||
tagged_item_manager_attr='tagged'):
|
||||
"""
|
||||
Sets the given model class up for working with tags.
|
||||
"""
|
||||
if model in registry:
|
||||
raise AlreadyRegistered(
|
||||
_('The model %s has already been registered.') % model.__name__)
|
||||
registry.append(model)
|
||||
|
||||
# Add tag descriptor
|
||||
setattr(model, tag_descriptor_attr, TagDescriptor())
|
||||
|
||||
# Add custom manager
|
||||
ModelTaggedItemManager().contribute_to_class(model,
|
||||
tagged_item_manager_attr)
|
@ -1,5 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from tagging.models import Tag, TaggedItem
|
||||
|
||||
admin.site.register(TaggedItem)
|
||||
admin.site.register(Tag)
|
@ -1,107 +0,0 @@
|
||||
"""
|
||||
A custom Model Field for tagging.
|
||||
"""
|
||||
from django.db.models import signals
|
||||
from django.db.models.fields import CharField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from tagging import settings
|
||||
from tagging.models import Tag
|
||||
from tagging.utils import edit_string_for_tags
|
||||
|
||||
class TagField(CharField):
|
||||
"""
|
||||
A "special" character field that actually works as a relationship to tags
|
||||
"under the hood". This exposes a space-separated string of tags, but does
|
||||
the splitting/reordering/etc. under the hood.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['max_length'] = kwargs.get('max_length', 255)
|
||||
kwargs['blank'] = kwargs.get('blank', True)
|
||||
super(TagField, self).__init__(*args, **kwargs)
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
super(TagField, self).contribute_to_class(cls, name)
|
||||
|
||||
# Make this object the descriptor for field access.
|
||||
setattr(cls, self.name, self)
|
||||
|
||||
# Save tags back to the database post-save
|
||||
signals.post_save.connect(self._save, cls, True)
|
||||
|
||||
def __get__(self, instance, owner=None):
|
||||
"""
|
||||
Tag getter. Returns an instance's tags if accessed on an instance, and
|
||||
all of a model's tags if called on a class. That is, this model::
|
||||
|
||||
class Link(models.Model):
|
||||
...
|
||||
tags = TagField()
|
||||
|
||||
Lets you do both of these::
|
||||
|
||||
>>> l = Link.objects.get(...)
|
||||
>>> l.tags
|
||||
'tag1 tag2 tag3'
|
||||
|
||||
>>> Link.tags
|
||||
'tag1 tag2 tag3 tag4'
|
||||
|
||||
"""
|
||||
# Handle access on the model (i.e. Link.tags)
|
||||
if instance is None:
|
||||
return edit_string_for_tags(Tag.objects.usage_for_model(owner))
|
||||
|
||||
tags = self._get_instance_tag_cache(instance)
|
||||
if tags is None:
|
||||
if instance.pk is None:
|
||||
self._set_instance_tag_cache(instance, '')
|
||||
else:
|
||||
self._set_instance_tag_cache(
|
||||
instance, edit_string_for_tags(Tag.objects.get_for_object(instance)))
|
||||
return self._get_instance_tag_cache(instance)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
"""
|
||||
Set an object's tags.
|
||||
"""
|
||||
if instance is None:
|
||||
raise AttributeError(_('%s can only be set on instances.') % self.name)
|
||||
if settings.FORCE_LOWERCASE_TAGS and value is not None:
|
||||
value = value.lower()
|
||||
self._set_instance_tag_cache(instance, value)
|
||||
|
||||
def _save(self, **kwargs): #signal, sender, instance):
|
||||
"""
|
||||
Save tags back to the database
|
||||
"""
|
||||
tags = self._get_instance_tag_cache(kwargs['instance'])
|
||||
if tags is not None:
|
||||
Tag.objects.update_tags(kwargs['instance'], tags)
|
||||
|
||||
def __delete__(self, instance):
|
||||
"""
|
||||
Clear all of an object's tags.
|
||||
"""
|
||||
self._set_instance_tag_cache(instance, '')
|
||||
|
||||
def _get_instance_tag_cache(self, instance):
|
||||
"""
|
||||
Helper: get an instance's tag cache.
|
||||
"""
|
||||
return getattr(instance, '_%s_cache' % self.attname, None)
|
||||
|
||||
def _set_instance_tag_cache(self, instance, tags):
|
||||
"""
|
||||
Helper: set an instance's tag cache.
|
||||
"""
|
||||
setattr(instance, '_%s_cache' % self.attname, tags)
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'CharField'
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
from tagging import forms
|
||||
defaults = {'form_class': forms.TagField}
|
||||
defaults.update(kwargs)
|
||||
return super(TagField, self).formfield(**defaults)
|