Changeset 3513

Show
Ignore:
Timestamp:
04/14/08 01:28:38 (8 months ago)
Author:
tcoulter
Message:

First implementation finished! If there are incoming links for a ticket, they will be displayed in the "Incoming Links" section right after the ticket info.

Most of the changes made since the last commit were simply to get the data to display properly/nicely.

Because of the use of Transformer, this plugin requires Genshi trunk.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trashtalkplugin/0.11/setup.py

    r3509 r3513  
    11from setuptools import find_packages, setup 
    22 
    3 # name can be any name.  This name will be used to create .egg file. 
    4 # name that is used in packages is the one that is used in the trac.ini file. 
    5 # use package name as entry_points 
    63setup( 
    7     name='TrashTalk', version='0.1', 
    8     packages=find_packages(exclude=['*.tests*']), 
    9     entry_points = """ 
    10         [trac.plugins] 
    11         trashtalk = trashtalk 
    12     """, 
     4    name='TrashTalk', 
     5    version='0.1', 
     6    packages=['trashtalk'], 
     7    package_data={ 'trashtalk' : ['htdocs/*.css'] }, 
     8     
     9    author='Tim Coulter', 
     10    author_email='tim@timothyjcoulter.com', 
     11    description='Logs URLs linking to tickets in order to show community impact.', 
     12    url='http://trac-hacks.org/wiki/TrashTalkPlugin', 
     13     
     14    classifiers=[ 
     15        'Framework :: Trac', 
     16    ], 
     17     
     18    install_requires = ['Trac', 'Genshi >= 0.5.dev-r698,==dev'], 
     19     
     20    entry_points = { 
     21        'trac.plugins': ['trashtalk = trashtalk'] 
     22    } 
     23     
    1324) 
    1425 
  • trashtalkplugin/0.11/trashtalk/trashtalk.py

    r3512 r3513  
    2929 
    3030Some methods below were legally pilferred from the tracpasteplugin: 
    31 http://www.trac-hacks.org/wiki/TracPastePlugind 
     31http://www.trac-hacks.org/wiki/TracPastePlugin 
    3232""" 
    3333 
    3434import re 
    3535 
     36from genshi.core import Markup 
    3637from genshi.builder import tag 
     38from genshi.filters.transform import Transformer 
    3739 
    3840from trac.core import * 
     
    4042from trac.db import Table, Column, Index 
    4143from trac.resource import ResourceNotFound 
    42 from trac.web.api import * 
    4344from trac.ticket import Ticket 
    4445from trac.util.datefmt import utc, to_timestamp 
    4546from datetime import datetime 
    4647 
    47 def sql_escape_percent(sql): 
    48     import re 
    49     return re.sub("'((?:[^']|(?:''))*)'", lambda m: m.group(0).replace('%', '%%'), sql) 
    50  
    51 def get_incoming_links_for_ticket(env, ticket, db=None): 
    52     """Return all incoming links for the given ticket.""" 
    53     cursor = (db or env.get_db_cnx()).cursor() 
    54     cursor.execute('select * from incoming_links where id=%s order by first desc', (ticket,)) 
    55     result = [] 
    56     for row in cursor: 
    57         result.append({ 
    58             'id':                   row[0], 
    59             'ticket':               row[1], 
    60             'external_url':         row[2], 
    61             'click_count':          row[3], 
    62             'first':                datetime.fromtimestamp(row[4], utc), 
    63             'most_recent':          datetime.fromtimestamp(row[5], utc) 
    64         }) 
    65     return result 
     48from trac.web.api import IRequestFilter, ITemplateStreamFilter 
     49from trac.web.chrome import ITemplateProvider, add_stylesheet 
    6650 
    6751class TrashTalkPlugin(Component): 
    68     implements(IRequestFilter, IEnvironmentSetupParticipant
     52    implements(IEnvironmentSetupParticipant, ITemplateStreamFilter, ITemplateProvider, IRequestFilter
    6953 
    7054    SCHEMA = [ 
     
    7357            Column('ticket'),                  # The id of the ticket that has been linked to. 
    7458            Column('external_url'),            # The url linking to the ticket. 
    75             Column('click_count'),            # The number of times the external url has been clicked on. 
     59            Column('click_count', type='int'), # The number of times the external url has been clicked on. 
    7660            Column('first', type='int'),       # The first time the external_url linked to the ticket. 
    7761            Column('most_recent', type='int')  # The most recent time the external_url linked to the ticket. 
     
    8973 
    9074    TICKET_URI_PATTERN = re.compile("^/ticket/(\d+)(?:$|.*$)", re.VERBOSE) 
    91  
     75     
    9276    # IEnvironmentSetupParticipant methods 
    9377    def environment_created(self): 
     
    10791        self._upgrade_db(db) 
    10892 
    109  
     93    # ITemplateStreamFilter methods 
     94    def filter_stream(self, req, method, filename, stream, data): 
     95         
     96        if self._is_ticket(req): 
     97             
     98            if self._is_external(req): 
     99                self._talk_about(req) 
     100             
     101            ticket = self._get_ticket_from_request(req) 
     102            links = self._get_incoming_links_for_ticket(ticket) 
     103             
     104            if len(links) != 0: 
     105         
     106                ticket = self._get_ticket_from_request(req) 
     107                 
     108                div = tag.div(id="trashtalk") 
     109     
     110                for link in links: 
     111                    a = tag.a(href=link['external_url']) 
     112                    a(link['external_url']) 
     113                    div(a) 
     114                    div(" (%s)" % link['click_count']) 
     115                    div(tag.br) 
     116                 
     117                stream |= Transformer('//div[@id="ticket"]').after(div).after(tag.h2("Incoming Links")) 
     118 
     119        return stream 
     120     
    110121    # IRequestFilter methods 
    111122    def pre_process_request(self, req, handler): 
    112         if self._is_external(req) and self._is_ticket(req): 
    113             self._talk_about(req) 
    114123        return handler 
    115      
     124 
    116125    # for ClearSilver templates 
    117126    def post_process_request(self, req, template, content_type): 
    118127        return (template, content_type) 
    119      
     128 
    120129    # for Genshi templates 
    121130    def post_process_request(self, req, template, data, content_type): 
     131        #if self._is_ticket(req): 
     132        add_stylesheet(req, 'trashtalk/trashtalk.css') 
     133 
    122134        return (template, data, content_type) 
     135     
     136    # ITemplateProvider methods 
     137    def get_htdocs_dirs(self): 
     138        """Return the absolute path of a directory containing additional 
     139        static resources (such as images, style sheets, etc). 
     140        """ 
     141        from pkg_resources import resource_filename 
     142        return [('trashtalk', resource_filename(__name__, 'htdocs'))] 
     143 
     144    def get_templates_dirs(self): 
     145        """Return the absolute path of the directory containing the provided 
     146        ClearSilver templates. 
     147        """ 
     148        #from pkg_resources import resource_filename 
     149        #return [resource_filename(__name__, 'templates')] 
     150        return [] 
    123151     
    124152    # Private methods 
     
    128156        # Right now though, for a first implementation, this will suffice. 
    129157         
    130         ticket = int(self.TICKET_URI_PATTERN.findall(req.path_info)[0]
     158        ticket = self._get_ticket_from_request(req
    131159        external_url = req.get_header("referer") 
    132160        click_count = 1 
     
    165193    def _is_ticket(self, req): 
    166194        return len(self.TICKET_URI_PATTERN.findall(req.path_info)) > 0 
     195     
     196    def _get_ticket_from_request(self, req): 
     197        return int(self.TICKET_URI_PATTERN.findall(req.path_info)[0]) 
     198     
     199    def _get_incoming_links_for_ticket(self, ticket): 
     200        # Let's get a database connection. 
     201        db = self.env.get_db_cnx() 
     202        cursor = db.cursor() 
     203        cursor.execute('select * from incoming_links where ticket=%s order by first desc', str(ticket)) 
     204         
     205        rows = cursor.fetchall() 
     206         
     207        result = [] 
     208        for row in rows: 
     209            result.append({ 
     210                'id':                   row[self.SCHEMA_LOOKUP['id']], 
     211                'ticket':               row[self.SCHEMA_LOOKUP['ticket']], 
     212                'external_url':         row[self.SCHEMA_LOOKUP['external_url']], 
     213                'click_count':          row[self.SCHEMA_LOOKUP['click_count']], 
     214                'first':                row[self.SCHEMA_LOOKUP['first']], 
     215                'most_recent':          row[self.SCHEMA_LOOKUP['most_recent']] 
     216            }) 
     217        return result 
    167218     
    168219    def _upgrade_db(self, db): 
     
    184235            self.env.log.error(e, exc_info=1) 
    185236            raise TracError(str(e)) 
    186      
     237         
     238 
     239