mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to pluginize
This commit is contained in:
commit
394b35b435
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.5.4'
|
||||
__version__ = '0.5.5'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
'''
|
||||
Various run time constants.
|
||||
|
@ -6,6 +6,9 @@ Code for the conversion of ebook formats and the reading of metadata
|
||||
from various formats.
|
||||
'''
|
||||
|
||||
import traceback, os
|
||||
from calibre import CurrentDir
|
||||
|
||||
class ConversionError(Exception):
|
||||
|
||||
def __init__(self, msg, only_msg=False):
|
||||
@ -22,3 +25,54 @@ BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm',
|
||||
'html', 'xhtml', 'pdf', 'prc', 'mobi', 'azw',
|
||||
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'oebzip',
|
||||
'rb', 'imp', 'odt']
|
||||
|
||||
class HTMLRenderer(object):
|
||||
|
||||
def __init__(self, page, loop):
|
||||
self.page, self.loop = page, loop
|
||||
self.data = ''
|
||||
self.exception = self.tb = None
|
||||
|
||||
def __call__(self, ok):
|
||||
from PyQt4.Qt import QImage, QPainter, QByteArray, QBuffer
|
||||
try:
|
||||
if not ok:
|
||||
raise RuntimeError('Rendering of HTML failed.')
|
||||
image = QImage(self.page.viewportSize(), QImage.Format_ARGB32)
|
||||
image.setDotsPerMeterX(96*(100/2.54))
|
||||
image.setDotsPerMeterY(96*(100/2.54))
|
||||
painter = QPainter(image)
|
||||
self.page.mainFrame().render(painter)
|
||||
painter.end()
|
||||
ba = QByteArray()
|
||||
buf = QBuffer(ba)
|
||||
buf.open(QBuffer.WriteOnly)
|
||||
image.save(buf, 'JPEG')
|
||||
self.data = str(ba.data())
|
||||
except Exception, e:
|
||||
self.exception = e
|
||||
self.traceback = traceback.format_exc()
|
||||
finally:
|
||||
self.loop.exit(0)
|
||||
|
||||
|
||||
def render_html(path_to_html, width=590, height=750):
|
||||
from PyQt4.QtWebKit import QWebPage
|
||||
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize
|
||||
path_to_html = os.path.abspath(path_to_html)
|
||||
with CurrentDir(os.path.dirname(path_to_html)):
|
||||
page = QWebPage()
|
||||
pal = page.palette()
|
||||
pal.setBrush(QPalette.Background, Qt.white)
|
||||
page.setPalette(pal)
|
||||
page.setViewportSize(QSize(width, height))
|
||||
page.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
|
||||
page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
||||
loop = QEventLoop()
|
||||
renderer = HTMLRenderer(page, loop)
|
||||
|
||||
page.connect(page, SIGNAL('loadFinished(bool)'), renderer)
|
||||
page.mainFrame().load(QUrl.fromLocalFile(path_to_html))
|
||||
loop.exec_()
|
||||
return renderer
|
||||
|
||||
|
@ -339,7 +339,7 @@ OptionRecommendation(name='language',
|
||||
trimmer = ManifestTrimmer()
|
||||
trimmer(self.oeb, self.opts)
|
||||
|
||||
self.log.info('Creating %s output...'%self.output_plugin.name)
|
||||
self.log.info('Creating %s...'%self.output_plugin.name)
|
||||
self.output_plugin.convert(self.oeb, self.output, self.input_plugin, self.opts,
|
||||
self.log)
|
||||
|
||||
|
@ -208,6 +208,10 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
for tag in self.root.xpath('//table | //tr | //th | //td'):
|
||||
tag.tag = 'div'
|
||||
|
||||
# ADE can't handle & in an img url
|
||||
for tag in self.root.xpath('//img[@src]'):
|
||||
tag.set('src', tag.get('src', '').replace('&', ''))
|
||||
|
||||
|
||||
def save(self):
|
||||
for meta in list(self.root.xpath('//meta')):
|
||||
|
@ -162,9 +162,13 @@ class LRFStream(LRFObject):
|
||||
self.stream = stream.read(self.stream_size)
|
||||
if self.stream_flags & 0x200 !=0:
|
||||
l = len(self.stream);
|
||||
key = l % self._scramble_key + 0xF;
|
||||
key = self._scramble_key&0xFF
|
||||
if key != 0 and key <= 0xF0:
|
||||
key = l % key + 0xF
|
||||
else:
|
||||
key = 0
|
||||
if l > 0x400 and (isinstance(self, ImageStream) or isinstance(self, Font) or isinstance(self, SoundStream)):
|
||||
l = 0x400;
|
||||
l = 0x400
|
||||
self.stream = self.descramble_buffer(self.stream, l, key)
|
||||
if self.stream_flags & 0x100 !=0:
|
||||
decomp_size = struct.unpack("<I", self.stream[:4])[0]
|
||||
|
@ -266,12 +266,14 @@ class MobiReader(object):
|
||||
parse_cache[htmlfile] = root
|
||||
self.htmlfile = htmlfile
|
||||
ncx = cStringIO.StringIO()
|
||||
opf = self.create_opf(htmlfile, guide, root)
|
||||
opf, ncx_manifest_entry = self.create_opf(htmlfile, guide, root)
|
||||
self.created_opf_path = os.path.splitext(htmlfile)[0]+'.opf'
|
||||
opf.render(open(self.created_opf_path, 'wb'), ncx)
|
||||
opf.render(open(self.created_opf_path, 'wb'), ncx,
|
||||
ncx_manifest_entry=ncx_manifest_entry)
|
||||
ncx = ncx.getvalue()
|
||||
if ncx:
|
||||
open(os.path.splitext(htmlfile)[0]+'.ncx', 'wb').write(ncx)
|
||||
ncx_path = os.path.join(os.path.dirname(htmlfile), 'toc.ncx')
|
||||
open(ncx_path, 'wb').write(ncx)
|
||||
|
||||
with open('styles.css', 'wb') as s:
|
||||
s.write(self.base_css_rules+'\n\n')
|
||||
@ -284,8 +286,9 @@ class MobiReader(object):
|
||||
if self.book_header.exth is not None or self.embedded_mi is not None:
|
||||
self.log.debug('Creating OPF...')
|
||||
ncx = cStringIO.StringIO()
|
||||
opf = self.create_opf(htmlfile, guide, root)
|
||||
opf.render(open(os.path.splitext(htmlfile)[0]+'.opf', 'wb'), ncx)
|
||||
opf, ncx_manifest_entry = self.create_opf(htmlfile, guide, root)
|
||||
opf.render(open(os.path.splitext(htmlfile)[0]+'.opf', 'wb'), ncx,
|
||||
ncx_manifest_entry )
|
||||
ncx = ncx.getvalue()
|
||||
if ncx:
|
||||
open(os.path.splitext(htmlfile)[0]+'.ncx', 'wb').write(ncx)
|
||||
@ -434,7 +437,10 @@ class MobiReader(object):
|
||||
for ref in opf.guide:
|
||||
if ref.type.lower() == 'toc':
|
||||
toc = ref.href()
|
||||
|
||||
ncx_manifest_entry = None
|
||||
if toc:
|
||||
ncx_manifest_entry = 'toc.ncx'
|
||||
elems = root.xpath('//*[@id="%s"]'%toc.partition('#')[-1])
|
||||
tocobj = None
|
||||
ent_pat = re.compile(r'&(\S+?);')
|
||||
@ -461,7 +467,7 @@ class MobiReader(object):
|
||||
if tocobj is not None:
|
||||
opf.set_toc(tocobj)
|
||||
|
||||
return opf
|
||||
return opf, ncx_manifest_entry
|
||||
|
||||
|
||||
def sizeof_trailing_entries(self, data):
|
||||
@ -589,7 +595,7 @@ def get_metadata(stream):
|
||||
if mr.book_header.exth is None:
|
||||
mi = MetaInformation(mr.name, [_('Unknown')])
|
||||
else:
|
||||
mi = mr.create_opf('dummy.html')
|
||||
mi = mr.create_opf('dummy.html')[0]
|
||||
try:
|
||||
if hasattr(mr.book_header.exth, 'cover_offset'):
|
||||
cover_index = mr.book_header.first_image_index + \
|
||||
|
@ -44,7 +44,7 @@ class OEBOutput(OutputFormatPlugin):
|
||||
else:
|
||||
raw = etree.tostring(raw, encoding='utf-8',
|
||||
pretty_print=opts.pretty_print)
|
||||
raw = raw + '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||
raw = '<?xml version="1.0" encoding="utf-8" ?>\n'+raw
|
||||
if isinstance(raw, unicode):
|
||||
raw = raw.encode('utf-8')
|
||||
with open(path, 'wb') as f:
|
||||
|
@ -7,14 +7,12 @@ Defines various abstract base classes that can be subclassed to create powerful
|
||||
__docformat__ = "restructuredtext en"
|
||||
|
||||
|
||||
import logging, os, cStringIO, time, traceback, re, urlparse, sys, tempfile, functools
|
||||
import logging, os, cStringIO, time, traceback, re, urlparse, sys
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from contextlib import nested, closing
|
||||
|
||||
from PyQt4.Qt import QApplication, QFile, Qt, QPalette, QSize, QImage, QPainter, \
|
||||
QBuffer, QByteArray, SIGNAL, QUrl, QEventLoop, QIODevice
|
||||
from PyQt4.QtWebKit import QWebPage
|
||||
from PyQt4.Qt import QApplication, QFile, QIODevice
|
||||
|
||||
|
||||
from calibre import browser, __appname__, iswindows, \
|
||||
@ -22,14 +20,15 @@ from calibre import browser, __appname__, iswindows, \
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||
from calibre.ebooks.lrf import entity_to_unicode
|
||||
from calibre.ebooks import render_html
|
||||
from calibre.ebooks.metadata.toc import TOC
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.web.feeds import feed_from_xml, templates, feeds_from_index, Feed
|
||||
from calibre.web.fetch.simple import option_parser as web2disk_option_parser
|
||||
from calibre.web.fetch.simple import RecursiveFetcher
|
||||
from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.gui2 import images_rc # Needed for default cover
|
||||
from calibre.ptempfile import PersistentTemporaryFile, \
|
||||
PersistentTemporaryDirectory
|
||||
|
||||
|
||||
class BasicNewsRecipe(object):
|
||||
@ -787,15 +786,18 @@ class BasicNewsRecipe(object):
|
||||
'''
|
||||
Create a generic cover for recipes that dont have a cover
|
||||
'''
|
||||
from calibre.gui2 import images_rc # Needed for access to logo
|
||||
images_rc
|
||||
if QApplication.instance() is None: QApplication([])
|
||||
f = QFile(':/library')
|
||||
f.open(QIODevice.ReadOnly)
|
||||
img = str(f.readAll())
|
||||
img_data = str(f.readAll())
|
||||
tdir = PersistentTemporaryDirectory('_default_cover')
|
||||
img = os.path.join(tdir, 'logo.png')
|
||||
with open(img, 'wb') as g:
|
||||
g.write(img_data)
|
||||
f.close()
|
||||
f = tempfile.NamedTemporaryFile(suffix='library.png')
|
||||
f.write(img)
|
||||
f.flush()
|
||||
img = f.name
|
||||
img = os.path.basename(img)
|
||||
html= u'''\
|
||||
<html>
|
||||
<head>
|
||||
@ -834,38 +836,16 @@ class BasicNewsRecipe(object):
|
||||
date=strftime(self.timefmt),
|
||||
app=__appname__ +' '+__version__,
|
||||
img=img)
|
||||
f2 = tempfile.NamedTemporaryFile(suffix='cover.html')
|
||||
f2.write(html.encode('utf-8'))
|
||||
f2.flush()
|
||||
page = QWebPage()
|
||||
pal = page.palette()
|
||||
pal.setBrush(QPalette.Background, Qt.white)
|
||||
page.setPalette(pal)
|
||||
page.setViewportSize(QSize(590, 750))
|
||||
page.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
|
||||
page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
||||
loop = QEventLoop()
|
||||
def render_html(page, loop, ok):
|
||||
try:
|
||||
image = QImage(page.viewportSize(), QImage.Format_ARGB32)
|
||||
image.setDotsPerMeterX(96*(100/2.54))
|
||||
image.setDotsPerMeterY(96*(100/2.54))
|
||||
painter = QPainter(image)
|
||||
page.mainFrame().render(painter)
|
||||
painter.end()
|
||||
ba = QByteArray()
|
||||
buf = QBuffer(ba)
|
||||
buf.open(QBuffer.WriteOnly)
|
||||
image.save(buf, 'JPEG')
|
||||
image_data = str(ba.data())
|
||||
cover_file.write(image_data)
|
||||
hf = os.path.join(tdir, 'cover.htm')
|
||||
with open(hf, 'wb') as f:
|
||||
f.write(html.encode('utf-8'))
|
||||
renderer = render_html(hf)
|
||||
if renderer.tb is not None:
|
||||
self.logger.warning('Failed to render default cover')
|
||||
self.logger.debug(renderer.tb)
|
||||
else:
|
||||
cover_file.write(renderer.data)
|
||||
cover_file.flush()
|
||||
finally:
|
||||
loop.exit(0)
|
||||
|
||||
page.connect(page, SIGNAL('loadFinished(bool)'), functools.partial(render_html, page, loop))
|
||||
page.mainFrame().load(QUrl.fromLocalFile(f2.name))
|
||||
loop.exec_()
|
||||
|
||||
|
||||
def create_opf(self, feeds, dir=None):
|
||||
|
@ -13,7 +13,6 @@ class Exiled(BasicNewsRecipe):
|
||||
__author__ = 'Darko Miletic'
|
||||
description = "Mankind's only alternative since 1997 - Formerly known as The eXile"
|
||||
publisher = 'Exiled Online'
|
||||
language = _('English')
|
||||
category = 'news, politics, international'
|
||||
oldest_article = 15
|
||||
max_articles_per_feed = 100
|
||||
@ -21,10 +20,12 @@ class Exiled(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
encoding = 'utf8'
|
||||
remove_javascript = True
|
||||
language = _('English')
|
||||
cover_url = 'http://exiledonline.com/wp-content/themes/exiledonline_theme/images/header-sm.gif'
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment' , description
|
||||
, '--base-font-size', '10'
|
||||
, '--category' , category
|
||||
, '--publisher' , publisher
|
||||
]
|
||||
@ -49,3 +50,8 @@ class Exiled(BasicNewsRecipe):
|
||||
soup.head.insert(0,mtag)
|
||||
return soup
|
||||
|
||||
def get_article_url(self, article):
|
||||
raw = article.get('link', None)
|
||||
final = raw + 'all/1/'
|
||||
return final
|
||||
|
||||
|
@ -6,7 +6,7 @@ __copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
laprensa.com.ni
|
||||
'''
|
||||
|
||||
import locale
|
||||
import datetime
|
||||
import time
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
@ -23,23 +23,9 @@ class LaPrensa_ni(BasicNewsRecipe):
|
||||
encoding = 'cp1252'
|
||||
remove_javascript = True
|
||||
language = _('Spanish')
|
||||
|
||||
#Locale setting to get appropriate date/month values in Spanish
|
||||
try:
|
||||
#Windows seting for locale
|
||||
locale.setlocale(locale.LC_TIME,'Spanish_Nicaragua')
|
||||
except locale.Error:
|
||||
#Linux setting for locale -- choose one appropriate for your distribution
|
||||
try:
|
||||
locale.setlocale(locale.LC_TIME,'es_NI')
|
||||
except locale.Error:
|
||||
try:
|
||||
locale.setlocale(locale.LC_TIME,'es_ES')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
current_index = time.strftime("http://www.laprensa.com.ni/archivo/%Y/%B/%d/noticias/")
|
||||
months_es = ['enero','febrero','marzo','abril','mayo','junio','julio','agosto','septiembre','octubre','noviembre','diciembre']
|
||||
current_month = months_es[datetime.date.today().month - 1]
|
||||
current_index = time.strftime("http://www.laprensa.com.ni/archivo/%Y/" + current_month + "/%d/noticias/")
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
@ -91,6 +77,3 @@ class LaPrensa_ni(BasicNewsRecipe):
|
||||
totalfeeds.append((feedtitle, articles))
|
||||
return totalfeeds
|
||||
|
||||
def cleanup(self):
|
||||
#Going back to the default locale
|
||||
locale.setlocale(locale.LC_TIME,'')
|
||||
|
@ -2,10 +2,10 @@ 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 calibre.www.apps.tagging.fields import TagField
|
||||
from tagging.fields import TagField
|
||||
from calibre.www.apps.blog.managers import PublicManager
|
||||
|
||||
import calibre.www.apps.tagging as tagging
|
||||
import tagging
|
||||
|
||||
class Category(models.Model):
|
||||
"""Category model."""
|
||||
|
@ -40,10 +40,10 @@ INSTALLED_APPS = (
|
||||
'django.contrib.sites',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.comments',
|
||||
'django.contrib.markup',
|
||||
'calibre.www.apps.inlines',
|
||||
'calibre.www.apps.tagging',
|
||||
'tagging',
|
||||
'calibre.www.apps.blog',
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
@ -2,14 +2,16 @@ 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()
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
|
||||
# (r'^admin/(.*)', admin.site.root),
|
||||
(r'^admin/(.*)', admin.site.root),
|
||||
|
||||
(r'^comments/', include('django.contrib.comments.urls')),
|
||||
(r'', include('calibre.www.apps.blog.urls')),
|
||||
|
||||
|
||||
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from calibre.www.apps.tagging.managers import ModelTaggedItemManager, TagDescriptor
|
||||
from tagging.managers import ModelTaggedItemManager, TagDescriptor
|
||||
|
||||
VERSION = (0, 3, 'pre')
|
||||
|
@ -1,5 +1,5 @@
|
||||
from django.contrib import admin
|
||||
from calibre.www.apps.tagging.models import Tag, TaggedItem
|
||||
from tagging.models import Tag, TaggedItem
|
||||
|
||||
admin.site.register(TaggedItem)
|
||||
admin.site.register(Tag)
|
@ -5,9 +5,9 @@ from django.db.models import signals
|
||||
from django.db.models.fields import CharField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from calibre.www.apps.tagging import settings
|
||||
from calibre.www.apps.tagging.models import Tag
|
||||
from calibre.www.apps.tagging.utils import edit_string_for_tags
|
||||
from tagging import settings
|
||||
from tagging.models import Tag
|
||||
from tagging.utils import edit_string_for_tags
|
||||
|
||||
class TagField(CharField):
|
||||
"""
|
||||
@ -101,7 +101,7 @@ class TagField(CharField):
|
||||
return 'CharField'
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
from calibre.www.apps.tagging import forms
|
||||
from tagging import forms
|
||||
defaults = {'form_class': forms.TagField}
|
||||
defaults.update(kwargs)
|
||||
return super(TagField, self).formfield(**defaults)
|
@ -4,9 +4,9 @@ Tagging components for Django's form library.
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from calibre.www.apps.tagging import settings
|
||||
from calibre.www.apps.tagging.models import Tag
|
||||
from calibre.www.apps.tagging.utils import parse_tag_input
|
||||
from tagging import settings
|
||||
from tagging.models import Tag
|
||||
from tagging.utils import parse_tag_input
|
||||
|
||||
class AdminTagForm(forms.ModelForm):
|
||||
class Meta:
|
@ -5,7 +5,7 @@ application.
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
|
||||
from calibre.www.apps.tagging.models import Tag, TaggedItem
|
||||
from tagging.models import Tag, TaggedItem
|
||||
|
||||
class ModelTagManager(models.Manager):
|
||||
"""
|
@ -13,9 +13,9 @@ from django.db import connection, models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from calibre.www.apps.tagging import settings
|
||||
from calibre.www.apps.tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input
|
||||
from calibre.www.apps.tagging.utils import LOGARITHMIC
|
||||
from tagging import settings
|
||||
from tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input
|
||||
from tagging.utils import LOGARITHMIC
|
||||
|
||||
qn = connection.ops.quote_name
|
||||
|
@ -159,7 +159,7 @@ def get_tag_list(tags):
|
||||
* A ``Tag`` ``QuerySet``.
|
||||
|
||||
"""
|
||||
from calibre.www.apps.tagging.models import Tag
|
||||
from tagging.models import Tag
|
||||
if isinstance(tags, Tag):
|
||||
return [tags]
|
||||
elif isinstance(tags, QuerySet) and tags.model is Tag:
|
||||
@ -201,7 +201,7 @@ def get_tag(tag):
|
||||
|
||||
If no matching tag can be found, ``None`` will be returned.
|
||||
"""
|
||||
from calibre.www.apps.tagging.models import Tag
|
||||
from tagging.models import Tag
|
||||
if isinstance(tag, Tag):
|
||||
return tag
|
||||
|
@ -5,8 +5,8 @@ from django.http import Http404
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.list_detail import object_list
|
||||
|
||||
from calibre.www.apps.tagging.models import Tag, TaggedItem
|
||||
from calibre.www.apps.tagging.utils import get_tag, get_queryset_and_model
|
||||
from tagging.models import Tag, TaggedItem
|
||||
from tagging.utils import get_tag, get_queryset_and_model
|
||||
|
||||
def tagged_object_list(request, queryset_or_model=None, tag=None,
|
||||
related_tags=False, related_tag_counts=True, **kwargs):
|
Loading…
x
Reference in New Issue
Block a user