Changeset 3906
- Timestamp:
- 06/26/08 12:29:39 (5 months ago)
- Files:
-
- fullblogplugin/0.11/tracfullblog/admin.py (modified) (2 diffs)
- fullblogplugin/0.11/tracfullblog/core.py (modified) (5 diffs)
- fullblogplugin/0.11/tracfullblog/templates/fullblog_admin.html (modified) (1 diff)
- fullblogplugin/0.11/tracfullblog/templates/fullblog_edit.html (modified) (3 diffs)
- fullblogplugin/0.11/tracfullblog/util.py (modified) (1 diff)
- fullblogplugin/0.11/tracfullblog/web_ui.py (modified) (13 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
fullblogplugin/0.11/tracfullblog/admin.py
r3131 r3906 39 39 self.env.config.set('fullblog', 'num_items_front', 40 40 req.args.get('numpostsfront')) 41 self.env.config.set('fullblog', 'default_postname', 42 req.args.get('defaultpostname')) 41 43 self.env.config.save() 42 44 elif req.args.get('savebloginfotext'): … … 55 57 blog_admin['numpostsfront'] = self.env.config.getint( 56 58 'fullblog', 'num_items_front') 59 blog_admin['defaultpostname'] = self.env.config.get( 60 'fullblog', 'default_postname') 57 61 58 62 return ('fullblog_admin.html', {'blog_admin': blog_admin}) fullblogplugin/0.11/tracfullblog/core.py
r3284 r3906 11 11 """ 12 12 13 from time import strftime 14 13 15 from genshi.builder import tag 14 16 15 17 from trac.attachment import ILegacyAttachmentPolicyDelegate 16 18 from trac.core import * 17 from trac.config import IntOption19 from trac.config import Option 18 20 from trac.perm import IPermissionRequestor 19 21 from trac.resource import IResourceManager 20 22 from trac.util.text import unicode_unquote 23 from trac.util.datefmt import to_datetime, utc 21 24 from trac.wiki.api import IWikiSyntaxProvider 22 25 … … 24 27 from api import IBlogChangeListener, IBlogManipulator 25 28 from model import BlogPost, get_blog_resources 26 29 from util import parse_period 27 30 28 31 class FullBlogCore(Component): … … 35 38 manipulators = ExtensionPoint(IBlogManipulator) 36 39 40 implements(IPermissionRequestor, IWikiSyntaxProvider, IResourceManager, 41 ILegacyAttachmentPolicyDelegate) 42 37 43 # Options 38 39 IntOption('fullblog', 'num_items_front', 20, 40 """Option to specify how many recent posts to display on the 41 front page of the Blog.""") 42 43 default_pagename = 'change_this_post_shortname' 44 45 Option('fullblog', 'default_postname', '%Y/%m/%d/my_topic', 46 """Option for a default naming scheme for new posts. The string 47 can include substitution markers for time (UTC) and user: %Y=year, 48 %m=month, %d=day, %H=hour, %M=minute, %S=second, $USER. 49 Example template string: `%Y/%m/%d/my_topic`""") 50 51 # Constants 52 44 53 reserved_names = ['create', 'view', 'edit', 'delete', 45 54 'archive', 'category', 'author'] 46 47 implements(IPermissionRequestor, IWikiSyntaxProvider, IResourceManager,48 ILegacyAttachmentPolicyDelegate)49 55 50 56 # IPermissionRequestor method … … 189 195 # Do basic checking for content existence 190 196 warnings.extend(bp.save(version_author, version_comment, verify_only=True)) 191 # Do some more fundamental checking 192 if bp.name in self.reserved_names: 193 warnings.append((req, "'%s' is a reserved name. Please change." % bp.name)) 194 if bp.name == self.default_pagename: 195 warnings.append(('post_name', "The default page shortname must be changed.")) 197 # Make sure name for the post is a valid name 198 warnings.extend(self._check_new_postname(req, bp.name)) 196 199 # Check if any plugins has objections with the contents 197 200 fields = { … … 278 281 warnings.append(('', "Unknown error. Not deleted.")) 279 282 return warnings 283 284 # Internal methods 285 286 def _get_default_postname(self, user=''): 287 """ Parses and returns the setting for default_postname. """ 288 opt = self.env.config.get('fullblog', 'default_postname') 289 if not opt: 290 return '' 291 # Perform substitutions 292 try: 293 now = to_datetime(None, utc).timetuple() 294 name = strftime(opt, now) 295 name = name.replace('$USER', user) 296 return name 297 except: 298 self.env.log.debug( 299 "FullBlog: Error parsing default_postname option: %s" % opt) 300 return '' 301 302 def _check_new_postname(self, req, name): 303 """ Does some checking on the postname to make sure it does 304 not conflict with existing commands. """ 305 warnings = [] 306 name = name.lower() 307 # Reserved names 308 for rn in self.reserved_names: 309 if name == rn: 310 warnings.append(('', 311 "'%s' is a reserved name. Please change." % name)) 312 if name.startswith(rn + '/'): 313 warnings.append(('', 314 "Name cannot start with a reserved name as first item in " 315 "path ('%s'). Please change." % rn)) 316 # Check to see if it is a date range 317 items = name.split('/') 318 if len(items) == 2 and parse_period(items) != (None, None): 319 warnings.append(('', 320 "'%s' is seen as a time period, and cannot " 321 "be used as a name. Please change." % name)) 322 return warnings fullblogplugin/0.11/tracfullblog/templates/fullblog_admin.html
r2710 r3906 22 22 <legend>Blog Settings:</legend> 23 23 <div class="field"> 24 <label for="numpostsfront">Number of posts on front page: 25 <input type="text" name="numpostsfront"24 <label for="numpostsfront">Number of posts on front page:<br /> 25 <input type="text" size="10" name="numpostsfront" 26 26 value="${blog_admin.numpostsfront}" /> 27 </label> 28 </div> 29 <div class="field"> 30 <label for="defaultpostname">Template for naming new blog posts:<br /> 31 (username and time substitution available, like <tt>${'$USER'}-%Y/%m/%d/my_topic</tt>)<br /> 32 <input type="text" size="35" name="defaultpostname" 33 value="${blog_admin.defaultpostname}" /> 27 34 </label> 28 35 </div> fullblogplugin/0.11/tracfullblog/templates/fullblog_edit.html
r2928 r3906 17 17 <div id="content" class="blog wiki"> 18 18 19 <py:with vars="is_create = not blog_edit.version; 20 can_create = 'BLOG_CREATE' in perm('blog'); 21 is_edit = bool(blog_edit.version); 22 can_edit = 'BLOG_MODIFY_ALL' in perm(blog_edit.resource) or defined( 23 'blog_orig_author') or ( 24 perm.username==blog_edit.author and 'BLOG_MODIFY_OWN' in perm(blog_edit.resource)); 25 is_allowed = (is_create and can_create) or (is_edit and can_edit); 26 "> 27 28 <div py:if="is_create and can_create" id="sidebar"> 29 <p><em>Naming your blog posts.</em></p> 30 <p>When naming your posts (post shortname), it is recommended to make them URL friendly 31 (for instance avoid spaces).</p> 32 <p>Blog posts can also be referenced with <tt>[blog:postname]</tt> wiki link syntax, 33 so you may not want to make them too hard to read or write.</p> 34 <p>Some names are reserved and not allowed:</p> 35 <ul> 36 <li>'view', 'create', 'edit', 'archive', 'delete', 'category', 'author' - 37 either on their own or as first item in a path.</li> 38 <li>Numbers evaluating to N/M are seen as time periods and 39 used for month-based browsing.</li> 40 </ul> 41 </div> 42 19 43 <div id="main"> 20 44 … … 22 46 <h1 py:if="blog_edit.version">Edit Blog Post</h1> 23 47 24 <py:with vars="is_create = not blog_edit.version;25 can_create = 'BLOG_CREATE' in perm('blog');26 is_edit = bool(blog_edit.version);27 can_edit = 'BLOG_MODIFY_ALL' in perm(blog_edit.resource) or defined(28 'blog_orig_author') or (29 perm.username==blog_edit.author and 'BLOG_MODIFY_OWN' in perm(blog_edit.resource));30 is_allowed = (is_create and can_create) or (is_edit and can_edit);31 ">32 48 <div py:if="not is_allowed" class="system-message">You do not have the required permission to 33 49 ${is_create and 'create a blog post' or 'edit this blog post'}.</div> … … 101 117 </fieldset> 102 118 </form> 103 </py:with>104 119 105 120 </div> 106 121 122 </py:with> 107 123 </div> 108 124 fullblogplugin/0.11/tracfullblog/util.py
r3695 r3906 33 33 """ Parses a list of items for elements of dates, and returns 34 34 a month as (from_dt, to_dt) if valid. (None, None) if not. """ 35 if not len(items) >= 2:35 if not len(items) == 2: 36 36 return None, None 37 37 try: fullblogplugin/0.11/tracfullblog/web_ui.py
r3892 r3906 13 13 import re 14 14 from pkg_resources import resource_filename 15 from trac.util.compat import itemgetter16 15 17 16 # Trac and Genshi imports 18 17 from genshi.builder import tag 19 18 from trac.attachment import AttachmentModule 20 from trac.config import ListOption, BoolOption 19 from trac.config import ListOption, BoolOption, IntOption 21 20 from trac.core import * 22 21 from trac.mimeview.api import Context … … 24 23 from trac.search.api import ISearchSource, shorten_result 25 24 from trac.timeline.api import ITimelineEventProvider 26 from trac.util.compat import sorted 25 from trac.util.compat import sorted, itemgetter 27 26 from trac.util.datefmt import utc 28 27 from trac.util.translation import _ … … 46 45 ITemplateProvider) 47 46 47 # Options 48 48 49 ListOption('fullblog', 'month_names', 49 50 doc = """Ability to specify a list of month names for display in groupings. … … 51 52 Enter list of 12 months like: 52 53 `month_names = January, February, ..., December` """) 54 53 55 BoolOption('fullblog', 'personal_blog', False, 54 56 """When using the Blog as a personal blog (only one author), setting to 'True' 55 57 will disable the display of 'Browse by author:' in sidebar, and also removes 56 58 various author links and references. """) 59 60 IntOption('fullblog', 'num_items_front', 20, 61 """Option to specify how many recent posts to display on the 62 front page of the Blog.""") 57 63 58 64 # INavigationContributor methods … … 90 96 91 97 blog_core = FullBlogCore(self.env) 92 default_pagename = blog_core.default_pagename93 reserved_names = blog_core.reserved_names94 95 98 format = req.args.get('format', '').lower() 96 97 # Parse out the path and actions from args 98 path_items = req.args.get('blog_path', '').split('/') 99 path_items = [item for item in path_items if item] # clean out empties 99 100 command, pagename, path_items, listing_data = self._parse_path(req) 100 101 action = req.args.get('action', 'view').lower() 101 102 try: … … 103 104 except: 104 105 version = 0 105 command = pagename = ''106 command = (len(path_items) and path_items[0]) or ''107 if command.lower() in [u'view', u'edit', 'delete'] and len(path_items) == 2:108 pagename = path_items[1]109 if command and command not in [110 'view', 'edit', 'create', 'archive', 'delete']:111 if len(path_items) == 1:112 # Assume it is a request for a specific post113 pagename = command114 command = 'view'115 else:116 # Assume it is a listing, do further parsing later117 command = 'listing'118 106 119 107 data = {} 120 121 108 template = 'fullblog_view.html' 122 109 data['blog_about'] = BlogPost(self.env, 'about') … … 202 189 elif command in ['create', 'edit']: 203 190 template = 'fullblog_edit.html' 204 pagename = pagename or req.args.get('name','') or default_pagename 205 the_post = BlogPost(self.env, pagename) 191 default_pagename = blog_core._get_default_postname(req.authname) 192 the_post = BlogPost(self.env, pagename or default_pagename) 193 warnings = [] 194 195 if command == 'create' and the_post.version: 196 if 'BLOG_CREATE' in req.perm and the_post.name == default_pagename \ 197 and not req.method == 'POST': 198 if default_pagename: 199 add_notice(req, "Suggestion for new name already exists " 200 "('%s'). Please make a new name." % the_post.name) 201 elif pagename: 202 warnings.append( 203 ('', "A post named '%s' already exists. Enter new name." 204 % the_post.name)) 205 the_post = BlogPost(self.env, '') 206 206 if command == 'edit': 207 207 req.perm(the_post.resource).require('BLOG_VIEW') # Starting point … … 220 220 else: 221 221 req.perm(the_post.resource).require('BLOG_MODIFY_ALL') 222 # Input verifications and warnings 223 warnings = [] 224 if command == 'create' and the_post.version: 225 warnings.append( 226 ('', "A post named '%s' already exists. Reverting to default name." 227 % the_post.name)) 228 the_post = BlogPost(self.env, default_pagename) 222 223 # Check input 229 224 orig_author = the_post.author 230 225 if not the_post.update_fields(req.args): … … 249 244 "edit the post again due to restricted permissions.") 250 245 data['blog_orig_author'] = orig_author 251 for field, reason in warnings:252 if field:253 add_warning(req, "Field '%s': %s" % (field, reason))254 else:255 add_warning(req, reason)246 for field, reason in warnings: 247 if field: 248 add_warning(req, "Field '%s': %s" % (field, reason)) 249 else: 250 add_warning(req, reason) 256 251 data['blog_edit'] = the_post 257 252 … … 301 296 add_warning(req, reason) 302 297 303 elif command == 'listing':298 elif command.startswith('listing-'): 304 299 # 2007/10 or category/something or author/theuser 305 300 title = category = author = '' 306 from_dt, to_dt = parse_period(path_items) 307 if from_dt: 301 from_dt = to_dt = None 302 if command == 'listing-month': 303 from_dt = listing_data['from_dt'] 304 to_dt = listing_data['to_dt'] 308 305 title = "Posts for the month of %s %d" % ( 309 306 blog_month_names[from_dt.month -1], from_dt.year) … … 311 308 'application/rss+xml', 'rss') 312 309 313 category = (path_items[0].lower() == 'category'314 and path_items[1]) or ''315 if category:316 title = "Posts in category %s" % category317 add_link(req, 'alternate', req.href.blog('category', category, format='rss'),318 'RSS Feed', 'application/rss+xml', 'rss')319 author = (path_items[0].lower() == 'author'320 and path_items[1]) or ''321 if author:322 title = "Posts by author %s" % author323 add_link(req, 'alternate', req.href.blog('author', author, format='rss'),324 'RSS Feed', 'application/rss+xml', 'rss')310 elif command == 'listing-category': 311 category = listing_data['category'] 312 if category: 313 title = "Posts in category %s" % category 314 add_link(req, 'alternate', req.href.blog('category', category, 315 format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') 316 elif command == 'listing-author': 317 author = listing_data['author'] 318 if author: 319 title = "Posts by author %s" % author 320 add_link(req, 'alternate', req.href.blog('author', author, 321 format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') 325 322 if not (author or category or (from_dt and to_dt)): 326 323 raise HTTPNotFound("Not a valid path for viewing blog posts.") … … 336 333 raise HTTPNotFound("Not a valid blog path.") 337 334 338 if (not command or command == 'listing') and format == 'rss':335 if (not command or command.startswith('listing-')) and format == 'rss': 339 336 data['context'] = Context.from_request(req, absurls=True) 340 337 return 'fullblog.rss', data, 'application/rss+xml' … … 467 464 """ Location of Trac templates provided by plugin. """ 468 465 return [resource_filename('tracfullblog', 'templates')] 466 467 # Internal methods 468 469 def _parse_path(self, req): 470 """ Parses the request path for the blog and returns a 471 ('command', 'pagename', 'path_items', 'listing_data') tuple. """ 472 # Parse out the path and actions from args 473 path = req.args.get('blog_path', '') 474 path_items = path.split('/') 475 path_items = [item for item in path_items if item] # clean out empties 476 command = pagename = '' 477 listing_data = {} 478 from_dt, to_dt = parse_period(path_items) 479 if not path_items: 480 pass # emtpy default for return is fine 481 elif len(path_items) > 1 and path_items[0].lower() in ['view', 'edit', 'delete']: 482 command = path_items[0].lower() 483 pagename = '/'.join(path_items[1:]) 484 elif len(path_items) == 1 and path_items[0].lower() == 'archive': 485 command = path_items[0].lower() 486 elif len(path_items) >= 1 and path_items[0].lower() == 'create': 487 command = path_items[0].lower() 488 pagename = req.args.get('name','') or (len(path_items) > 1 \ 489 and '/'.join(path_items[1:])) 490 elif len(path_items) > 1 and path_items[0].lower() in ['author', 'category']: 491 command = 'listing' + '-' + path_items[0].lower() 492 listing_data[path_items[0].lower()] = '/'.join(path_items[1:]) 493 elif len(path_items) == 2 and (from_dt, to_dt) != (None, None): 494 command = 'listing-month' 495 listing_data['from_dt'] = from_dt 496 listing_data['to_dt'] = to_dt 497 else: 498 # A request for a regular page 499 command = 'view' 500 pagename = path 501 return (command, pagename, path_items, listing_data)
