Changeset 2531

Show
Ignore:
Timestamp:
07/27/07 15:27:44 (1 year ago)
Author:
Blackhex
Message:

DownloadsPlugin:

  • Main UI implemented.
  • [download:<id>] macro implemented.

NOTE: No Python traceback should appear from now altrought not all desired features are implemented. For those who had tried this plugin before: Clear all database entries of this plugin first before trying this revision.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • downloadsplugin/0.10/tracdownloads/api.py

    r2522 r2531  
    1 import os, time, re 
     1import os, time, re, mimetypes 
    22 
    33from trac.core import * 
     
    55from trac.wiki import wiki_to_html, wiki_to_oneliner 
    66from trac.util import format_datetime, pretty_timedelta, pretty_size 
    7 from trac.web.chrome import add_stylesheet 
     7from trac.web.chrome import add_stylesheet, add_script 
    88 
    99class IDownloadChangeListener(Interface): 
     
    2626class DownloadsApi(Component): 
    2727 
     28    title = Option('downloads', 'title', 'Downloads', 
     29      'Main navigation bar button title.') 
    2830    path = Option('downloads', 'path', '/var/lib/trac/downloads', 
    2931      'Directory to store uploaded downloads.') 
     
    6668 
    6769    def get_downloads(self, req, cursor, order_by = 'id', desc = False): 
    68         columns = ('id', 'file', 'description', 'size', 'time', 'author', 
    69           'tags', 'component', 'version', 'architecture', 'platform', 'type') 
    70         sql = "SELECT id, file, description, size, time, author, tags," \ 
     70        columns = ('id', 'file', 'description', 'size', 'time', 'count', 
     71          'author', 'tags', 'component', 'version', 'architecture', 'platform', 
     72          'type') 
     73        sql = "SELECT id, file, description, size, time, count, author, tags," \ 
    7174          " component, version, architecture, platform, type FROM download " \ 
    7275          "ORDER BY " + order_by + (" ASC", " DESC")[bool(desc)] 
     
    7982            row['size'] = pretty_size(row['size']) 
    8083            row['time'] = pretty_timedelta(row['time']) 
     84            row['count'] = row['count'] or 0 
    8185            downloads.append(row) 
    8286 
     
    135139 
    136140    def get_download(self, cursor, id): 
    137         columns = ('id', 'file', 'description', 'size', 'time', 'author', 
    138           'tags', 'component', 'version', 'architecture', 'platform', 'type') 
    139         sql = "SELECT id, file, description, size, time, author, tags," \ 
     141        columns = ('id', 'file', 'description', 'size', 'time', 'count', 
     142          'author', 'tags', 'component', 'version', 'architecture', 'platform', 
     143          'type') 
     144        sql = "SELECT id, file, description, size, time, count, author, tags," \ 
    140145          " component, version, architecture, platform, type FROM download" \ 
    141146          " WHERE id = %s" 
     
    144149        for row in cursor: 
    145150            row = dict(zip(columns, row)) 
     151            row['count'] = row['count'] or 0 
    146152            return row 
    147153 
    148154    def get_download_by_time(self, cursor, time): 
    149         columns = ('id', 'file', 'description', 'size', 'time', 'author', 
    150           'tags', 'component', 'version', 'architecture', 'platform', 'type') 
    151         sql = "SELECT id, file, description, size, time, author, tags," \ 
     155        columns = ('id', 'file', 'description', 'size', 'time', 'count', 
     156          'author', 'tags', 'component', 'version', 'architecture', 'platform', 
     157          'type') 
     158        sql = "SELECT id, file, description, size, time, count, author, tags," \ 
    152159          " component, version, architecture, platform, type FROM download" \ 
    153160          " WHERE time = %s" 
     
    156163        for row in cursor: 
    157164            row = dict(zip(columns, row)) 
     165            row['count'] = row['count'] or 0 
    158166            return row 
    159167 
     
    184192            row = dict(zip(columns, row)) 
    185193            return row 
     194 
     195    def get_description(self, req, cursor): 
     196        sql = "SELECT value FROM system WHERE name = 'downloads_description'" 
     197        self.log.debug(sql) 
     198        cursor.execute(sql) 
     199        for row in cursor: 
     200            return (row[0], wiki_to_html(row[0], self.env, req)) 
    186201 
    187202    # Add item functions. 
     
    229244        self._edit_item(cursor, 'download_type', id, type) 
    230245 
     246    def edit_description(self, cursor, description): 
     247        sql = "UPDATE system SET value = %s WHERE name = 'downloads_description'" 
     248        self.log.debug(sql % (description,)) 
     249        cursor.execute(sql, (description,)) 
     250 
    231251    # Delete item functions. 
    232252 
     
    271291        add_stylesheet(req, 'downloads/css/admin.css') 
    272292 
     293        # Add JavaScripts 
     294        add_script(req, 'common/js/trac.js') 
     295        add_script(req, 'common/js/wikitoolbar.js') 
     296 
    273297        # Fill up HDF structure and return template 
    274298        req.hdf['download.authname'] = req.authname 
    275         req.hdf['downloads.href'] = req.href.admin('downloads', req.args.get( 
    276           'page')) 
    277299        req.hdf['download.time'] = format_datetime(time.time()) 
    278300        return modes[-1] + '.cs', None 
     
    285307        page = req.args.get('page') 
    286308        action = req.args.get('action') 
    287         self.log.debug('component: %s page: %s action: %s' % (context, page, 
     309        self.log.debug('context: %s page: %s action: %s' % (context, page, 
    288310          action)) 
     311 
     312        if context == 'admin': 
     313            req.hdf['downloads.href'] = req.href.admin('downloads', page) 
     314        elif context == 'core': 
     315            req.hdf['downloads.href'] = req.href.downloads() 
    289316 
    290317        # Determine mode. 
     
    326353                else: 
    327354                    return ['admin-types-list'] 
     355        elif context == 'core': 
     356            if action == 'get-file': 
     357                return ['get-file'] 
     358            elif action == 'edit': 
     359                return ['description-edit', 'downloads-list'] 
     360            elif action == 'post-edit': 
     361                return ['description-post-edit', 'downloads-list'] 
     362            else: 
     363                return ['downloads-list'] 
    328364        else: 
    329365            pass 
     
    331367    def _do_action(self, req, cursor, modes): 
    332368        for mode in modes: 
    333             if mode == 'downloads-list': 
    334                req.perm.assert_permission('DOWNLOADS_VIEW') 
     369            if mode == 'get-file': 
     370                req.perm.assert_permission('DOWNLOADS_VIEW') 
     371 
     372                # Get form values. 
     373                download_id = req.args.get('id') 
     374 
     375                # Get download. 
     376                download = self.get_download(cursor, download_id) 
     377 
     378                if download: 
     379                    path = os.path.join(self.path, unicode(download['id']), 
     380                      download['file']) 
     381                    self.log.debug('path: %s' % (path,)) 
     382 
     383                    # Increase downloads count. 
     384                    db = self.env.get_db_cnx() 
     385                    cursor = db.cursor() 
     386                    self.edit_download(cursor, download['id'], {'count' : 
     387                      download['count'] + 1}) 
     388                    db.commit() 
     389 
     390                    # Return uploaded file to request. 
     391                    type = mimetypes.guess_type(path)[0] 
     392                    req.send_file(path, type) 
     393                else: 
     394                    raise TracError('File not found.') 
     395 
     396            elif mode == 'downloads-list': 
     397                req.perm.assert_permission('DOWNLOADS_VIEW') 
     398 
     399                # Get form values. 
     400                order = req.args.get('order') or 'id' 
     401                desc = req.args.get('desc') 
     402 
     403                req.hdf['downloads.order'] = order 
     404                req.hdf['downloads.desc'] = desc 
     405                req.hdf['downloads.has_tags'] = self.env.is_component_enabled( 
     406                  'TagEngine') 
     407                req.hdf['downloads.title'] = self.title 
     408                req.hdf['downloads.description'] = self.get_description(req, cursor) 
     409                req.hdf['downloads.downloads'] = self.get_downloads(req, 
     410                  cursor, order, desc) 
    335411 
    336412            elif mode == 'admin-downloads-list': 
     
    359435                  cursor) 
    360436                req.hdf['downloads.types'] = self.get_types(req, cursor) 
    361                 return mode + '.cs', None 
     437 
     438            elif mode == 'description-edit': 
     439                req.perm.assert_permission('DOWNLOADS_ADMIN') 
     440 
     441            elif mode == 'description-post-edit': 
     442                req.perm.assert_permission('DOWNLOADS_ADMIN') 
     443 
     444                # Get form values. 
     445                description = req.args.get('description') 
     446 
     447                # Set new description. 
     448                self.edit_description(cursor, description) 
    362449 
    363450            elif mode == 'downloads-post-add': 
  • downloadsplugin/0.10/tracdownloads/core.py

    r2522 r2531  
    2323    title = Option('downloads', 'title', 'Downloads', 
    2424      'Main navigation bar button title.') 
    25     path = Option('downloads', 'path', '/var/lib/trac/downloads', 
    26       'Directory to store uploaded downloads.') 
    2725 
    2826    # IPermissionRequestor methods. 
     
    5048    # IRequestHandler methods. 
    5149    def match_request(self, req): 
    52         if re.match(r'''^/downloads($|/$)''', req.path_info): 
     50        match = re.match(r'''^/downloads($|/$)''', req.path_info) 
     51        if match: 
    5352            return True 
    54         if re.match(r'''^/downloads/(\d+)$''', 
    55           req.path_info): 
    56             req.args['action'] = 'get_file' 
     53        match = re.match(r'''^/downloads/(\d+)$''',req.path_info) 
     54        if match: 
     55            req.args['action'] = 'get-file' 
     56            req.args['id'] = match.group(1) 
    5757            return True 
    5858        return False 
    5959 
    6060    def process_request(self, req): 
    61         # Create API object. 
    62         self.api = DownloadsApi(self.env) 
    63  
    6461        # Get cursor. 
    6562        db = self.env.get_db_cnx() 
    6663        cursor = db.cursor() 
    6764 
    68         # CSS styles 
    69         add_stylesheet(req, 'downloads/css/downloads.css') 
     65        # Prepare arguments and HDF structure. 
     66        req.args['context'] = 'core' 
    7067 
    71         # Prepare HDF structure. 
    72         req.hdf['downloads.href'] = req.href.downloads() 
    73         req.hdf['downloads.title'] = self.title 
    74  
    75         # Do actions and return content. 
    76         modes = self._get_modes(req) 
    77         self.log.debug('modes: %s' % (modes,)) 
    78         content = self._do_actions(req, cursor, modes) 
     68        # Return page content. 
     69        api = DownloadsApi(self.env) 
     70        content = api.process_downloads(req, cursor) 
    7971        db.commit() 
    8072        return content 
    81  
    82     # Private functions. 
    83  
    84     def _get_modes(self, req): 
    85         action = req.args.get('action') 
    86         self.log.debug('action: %s' % (action,)) 
    87         if action == 'get_file': 
    88             return ['get-file'] 
    89         else: 
    90             return ['downloads-list'] 
    91  
    92     def _do_actions(self, req, cursor, modes): 
    93         for mode in modes: 
    94             if mode == 'get-file': 
    95                 req.perm.assert_permission('DOWNLOADS_VIEW') 
    96  
    97             elif mode == 'downloads-list': 
    98                 req.perm.assert_permission('DOWNLOADS_VIEW') 
    99  
    100                 # Get form values 
    101                 order = req.args.get('order') or 'id' 
    102                 desc = req.args.get('desc') 
    103  
    104                 # Fill HDF structure 
    105                 req.hdf['downloads.order'] = order 
    106                 req.hdf['downloads.desc'] = desc 
    107                 req.hdf['downloads.downloads'] = self.api.get_downloads(req, 
    108                   cursor) 
    109  
    110                 return 'downloads.cs', None 
    111                 #return mode + '.cs', None 
  • downloadsplugin/0.10/tracdownloads/db/db1.py

    r2522 r2531  
    1010    Column('size', type = 'integer'), 
    1111    Column('time', type = 'integer'), 
     12    Column('count', type = 'integer'), 
    1213    Column('author'), 
    1314    Column('tags'), 
     
    5051  "INSERT INTO download_type (name) VALUES ('data')", 
    5152  "INSERT INTO download_type (name) VALUES ('other')", 
     53  "INSERT INTO system (name, value) VALUES ('downloads_description', 'Here is a list of available downloads:')" 
    5254] 
    5355 
     
    6466        cursor.execute(statement) 
    6567 
    66     # Set database schema version 
     68    # Set database schema version. 
    6769    cursor.execute("INSERT INTO system (name, value) VALUES" 
    6870      " ('downloads_version', '1')") 
  • downloadsplugin/0.10/tracdownloads/htdocs/css/admin.css

    r2522 r2531  
    1111} 
    1212 
    13 .listing td.id, .listing td.size, .listing td.time, .listing td.author, 
    14 .listing td.tags, .listing td.component, .listing td.version, 
    15 .listing td.architecture, .listing td.platform, .listing td.type 
     13.listing td.id, .listing td.size, .listing td.time, .listing td.count, 
     14.listing td.author, .listing td.tags, .listing td.component, 
     15.listing td.version, .listing td.architecture, .listing td.platform, 
     16.listing td.type 
    1617{ 
    1718  text-align: center; 
  • downloadsplugin/0.10/tracdownloads/htdocs/css/downloads.css

    r1227 r2531  
     1div.downloads-list 
     2{ 
     3  margin: 2em; 
     4} 
  • downloadsplugin/0.10/tracdownloads/templates/admin-downloads-list.cs

    r2522 r2531  
    128128          <?cs call:sortable_th(downloads.order, downloads.desc, 'size', 'Size', downloads.href) ?> 
    129129          <?cs call:sortable_th(downloads.order, downloads.desc, 'time', 'Uploaded', downloads.href) ?> 
     130          <?cs call:sortable_th(downloads.order, downloads.desc, 'count', 'Downloads', downloads.href) ?> 
    130131          <?cs call:sortable_th(downloads.order, downloads.desc, 'author', 'Uploader', downloads.href) ?> 
    131132          <?cs if:downloads.has_tags ?> 
     
    164165            <td class="description"> 
    165166              <div class="description"> 
    166                 <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>?order=<?cs var:downloads.order ?>;desc=<?cs var:downloads.desc ?>"> 
    167                   <?cs var:download.description ?> 
    168                 </a> 
     167                <?cs var:download.description ?> 
    169168              </div> 
    170169            </td> 
     
    182181                <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>?order=<?cs var:downloads.order ?>;desc=<?cs var:downloads.desc ?>"> 
    183182                  <?cs var:download.time ?> 
     183                </a> 
     184              </div> 
     185            </td> 
     186 
     187            <td class="count"> 
     188              <div class="count"> 
     189                <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>?order=<?cs var:downloads.order ?>;desc=<?cs var:downloads.desc ?>"> 
     190                  <?cs var:download.count ?> 
    184191                </a> 
    185192              </div> 
     
    243250              </div> 
    244251            </td> 
    245  
     252          </tr> 
    246253        <?cs /each ?> 
    247254      </tbody> 
  • downloadsplugin/0.10/tracdownloads/templates/downloads-list.cs

    r2522 r2531  
    11<?cs include "header.cs" ?> 
    2 <?cs include "macros.cs" ?> 
    3 <?cs include "my_macros.cs" ?> 
     2<?cs include "downloads-macros.cs" ?> 
    43 
    54<div id="ctxtnav"> 
     
    109    <h1><?cs var:downloads.title ?></h1> 
    1110  </div> 
     11 
     12  <?cs var:downloads.description.1 ?> 
     13 
     14  <?cs if:trac.acl.DOWNLOADS_ADMIN ?> 
     15    <form method="post" action="<?cs var:downloads.href ?>"> 
     16      <fieldset> 
     17        <legend> 
     18          Edit Description: 
     19        </legend> 
     20 
     21        <?cs if:args.action == 'edit' ?> 
     22          <div class="field"> 
     23            <textarea id="description" name="description" class="wikitext" rows="10" cols="78"><?cs var:downloads.description.0 ?></textarea> 
     24          </div> 
     25        <?cs /if ?> 
     26 
     27        <div class="buttons"> 
     28          <input type="submit" name="submit" value="Edit"/> 
     29          <?cs if:args.action == 'edit' ?> 
     30            <input type="button" name="cancel" value="Cancel" onclick="location.replace('<?cs var:downloads.href ?>?order=<?cs var:downloads.order ?>;desc=<?cs var:downloads.desc ?>')"/> 
     31            <input type="hidden" name="action" value="post-edit"/> 
     32          <?cs else ?> 
     33            <input type="hidden" name="action" value="edit"/> 
     34          <?cs /if ?> 
     35          <input type="hidden" name="order" value="<?cs var:downloads.order ?>"/> 
     36          <input type="hidden" name="desc" value="<?cs var:downloads.desc ?>"/> 
     37        </div> 
     38      </fieldset> 
     39    </form> 
     40  <?cs /if ?> 
     41 
    1242  <?cs if:downloads.downloads.0.id ?> 
    13     <table class="listing"> 
    14       <thead> 
    15         <tr> 
    16           <?cs call:my_sortable_th(downloads.order, downloads.desc, 'id', 'ID', downloads.href + '?') ?> 
    17         </tr> 
    18       </thead> 
    19       <tbody> 
    20         <?cs each:download = downloads.downloads ?> 
    21           <tr class="<?cs if:download.id % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
    22             <td class="id"> 
    23               <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
    24                 <div class="id"><?cs var:download.id ?></div> 
    25               </a> 
    26             </td> 
     43    <div class="downloads-list"> 
     44      <table class="listing"> 
     45        <thead> 
     46          <tr> 
     47            <?cs call:sortable_th(downloads.order, downloads.desc, 'id', 'ID', downloads.href) ?> 
     48            <?cs call:sortable_th(downloads.order, downloads.desc, 'file', 'File', downloads.href) ?> 
     49            <?cs call:sortable_th(downloads.order, downloads.desc, 'description', 'Description', downloads.href) ?> 
     50            <?cs call:sortable_th(downloads.order, downloads.desc, 'size', 'Size', downloads.href) ?> 
     51            <?cs call:sortable_th(downloads.order, downloads.desc, 'time', 'Uploaded', downloads.href) ?> 
     52            <?cs call:sortable_th(downloads.order, downloads.desc, 'count', 'Downloads', downloads.href) ?> 
     53            <?cs call:sortable_th(downloads.order, downloads.desc, 'author', 'Uploader', downloads.href) ?> 
     54            <?cs if:downloads.has_tags ?> 
     55              <?cs call:sortable_th(downloads.order, downloads.desc, 'tags', 'Tags', downloads.href) ?> 
     56            <?cs /if ?> 
     57            <?cs call:sortable_th(downloads.order, downloads.desc, 'component', 'Component', downloads.href) ?> 
     58            <?cs call:sortable_th(downloads.order, downloads.desc, 'version', 'Version', downloads.href) ?> 
     59            <?cs call:sortable_th(downloads.order, downloads.desc, 'architecture', 'Architecture', downloads.href) ?> 
     60            <?cs call:sortable_th(downloads.order, downloads.desc, 'platform', 'Platform', downloads.href) ?> 
     61            <?cs call:sortable_th(downloads.order, downloads.desc, 'type', 'Type', downloads.href) ?> 
    2762          </tr> 
    28         <?cs /each ?> 
    29       </tbody> 
    30     </table> 
     63        </thead> 
     64        <tbody> 
     65          <?cs each:download = downloads.downloads ?> 
     66            <tr class="<?cs if:download.id % #2 ?>odd<?cs else ?>even<?cs /if ?>"> 
     67              <td class="id"> 
     68                <div class="id"> 
     69                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     70                    <?cs var:download.id ?> 
     71                  </a> 
     72                </div> 
     73              </td> 
     74 
     75              <td class="file"> 
     76                <div class="file"> 
     77                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     78                    <?cs var:download.file ?> 
     79                  </a> 
     80                </div> 
     81              </td> 
     82 
     83              <td class="description"> 
     84                <div class="description"> 
     85                  <?cs var:download.description ?> 
     86                </div> 
     87              </td> 
     88 
     89              <td class="size"> 
     90                <div class="size"> 
     91                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     92                    <?cs var:download.size ?> 
     93                  </a> 
     94                </div> 
     95              </td> 
     96 
     97              <td class="time"> 
     98                <div class="time"> 
     99                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     100                    <?cs var:download.time ?> 
     101                  </a> 
     102                </div> 
     103              </td> 
     104 
     105              <td class="count"> 
     106                <div class="count"> 
     107                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     108                    <?cs var:download.count ?> 
     109                  </a> 
     110                </div> 
     111              </td> 
     112 
     113              <td class="author"> 
     114                <div class="author"> 
     115                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     116                    <?cs var:download.author ?> 
     117                  </a> 
     118                </div> 
     119              </td> 
     120 
     121              <?cs if:downloads.has_tags ?> 
     122                <td class="tags"> 
     123                  <div class="tags"> 
     124                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     125                      <?cs var:download.tags ?> 
     126                    </a> 
     127                  </div> 
     128                </td> 
     129              <?cs /if ?> 
     130 
     131              <td class="component"> 
     132                <div class="component"> 
     133                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     134                    <?cs var:download.component ?> 
     135                  </a> 
     136                </div> 
     137              </td> 
     138 
     139              <td class="version"> 
     140                <div class="version"> 
     141                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     142                    <?cs var:download.version ?> 
     143                  </a> 
     144                </div> 
     145              </td> 
     146 
     147              <td class="architecture"> 
     148                <div class="architecture"> 
     149                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     150                    <?cs var:download.architecture.name ?> 
     151                  </a> 
     152                </div> 
     153              </td> 
     154 
     155              <td class="platform"> 
     156                <div class="platform"> 
     157                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     158                    <?cs var:download.platform.name ?> 
     159                  </a> 
     160                </div> 
     161              </td> 
     162 
     163              <td class="type"> 
     164                <div class="type"> 
     165                  <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>"> 
     166                    <?cs var:download.type.name ?> 
     167                  </a> 
     168                </div> 
     169              </td> 
     170            </tr> 
     171          <?cs /each ?> 
     172        </tbody> 
     173      </table> 
     174    </div> 
    31175  <?cs else ?> 
    32176    <p class="help">There are no downloads created.</p> 
  • downloadsplugin/0.10/tracdownloads/wiki.py

    r1227 r2531  
     1 
     2from trac.core import * 
     3from trac.util.html import html 
     4 
     5from trac.wiki import IWikiSyntaxProvider 
     6 
    17from tracdownloads.api import * 
    2 from trac.core import * 
    3 from trac.wiki import IWikiSyntaxProvider 
    4 from trac.util.html import html 
    58 
    69class DownloadsWiki(Component): 
     
    2124    def _download_link(self, formatter, ns, params, label): 
    2225        if ns == 'download': 
    23             # Get cursor. 
    24             db = self.env.get_db_cnx() 
    25             cursor = db.cursor() 
     26            if formatter.req.perm.has_permission('DOWNLOADS_VIEW'): 
     27                # Get cursor. 
     28                db = self.env.get_db_cnx() 
     29                cursor = db.cursor() 
    2630 
    27             # Get referenced screenshot. 
    28             api = DownloadsApi(self
     31                # Get API component. 
     32                api = DownloadsApi(self.env
    2933 
    30             return html.a(label, href = formatter.href.downloads(), 
    31               title = params, class_ = 'missing') 
     34                # Get download. 
     35                download = api.get_download(cursor, params) 
     36 
     37                if download: 
     38                    # Return link to existing file. 
     39                    return html.a(label, href = formatter.href.downloads(params), 
     40                      title = download['file']) 
     41                else: 
     42                    # Return link to non-existing file. 
     43                    return html.a(label, href = '#', title = 'File not found.', 
     44                      class_ = 'missing') 
     45            else: 
     46                # Return link to file to which is no permission.  
     47                return html.a(label, href = '#', title = 'No permission to file.', 
     48                   class_ = 'missing')