PK ~68“×2EGG-INFO/zip-safe PK~68}è.EGG-INFO/SOURCES.txtsetup.cfg setup.py blog/__init__.py blog/admin.py blog/new_blog.py blog/web_ui.py tBlog.egg-info/PKG-INFO tBlog.egg-info/SOURCES.txt tBlog.egg-info/dependency_links.txt tBlog.egg-info/entry_points.txt tBlog.egg-info/requires.txt tBlog.egg-info/top_level.txt PK~68¹ÔºuEGG-INFO/entry_points.txt[trac.plugins] tBlog = tBlog PK~68“×2EGG-INFO/dependency_links.txt PK~68^¶íxEGG-INFO/PKG-INFOMetadata-Version: 1.0 Name: tBlog Version: 0.2.1dev Summary: Bloging system plugin for Trac Home-page: http://trac-hacks.org/wiki/TracBlogPlugin Author: John Hampton Author-email: pacopablo@asylumware.com License: BSD Description: UNKNOWN Platform: UNKNOWN PK~68¢õOEEGG-INFO/requires.txtTracTags>=0.3,<0.5 TracWebAdminPK~68…5PûEGG-INFO/top_level.txttBlog PKÈ“ƒ4ì·E\fftBlog/__init__.pyfrom web_ui import * try: from admin import * except ImportError: pass from new_blog import * PK ~68À9{5k5ktBlog/web_ui.pyc;ò <ÁÆEc@sÒdkZdkZdkZdkZdkZdkZdklZdklZdk Tdk l Z dk l Z lZlZdk lZdklZlZlZlZydklZWnej od „ZnXd klZlZlZd klZd kl Z d k!l"Z"dk#l$Z$dk%l&Z&ei'dei(ƒi)Z*ddddddgZ+dgZ,ei'dƒZ-d„Z.d„Z/defd„ƒYZ0e1de2gd„Z3de4fd„ƒYZ5dS( N(sStringIO(sresource_filename(s*(sIRequestHandler(sITemplateProvidersadd_stylesheetsadd_link(sINavigationContributor(sMarkups format_datesformat_datetimes http_date(s to_unicodecCs|S(N(sx(sx((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys!s(s Formatterswiki_to_oneliners wiki_to_html(sWikiPage(sIWikiMacroProvider(sIPermissionRequestor(s TagEngine(s parseargss^=+\s+([^\n\r=]+?)\s+=+\s+(.+)$struesyessoksonsenableds1sTracBlogPlugins[,\s]+cCsPt|tƒo|Snt|ttfƒo|iƒiƒtjSntSdS(s?Returns whether or not a values represents a boolen value N( s isinstancesvalsboolsstrsunicodesstripslowers BOOLS_TRUEsNone(sval((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pysbool_val3s cCs4ti||ƒ\}}||jo|p|SdS(sIMakes sure that the day passed in is avalid for the month and yearN(scalendars monthrangesyearsmonths_snumdayssday(syearsmonthsdays_snumdays((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys date_crop=ssNoFloatFormattercBs tZdZd„Zd„ZRS(s@A modified formatter that inserts macro_no_float=1 into the HDF.cOs d|jo|d|_|d=nd|jod|didBlog(sbool_valsselfsenvsconfigsgetsTruesnav_barsreqspermshas_permissionshrefsblogshdfsMarkup(sselfsreqsnav_bar((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pysget_navigation_items¤s ! ccsdVdS(NsBlogShow((sself((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys get_macros¬scCsti|iƒSdS(s Return the subclass's docstring.N(sinspectsgetdocsselfs __class__(sselfsname((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pysget_macro_description¯sc CsÖt|dƒ|i|ƒ\}} | of|iii dddƒ}gi }t i|ƒD]'}|iƒo||iƒƒq\q\~}n|i||| Žt|id<|iidƒ}|iidƒSdS(s# Display the blog in the wiki page sblog/css/blog.csssblogs default_tags blog.macrosblog.csN(sadd_stylesheetsreqsselfs_split_macro_argsscontentstagsskwargssenvsconfigsgetststrsappends_[1]s _tag_splitssplitstsstrips_generate_blogsTrueshdfsrendersdata( sselfsreqsnamescontents_[1]stagssdataststrstskwargs((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys render_macro³s K cCs7g}h}|ot|ƒ\}}n||fSdS(sKReturn a list of arguments and a dictionary of keyword arguements N(sargsskwargssargvs parseargs(sselfsargvsargsskwargs((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys_split_macro_argsÅs cCs|idjSdS(Ns/blog(sreqs path_info(sselfsreq((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys match_requestÏsc Csh|iidƒt|dƒt|dƒ|iidƒ}|iidƒ}h}x<|ii ƒD]+}|djo|i||||d|'ƒ}|,i@}tBiCdtBiDƒ}|iFd |ƒ}tBiCdtBiDƒ}*|*iFd |ƒ}|id||t+ƒ}| ot|t+ƒ o%t+|iiidddƒƒ}n|iI||#|ƒ}gi#}-|iJ|#ƒD]!}||1jo|-|ƒq'q'~-}&g} xctN|&d ƒD]Q\})}hd|<d|<d|)t |&d ƒdj<} | i#| ƒqhqhWtR|ƒ}|o"|iTdƒ}|iTdƒ}n|#}tV||i|ƒ} tXiYtZ| ƒƒ}hd|#<d |<d!|ii\i|#ƒ<d"t]|"|#|iƒ<d#|<d$t^|ƒ<d%|<d&t_||i|d |2ƒ<d'|<d(t]||iƒ<d)hd*t |&ƒ<d)| <d+t |&ƒdjpd<<}|oJ|iad,ƒd-jo||d.|+d|'ƒ}/|/|d0    !% A 5  ;?i     2  cCstiiƒ}|id|tdtƒp|i}|id|tdtƒp|i}|id|tdtƒp|i }t |||ƒ}ti|||ƒ} |i i idddƒ}tt|iƒƒ}ti|ƒti||ƒ}h}x¡t|ƒD]“\}} hd|<d g<||td|dƒD])}hdd<dg<|||)*    cCsr|ii|tƒ}| o"y||}Wq?t}q?Xn|o$y||ƒ}Wqjt}qjXn|SdS(s¸Return the value for the specified key from either req or kwargs If the value is present in both, then the value from req is used. If the value doesn't exist in either, then None is returned. An optional conversion function can be passed. If present, the value will be passed to the conversion function before returning. If the conversion function raises an error, None will be returned N(sreqsargssgetskeysNonesvalskwargssconvert(sselfskeysreqskwargssconvertsval((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys _choose_value5s c CsKg}d} d}t}|idƒ}| o@t i d|t i ƒ}|i |o |idƒpdƒnx™|D]‘} |d7}| t| ƒ7} |i | ƒ| o| iƒdj}n|o| iƒdj}| |jo|o|i dƒnPqvqvqvW|t|ƒjo|o|i d|ƒndi|ƒSd S( sTrim the page text to the {{{post_size}} in trac.ini The timming isn't exact. It trims to the first line that causes the page to exceed the value storing in {{{post_size}}}. If the line is in the middle of a code block, it will close the block. is s ^\s*=\s+.*=sis{{{s}}}s''[wiki:%s (...)]''N(stliness entry_sizes line_countsFalses in_code_blockstextssplitsliness post_sizesressearchsMsretsappendsgroupslineslensstrips page_namesjoin( sselfstexts page_names post_sizestliness in_code_blocks line_countslinessrets entry_sizesline((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys _trim_pageMs2(   cCsttdƒgSdS(sb Return the absolute path of the directory containing the provided templates s templatesN(sresource_filenames__name__(sself((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pysget_templates_dirsqscCsdttdƒfgSdS(s  Return a list of directories with static resources (such as style sheets, images, etc.) Each item in the list must be a `(prefix, abspath)` tuple. The `prefix` part defines the path in the URL that requests to these resources are prefixed with. The `abspath` is the absolute path to the directory containing the resources on the local file system. sblogshtdocsN(sresource_filenames__name__(sself((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pysget_htdocs_dirsxs (s__name__s __module__s__doc__s implementssIRequestHandlersITemplateProvidersINavigationContributorsIWikiMacroProvidersIPermissionRequestorsget_permission_actionssget_active_navigation_itemsget_navigation_itemss get_macrossget_macro_descriptions render_macros_split_macro_argss match_requestsprocess_requests_generate_blogs_generate_calendarsNones _get_tallys_add_to_talliess_get_time_ranges _choose_values _trim_pagesget_templates_dirssget_htdocs_dirs(((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pysTracBlogPlugincs* 3           ’ J " 8  $ (6ssysstimesdatetimesinspectscalendarsresStringIOs pkg_resourcessresource_filenames trac.corestrac.websIRequestHandlerstrac.web.chromesITemplateProvidersadd_stylesheetsadd_linksINavigationContributors trac.utilsMarkups format_datesformat_datetimes http_datestrac.util.texts to_unicodes ImportErrorstrac.wiki.formatters Formatterswiki_to_oneliners wiki_to_htmlstrac.wiki.modelsWikiPages trac.wiki.apisIWikiMacroProviders trac.permsIPermissionRequestors tractags.apis TagEnginestractags.parseargss parseargsscompilesDOTALLsmatchs_title_split_matchs BOOLS_TRUEs__all__s _tag_splitsbool_vals date_cropsNoFloatFormattersNonesFalseswiki_to_nofloat_htmls ComponentsTracBlogPlugin(#s to_unicodesIRequestHandlers wiki_to_htmlsresource_filenamesdatetimesITemplateProviders http_datesadd_linkscalendars Formatters BOOLS_TRUEs format_datesWikiPages__all__sadd_stylesheetsres_title_split_matchsINavigationContributorsIWikiMacroProvidersIPermissionRequestorsMarkupsinspectssyss _tag_splitswiki_to_oneliners TagEngines date_cropsStringIOs parseargssformat_datetimesTracBlogPluginsbool_valstimeswiki_to_nofloat_htmlsNoFloatFormatter((s0build/bdist.darwin-8.0.1-x86/egg/tBlog/web_ui.pys?s@                  PK ~68@ƒ\ã+ã+tBlog/new_blog.pyc;ò #µbEc@sîdkZdkZdkZdkZdkZdklZdkTdkl Z dk l Z l Z dk lZdklZdklZdklZd klZd klZd klZd gZeid ƒZd efd„ƒYZdS(N(sresource_filename(s*(sIRequestHandler(sITemplateProvidersadd_stylesheet(sIPermissionRequestor(sMarkup(sIWikiMacroProvider(s wiki_to_html(sWikiPage(s TagEngine(s parseargssBlogPosts[,\s]+cBstZdZeeeeeƒd„Zd„Z d„Z d„Z d„Z d„Z d„Zd„Zed „Zd „Zd „Zd „ZRS( srInserts a link to create a new blog post Accepts keyword arguments that specify default parameters. The macro will be hidden unless the user has {{{BLOG_POSTER}}} permissions. '''tag''' - Tag that populates the "Tag under" field. This key may be specified as a tuple or list to pass multiple values.[[br]] '''blogtitle''' - Default blog entry title.[[br]] '''text''' - Default entry body text.[[br]] '''pagename''' - Default wiki page name.[[br]] '''readonly''' - Default readonly page status.[[br]] '''link''' - Text to display as the link.[[br]] === Examples === {{{ [[BlogPost()]] [[BlogPost(tag=(blog,pacopablo))]] [[BlogPost(tag=blog,blogtitle="A Simple Title",text="Body Text")]] [[BlogPost(tag=blog,pagename=blog/newpage,readonly=1)]] [[BlogPost(tag=(blog,pacopablo),link="A New Blog Post")]] }}} cCs dgSdS(Ns BLOG_POSTER((sself((s2build/bdist.darwin-8.0.1-x86/egg/tBlog/new_blog.pysget_permission_actionsAsccsdVdS(NsBlogPost((sself((s2build/bdist.darwin-8.0.1-x86/egg/tBlog/new_blog.pys get_macrosEscCsti|iƒSdS(s Return the subclass's docstring.N(sinspectsgetdocsselfs __class__(sselfsname((s2build/bdist.darwin-8.0.1-x86/egg/tBlog/new_blog.pysget_macro_descriptionHscCsœ|iidƒo|i|ƒ\}}y|d}|d=Wn.t j o"|i i i dddƒ}nXt d|i iid||ƒSndSd S( s# Display the blog in the wiki page s BLOG_POSTERslinksblogs new_blog_links New Blog Posts%ssnewsN(sreqspermshas_permissionsselfs_split_macro_argsscontentsargsskwargss blog_linksKeyErrorsenvsconfigsgetsMarkupshrefsblog(sselfsreqsnamescontentsargss blog_linkskwargs((s2build/bdist.darwin-8.0.1-x86/egg/tBlog/new_blog.pys render_macroLs   cCs7g}h}|ot|ƒ\}}n||fSdS(sJReturn a list of arguments and a dictionary of keyword arguments N(sargsskwargssargvs parseargs(sselfsargvsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/tBlog/new_blog.pys_split_macro_args[s cCs|idjSdS(Ns /blog/new(sreqs path_info(sselfsreq((s2build/bdist.darwin-8.0.1-x86/egg/tBlog/new_blog.pys match_requestescCs‡|iidƒt|dƒt|dƒ|i|ƒ|iidƒp |idƒp|i i i ƒ}||i d # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at: # http://trac-hacks.org/wiki/TracBlogPlugin # # Author: John Hampton import sys import time import datetime import inspect import calendar import re from StringIO import StringIO from pkg_resources import resource_filename from trac.core import * from trac.web import IRequestHandler from trac.web.chrome import ITemplateProvider, add_stylesheet, add_link from trac.web.chrome import INavigationContributor from trac.util import Markup, format_date, format_datetime, http_date try: from trac.util.text import to_unicode except ImportError: to_unicode = lambda x: x from trac.wiki.formatter import Formatter, wiki_to_oneliner, wiki_to_html from trac.wiki.model import WikiPage from trac.wiki.api import IWikiMacroProvider from trac.perm import IPermissionRequestor from tractags.api import TagEngine from tractags.parseargs import parseargs _title_split_match = re.compile(r'^=+\s+([^\n\r=]+?)\s+=+\s+(.+)$', re.DOTALL).match BOOLS_TRUE = ['true', 'yes', 'ok', 'on', 'enabled', '1'] __all__ = ['TracBlogPlugin'] _tag_split = re.compile('[,\s]+') def bool_val(val): """Returns whether or not a values represents a boolen value """ if isinstance(val, bool): return val if isinstance(val, (str, unicode)): return val.strip().lower() in BOOLS_TRUE return None def date_crop(year, month, day): """Makes sure that the day passed in is avalid for the month and year""" _, numdays = calendar.monthrange(year, month) return (day <= numdays) and day or numdays class NoFloatFormatter(Formatter): """A modified formatter that inserts macro_no_float=1 into the HDF.""" def __init__(self, *args, **kwords): if 'macro_blacklist' in kwords: self.macro_blacklist = kwords['macro_blacklist'] del kwords['macro_blacklist'] if 'req' in kwords: kwords['req'].hdf['macro_no_float'] = 1 else: for arg in args: if hasattr(arg, 'hdf'): arg.hdf['macro_no_float'] = 1 break else: raise TracError, "Unable to isolate req" super(NoFloatFormatter,self).__init__(*args, **kwords) def _macro_formatter(self, match, fullmatch): name = fullmatch.group('macroname') if name in self.macro_blacklist: return '' return super(NoFloatFormatter, self)._macro_formatter(match, fullmatch) def wiki_to_nofloat_html(wikitext, env, req, db=None, absurls=0, escape_newlines=False, macro_blacklist=[]): out = StringIO() NoFloatFormatter(env, req, absurls, db, macro_blacklist=macro_blacklist ).format(wikitext, out, escape_newlines) return Markup(out.getvalue()) class TracBlogPlugin(Component): """Displays a blog based on tags The list of tags to be shown can be specified as arguments to the macro. If no tags are specified as parameters, then the default 'blog' tag is used. Because any wiki page can be a blog entry,it is suggested that one uses a unique tag for any page that should appear in the blog, such as the tag: 'blog' (the default). The following options can be specified: '''union''' - Specify whether the join for the tags listed should be a union or intersection(default).[[br]] '''num_posts''' - Number of posts to display.[[br]] '''year''' - Year for which to show posts.[[br]] '''month''' - Month for which to show posts.[[br]] '''day''' - Day of the month for which to show posts.[[br]] '''delta''' - How many days of posts should be shown.[[br]] '''mark_update''' - Specify whether to show "Updated on" for posts that have been updated.[[br]] '''format''' - Show as RSS feed ('rss') or HTML (else).[[br]] '''post_size''' - Number of bytes to show before truncating the post and providing a ''(...)'' link. Posts are truncated at the next line break after the byte count is reached. If specifying dates with {{{year}}}, {{{month}}}, and/or {{{day}}}, the current value is specified if missing. For example, if {{{day}}} is specified but {{{year}}} and {{{month}}} are not, then {{{year}}} will be filled in with the current year and {{{month}}} will be filled with the current month. If only {{{year}}} and {{{month}}} are specified, then that indicates the whole month is desired. The {{{num_posts}}} options is bounded by the date options if combined. For example, if {{{num_posts=5}}} and {{{month=4}}} is specified, it will show up to 5 posts from the month of April. If only 3 posts exist, then only 3 are shown. If a date option is not specified, then it will show the last {{{num_posts}}} posts. === Examples === {{{ [[BlogShow()]] [[BlogShow(blog,pacopablo)]] [[BlogShow(blog,pacopablo,union=True)]] [[BlogShow(blog,pacopablo,num_posts=5)]] [[BlogShow(blog,pacopablo,month=4,num_posts=5)]] [[BlogShow(blog,pacopablo,year=2006,month=4)]] [[BlogShow(blog,pacopablo,year=2006,month=4,day=12)]] [[BlogShow(blog,pacopablo,delta=5)]] [[BlogShow(blog,pacopablo,delta=5,mark_updated=False)]] }}} """ implements(IRequestHandler, ITemplateProvider, INavigationContributor, IWikiMacroProvider, IPermissionRequestor) # IPermissionRequestor def get_permission_actions(self): return ['BLOG_VIEW'] # INavigationContributor methods def get_active_navigation_item(self, req): if req.perm.has_permission('BLOG_VIEW'): return 'blog' def get_navigation_items(self, req): nav_bar = bool_val(self.env.config.get('blog', 'nav_bar', True)) if req.perm.has_permission('BLOG_VIEW') and nav_bar: req.hdf['trac.href.blog'] = self.env.href.blog() yield 'mainnav', 'blog', Markup('Blog', self.env.href.blog()) # IWikiMacroProvider def get_macros(self): yield "BlogShow" def get_macro_description(self, name): """Return the subclass's docstring.""" return inspect.getdoc(self.__class__) def render_macro(self, req, name, content): """ Display the blog in the wiki page """ add_stylesheet(req, 'blog/css/blog.css') tags, kwargs = self._split_macro_args(content) if not tags: tstr = self.env.config.get('blog', 'default_tag', 'blog') tags = [t.strip() for t in _tag_split.split(tstr) if t.strip()] self._generate_blog(req, *tags, **kwargs) req.hdf['blog.macro'] = True data = req.hdf.render('blog.cs') return req.hdf.render('blog.cs') # Originally this was put in for ticket #306. [3141] fixed the issue in trac # proper, however, I left this in due to a suggestion from cboos # http://lists.edgewall.com/archive/trac-dev/2006-April/000397.html # However, it looks like 0.9.x can't handle it. So, we'll see what other # wonderful unicode errors we'll get now :) # return unicode(req.hdf.render('blog.cs'), 'utf-8') def _split_macro_args(self, argv): """Return a list of arguments and a dictionary of keyword arguements """ args = [] kwargs = {} if argv: args, kwargs = parseargs(argv) return args, kwargs def match_request(self, req): return req.path_info == '/blog' def process_request(self, req): req.perm.assert_permission('BLOG_VIEW') add_stylesheet(req, 'blog/css/blog.css') add_stylesheet(req, 'common/css/wiki.css') tags = req.args.getlist('tag') format = req.args.get('format') kwargs = {} for key in req.args.keys(): if key != 'tag': kwargs[key] = req.args[key] continue if not tags: tstr = self.env.config.get('blog', 'default_tag', 'blog') tags = [t.strip() for t in _tag_split.split(tstr) if t.strip()] self._generate_blog(req, *tags, **kwargs) if format == 'rss': return 'blog_rss.cs', 'application/rss+xml' else: add_link(req, 'alternate', self.env.href.blog(format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') return 'blog.cs', None def _generate_blog(self, req, *args, **kwargs): """Extract the blog pages and fill the HDF. *args is a list of tags to use to limit the blog scope **kwargs are any aditional keyword arguments that are needed """ tallies = {} tags = TagEngine(self.env).tagspace.wiki try: union = kwargs['union'] except KeyError: union = False # Formatting read_post = "[wiki:%s Read Post]" entries = {} if not len(args): tlist = [self.env.config.get('blog', 'default_tag', 'blog')] else: tlist = args if union: blog = tags.get_tagged_names(tlist, operation='union') else: blog = tags.get_tagged_names(tlist, operation='intersection') macropage = req.args.get('page', None) poststart, postend, default_times = self._get_time_range(req, **kwargs) mark_updated = self._choose_value('mark_updated', req, kwargs, convert=bool_val) if not mark_updated and (not isinstance(mark_updated, bool)): mark_updated = bool_val(self.env.config.get('blog', 'mark_updated', True)) macro_bl = self.env.config.get('blog', 'macro_blacklist', '').split(',') macro_bl = [name.strip() for name in macro_bl if name.strip()] macro_bl.append('BlogShow') # Get the email addresses of all known users email_map = {} for username, name, email in self.env.get_known_users(): if email: email_map[username] = email num_posts = self._choose_value('num_posts', req, kwargs, convert=int) if num_posts and default_times: poststart = sys.maxint postend = 0 for blog_entry in blog: if blog_entry == macropage: continue try: page = WikiPage(self.env, version=1, name=blog_entry) version, post_time, author, comment, ipnr = page.get_history( ).next() self._add_to_tallies(tallies, post_time, blog_entry) page = WikiPage(self.env, name=blog_entry) version, modified, author, comment, ipnr = page.get_history( ).next() except: self.log.debug("Error loading wiki page %s" % blog_entry, exc_info=True) continue if poststart >= post_time >= postend: time_format = self.env.config.get('blog', 'date_format') \ or '%x %X' timeStr = format_datetime(post_time, format=time_format) fulltext = page.text # remove comments in blog view: del_comments = re.compile('==== Comment.*\Z', re.DOTALL) fulltext = del_comments.sub('', fulltext) # remove the [[AddComment...]] tag, otherwise it would appeare # more than one and crew up the blog view: del_addcomment = re.compile('\[\[AddComment.*\Z', re.DOTALL) fulltext = del_addcomment.sub('', fulltext) # limit length of preview: post_size = self._choose_value('post_size', req, kwargs, int) if not post_size and (not isinstance(post_size, int)): post_size = int(self.env.config.get('blog', 'post_size', 1024)) text = self._trim_page(fulltext, blog_entry, post_size) pagetags = [x for x in tags.get_name_tags(blog_entry) if x not in tlist] tagtags = [] for i, t in enumerate(pagetags[:3]): d = { 'link' : t, 'name' : t, 'last' : i == (len(pagetags[:3]) - 1), } tagtags.append(d) continue # extract title from text: match = _title_split_match(fulltext) if match: title = match.group(1) fulltext = match.group(2) else: title = blog_entry html_text = wiki_to_html(fulltext, self.env, req) rss_text = Markup.escape(to_unicode(html_text)) data = { 'name' : blog_entry, 'title' : title, 'href' : self.env.href.wiki(blog_entry), 'wiki_link' : wiki_to_oneliner(read_post % blog_entry, self.env), 'time' : timeStr, 'date' : http_date(post_time), # 'date' : http_date(page.time), 'author' : author, 'wiki_text' : wiki_to_nofloat_html(text, self.env, req, macro_blacklist=macro_bl), 'rss_text' : rss_text, 'comment' : wiki_to_oneliner(comment, self.env), 'tags' : { 'present' : len(pagetags), 'tags' : tagtags, 'more' : len(pagetags) > 3 or 0, }, } if author: # For RSS, author must be an email address if author.find('@') != -1: data['author.email'] = author elif email_map.has_key(author): data['author.email'] = email_map[author] if (modified != post_time) and mark_updated: data['modified'] = 1 mod_str = format_datetime(modified, format=time_format) data['mod_time'] = mod_str entries[post_time] = data continue tlist = entries.keys() tlist.sort() tlist.reverse() if num_posts and (num_posts <= len(tlist)): tlist = tlist[:num_posts] if tlist: entries[tlist[-1]]['last'] = 1 req.hdf['blog.entries'] = [entries[x] for x in tlist] bloglink = self.env.config.get('blog', 'new_blog_link', 'New Blog Post') req.hdf['blog.newblog'] = bloglink hidecal = self._choose_value('hidecal', req, kwargs) if not hidecal: self._generate_calendar(req, tallies) req.hdf['blog.hidecal'] = hidecal pass def _generate_calendar(self, req, tallies): """Generate data necessary for the calendar """ now = datetime.datetime.now() year = self._choose_value('year', req, None, convert=int) or \ now.year month = self._choose_value('month', req, None, convert=int) or \ now.month day = self._choose_value('day', req, None, convert=int) or \ now.day day = date_crop(year, month, day) baseday = datetime.datetime(year, month, day) week_day = self.env.config.get('blog', 'first_week_day', 'SUNDAY') first_day = getattr(calendar, week_day.upper()) calendar.setfirstweekday(first_day) cal = calendar.monthcalendar(year, month) statcal = {} for week_num, week in enumerate(cal): statcal[week_num] = { 'num' : week_num, 'days' : [] } for day_num in week: d = { 'num' : day_num, 'count' : 0, } if day_num: d['count'] = self._get_tally(tallies, year, month, day_num) statcal[week_num]['days'].append(d) continue continue week = [week for week in xrange(len(cal)) if day in cal[week]][0] monthname = format_datetime(time.mktime(baseday.timetuple()), format="%b") lastyear = abs(year - 1) nextyear = year + 1 # for the prev/next navigation links lastmonth = month - 1 lastmonth_year = year nextmonth = month + 1 nextmonth_year = year # check for year change (KISS version) if lastmonth <= 0: lastmonth = 12 lastmonth_year -= 1 if nextmonth >= 13: nextmonth = 1 nextmonth_year += 1 hdfdate = { 'year' : year, 'yearcount' : self._get_tally(tallies, year), 'month' : month, 'monthcount' : self._get_tally(tallies, year, month), 'day' : day, 'lastyear' : lastyear, 'nextyear' : nextyear, 'lastmonth' : { 'year' : lastmonth_year, 'month' : lastmonth, }, 'nextmonth' : { 'year' : nextmonth_year, 'month' : nextmonth, }, 'daynames' : calendar.weekheader(2).split(), 'week' : week, 'monthname' : monthname, } req.hdf['blog.date'] = hdfdate req.hdf['blog.cal'] = statcal req.hdf['blog.path_info'] = self.env.href(req.path_info) pass def _get_tally(self, tallies, year, month=None, day=None): """Return the tally for the given date Returns 0 if no tally is present """ if day and month: try: tally = tallies[year][month][day]['total'] except KeyError: tally = 0 elif month: try: tally = tallies[year][month]['total'] except KeyError: tally = 0 else: try: tally = tallies[year]['total'] except KeyError: tally = 0 return tally def _add_to_tallies(self, tallies, post_time, page_name): """Create a running tally of blog page data """ def _gen_blank_year_total(year): blank_year = {} for month in xrange(1, 13): mrange = calendar.monthrange(year, month)[1] blank_year[month] = { 'pages' : [], 'total' : 0, } for day in xrange(1, mrange + 1): blank_year[month][day] = { 'total' : 0, 'pages' : [], } continue continue return blank_year d = datetime.datetime.fromtimestamp(post_time) try: tallies['total'] += 1 except KeyError: tallies['total'] = 1 try: tallies[d.year]['total'] += 1 tallies[d.year]['pages'].append(page_name) except (KeyError, AttributeError): tallies[d.year] = _gen_blank_year_total(d.year) tallies[d.year]['total'] = 1 tallies[d.year]['pages'] = [page_name] tallies[d.year][d.month]['total'] += 1 tallies[d.year][d.month]['pages'].append(page_name) tallies[d.year][d.month][d.day]['total'] += 1 tallies[d.year][d.month][d.day]['pages'].append(page_name) pass def _get_time_range(self, req, **kwargs): """Return a start and end date range Parameters can be passed in via the req object or via **kwargs. The req object always overrides **kwargs. In practice, this shouldn't matter much as the macro will use **kwargs and the nav item will use req. Just documenting behavior. The range of days can be specified in multiple ways. * Specify date range with startdate and enddate. Value should be seconds since epoch * Specify startdate and delta. delta should be a number of seconds. If both enddate and delta are specified, enddate is preferred. * Specify any combination of year, month, day. If any one of the values is specified, then it will use this method in place of startdate, enddate and delta. If a value is missing that is required such as a year if month is specified, then it deafults to the current value. Otherwise it generates a range according to the values passed in. For example, year=2006 and month=12 will return all posts for December 2006. """ DAY = 86400 HISTORY = int(self.env.config.get('blog', 'history_days') or 30) * DAY startdate = self._choose_value('startdate', req, kwargs, convert=int) enddate = self._choose_value('enddate', req, kwargs, convert=int) delta = self._choose_value('delta', req, kwargs, convert=int) year = self._choose_value('year', req, kwargs, convert=int) month = self._choose_value('month', req, kwargs, convert=int) day = self._choose_value('day', req, kwargs, convert=int) defaults = not (startdate or enddate or delta or year or month or day) now = datetime.datetime.now() oneday = datetime.timedelta(days=1) if year or month or day: if day: year = year or now.year month = month or now.month if month: year = year or now.year day = date_crop(year, month or 12, day or 1) start = datetime.datetime(year, month or 12, day) start += oneday end = datetime.datetime(year, month or 1, day) start = time.mktime(start.timetuple()) end = time.mktime(end.timetuple()) else: start = startdate or time.mktime(now.timetuple()) if enddate: end = enddate elif delta: end = start - delta else: end = start - HISTORY return start, end, defaults def _choose_value(self, key, req, kwargs, convert=None): """Return the value for the specified key from either req or kwargs If the value is present in both, then the value from req is used. If the value doesn't exist in either, then None is returned. An optional conversion function can be passed. If present, the value will be passed to the conversion function before returning. If the conversion function raises an error, None will be returned """ val = req.args.get(key, None) if not val: try: val = kwargs[key] except: val = None if convert: try: val = convert(val) except: val = None return val def _trim_page(self, text, page_name, post_size): """Trim the page text to the {{{post_size}} in trac.ini The timming isn't exact. It trims to the first line that causes the page to exceed the value storing in {{{post_size}}}. If the line is in the middle of a code block, it will close the block. """ tlines = [] entry_size = 0 line_count = 0 in_code_block = False lines = text.split('\n') if not post_size: ret = re.search('^\s*=\s+.*=', text, re.M) tlines.append(ret and ret.group(0) or '') else: for line in lines: line_count += 1 entry_size += len(line) tlines.append(line) if not in_code_block: in_code_block = line.strip() == '{{{' else: in_code_block = in_code_block and line.strip() != '}}}' if entry_size > post_size: if in_code_block: tlines.append('}}}') break continue if (line_count < len(lines)) and post_size: tlines.append("''[wiki:%s (...)]''" % page_name) return '\n'.join(tlines) # ITemplateProvider def get_templates_dirs(self): """ Return the absolute path of the directory containing the provided templates """ return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): """ Return a list of directories with static resources (such as style sheets, images, etc.) Each item in the list must be a `(prefix, abspath)` tuple. The `prefix` part defines the path in the URL that requests to these resources are prefixed with. The `abspath` is the absolute path to the directory containing the resources on the local file system. """ return [('blog', resource_filename(__name__, 'htdocs'))] PK‘$6ÑîstBlog/admin.py# -*- coding: utf-8 -*- # # Copyright (C) 2006 John Hampton # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at: # http://trac-hacks.org/wiki/TracBlogPlugin # # Author: John Hampton from trac.core import * from trac.web import IRequestHandler from trac.web.chrome import ITemplateProvider, add_stylesheet from trac.perm import IPermissionRequestor from trac.util import escape, Markup, format_date, format_datetime from trac.wiki.formatter import wiki_to_html from webadmin.web_ui import IAdminPageProvider import os import os.path from pkg_resources import resource_filename __all__ = ['BlogAdminPlugin'] class BlogAdminPlugin(Component): """ Provides functions related to registration """ implements(ITemplateProvider, IAdminPageProvider, IPermissionRequestor) # IPermissionRequestor def get_permission_actions(self): return [('BLOG_ADMIN', ['BLOG_POSTER', 'BLOG_VIEW'])] # IAdminPageProvider methods def get_admin_pages(self, req): if req.perm.has_permission('BLOG_ADMIN'): yield ('blog', 'Blog System', 'defaults', 'Defaults') def process_admin_request(self, req, cat, page, path_info): assert req.perm.has_permission('BLOG_ADMIN') add_stylesheet(req, 'blog/css/blog.css') add_stylesheet(req, 'common/css/wiki.css') req.hdf['blogadmin.page'] = page admin_fields = { 'date_format' : '%x %X', 'page_format' : '%Y/%m/%d/%H.%M', 'default_tag' : 'blog', 'post_size' : 1024, 'history_days' : 30, 'new_blog_link' : 'New Blog Post', 'first_week_day' : 'SUNDAY', 'mark_updated' : 'true', 'nav_bar' : 'true', 'macro_blacklist': '', 'footer': '', } if req.method == 'POST': if page == 'defaults': for field in admin_fields.keys(): self._set_field_value(req, field) self.env.config.save() for field, default in admin_fields.items(): self._get_field_value(req, field, default) req.hdf['blogadmin.docs'] = wiki_to_html(self._get_docs(page), self.env, req) return 'blog_admin.cs', None def _get_docs(self, page): """Return the wikitext documentation for the page options. """ if page == 'defaults': doc = """ '''Date Format String''':: string in {{{strftime}}} format '''Page Name Format String''':: string in {{{strftime}}} format '''Default Tag''':: tag to use as default when none are specified '''Post Max Size''':: number of bytes to show before truncating the post and providing a ''(...)'' link. Posts are truncated at line breaks, and wiki formatting is included in the byte count, so truncation will not be exact. This means that if a line is 2KB long and ''Post Max Size'' is 1KB, then the full 2KB will be shown, as it's the next line break after the ''Post Max Size'' is reached '''Days of History''':: number of days for which to show blog posts '''New Blog Link''':: text to show for the new blog link '''Calendar Week Start Day''':: name of day that acts as the first day of the week. Must be full day name. '''Mark Updated Posts''':: whether or not to mark posts that have been updated with an "Updated on" message. '''Show Link in Nav Bar''':: whether or not a '''Blog''' link should be shown in the navigation menu bar. '''Macro Blacklist''':: comma separated list of macros to strip from blog output. '''Footer''':: Wiki Formatted data, that will be added to end of every document. You may use Macros. Also use $U for name of current user and $D for "blog post date" according to "Date Format String" '''strftime formatting''':: ||%a||Locale's abbreviated weekday name.|| ||%A||Locale's full weekday name.|| ||%b||Locale's abbreviated month name.|| ||%B||Locale's full month name.|| ||%c||Locale's appropriate date and time representation.|| ||%d||Day of the month as a decimal number [01,31].|| ||%H||Hour (24-hour clock) as a decimal number [00,23].|| ||%I||Hour (12-hour clock) as a decimal number [01,12].|| ||%j||Day of the year as a decimal number [001,366].|| ||%m||Month as a decimal number [01,12].|| ||%M||Minute as a decimal number [00,59].|| ||%p||Locale's equivalent of either AM or PM.|| ||%S||Second as a decimal number [00,61].|| ||%U||Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0.|| ||%w||Weekday as a decimal number [0(Sunday),6].|| ||%W||Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0.|| ||%x||Locale's appropriate date representation.|| ||%X||Locale's appropriate time representation.|| ||%y||Year without century as a decimal number [00,99].|| ||%Y||Year with century as a decimal number.|| ||%Z||Time zone name (no characters if no time zone exists).|| ||%%||A literal "%" character.|| ||%@||An URL friendly version of the Blog Entry Title|| ||$U||Name of the current user|| """ return doc def _set_field_value(self, req, field_name, default=None): """Set the trac.ini field value for the specified name. """ if field_name in req.args: field = req.args.get(field_name) self.env.config.set('blog', field_name, field) pass def _get_field_value(self, req, field_name, default=None): """Get the field from trac.ini and set the hdf appropriately. """ field = self.env.config.get('blog', field_name) or default req.hdf['blogadmin.' + field_name] = field pass # INavigationContributor def get_templates_dirs(self): """ Return the absolute path of the directory containing the provided templates """ return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): """ Return a list of directories with static resources (such as style sheets, images, etc.) Each item in the list must be a `(prefix, abspath)` tuple. The `prefix` part defines the path in the URL that requests to these resources are prefixed with. The `abspath` is the absolute path to the directory containing the resources on the local file system. """ return [('blog', resource_filename(__name__, 'htdocs'))] PK«u5éWŒ]»'»'tBlog/new_blog.py# -*- coding: utf-8 -*- # # Copyright (C) 2006 John Hampton # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at: # http://trac-hacks.org/wiki/TracBlogPlugin # # Author: John Hampton import os import os.path import time import inspect import re from pkg_resources import resource_filename from trac.core import * from trac.web import IRequestHandler from trac.web.chrome import ITemplateProvider, add_stylesheet from trac.perm import IPermissionRequestor from trac.util import Markup from trac.wiki.api import IWikiMacroProvider from trac.wiki.formatter import wiki_to_html from trac.wiki.model import WikiPage from tractags.api import TagEngine from tractags.parseargs import parseargs __all__ = ['BlogPost'] _tag_split = re.compile('[,\s]+') class BlogPost(Component): """Inserts a link to create a new blog post Accepts keyword arguments that specify default parameters. The macro will be hidden unless the user has {{{BLOG_POSTER}}} permissions. '''tag''' - Tag that populates the "Tag under" field. This key may be specified as a tuple or list to pass multiple values.[[br]] '''blogtitle''' - Default blog entry title.[[br]] '''text''' - Default entry body text.[[br]] '''pagename''' - Default wiki page name.[[br]] '''readonly''' - Default readonly page status.[[br]] '''link''' - Text to display as the link.[[br]] === Examples === {{{ [[BlogPost()]] [[BlogPost(tag=(blog,pacopablo))]] [[BlogPost(tag=blog,blogtitle="A Simple Title",text="Body Text")]] [[BlogPost(tag=blog,pagename=blog/newpage,readonly=1)]] [[BlogPost(tag=(blog,pacopablo),link="A New Blog Post")]] }}} """ implements(IRequestHandler, ITemplateProvider, IWikiMacroProvider, IPermissionRequestor) # IPermissionRequestor def get_permission_actions(self): return ['BLOG_POSTER'] # IWikiMacroProvider def get_macros(self): yield "BlogPost" def get_macro_description(self, name): """Return the subclass's docstring.""" return inspect.getdoc(self.__class__) def render_macro(self, req, name, content): """ Display the blog in the wiki page """ if req.perm.has_permission('BLOG_POSTER'): args, kwargs = self._split_macro_args(content) try: blog_link = kwargs['link'] del kwargs['link'] except KeyError: blog_link = self.env.config.get('blog', 'new_blog_link', 'New Blog Post') return Markup('%s', self.env.href.blog('new',**kwargs), blog_link) return '' def _split_macro_args(self, argv): """Return a list of arguments and a dictionary of keyword arguments """ args = [] kwargs = {} if argv: args, kwargs = parseargs(argv) return args, kwargs def match_request(self, req): return req.path_info == '/blog/new' def process_request(self, req): req.perm.assert_permission('BLOG_POSTER') add_stylesheet(req, 'blog/css/blog.css') add_stylesheet(req, 'common/css/wiki.css') self._new_blog_post(req) referer = req.args.get('referer') or req.get_header('Referer') or self.env.href.blog() req.hdf['blog.referer'] = referer return 'new_blog.cs', None def _new_blog_post(self, req): """ Generate a new blog post """ action = req.args.get('action', 'edit') pg_name_fmt = self.env.config.get('blog', 'page_format', '%Y/%m/%d/%H.%M') wikitext = req.args.get('text', '') blogtitle = req.args.get('blogtitle', '') pagename = req.args.get('pagename', pg_name_fmt) pagename = time.strftime(pagename) if '%@' in pagename and blogtitle: urltitle = re.sub(r'[^\w]+', '-', blogtitle).lower() pagename = pagename.replace('%@', urltitle) while '-' in pagename and len(pagename) > 60: pagename = '-'.join(pagename.split('-')[:-1]) pagename = pagename.strip('-') if '$U' in pagename: pagename = pagename.replace('$U', req.authname) comment = req.args.get('comment', '') readonly = int(req.args.has_key('readonly')) edit_rows = int(req.args.get('edite_rows', 20)) req_tags = req.args.get('tags', []) if req.method == 'POST': if action == 'edit': if req.args.has_key('cancel'): referrer = req.args.get('referer') or req.get_header('Referer') or self.env.href.blog() req.redirect(referrer) page = WikiPage(self.env, pagename, None) tags = TagEngine(self.env).tagspace.wiki if req.args.has_key('preview'): req.hdf['blog.action'] = 'preview' self._render_editor(req, page, self.env.get_db_cnx(), preview=True) else: titleline = ' '.join(["=", blogtitle, "=\n"]) if blogtitle: page.text = ''.join([titleline, wikitext]) else: page.text = wikitext # Add footer page.text = page.text.join(["\n\n",self.variable_substitution(req,self.env.config.get('blog', 'footer', ''))]) page.readonly = readonly page.save(req.authname, comment, req.remote_addr) # taglist = [x.strip() for x in req_tags.split(',') if x] taglist = [t.strip() for t in _tag_split.split(req.args.get('tags')) if t.strip()] tags.add_tags(req, pagename, taglist) referrer = req.args.get('referer') or req.get_header('Referer') or self.env.href.blog() req.redirect(referrer) else: info = { 'title' : blogtitle, 'pagename': pagename, 'page_source': wikitext, 'comment': comment, 'readonly': readonly, 'edit_rows': edit_rows, 'scroll_bar_pos': req.args.get('scroll_bar_pos', '') } req.hdf['blog'] = info req.hdf['title'] = 'New Blog Entry' tlist = req.args.getlist('tag') if not tlist: tlist = [self.env.config.get('blog', 'default_tag', 'blog')] req.hdf['tags'] = ', '.join(tlist) pass def _render_editor(self, req, page, db, preview=False): blogtitle = req.args.get('blogtitle') titleline = ' '.join(["=", blogtitle, "=\n"]) if req.args.has_key('text'): page.text = req.args.get('text') if preview: page.readonly = req.args.has_key('readonly') author = req.authname comment = req.args.get('comment', '') editrows = req.args.get('editrows') tags = req.args.get('tags') req.hdf['tags'] = tags if editrows: pref = req.session.get('wiki_editrows', '20') if editrows != pref: req.session['wiki_editrows'] = editrows else: editrows = req.session.get('wiki_editrows', '20') req.hdf['title'] = page.name + ' (edit)' info = { 'title' : blogtitle, 'pagename': page.name, 'page_source': page.text, 'author': author, 'comment': comment, 'readonly': page.readonly, 'edit_rows': editrows, 'scroll_bar_pos': req.args.get('scroll_bar_pos', '') } if preview: if blogtitle: info['page_html'] = wiki_to_html(''.join([titleline, req.args.get('text'), "\n\n",self.variable_substitution(req,self.env.config.get('blog', 'footer', ''))]), self.env, req, db) else: info['page_html'] = wiki_to_html(page.text.join(["\n\n", self.variable_substitution(req,self.env.config.get('blog', 'footer', ''))]), self.env, req, db) info['readonly'] = int(req.args.has_key('readonly')) req.hdf['blog'] = info def variable_substitution(self,req,string): string = string.replace('$U',req.authname) string = string.replace('$D',time.strftime(self.env.config.get('blog', 'date_format', '%x %X'))) return string # ITemplateProvider def get_templates_dirs(self): """ Return the absolute path of the directory containing the provided templates """ return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): """ Return a list of directories with static resources (such as style sheets, images, etc.) Each item in the list must be a `(prefix, abspath)` tuple. The `prefix` part defines the path in the URL that requests to these resources are prefixed with. The `abspath` is the absolute path to the directory containing the resources on the local file system. """ return [('blog', resource_filename(__name__, 'htdocs'))] PKI—‡4–Î[šRRtBlog/htdocs/css/blog.css/* Navigation */ .postmeta h2, .postmeta hr { display: none } .postmeta ul { font-size: 10px; list-style: none; margin: 0; text-align: right } .postmeta li { border-right: 1px solid #d7d7d7; display: inline; padding: 0 .75em; white-space: nowrap; } .postmeta li.last { border-right: none } .blognav { float: right; border: solid 1px; margin: 0.5em; padding: 0.1em 1em 0.1em 0.1em; /* width: 400px; background: #dfd; */ } #blognav h3 { padding: 0.1em; margin: 0.4em; } #blognav ul { margin: 0.1em; } #blognav li { font-size: 0.8em; padding: 0.1em; } .blogdocs { float: right; width: 60%; margin: 0.5em; padding: 0.1em 1em 0.1em 0.1em; } .blogoptions { float: left; } .updated p { font-size: 90%; font-style: italic; font-weight: bold; } /* Calendar display stuff */ div.blog-calendar { padding: .5em 1em; margin: 5px 0 2em 1em; float: right; border: 1px outset #ddc; background: #ffd; font-size: 85%; position: relative; } div.blog-calendar table { /* border-collapse: collapse; */ } div.blog-calendar td { padding: .25em; } tr.blog-calendar-current { background: #ffff75; } td.blog-calendar-current a { color: #000000; font-weight: bold; } a.blog-calendar-title { font-size: 130%; color: black; } caption.blog-calendar-caption { padding-bottom: 4px; } PK E6ÆnKbbtBlog/templates/blog_rss.cs <?cs var:project.name_encoded ?>: <?cs var:title ?> <?cs var:title ?> en-us Trac v <?cs var:project.name_encoded ?> <?cs var:bentry.title ?> <p>Posted in: <a href="/tags/"> </a>,</p> PK«u58“33tBlog/templates/new_blog.cs

Create Blog Entry

Preview (skip)

Note: See WikiFormatting and TracWiki for help on editing wiki content.
Change information


       
PK粇4T÷q˜ ˜ tBlog/templates/blog_calendar.cs ?year=&month=&day= ?year=&month= ?year=
class="blog-calendar-current" >
<<  <      >  >>
class="blog-calendar-current"> class="missing" title=" Post(s)" href="">  
PKÎjk5NÐÑ›uutBlog/templates/blog.cs
This is some navigation things
    Posts Per Month:
  • March (7)

Updated on


PK‘$6´ýùÖ÷ ÷ tBlog/templates/blog_admin.cs

Blog Admin -- Defaults

Defaults
Docs
PK ~68“×2¤EGG-INFO/zip-safePK~68}è.¤0EGG-INFO/SOURCES.txtPK~68¹Ôºu¤dEGG-INFO/entry_points.txtPK~68“×2¤¹EGG-INFO/dependency_links.txtPK~68^¶íx¤õEGG-INFO/PKG-INFOPK~68¢õOE¤%EGG-INFO/requires.txtPK~68…5Pû¤wEGG-INFO/top_level.txtPKÈ“ƒ4ì·E\ff¤±tBlog/__init__.pyPK ~68À9{5k5k¤FtBlog/web_ui.pycPK ~68@ƒ\ã+ã+¤©otBlog/new_blog.pycPK ~68Ž9·Öøø¤¼›tBlog/__init__.pycPK ~68vPevŒ!Œ!¤äœtBlog/admin.pycPKôE6SýÃ