import random, itertools from zope.interface import implements from twisted.python import filepath from nevow import tags, url, static from nevow.page import renderer from nevow.athena import expose from nevow.vhost import VHostMonsterResource from axiom.attributes import integer, text from axiom.item import Item from axiom.upgrade import registerUpgrader from axiom.errors import ItemNotFound from xmantissa import website, publicweb from xmantissa.ixmantissa import IPublicPage, ISiteRootPlugin, ISessionlessSiteRootPlugin from xmantissa.webtheme import ThemedElement, ThemedDocumentFactory from circus import const from circus.icircus import IQuoteDatabase from circus.color import colorizeLine TITLE = u'Slipgate QDB' root = url.root.child('FlyingCircus') class FlyingCircusPublicPage(Item): implements(IPublicPage) typeName = 'flyingcircus_public_page' schemaVersion = 2 powerupInterfaces = [IPublicPage] dummy = integer() def getResource(self): return PublicRootPage(self.getQdb()) def getQdb(self): return IQuoteDatabase(self.store) def pp1to2(old): return old.upgradeVersion(FlyingCircusPublicPage.typeName, 1, 2) registerUpgrader(pp1to2, FlyingCircusPublicPage.typeName, 1, 2) class PublicContentPage(publicweb.PublicAthenaLivePage): docFactory = ThemedDocumentFactory('qdb-shell', 'templateResolver') chirps = ['...', ':P', ';P', ';)', ':)', '8)', ':D', 'wtf', 'stfu', 'lol', 'rofl', 'hax', 'pwnt'] def __init__(self, **kw): store = kw.pop('store', None) if store is None: store = getattr(self, 'store', None) assert store is not None while store.parent: store = store.parent self.store = store fragment = kw.pop('fragment', None) if fragment is None: fragment = getattr(self, 'getFragment', lambda: None)() super(PublicContentPage, self).__init__(store, fragment, **kw) def render_emoticon(self, ctx, data): tag = ctx.tag return tag[random.sample(self.chirps, 1)] class PublicRootPage(PublicContentPage): addSlash = True def __init__(self, qdb, **kw): super(PublicRootPage, self).__init__(store=qdb.store, **kw) self.qdb = qdb def quotesList(self, quotes, name): return QuoteListPage(store=self.qdb.store, quotes=quotes, name=name) def child_static(self, ctx): s = filepath.FilePath(__file__).parent().child('static') return static.File(s.path) def child_recent(self, ctx): return self.quotesList(self.qdb.getRecentQuotes(), u'Recent') def child_top(self, ctx): return self.quotesList(self.qdb.getTopQuotes(), u'Top') def child_worst(self, ctx): return self.quotesList(self.qdb.getBottomQuotes(), u'Worst') def child_random(self, ctx): return self.quotesList(self.qdb.getRandomQuotes(), u'Random') def child_addQuote(self, ctx): return AddQuotePage(self.qdb) def child_names(self, ctx): return NicknamesPage(self.qdb) child_ = child_recent #def locateChild(self, ctx, segments): # if len(segments) == 2 and segments[0] == 'names': # nicknames = unicode(segments[1]).split(u'+') # return self.quotesList(self.qdb.getQuotesByNicknames(nicknames)), segments[2:] # # return super(PublicRootPage, self).locateChild(ctx, segments) def childFactory(self, ctx, name): try: qid = int(name) except ValueError: return None try: quote = self.qdb.getQuote(qid) except ItemNotFound: return None return QuotePage(store=self.qdb.store, quote=quote) class QuoteElement(ThemedElement): fragmentName = 'quote' jsClass = u'FlyingCircus.Quotes.Quote' def __init__(self, quote): super(QuoteElement, self).__init__() self.quote = quote @property def title(self): return u'%s - #%s' % (TITLE, self.quote.qid,) def getInitialArguments(self): return (self.quote.qid,) + self.getRating() def linkColoredNick(self, nickname, color): # XXX: this is a rather horrible hack nicknames = set(getattr(self.fragmentParent, 'nicknames', [])) style = 'color: rgb(%d, %d, %d);' % color if nickname not in nicknames: nicknames.add(nickname) child = u'+'.join(nicknames) title = u'Add "%s" to the filter' % (nickname,) return tags.span(class_='nick')[ tags.a(style=style, href=root.child('names').child(nickname))[nickname], tags.a(class_='filter-add', title=title, href=root.child('names').child(child))[' ']] return tags.span(style=style)[nickname] def colorizeContent(self, content): return (colorizeLine(self.linkColoredNick, line, self.quote.participants) for line in content.splitlines()) @renderer def quoteContent(self, req, tag): tag.fillSlots('permalink', root.child(self.quote.qid)) tag.fillSlots('qid', self.quote.qid) tag.fillSlots('content', self.colorizeContent(self.quote.content)) # XXX: find somewhere to put this information #added = self.quote.added.asDatetime(const.timezone).strftime('%Y-%m-%d %H:%M:%S') #tag.fillSlots('created', added) return tag @expose def getRating(self): return self.quote.rating, self.quote.votes @expose def plus(self): self.quote.plus() return self.getRating() @expose def minus(self): self.quote.minus() return self.getRating() class QuotePage(PublicContentPage): addSlash = True def __init__(self, store, quote, **kw): self.quote = quote fragment = QuoteElement(quote) super(QuotePage, self).__init__(store=store, fragment=fragment, **kw) def child_raw(self, ctx): quote = self.quote lines = quote.content.splitlines() lines.insert(0, u'#%s (%s/%s)' % (quote.qid, quote.rating, quote.votes)) text = '\n'.join(lines) return static.Data(text.encode('utf-8'), 'text/plain; charset=UTF-8') class QuoteListElement(ThemedElement): fragmentName = 'quotes' def __init__(self, quotes, name): super(QuoteListElement, self).__init__() self.quotes = quotes self.name = name @property def title(self): return u'%s - %s' % (TITLE, self.name) @renderer def quoteList(self, req, tag): def quoteElement(quote): e = QuoteElement(quote) e.setFragmentParent(self) return e if self.quotes: oddeven = itertools.cycle(['even', 'odd']) return tag[ (tags.div[quoteElement(q)](class_=cls) for q, cls in itertools.izip(self.quotes, oddeven))] else: return tags.h2(class_='quote', style='text-align: center;')[u'No quotes.'] class QuoteListPage(PublicContentPage): addSlash = True def __init__(self, store, quotes, name, **kw): fragment = QuoteListElement(list(quotes), name) super(QuoteListPage, self).__init__(store=store, fragment=fragment, **kw) class QuoteAdderElement(ThemedElement): fragmentName = 'add-quote' jsClass = u'FlyingCircus.Quotes.QuoteAdder' title = u'%s - Add Quote' % (TITLE,) def __init__(self, qdb): super(QuoteAdderElement, self).__init__() self.qdb = qdb @expose def addQuote(self, content): quote = self.qdb.addQuote(content) url = u'/FlyingCircus/%d' % (quote.qid,) return quote.qid, url class AddQuotePage(PublicContentPage): def __init__(self, qdb, **kw): fragment = QuoteAdderElement(qdb) super(AddQuotePage, self).__init__(store=qdb.store, fragment=fragment, **kw) class NicknamesElement(QuoteListElement): fragmentName = 'quotes-nicknames' def __init__(self, qdb, nicknames): self.qdb = qdb self.nicknames = sorted(nicknames, key=lambda n: n.lower()) quotes = list(self.qdb.getQuotesByNicknames(nicknames)) super(NicknamesElement, self).__init__(quotes, u'Quotes involving: ' + u', '.join(self.nicknames)) @renderer def nicknameList(self, req, tag): nicknames = set(self.nicknames) def linkSans(nickname): names = nicknames - set([nickname]) if names: href = u'+'.join(names) else: href = root return u' (', tags.a(href=href)[u'-'], u')' def link(nickname): yield tags.a(href=nickname)[nickname] yield linkSans(nickname) def links(): it = iter(self.nicknames) yield link(it.next()) for name in it: yield u', ' yield link(name) return tag[u'Quotes featuring: ', links()] class NicknamesPage(PublicContentPage): def __init__(self, qdb, nicknames=[], **kw): self.qdb = qdb self.nicknames = nicknames fragment = NicknamesElement(self.qdb, self.nicknames) super(NicknamesPage, self).__init__(store=qdb.store, fragment=fragment, **kw) def childFactory(self, ctx, name): nicknames = set(self.nicknames) nicknames.update(unicode(name).split(u'+')) return NicknamesPage(self.qdb, list(nicknames)) class FrontPage(Item, website.PrefixURLMixin): implements(ISiteRootPlugin) typeName = 'flyingcircus_front_page' schemaVersion = 1 sessioned = True publicViews = integer(doc=""" The number of times this object has been viewed in a public (non-authenticated) context. This includes renderings of the front page only. """, default=0) privateViews = integer(doc=""" The number of times this object has been viewed in a private (authenticated) context. This only counts the number of times users have been redirected from "/" to "/private". """, default=0) prefixURL = text(doc=""" See L{website.PrefixURLMixin}. """, default=u'', allowNone=False) def createResource(self): pfp = PublicFrontPage(self, staticContent=None, forUser=None) #pfp.remember(FourOhFourPage(self.store), ICanHandleNotFound) return pfp class PublicFrontPage(publicweb.PublicFrontPage): def child_(self, ctx): return root class VHost(Item, website.PrefixURLMixin): implements(ISessionlessSiteRootPlugin) typeName = 'flyingcircus_vhost' schemaVersion = 1 sessionless = True prefixURL = text(default=u'vhost') def createResource(self): return VHostMonsterResource()