view frontends/ZRoundup/ZRoundup.py @ 7752:b2dbab2b34bc

fix(refactor): multiple fixups using ruff linter; more testing. Converting to using the ruff linter and its rulesets. Fixed a number of issues. admin.py: sort imports use immutable tuples as default value markers for parameters where a None value is valid. reduced some loops to list comprehensions for performance used ternary to simplify some if statements named some variables to make them less magic (e.g. _default_savepoint_setting = 1000) fixed some tests for argument counts < 2 becomes != 2 so 3 is an error. moved exception handlers outside of loops for performance where exception handler will abort loop anyway. renamed variables called 'id' or 'dir' as they shadow builtin commands. fix translations of form _("string %s" % value) -> _("string %s") % value so translation will be looked up with the key before substitution. end dicts, tuples with a trailing comma to reduce missing comma errors if modified simplified sorted(list(self.setting.keys())) to sorted(self.setting.keys()) as sorted consumes whole list. in if conditions put compared variable on left and threshold condition on right. (no yoda conditions) multiple noqa: suppression removed unneeded noqa as lint rulesets are a bit different do_get - refactor output printing logic: Use fast return if not special formatting is requested; use isinstance with a tuple rather than two isinstance calls; cleaned up flow and removed comments on algorithm as it can be easily read from the code. do_filter, do_find - refactor output printing logic. Reduce duplicate code. do_find - renamed variable 'value' that was set inside a loop. The loop index variable was also named 'value'. do_pragma - added hint to use list subcommand if setting was not found. Replaced condition 'type(x) is bool' with 'isinstance(x, bool)' for various types. test_admin.py added testing for do_list better test coverage for do_get includes: -S and -d for multilinks, error case for -d with non-link. better testing for do_find including all output modes better testing for do_filter including all output modes fixed expected output for do_pragma that now includes hint to use pragma list if setting not found.
author John Rouillard <rouilj@ieee.org>
date Fri, 01 Mar 2024 14:53:18 -0500
parents 88dbacd11cd1
children fed0f839c260
line wrap: on
line source

# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
# This module is free software, and you may redistribute it and/or modify
# under the same terms as Python, so long as this copyright message and
# disclaimer are retained in their original form.
#
# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
# 
''' ZRoundup module - exposes the roundup web interface to Zope

This frontend works by providing a thin layer that sits between Zope and the
regular CGI interface of roundup, providing the web frontend with the minimum
of effort.

This means that the regular CGI interface does all authentication quite
independently of Zope. The roundup code is kept in memory though, and it
runs in the same server as all your other Zope stuff, so it does have _some_
advantages over regular CGI :)
'''

from roundup.anypy import urllib_

from Globals import InitializeClass, HTMLFile
from OFS.SimpleItem import Item
from OFS.PropertyManager import PropertyManager
from Acquisition import Explicit, Implicit
from Persistence import Persistent
from AccessControl import ClassSecurityInfo
from AccessControl import ModuleSecurityInfo
modulesecurity = ModuleSecurityInfo()

import roundup.instance
from roundup.cgi import client

modulesecurity.declareProtected('View management screens',
    'manage_addZRoundupForm')
manage_addZRoundupForm = HTMLFile('dtml/manage_addZRoundupForm', globals())

modulesecurity.declareProtected('Add Z Roundups', 'manage_addZRoundup')
def manage_addZRoundup(self, id, instance_home, REQUEST):
    """Add a ZRoundup product """
    # validate the instance_home
    roundup.instance.open(instance_home)
    self._setObject(id, ZRoundup(id, instance_home))
    return self.manage_main(self, REQUEST)

class RequestWrapper:
    '''Make the Zope RESPONSE look like a BaseHTTPServer
    '''
    def __init__(self, RESPONSE):
        self.RESPONSE = RESPONSE
        self.wfile = self.RESPONSE
    def send_response(self, status):
        self.RESPONSE.setStatus(status)
    def send_header(self, header, value):
        self.RESPONSE.addHeader(header, value)
    def end_headers(self):
        # not needed - the RESPONSE object handles this internally on write()
        pass
    def start_response(self, headers, response):
        self.send_response(response)
        for key, value in headers:
            self.send_header(key, value)
        self.end_headers()

class FormItem:
    '''Make a Zope form item look like a cgi.py one
    '''
    def __init__(self, value):
        self.value = value
        if hasattr(self.value, 'filename'):
            self.filename = self.value.filename
            self.value = self.value.read()

class FormWrapper:
    '''Make a Zope form dict look like a cgi.py one
    '''
    def __init__(self, form):
        self.__form = form
    def __getitem__(self, item):
        entry = self.__form[item]
        if isinstance(entry, type([])):
            entry = list(map(FormItem, entry))
        else:
            entry = FormItem(entry)
        return entry
    def __iter__(self):
        return iter(self.__form)
    def getvalue(self, key, default=None):
        if key in self.__form:
            return self.__form[key]
        else:
            return default
    def has_key(self, item):
        return item in self.__form
    def keys(self):
        return list(self.__form.keys())

    def __repr__(self):
        return '<ZRoundup.FormWrapper %r>'%self.__form

class ZRoundup(Item, PropertyManager, Implicit, Persistent):
    '''An instance of this class provides an interface between Zope and
       roundup for one roundup instance
    '''
    meta_type =  'Z Roundup'
    security = ClassSecurityInfo()

    def __init__(self, id, instance_home):
        self.id = id
        self.instance_home = instance_home

    # define the properties that define this object
    _properties = (
        {'id':'id', 'type': 'string', 'mode': 'w'},
        {'id':'instance_home', 'type': 'string', 'mode': 'w'},
    )
    property_extensible_schema__ = 0

    # define the tabs for the management interface
    manage_options= PropertyManager.manage_options + (
        {'label': 'View', 'action':'index_html'},
    ) + Item.manage_options

    icon = "misc_/ZRoundup/icon"

    security.declarePrivate('roundup_opendb')
    def roundup_opendb(self):
        '''Open the roundup instance database for a transaction.
        '''
        tracker = roundup.instance.open(self.instance_home)
        request = RequestWrapper(self.REQUEST['RESPONSE'])
        env = self.REQUEST.environ

        # figure out the path components to set
        url = urllib_.urlparse( self.absolute_url() )
        path = url[2]
        path_components = path.split( '/' )

        # special case when roundup is '/' in this virtual host,
        if path == "/" :
            env['SCRIPT_NAME'] = "/"
            env['TRACKER_NAME'] = ''
        else :
            # all but the last element is the path
            env['SCRIPT_NAME'] = '/'.join( path_components[:-1] )
            # the last element is the name
            env['TRACKER_NAME'] = path_components[-1]

        form = FormWrapper(self.REQUEST.form)
        if hasattr(tracker, 'Client'):
            return tracker.Client(tracker, request, env, form)
        return client.Client(tracker, request, env, form)

    security.declareProtected('View', 'index_html')
    def index_html(self):
        '''Alias index_html to roundup's index
        '''
        # Redirect misdirected requests -- bugs 558867 , 565992
        # PATH_INFO, as defined by the CGI spec, has the *real* request path
        orig_path = self.REQUEST.environ['PATH_INFO']
        if orig_path[-1] != '/' : 
            url = urllib_.urlparse( self.absolute_url() )
            url = list( url ) # make mutable
            url[2] = url[2]+'/' # patch
            url = urllib_.urlunparse( url ) # reassemble
            RESPONSE = self.REQUEST.RESPONSE
            RESPONSE.setStatus( "MovedPermanently" ) # 301
            RESPONSE.setHeader( "Location" , url )
            return RESPONSE

        client = self.roundup_opendb()
        # fake the path that roundup should use
        client.split_path = ['index']
        return client.main()

    def __getitem__(self, item):
        '''All other URL accesses are passed throuh to roundup
        '''
        return PathElement(self, item).__of__(self)

class PathElement(Item, Implicit):
    def __init__(self, zr, path):
        self.zr = zr
        self.path = path

    def __getitem__(self, item):
        ''' Get a subitem.
        '''
        return PathElement(self.zr, self.path + '/' + item).__of__(self)

    def index_html(self, REQUEST=None):
        ''' Actually call through to roundup to handle the request.
        '''
        try:
            client = self.zr.roundup_opendb()
            # fake the path that roundup should use
            client.path = self.path
            # and call roundup to do something 
            client.main()
            return ''
        except client.NotFound:
            raise Exception('NotFound ' + REQUEST.URL)
            pass
        except:
            import traceback
            traceback.print_exc()
            # all other exceptions in roundup are valid
            raise

InitializeClass(ZRoundup)
modulesecurity.apply(globals())


# vim: set filetype=python ts=4 sw=4 et si

Roundup Issue Tracker: http://roundup-tracker.org/