Changeset 102

Show
Ignore:
Timestamp:
04/09/08 00:17:10 (8 months ago)
Author:
pragma
Message:

Dirty Commit - code has not been tested, please use previous revision

  • Refactored controllers to use method decorators on the various page handlers
  • Added alternate (non-HTML) content support

Page handlers can now be bound to multiple mimetypes and corresponding .cs templates. While not the
most flexible approach, this allows for very easy integration for RSS, CSV, HTML and plain-text output
to co-exist from the same controller content hook. Now a mimetype is simply defined by what .cs template
generates its output. The various links at the footer of the page are also driven from the same dynamic
information (thanks to method decorators).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/tracforums/model.py

    r99 r102  
    246246        UIContext.__init__(self,req,env,db) 
    247247         
     248        # build up the handlers 
     249        memberMap = self.__class__.__dict__ 
     250        self.handlerMap = {} 
     251        for name in memberMap: 
     252            member = memberMap[name] 
     253            if isinstance(member,IPageHandler): 
     254                for name in member.getPageNames(): 
     255                    self.handlerMap[name] = member 
     256         
    248257    def __getitem__(self,name): 
    249         def handle(*args): 
    250             return self.templates[name](self,*args) 
    251         return handle 
     258        if name in self.handlerMap: 
     259            return self.handlerMap[name] 
     260        else: 
     261            raise TracError("Unknown handler '" + name + "'") 
    252262         
    253263    def assertAction(self,test,message): 
    254264        if not test: 
    255265            raise TracError(message) 
     266             
     267    def supports(self,view,mimetype): 
     268        return view in self.handlerMap and self.handlerMap[view].supports(mimetype) 
     269             
     270    def invoke(self,view,mimetype): 
     271        if view in self.handlerMap: 
     272            return self.handlerMap[view][mimetype](self) 
     273        else: 
     274            raise TracError("Unknown handler '" + view + "'") 
     275 
     276             
     277class IPageHandler: 
     278    def getPageNames(self):            pass 
     279    def __call__(self,*args,**kwargs): pass 
     280    def __getitem__(self,format):      pass 
     281    def getInner(self,format):         pass 
     282    def supports(self,format):         pass 
     283             
     284def PageHandler(pageNames,mimetypeTemplateMap): 
     285    """ 
     286        Decorator for Controller methods.  Supply a list of page names and a  
     287        mimetype:template map to bind a method for page handling. 
     288         
     289        Access a specific type handler via the array index operator: handler["html"]. 
     290         
     291        Call directly (__call__) to default to the 'html' type. 
     292         
     293        Returns a (templateName,assetMap) tuple 
     294    """ 
     295    class PageHandlerImpl(IPageHandler): 
     296        def getPageNames(self): return self.pageNames 
     297     
     298        def __init__(self,func): 
     299            self.func = func 
     300            self.mimetypeTemplateMap = mimetypeTemplateMap 
     301             
     302            if isinstance(pageNames,list): 
     303                self.pageNames = pageNames 
     304            else: 
     305                self.pageNames = [pageNames,] 
     306             
     307        def __call__(self,*args,**kwargs): 
     308            return self.getInner('text/html')(*args,**kwargs) 
     309             
     310        def __getitem__(self,mimetype): 
     311            return self.getInner(mimetype) 
     312         
     313        def getInner(self,mimetype): 
     314            if mimetype in self.mimetypeTemplateMap: 
     315                def innerFunc(*args,**kwargs): 
     316                    return (self.mimetypeTemplateMap[mimetype],self.func(*args,**kwargs),mimetype) 
     317                return innerFunc 
     318            else: 
     319                raise TracError("Unknown or unsupported mimetype '" + mimetype + "'") 
     320                 
     321        def supports(self,mimetype): 
     322            return mimetype in self.mimetypeTemplateMap 
     323        
     324    return PageHandlerImpl 
     325     
     326     
     327def ContentHandler(pageNames): 
     328    """ 
     329        Decorator for Controller methods.  Supply a list of page names to handle. 
     330        The decorated handle is responsible for returning the (template,assets,type) tuple 
     331         
     332        Returns a (templateName,assetMap,mimetype) tuple 
     333    """         
     334    class PageHandlerImpl(IPageHandler): 
     335        def getPageNames(self): return self.pageNames 
     336     
     337        def __init__(self,func): 
     338            self.func = func 
     339             
     340            if isinstance(pageNames,list): 
     341                self.pageNames = pageNames 
     342            else: 
     343                self.pageNames = [pageNames,] 
     344             
     345        def __call__(self,*args,**kwargs): 
     346            return self.getInner(None)(*args,**kwargs) 
     347             
     348        def __getitem__(self,mimetype): 
     349            return self.getInner(mimetype) 
     350         
     351        def getInner(self,mimetype): 
     352            def innerFunc(*args,**kwargs): 
     353                return self.func(*args,**kwargs) 
     354            return innerFunc 
     355                 
     356        def supports(self,format): 
     357            return True 
     358        
     359    return PageHandlerImpl     
  • trunk/tracforums/models/avatar.py

    r100 r102  
    184184        Controller.__init__(self,req,env,db) 
    185185         
     186    @ContentHandler(["img","default"]) 
    186187    def doView(self): 
    187188        args = self.getArgs() 
     
    189190 
    190191        self.renderAsFile(avatar.getTargetPath(),avatar.mimetype) 
    191         return "", None         
    192          
    193     templates = { 
    194         "img":     doView, 
    195         "default": doView 
    196     } 
     192        return ("", None, avatar.mimetype) 
  • trunk/tracforums/models/forum.py

    r101 r102  
    195195    def __init__(self,req,env,db): 
    196196        Controller.__init__(self,req,env,db) 
    197                                              
     197                                 
     198    @PageHandler(["view","default"],{ 
     199        "text/html": "forum/view.cs" 
     200    })                     
    198201    def doView(self): 
    199202        args = self.getArgs() 
     
    234237            validateErrors = e.reasons 
    235238                                          
    236         return ("forum/view.cs",
     239        return
    237240            "returnto": self.req.abs_href.forums() + "/forum/view/" + str(forum.id), 
    238241            "forum": forum, 
     
    240243            "topics": TopicModelWithLeadMessage(self.db,self).getMany({"forumid": forum.id}), 
    241244            "watching": ForumWatchModelWithProfile(self.db,self).getMany({"forumid": forum.id}), 
    242         }) 
    243          
     245        }      
     246         
     247    @PageHandler("edit",{ 
     248        "text/html": "forum/edit.cs" 
     249    })  
    244250    def doEdit(self): 
    245251        args = self.getArgs() 
     
    282288        print "\nforum:",forum 
    283289         
    284         return ("forum/edit.cs",
     290        return
    285291            "returnto": args.get("returnto",None), 
    286292            "forum": forum, 
     
    289295            "forum_description_rows": self.getSessionVar("forum_description_rows",8), 
    290296            "validateErrors": validateErrors 
    291         }) 
    292          
     297        } 
     298         
     299    @PageHandler("manage",{ 
     300        "text/html": "forum/manage.cs" 
     301    })          
    293302    def doManage(self): 
    294303        args = self.getArgs() 
     
    346355            validateErrors = e.reasons         
    347356                  
    348         return ("forum/manage.cs",
     357        return
    349358            "returnto": self.req.abs_href.forums() + "/forum/manage/" + str(forum.id), 
    350359            "forum": forum, 
     
    353362            "canMoveForums": self.isForumAdmin(), 
    354363            "forums": ForumModel(self.db,self).getMany({"projectid": self.getProjectId()}), 
    355         })         
    356                  
    357     templates = { 
    358         "view":    doView, 
    359         "edit":    doEdit, 
    360         "manage":  doManage, 
    361         "default": doView 
    362     } 
     364        } 
  • trunk/tracforums/models/main.py

    r101 r102  
    66    def __init__(self,req,env,db): 
    77        Controller.__init__(self,req,env,db) 
    8                          
     8         
     9    @PageHandler(["index","default"],{ 
     10        "text/html": "main/index.cs", 
     11        "application/rss+xml": "main/rss.cs" 
     12    }) 
    913    def doIndex(self): 
    1014        self.assertAction( 
     
    5559            if forum.canView: plainForums.append(forum) 
    5660                 
    57         return ("main/index.cs",
     61        return
    5862            "returnto": self.req.abs_href.forums() + "main/index", 
    5963            "validateErrors": validateErrors, 
     
    6367            "canWatch": self.isForumUser(), 
    6468            "watching": ProjectWatchModelWithProfile(self.db,self).getMany({"projectid": self.getProjectId()}), 
    65         })    
    66                  
     69        } 
     70         
     71    @PageHandler("manage",{ 
     72        "text/html": "main/manage.cs" 
     73    })   
    6774    def doManage(self): 
    6875        self.assertAction( 
     
    180187        from tracforums.models.category import CategoryModelWithForums 
    181188        from tracforums.models.forum import ForumModel  
    182                                      
    183         return ("main/manage.cs",{ 
     189        from tracforums.models.project import ProjectModel 
     190                                             
     191        return { 
    184192            "returnto": self.req.abs_href.forums() + "/main/manage/", 
    185193            "validateErrors": validateErrors, 
    186194            "canMoveProjects": self.isTracAdmin(), 
    187             "projects": [{"id":"foobar","name":"Foobar Project"}], #TODO: project listing 
     195            "projects": ProjectModel(self.db,self).getMany(), 
    188196            "categories": CategoryModelWithForums(self.db,self).getMany({"projectid": self.getProjectId()}), 
    189197            "forums": ForumModel(self.db,self).getMany({"projectid": self.getProjectId(),"categoryid":0}) 
    190         }) 
    191          
     198        } 
     199         
     200    @PageHandler("recent",{ 
     201        "text/html": "main/recent.cs", 
     202        "rss": "main/rss.cs" 
     203    }) 
    192204    def doRecent(self): 
    193205        from tracforums.models.topic import TouchedTopic 
     
    197209        for topic in TouchedTopic(self.db,self).getMany(query={"touched":False},params={"username":self.getAuthname()}): 
    198210            if topic.forum.canView: 
    199                 topics.append(topic)      
     211                topics.append(topic) 
    200212                print topic.touched 
    201213         
    202         return ("main/recent.cs",
     214        return
    203215            "topics": topics 
    204         }) 
    205          
     216        } 
     217         
     218    @PageHandler("profiles",{ 
     219        "text/html": "main/profiles.cs" 
     220    }) 
    206221    def doProfiles(self): 
    207222        self.assertAction( 
     
    212227        from tracforums.models.profile import ProfileModelWithAvatar 
    213228 
    214         return ("main/profiles.cs",
     229        return
    215230            "profiles": ProfileModelWithAvatar(self.db,self).getMany() #get all 
    216         }) 
    217          
    218     def doRss(self): 
    219         #TODO: should probably do recent for site, rather than current user 
    220         (template,assets) = self.doRecent() 
    221         return ("rss.cs",assets,'text/xml')#'application/rss+xml') 
    222          
    223     templates = { 
    224         "index":    doIndex, 
    225         "recent":   doRecent, 
    226         "manage":   doManage, 
    227         "profiles": doProfiles, 
    228         "rss":      doRss, 
    229         "default":  doIndex 
    230     } 
     231        } 
  • trunk/tracforums/models/message.py

    r101 r102  
    4646    )): 
    4747    def format(self): 
    48         "MESSAGE FORMAT" 
    49         print ("MESSAGE FORMAT") 
    5048        #TODO: replace with a smarter, wiki 'null formatter' 
    5149        self.synopsis = self.body        
     
    121119        Controller.__init__(self,req,env,db) 
    122120         
     121    @PageHandler(["edit","default"],{ 
     122        "text/html": "message/edit.cs" 
     123    }) 
    123124    def doEdit(self): 
    124125        args = self.getArgs() 
     
    182183        canSetAvatar = self.isForumAdmin or self.getAuthname() == message.username or message.id == 0 
    183184                         
    184         return ("message/edit.cs",
     185        return
    185186            "returnto": args.get("returnto",None), 
    186187            "message": message, 
     
    192193            "validateErrors": validateErrors, 
    193194            "canSetAvatar": canSetAvatar 
    194         }) 
    195          
    196     templates = { 
    197         "edit":    doEdit, 
    198         "default": doEdit 
    199     } 
     195        } 
  • trunk/tracforums/models/profile.py

    r98 r102  
    131131        else: 
    132132            return False 
    133                          
     133                
     134    @PageHandler(["view","default"],{ 
     135        "text/html": "profile/view.cs" 
     136    })              
    134137    def doView(self): 
    135138        args = self.getArgs() 
     
    161164        moderatedForums = ModeratorModelWithForum(self.db,self).getMany({"username":profile.username}) 
    162165         
    163         return ("profile/view.cs",
     166        return
    164167            "profile": profile, 
    165168            "title": title, 
    166169            "project": project.name, 
    167170            "moderatedForums": moderatedForums 
    168         }) 
    169          
     171        } 
     172         
     173    @PageHandler("edit",{ 
     174        "text/html": "profile/edit.cs" 
     175    })  
    170176    def doEdit(self): 
    171177        args = self.getArgs() 
     
    189195        profile = ProfileModelWithAssets(self.db,self).load({"profileid": args["viewid"]}) 
    190196         
    191         return ("profile/edit.cs",
     197        return
    192198            "returnto": args.get("returnto",None), 
    193199            "profile_bio_rows": self.getSessionVar('profile_bio_rows',8), 
    194200            "profile":          profile, 
    195201            "validateErrors":   validateErrors 
    196          }) 
     202         } 
    197203          
     204    @PageHandler("watches",{ 
     205        "text/html": "profile/watches.cs" 
     206    })           
    198207    def doWatchLists(self): 
    199208        args = self.getArgs() 
     
    254263         
    255264        from tracforums.models.watch import CrossProjectWatchDetails,ForumWatchModelWithForum,TopicWatchModelWithTopic 
    256         return ("profile/watches.cs",
     265        return
    257266            "profile":        profile, 
    258267            "watchedForums":  ForumWatchModelWithForum(self.db,self).getMany({"watchusername": profile.username,"projectid":self.getProjectId()}), 
     
    264273                              )), 
    265274            "validateErrors": validateErrors 
    266         }) 
    267          
     275        } 
     276         
     277    @PageHandler("avatars",{ 
     278        "text/html": "profile/avatars.cs" 
     279    })         
    268280    def doManageAvatars(self): 
    269281        args = self.getArgs() 
     
    331343         
    332344        from tracforums.models.avatar import AvatarModelWithDetails 
    333         return ("profile/avatars.cs",
     345        return
    334346            "profile": profile, 
    335347            "avatars": AvatarModelWithDetails(self.db,self).getMany({"username":profile.username}), 
    336348            "validateErrors": validateErrors 
    337         }) 
     349        } 
    338350        
    339351                 
  • trunk/tracforums/models/topic.py

    r101 r102  
    188188    def canViewAll(self): 
    189189        return self.isForumUser() 
    190                                      
     190                          
     191    @PageHandler(["view","default"],{ 
     192        "text/html": "topic/view.cs" 
     193    })                    
    191194    def doView(self): 
    192195        args = self.getArgs() 
     
    233236        from tracforums.models.forum   import ForumModel             
    234237         
    235         return ("topic/view.cs",
     238        return
    236239            "returnto": self.req.abs_href.forums() + "/topic/view/" + str(topic.id), 
    237240            "validateErrors": validateErrors, 
     
    243246            }), 
    244247            "watching": TopicWatchModelWithProfile(self.db,self).getMany({"topicid": topic.id}) 
    245         }) 
    246          
     248        } 
     249         
     250    @PageHandler("edit",{ 
     251        "text/html": "topic/edit.cs" 
     252    })         
    247253    def doEdit(self): 
    248254        args = self.getArgs() 
     
    312318               validateErrors = e.reasons 
    313319                                                     
    314         return ("topic/edit.cs",
     320        return
    315321            "returnto": args.get("returnto",None), 
    316322            "topic": topic, 
     
    319325            "message_body_rows": self.getSessionVar("message_body_rows",8), 
    320326            "validateErrors": validateErrors 
    321         }) 
    322          
     327        } 
     328         
     329    @PageHandler("manage",{ 
     330        "text/html": "topic/manage.cs" 
     331    })         
    323332    def doManage(self): 
    324333        args = self.getArgs() 
     
    365374            "canMoveMessages": self.isForumAdmin() 
    366375        }) 
    367          
    368     templates = { 
    369         "view":    doView, 
    370         "edit":    doEdit, 
    371         "manage":  doManage, 
    372         "default": doView 
    373     } 
  • trunk/tracforums/orm.py

    r101 r102  
    347347         
    348348    def doFormat(self,row,obj): 
    349         print "\nORMJoin doFormat row: ",row 
     349        #print "\nORMJoin doFormat row: ",row 
    350350        isNull = True 
    351351        for val in row: 
     
    353353                isNull = False 
    354354                value = self.getModel()(obj.db,obj.formatContext).mapRowToModel(row) 
    355                 print "MAPPED\n" 
     355                #print "MAPPED\n" 
    356356                value.format() 
    357357                break 
     
    469469            return "" 
    470470        else: 
    471             #TODO: fix me - this is broken across the whole ORM 
    472             print "orderby: ",orderby 
     471            #print "orderby: ",orderby 
    473472            return " order by " + ",".join(map(lambda x: x[0] + " " + x[1],orderby))        
    474473             
     
    698697                self.must(self.validateSave()) 
    699698                         
    700                 print "\n",self.schema.updateSQL,self._getQueryValues(),"\n",self.__dict__ 
     699                #print "\n",self.schema.updateSQL,self._getQueryValues(),"\n",self.__dict__ 
    701700                cursor.execute(self.schema.updateSQL,self._getQueryValues()) 
    702701                self.db.commit() 
     
    704703                self.must(self.validateCreate()) 
    705704                
    706                 print "\n",self.schema.insertSQL,self._getQueryValues() 
     705                #print "\n",self.schema.insertSQL,self._getQueryValues() 
    707706                cursor.execute(self.schema.insertSQL,self._getQueryValues()) 
    708707                self.db.commit() 
  • trunk/tracforums/web_ui.py

    r98 r102  
    11from trac.core import * 
    2 from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_script 
     2from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_script, add_link 
    33from trac.web.main import IRequestHandler 
    44from trac.wiki import wiki_to_html, wiki_to_oneliner 
     
    212212        "message": MessageController, 
    213213    } 
     214     
     215    alternateFormats = { 
     216        "application/rss+xml":  ('alternate', '?format=rss', 'RSS Feed', 'application/rss+xml', 'rss') 
     217    } 
     218     
     219    mimetypeMap = { 
     220        "html": "text/html", 
     221        "rss":  "application/rss+xml" 
     222    } 
    214223         
    215224    """ The process_request handle in this class behaves like a state machine for  
     
    218227    def process_request(self,req): 
    219228        req.args['authname'] = req.authname # grab authname here 
    220          
     229         
    221230        db = get_forumDB(self.env) 
    222         viewName = req.args['view'] 
     231         
     232        viewName = req.args['view']      
    223233        if viewName in self.controllers: 
    224234            controller = self.controllers[viewName](req,self.env,db) 
    225235        else: 
    226236            raise TracError("No such forum view: '" + viewName + "'") 
    227                          
    228         # process the current mode 
    229         results = controller[req.args["mode"]]() 
    230         if len(results) == 3: 
    231             (templateName,assets,mimetype) = results 
    232         else: 
    233             (templateName,assets) = results 
    234             mimetype = 'text/html' 
     237                                     
     238        # process the current mode         
     239        format = req.args.get('format','html') # get the format of the request 
     240        mode = req.args["mode"] 
     241         
     242        # generate links for all the supported formats 
     243        for alternate in self.alternateFormats: 
     244            if controller.supports(mode,alternate): 
     245                args = (req,) + self.alternateFormats[alternate] 
     246                add_link(*args) 
     247         
     248        # generate the content by requesting a given mimetype 
     249        #NOTE: we call even though it may report as supporting nothing 
     250        mimetype = self.mimetypeMap[format] 
     251        (templateName,assets,mimetype) = controller.invoke(mode,mimetype)         
    235252         
    236253        # set styles and links 
     
    241258        add_script(req, "forums/js/forums.js") 
    242259         
    243         print assets         
    244260        req.hdf["forums"] = assets 
    245261