Changeset cd4e48 in indico


Ignore:
Timestamp:
08/23/11 15:42:07 (22 months ago)
Author:
Jose Benito <jose.benito.gonzalez@…>
Branches:
master, hello-world-walkthrough, ipv6, v0.98-series, v0.98.2, v0.98.3, v0.98b2, v0.99, b8c30da8ebdbdcbd675a873997cc3e95f567de49, 4287315ec967a3da168d83963c14001db8487d53
Children:
05e29d
Parents:
64b4b4
git-author:
Jose Benito <jose.benito.gonzalez@…> (08/23/11 10:27:33)
git-committer:
Jose Benito <jose.benito.gonzalez@…> (08/23/11 15:42:07)
Message:

[REF] Refactoring Export interface

  • Fixed bug in RE (not accepting white space for location in path)
  • Moving getQueryParams (ExportInterface?) to getParams (Exporter)
  • Renamed class Exporter to HTTPAPIHook, and all sub-classes
  • Renamed class ExportInterface? to DataFetcher?, and all sub-classes
  • Renamed variable urlParams to pathParams, and qdata to queryParams
  • Move contrib/* to rst doc
  • Adding examples and missing information to the documentation
  • Missing i18n strings
Files:
2 deleted
20 edited

Legend:

Unmodified
Added
Removed
  • doc/guides/ExportAPI/access.rst

    re0a8fc rcd4e48  
    1717* *WHAT* is the element you want to export (one of *categ*, *event*, *room*, *reservation*) 
    1818* *LOC* is the location of the element(s) specified by *ID* and only used 
    19   for certain elements. 
    20 * *ID* is the ID of the element you want to export (can be a *-* separated list) 
     19  for certain elements, for example, for the room booking (https://indico.server/export/room/CERN/120.json?ak=0...) 
     20* *ID* is the ID of the element you want to export (can be a *-* separated list). As for example, the 120 in the above URL. 
    2121* *TYPE* is the output format (one of *json*, *jsonp*, *xml*, *html*, *ics*, *atom*) 
    2222* *PARAMS* are various parameters affecting (filtering, sorting, ...) the 
     
    2424* *KEY*, *TS*, *SIG* are part of the :ref:`api-authentication`. 
    2525 
     26 
     27Some examples could be: 
     28 
     29 * Export data about events in a category: https://my.indico/export/categ/2.json?from=today&to=today&pretty=yes 
     30 * Export data about a event: https://indico.server/export/event/137346.json?occ=yes&pretty=yes 
     31 * Export data about rooms: https://indico.server/export/room/CERN/120.json?ak=00000000-0000-0000-0000-000000000000&pretty=yes 
     32 * Export your reservations: https://indico.server/export/reservation/CERN.json?ak=00000000-0000-0000-0000-000000000000&detail=reservations&from=today&to=today&bookedfor=USERNAME&pretty=yes 
     33 
     34 
     35See more details about querying in `Exporters <exporters/index.html>`_. 
    2636 
    2737.. _api-authentication: 
     
    6878the generated URL as it is likely to expire soon. 
    6979 
    70 You can find example code for Python and PHP in the *contrib/http_api* 
    71 folder. 
     80You can find example code for Python and PHP in the following sections. 
     81 
     82Request Signing for Python 
     83^^^^^^^^^^^^^^^^^^^^^^^^^^ 
     84 
     85A simple example in Python:: 
     86 
     87    import hashlib 
     88    import hmac 
     89    import urllib 
     90    import time 
     91 
     92 
     93    def build_indico_request(path, params, api_key=None, secret_key=None, only_public=False): 
     94        items = params.items() if hasattr(params, 'items') else list(params) 
     95        if api_key: 
     96            items.append(('apikey', api_key)) 
     97        if only_public: 
     98            items.append(('onlypublic', 'yes')) 
     99        if secret_key: 
     100            items.append(('timestamp', str(int(time.time())))) 
     101            items = sorted(items, key=lambda x: x[0].lower()) 
     102            url = '%s?%s' % (path, urllib.urlencode(items)) 
     103            signature = hmac.new(secret_key, url, hashlib.sha1).hexdigest() 
     104            items.append(('signature', signature)) 
     105        if not items: 
     106            return path 
     107        return '%s?%s' % (path, urllib.urlencode(items)) 
     108 
     109 
     110    if __name__ == '__main__': 
     111        API_KEY = '00000000-0000-0000-0000-000000000000' 
     112        SECRET_KEY = '00000000-0000-0000-0000-000000000000' 
     113        PATH = '/export/categ/1337.json' 
     114        PARAMS = { 
     115            'nocache': 'yes', 
     116            'limit': 123 
     117        } 
     118        print build_indico_request(PATH, PARAMS, API_KEY, SECRET_KEY) 
     119 
     120Request Signing for PHP 
     121^^^^^^^^^^^^^^^^^^^^^^^ 
     122 
     123A simple example in PHP:: 
     124 
     125    <?php 
     126 
     127    function build_indico_request($path, $params, $api_key = null, $secret_key = null, $only_public = false) { 
     128        if($api_key) { 
     129            $params['apikey'] = $api_key; 
     130        } 
     131 
     132        if($only_public) { 
     133            $params['onlypublic'] = 'yes'; 
     134        } 
     135 
     136        if($secret_key) { 
     137            $params['timestamp'] = time(); 
     138            uksort($params, 'strcasecmp'); 
     139            $url = $path . '?' . http_build_query($params); 
     140            $params['signature'] = hash_hmac('sha1', $url, $secret_key); 
     141        } 
     142 
     143        if(!$params) { 
     144            return $path; 
     145        } 
     146 
     147        return $path . '?' . http_build_query($params); 
     148    } 
     149 
     150    if(true) { // change to false if you want to include this file 
     151        $API_KEY = '00000000-0000-0000-0000-000000000000'; 
     152        $SECRET_KEY = '00000000-0000-0000-0000-000000000000'; 
     153        $PATH = '/export/categ/1337.json'; 
     154        $PARAMS = array( 
     155            'nocache' => 'yes', 
     156            'limit' => 123 
     157        ); 
     158        echo build_indico_request($PATH, $PARAMS, $API_KEY, $SECRET_KEY) . "\n"; 
     159    } 
  • doc/guides/ExportAPI/common.rst

    re0a8fc rcd4e48  
    88Param       Short  Description 
    99==========  =====  ======================================================= 
     10from/to     f/t    Accepted formats: 
     11                      * ISO 8601 subset - YYYY-MM-DD[THH:MM] 
     12                      * 'today', 'yesterday', 'tomorrow' and 'now' 
     13                      * days in the future/past: '[+/-]DdHHhMMm' 
    1014nocache     nc     Do not return cached results (the results are written 
    1115                   to the cache though). 
  • doc/guides/ExportAPI/exporters/categ.rst

    rb69320d rcd4e48  
    2828~~~~~~ 
    2929 
    30 Returns basic data about the events in the category:: 
     30Returns basic data about the events in the category. 
     31 
     32This is the result of the following the query https://my.indico/export/categ/2.json?from=today&to=today&pretty=yes:: 
    3133 
    3234    { 
  • doc/guides/ExportAPI/exporters/event.rst

    r816a09 rcd4e48  
    2525~~~~~~ 
    2626Returns basic data about the event. In this example occurrences are 
    27 included, too. :: 
     27included, too. 
     28 
     29Result for https://indico.server/export/event/137346.json?occ=yes&pretty=yes:: 
    2830 
    2931    { 
     
    9395contributions 
    9496~~~~~~~~~~~~~ 
    95 Includes the contributions of the event. :: 
     97Includes the contributions of the event. 
     98 
     99Output for https://indico.server/export/event/137346.json?detail=contributions&pretty=yes:: 
    96100 
    97101    { 
     
    234238sessions. The top-level *contributions* list only contains contributions 
    235239which are not assigned to any session. Subcontributions are included in 
    236 this details level, too. :: 
     240this details level, too. 
     241 
     242For example, https://indico.server/export/event/137346.json?detail=sessions&pretty=yes:: 
    237243 
    238244    { 
  • doc/guides/ExportAPI/exporters/reservation.rst

    rf2dba1 rcd4e48  
    3535 
    3636Returns detailed data about the reservations and the most important 
    37 information about the booked room:: 
     37information about the booked room. 
     38 
     39For example, https://indico.server/export/reservation/CERN.json?ak=00000000-0000-0000-0000-000000000000&detail=reservations&from=today&to=today&bookedfor=*MONNICH*&pretty=yes:: 
    3840 
    3941    { 
  • doc/guides/ExportAPI/exporters/room.rst

    rf2dba1 rcd4e48  
    3131~~~~~ 
    3232 
    33 Returns basic data about the rooms:: 
     33Returns basic data about the rooms. 
     34 
     35For example, https://indico.server/export/room/CERN/120.json?ak=00000000-0000-0000-0000-000000000000&pretty=yes:: 
    3436 
    3537    { 
     
    6264~~~~~~~~~~~~ 
    6365 
    64 Returns basic data about the rooms and their reservations in the given timeframe:: 
     66Returns basic data about the rooms and their reservations in the given timeframe. 
     67 
     68Output for https://indico.server/export/room/CERN/120.json?ak=00000000-0000-0000-0000-000000000000&detail=reservations&from=today&to=today&pretty=yes:: 
    6569 
    6670    { 
  • doc/guides/UserGuide/Export.rst

    re0a8fc rcd4e48  
    6363|image164| 
    6464 
    65 -------------- 
    66  
    67 Using the export.py script 
    68 -------------------------- 
    69  
    70 Indico allows you to programmatically access the content of its 
    71 database by exposing its events through a web service, the 
    72 export.py script. 
    73  
    74 A typical example of how to use this script is: 
    75  
    76 http://my.indico.server/tools/export.py?fid=2l12&date=today&days=1000&of=xml 
    77  
    78 where: 
    79  
    80 * *fid* is the category from which you want to extract the events (can be a *+* separated list) 
    81 * *date* is the starting date of the event (format: *yyyy-mm-dd* or *today*) 
    82 * *days* is the number of days to export the events (starting on *date*) 
    83 * *of* is the output format (one of *xml*, *html*, *ical*, *rss*) 
    84  
    85  
    8665Using the HTTP Export API 
    8766------------------------- 
    8867 
    89 Basides the export.py script Indico has an export API which allows you to 
     68Indico has an export API which allows you to 
    9069export categories, events, rooms and room bookings in various formats such 
    9170as JSON, XML, iCal and Atom. For details no how to use this API, see 
  • indico/MaKaC/common/indexes.py

    rf3a845 rcd4e48  
    740740 
    741741    def iterateObjectsIn(self, sDate, eDate): 
     742        """ 
     743        Returns all the events between two dates taking into account the starting and ending times. 
     744        """ 
    742745        sDay = datetime(sDate.year, sDate.month, sDate.day) if sDate else None 
    743746        eDay = datetime(eDate.year, eDate.month, eDate.day) if eDate else None 
     
    749752                        yield event 
    750753            return 
    751  
    752         # keep track of the records that have been already sent 
    753754 
    754755        if sDay and int(datetimeToUnixTime(sDay)) in self._idxDay: 
     
    783784 
    784785    def iterateObjectsInDays(self, sDate=None, eDate=None): 
     786        """ 
     787        Returns all the events between two dates WITHOUT taking into account the starting and ending times. 
     788        """ 
    785789 
    786790        sDay = int(datetimeToUnixTime(datetime(sDate.year, sDate.month, sDate.day))) if sDate else None 
  • indico/MaKaC/common/mail.py

    refefcc rcd4e48  
    3030    @staticmethod 
    3131    def send(notification): 
    32         server=smtplib.SMTP(*Config.getInstance().getSmtpServer()) 
     32        server=smtplib.SMTP(Config.getInstance().getSmtpServer()) 
    3333        if Config.getInstance().getSmtpUseTLS(): 
    3434            server.ehlo() 
  • indico/MaKaC/plugins/RoomBooking/export.py

    r943a54 rcd4e48  
    2424from dateutil import rrule 
    2525from datetime import datetime, timedelta 
    26 from indico.web.http_api import ExportInterface, Exporter 
     26from indico.web.http_api import DataFetcher, HTTPAPIHook 
    2727from indico.web.http_api.util import get_query_parameter 
    2828from indico.util.fossilize import fossilize, IFossil 
     
    3838 
    3939 
    40 globalExporters = ['RoomExporter', 'ReservationExporter'] 
     40globalHTTPAPIHooks = ['RoomHook', 'ReservationHook'] 
    4141MAX_DATETIME = datetime(2099, 12, 31, 23, 59, 00) 
    4242MIN_DATETIME = datetime(2000, 1, 1, 00, 00, 00) 
     
    4747    return utc2server(d) 
    4848 
    49 class RoomBookingExporter(Exporter): 
     49class RoomBookingHook(HTTPAPIHook): 
    5050    GUEST_ALLOWED = False 
     51 
     52    def _getParams(self): 
     53        super(RoomBookingHook, self)._getParams() 
     54 
     55        self._fromDT = utcdate(self._fromDT) if self._fromDT else None 
     56        self._toDT = utcdate(self._toDT) if self._toDT else None 
     57        self._occurrences = get_query_parameter(self._queryParams, ['occ', 'occurrences'], 'no') == 'yes' 
     58        self._resvFilter = getResvStateFilter(self._queryParams) 
    5159 
    5260    def _hasAccess(self, aw): 
     
    6876        return False 
    6977 
    70 class RoomExporter(RoomBookingExporter): 
     78class RoomHook(RoomBookingHook): 
    7179    """ 
    7280    Example: /room/CERN/23.xml 
    7381    """ 
    7482    TYPES = ('room', ) 
    75     RE = r'(?P<location>\w+)/(?P<idlist>\w+(?:-\w+)*)' 
     83    RE = r'(?P<location>[\w\s]+)/(?P<idlist>\w+(?:-[\w\s]+)*)' 
    7684    DEFAULT_DETAIL = 'rooms' 
    7785    MAX_RECORDS = { 
     
    8694 
    8795    def _getParams(self): 
    88         super(RoomExporter, self)._getParams() 
    89         self._location = self._urlParams['location'] 
    90         self._idList = self._urlParams['idlist'].split('-') 
     96        super(RoomHook, self)._getParams() 
     97        self._location = self._pathParams['location'] 
     98        self._idList = self._pathParams['idlist'].split('-') 
    9199 
    92100    def export_room(self, aw): 
    93         expInt = RoomExportInterface(aw, self) 
    94         return expInt.room(self._location, self._idList, self._qdata) 
    95  
    96  
    97 class ReservationExporter(RoomBookingExporter): 
     101        expInt = RoomFetcher(aw, self) 
     102        return expInt.room(self._location, self._idList) 
     103 
     104 
     105class ReservationHook(RoomBookingHook): 
    98106    TYPES = ('reservation', ) 
    99     RE = r'(?P<loclist>\w+(?:-\w+)*)' 
     107    RE = r'(?P<loclist>[\w\s]+(?:-[\w\s]+)*)' 
    100108    DEFAULT_DETAIL = 'reservations' 
    101109    MAX_RECORDS = { 
     
    109117 
    110118    def _getParams(self): 
    111         super(ReservationExporter, self)._getParams() 
    112         self._locList = self._urlParams['loclist'].split('-') 
     119        super(ReservationHook, self)._getParams() 
     120        self._locList = self._pathParams['loclist'].split('-') 
    113121 
    114122    def export_reservation(self, aw): 
    115         expInt = ReservationExportInterface(aw, self) 
    116         return expInt.reservation(self._locList, self._qdata) 
     123        expInt = ReservationFetcher(aw, self) 
     124        return expInt.reservation(self._locList) 
    117125 
    118126 
     
    227235 
    228236 
    229 class RoomBookingExportInterface(ExportInterface): 
     237class RoomBookingFetcher(DataFetcher): 
    230238    """ 
    231239    Base export interface for RB related stuff 
    232240    """ 
    233241 
    234     def _getQueryParams(self, qdata): 
    235         super(RoomBookingExportInterface, self)._getQueryParams(qdata) 
    236  
    237         self._fromDT = utcdate(self._fromDT) if self._fromDT else None 
    238         self._toDT = utcdate(self._toDT) if self._toDT else None 
    239         self._occurrences = get_query_parameter(qdata, ['occ', 'occurrences'], 'no') == 'yes' 
    240         self._resvFilter = getResvStateFilter(qdata) 
     242    def __init__(self, aw, hook): 
     243        super(RoomBookingFetcher, self).__init__(aw, hook) 
     244        self._occurrences = hook._occurrences 
     245        self._resvFilter = hook._resvFilter 
    241246 
    242247    @staticmethod 
     
    266271 
    267272 
    268 class RoomExportInterface(RoomBookingExportInterface): 
     273class RoomFetcher(RoomBookingFetcher): 
    269274    DETAIL_INTERFACES = { 
    270275        'rooms': IRoomMetadataFossil, 
     
    306311        return fossil 
    307312 
    308     def room(self, location, idlist, qdata): 
    309  
    310         self._getQueryParams(qdata) 
     313    def room(self, location, idlist): 
    311314 
    312315        Factory.getDALManager().connect() 
     
    323326 
    324327 
    325 class ReservationExportInterface(RoomBookingExportInterface): 
     328class ReservationFetcher(RoomBookingFetcher): 
    326329    DETAIL_INTERFACES = { 
    327330        'reservations': IReservationMetadataFossil 
     
    332335 
    333336 
    334     def reservation(self, locList, qdata): 
    335  
    336         self._getQueryParams(qdata) 
     337    def reservation(self, locList): 
    337338 
    338339        Factory.getDALManager().connect() 
     
    358359 
    359360 
    360 def getResvStateFilter(qdata): 
    361     cancelled = get_query_parameter(qdata, ['cxl', 'cancelled']) 
    362     rejected = get_query_parameter(qdata, ['rej', 'rejected']) 
    363     confirmed = get_query_parameter(qdata, ['confirmed'], -1) 
    364     archival = get_query_parameter(qdata, ['arch', 'archival']) 
    365     repeating = get_query_parameter(qdata, ['rec', 'recurring', 'rep', 'repeating']) 
    366     avc = get_query_parameter(qdata, ['avc']) 
    367     avcSupport = get_query_parameter(qdata, ['avcs', 'avcsupport']) 
    368     bookedFor = get_query_parameter(qdata, ['bf', 'bookedfor']) 
     361def getResvStateFilter(queryParams): 
     362    cancelled = get_query_parameter(queryParams, ['cxl', 'cancelled']) 
     363    rejected = get_query_parameter(queryParams, ['rej', 'rejected']) 
     364    confirmed = get_query_parameter(queryParams, ['confirmed'], -1) 
     365    archival = get_query_parameter(queryParams, ['arch', 'archival']) 
     366    repeating = get_query_parameter(queryParams, ['rec', 'recurring', 'rep', 'repeating']) 
     367    avc = get_query_parameter(queryParams, ['avc']) 
     368    avcSupport = get_query_parameter(queryParams, ['avcs', 'avcsupport']) 
     369    bookedFor = get_query_parameter(queryParams, ['bf', 'bookedfor']) 
    369370    if not any((cancelled, rejected, confirmed != -1, archival, repeating, avc, avcSupport, bookedFor)): 
    370371        return None 
  • indico/MaKaC/plugins/base.py

    rd2f974 rcd4e48  
    488488        self.__options = {} 
    489489        self.__actions = {} 
    490         self.__exporters = [] 
     490        self.__HTTPAPIHooks = [] 
    491491 
    492492        self.__usable = False 
     
    707707    ############## end of actions related ############### 
    708708 
    709     ############## exporters related ############### 
    710     def updateAllExporters(self, retrievedPluginExporters): 
    711         self.__exporters = [] 
    712         if retrievedPluginExporters is not None: 
    713             self.__exporters = retrievedPluginExporters 
     709    ############## HTTPAPIHook related ############### 
     710    def updateAllHTTPAPIHooks(self, retrievedPluginHTTPAPIHooks): 
     711        self.__HTTPAPIHooks = [] 
     712        if retrievedPluginHTTPAPIHooks is not None: 
     713            self.__HTTPAPIHooks = retrievedPluginHTTPAPIHooks 
    714714        self._notifyModification() 
    715715 
    716     def getExporterList(self): 
     716    def getHTTPAPIHookList(self): 
    717717        try: 
    718             return self.__exporters 
     718            return self.__HTTPAPIHooks 
    719719        except: 
    720             self.__exporters = [] 
    721             return self.__exporters 
    722  
    723     ############## end of exporters related ############### 
     720            self.__HTTPAPIHooks = [] 
     721            return self.__HTTPAPIHooks 
     722 
     723    ############## end of HTTPAPIHooks related ############### 
    724724 
    725725    def _notifyModification(self): 
     
    804804 
    805805        if hasattr(pluginModule, "export") and \ 
    806                hasattr(pluginModule.options, "globalExporters"): 
    807             p.updateAllExporters(pluginModule.export.globalExporters) 
     806               hasattr(pluginModule.options, "globalHTTPAPIHooks"): 
     807            p.updateAllHTTPAPIHooks(pluginModule.export.globalHTTPAPIHooks) 
    808808 
    809809        self._updateComponentInfo(p, pluginModule) 
     
    844844        self.__visible = ptypeMetadata['visible'] 
    845845 
    846         # components, handlers, options, actions and exporters 
     846        # components, handlers, options, actions and HTTPAPIHooks 
    847847        self._updateComponentInfo(self, ptypeModule) 
    848848        self._updateRHMapInfo(self, ptypeModule) 
     
    850850        self.updateAllOptions(self._retrievePluginTypeOptions()) 
    851851        self.updateAllActions(self._retrievePluginTypeActions()) 
    852         self.updateAllExporters(self._retrievePluginTypeExporters()) 
     852        self.updateAllHTTPAPIHooks(self._retrievePluginTypeHTTPAPIHooks()) 
    853853 
    854854 
     
    918918            return None 
    919919 
    920     def _retrievePluginTypeExporters(self): 
     920    def _retrievePluginTypeHTTPAPIHooks(self): 
    921921 
    922922        hasExportModule = hasattr(self.getModule(), "export") 
    923         hasGlobalExportersVariable = hasExportModule and hasattr(self.getModule().export, "globalExporters") 
    924         if hasExportModule and hasGlobalExportersVariable: 
    925             return self.getModule().export.globalExporters 
     923        hasGlobalHTTPAPIHooksVariable = hasExportModule and hasattr(self.getModule().export, "globalHTTPAPIHooks") 
     924        if hasExportModule and hasGlobalHTTPAPIHooksVariable: 
     925            return self.getModule().export.globalHTTPAPIHooks 
    926926        else: 
    927927            return None 
  • indico/MaKaC/webinterface/rh/conferenceDisplay.py

    rdfedc8 rcd4e48  
    10541054                pending.setFax(params.get("fax","")) 
    10551055            participation = self._conf.getParticipation() 
    1056             if participation.alreadyParticipating(pending) != 0: 
     1056            if participation.alreadyParticipating(pending) != 0 or participation.alreadyPending(pending) != 0: 
    10571057                errorList.append("There is already a participant with the email address '%s' in this meeting." 
    10581058                                 % pending.getEmail()) 
  • indico/MaKaC/webinterface/tpls/AdminAPIKeys.tpl

    rf9e571 rcd4e48  
    2323                            <td>${key.getUser().getFullName()}</td> 
    2424                            <td>${key.getUseCount()}</td> 
    25                             <td>${formatDateTime(key.getLastUsedDT()) if key.getLastUsedDT() else 'Never'}</td> 
     25                            <td>${formatDateTime(key.getLastUsedDT()) if key.getLastUsedDT() else _('Never')}</td> 
    2626                            <td>${_('Yes') if key.isBlocked() else _('No')}</td> 
    2727                            <td>${ key.getKey() }</td> 
  • indico/MaKaC/webinterface/tpls/AdminAPIOptions.tpl

    rfcdc60 rcd4e48  
    1717            <td class="blacktext"> 
    1818                <select name="apiMode"> 
    19                     <option value="0"${' selected="selected"' if apiMode == 0 else ''}>Public requests without API key, authenticated requests with API key</option> 
    20                     <option value="1"${' selected="selected"' if apiMode == 1 else ''}>All requests require an API key</option> 
    21                     <option value="2"${' selected="selected"' if apiMode == 2 else ''}>Public requests without API key, authenticated requests with API key and signature</option> 
    22                     <option value="3"${' selected="selected"' if apiMode == 3 else ''}>All requests require an API key, authenticated requests additionally need a signature</option> 
    23                     <option value="4"${' selected="selected"' if apiMode == 4 else ''}>All requests require an API key and a signature</option> 
     19                    <option value="0"${' selected="selected"' if apiMode == 0 else ''}>${ _("Public requests without API key, authenticated requests with API key") }</option> 
     20                    <option value="1"${' selected="selected"' if apiMode == 1 else ''}>${ _("All requests require an API key") }</option> 
     21                    <option value="2"${' selected="selected"' if apiMode == 2 else ''}>${ _("Public requests without API key, authenticated requests with API key and signature") }</option> 
     22                    <option value="3"${' selected="selected"' if apiMode == 3 else ''}>${ _("All requests require an API key, authenticated requests additionally need a signature") }</option> 
     23                    <option value="4"${' selected="selected"' if apiMode == 4 else ''}>${ _("All requests require an API key and a signature") }</option> 
    2424                </select> 
    2525            </td> 
    2626        </tr> 
    2727        <tr> 
    28             <td class="dataCaptionTD"><span class="dataCaptionFormat">Cache TTL (seconds)</span></td> 
     28            <td class="dataCaptionTD"><span class="dataCaptionFormat">${ _("Cache TTL (seconds)") }</span></td> 
    2929            <td class="blacktext"> 
    3030                <input type="text" name="apiCacheTTL" id="apiCacheTTL" value="${apiCacheTTL}" /> 
     
    3232        </tr> 
    3333        <tr> 
    34             <td class="dataCaptionTD"><span class="dataCaptionFormat">Signature TTL (seconds)</span></td> 
     34            <td class="dataCaptionTD"><span class="dataCaptionFormat">${ _("Signature TTL (seconds)") }</span></td> 
    3535            <td class="blacktext"> 
    3636                <input type="text" name="apiSignatureTTL" id="apiSignatureTTL" value="${apiSignatureTTL}" /> 
  • indico/MaKaC/webinterface/tpls/UserAPI.tpl

    r15afa9 rcd4e48  
    3131        <td nowrap class="dataCaptionTD"><span class="dataCaptionFormat">${ _("Last used")}</span></td> 
    3232        <td class="blacktext"> 
    33             ${apiKey.getLastUsedDT() and formatDateTime(apiKey.getLastUsedDT()) or 'Never' if apiKey else _('n/a')} 
     33            ${apiKey.getLastUsedDT() and formatDateTime(apiKey.getLastUsedDT()) or _('Never') if apiKey else _('n/a')} 
    3434        </td> 
    3535    </tr> 
     
    3737        <td nowrap class="dataCaptionTD"><span class="dataCaptionFormat">${ _("Last used by")}</span></td> 
    3838        <td class="blacktext"> 
    39             ${apiKey.getLastUsedIP() or 'n/a' if apiKey else _('n/a')} 
     39            ${apiKey.getLastUsedIP() or _('n/a') if apiKey else _('n/a')} 
    4040        </td> 
    4141    </tr> 
     
    9393            </tr> 
    9494            <tr> 
    95                  <td nowrap class="dataCaptionTD"><span class="dataCaptionFormat">Old keys</span></td> 
     95                 <td nowrap class="dataCaptionTD"><span class="dataCaptionFormat">${ _("Old keys") }</span></td> 
    9696                 <td class="blacktext"> 
    9797                    % if apiKey.getOldKeys(): 
  • indico/web/http_api/__init__.py

    rd2f974 rcd4e48  
    2323""" 
    2424 
    25 from indico.web.http_api.export import ExportInterface, LimitExceededException, Exporter 
     25from indico.web.http_api.export import DataFetcher, LimitExceededException, HTTPAPIHook 
    2626 
    2727API_MODE_KEY            = 0 # public requests without API key, authenticated requests with api key 
  • indico/web/http_api/export.py

    r5dc223 rcd4e48  
    2828import pytz 
    2929import re 
     30import urllib 
    3031from zope.interface import Interface, implements 
    3132from datetime import datetime, timedelta, date, time 
     
    7475 
    7576 
    76 class Exporter(object): 
    77     EXPORTER_LIST = [] 
     77class HTTPAPIHook(object): 
     78    """This class is the hook between the query (path+params) and the generator of the results (fossil). 
     79       It is also in charge of checking the parameters and the access rights. 
     80    """ 
     81 
     82    HOOK_LIST = [] 
    7883    TYPES = None # abstract 
    7984    RE = None # abstract 
    8085    DEFAULT_DETAIL = None # abstract 
    8186    MAX_RECORDS = None # abstract 
    82     SERIALIZER_TYPE_MAP = {} 
     87    SERIALIZER_TYPE_MAP = {} # maps fossil type names to friendly names (useful for plugins e.g. RoomCERN --> Room) 
    8388    VALID_FORMATS = None # None = all formats 
    84     GUEST_ALLOWED = True 
    85  
    86     @classmethod 
    87     def parseRequest(cls, path, qdata): 
    88         """Parse a request path and return an exporter and the requested data type.""" 
    89         exporters = itertools.chain(cls.EXPORTER_LIST, cls._getPluginExporters()) 
    90         for expCls in exporters: 
     89    GUEST_ALLOWED = True # When False, it forces authentication 
     90 
     91    @classmethod 
     92    def parseRequest(cls, path, queryParams): 
     93        """Parse a request path and return a hook and the requested data type.""" 
     94        path = urllib.unquote(path) 
     95        hooks = itertools.chain(cls.HOOK_LIST, cls._getPluginHooks()) 
     96        for expCls in hooks: 
    9197            m = expCls._matchPath(path) 
    9298            if m: 
     
    95101                type = g[0] 
    96102                format = g[-1] 
    97                 if format not in ExportInterface.getAllowedFormats(): 
     103                if format not in DataFetcher.getAllowedFormats(): 
    98104                    return None, None 
    99105                elif expCls.VALID_FORMATS and format not in expCls.VALID_FORMATS: 
    100106                    return None, None 
    101                 return expCls(qdata, type, gd), format 
     107                return expCls(queryParams, type, gd), format 
    102108        return None, None 
    103109 
    104110    @staticmethod 
    105111    def register(cls): 
    106         """Register an exporter that is not part of a plugin. 
    107  
    108         To use it, simply decorate the exporter class with this method.""" 
     112        """Register a hook that is not part of a plugin. 
     113 
     114        To use it, simply decorate the hook class with this method.""" 
    109115        assert cls.RE is not None 
    110         Exporter.EXPORTER_LIST.append(cls) 
     116        HTTPAPIHook.HOOK_LIST.append(cls) 
    111117        return cls 
    112118 
     
    119125 
    120126    @classmethod 
    121     def _getPluginExporters(cls): 
     127    def _getPluginHooks(cls): 
    122128        for plugin in PluginsHolder().getPluginTypes(): 
    123             for expClsName in plugin.getExporterList(): 
     129            for expClsName in plugin.getHTTPAPIHookList(): 
    124130                yield getattr(plugin.getModule().export, expClsName) 
    125131 
    126     def __init__(self, qdata, type, urlParams): 
    127         self._qdata = qdata 
     132    def __init__(self, queryParams, type, pathParams): 
     133        self._queryParams = queryParams 
    128134        self._type = type 
    129         self._urlParams = urlParams 
     135        self._pathParams = pathParams 
    130136 
    131137    def _getParams(self): 
    132         self._offset = get_query_parameter(self._qdata, ['O', 'offset'], 0, integer=True) 
    133         self._orderBy = get_query_parameter(self._qdata, ['o', 'order'], 'start') 
    134         self._descending = get_query_parameter(self._qdata, ['c', 'descending'], False) 
    135         self._detail = get_query_parameter(self._qdata, ['d', 'detail'], self.DEFAULT_DETAIL) 
    136         tzName = get_query_parameter(self._qdata, ['tz'], None) 
     138        self._offset = get_query_parameter(self._queryParams, ['O', 'offset'], 0, integer=True) 
     139        self._orderBy = get_query_parameter(self._queryParams, ['o', 'order'], 'start') 
     140        self._descending = get_query_parameter(self._queryParams, ['c', 'descending'], False) 
     141        self._detail = get_query_parameter(self._queryParams, ['d', 'detail'], self.DEFAULT_DETAIL) 
     142        tzName = get_query_parameter(self._queryParams, ['tz'], None) 
    137143 
    138144        info = HelperMaKaCInfo.getMaKaCInfoInstance() 
     
    146152            raise HTTPAPIError("Bad timezone: '%s'" % e.message, apache.HTTP_BAD_REQUEST) 
    147153        max = self.MAX_RECORDS.get(self._detail, 1000) 
    148         self._userLimit = get_query_parameter(self._qdata, ['n', 'limit'], 0, integer=True) 
     154        self._userLimit = get_query_parameter(self._queryParams, ['n', 'limit'], 0, integer=True) 
    149155        if self._userLimit > max: 
    150156            raise HTTPAPIError("You can only request up to %d records per request with the detail level '%s'" % 
     
    152158        self._limit = self._userLimit if self._userLimit > 0 else max 
    153159 
     160        fromDT = get_query_parameter(self._queryParams, ['f', 'from']) 
     161        toDT = get_query_parameter(self._queryParams, ['t', 'to']) 
     162        dayDT = get_query_parameter(self._queryParams, ['day']) 
     163 
     164        if (fromDT or toDT) and dayDT: 
     165            raise HTTPAPIError("'day' can only be used without 'from' and 'to'", apache.HTTP_BAD_REQUEST) 
     166        elif dayDT: 
     167            fromDT = toDT = dayDT 
     168 
     169        self._fromDT = DataFetcher._getDateTime('from', fromDT, self._tz) if fromDT else None 
     170        self._toDT = DataFetcher._getDateTime('to', toDT, self._tz, aux=self._fromDT) if toDT else None 
     171 
    154172    def _hasAccess(self, aw): 
    155173        return True 
     
    159177        self._getParams() 
    160178        if not self.GUEST_ALLOWED and not aw.getUser(): 
    161             raise HTTPAPIError('Guest access to this exporter is forbidden.', apache.HTTP_FORBIDDEN) 
     179            raise HTTPAPIError('Guest access to this hook is forbidden.', apache.HTTP_FORBIDDEN) 
    162180        if not self._hasAccess(aw): 
    163             raise HTTPAPIError('Access to this exporter is restricted.', apache.HTTP_FORBIDDEN) 
     181            raise HTTPAPIError('Access to this hook is restricted.', apache.HTTP_FORBIDDEN) 
    164182        resultList = [] 
    165183        complete = True 
     
    178196 
    179197 
    180 class ExportInterface(object): 
     198class DataFetcher(object): 
    181199    DETAIL_INTERFACES = {} 
    182200 
     
    188206                    'title': lambda x: x.getTitle()} 
    189207 
    190     def __init__(self, aw, exporter): 
     208    def __init__(self, aw, hook): 
    191209        self._aw = aw 
    192         self._tz = exporter._tz 
    193         self._serverTZ = exporter._serverTZ 
    194         self._offset = exporter._offset 
    195         self._limit = exporter._limit 
    196         self._detail = exporter._detail 
    197         self._orderBy = exporter._orderBy 
    198         self._descending = exporter._descending 
     210        self._tz = hook._tz 
     211        self._serverTZ = hook._serverTZ 
     212        self._offset = hook._offset 
     213        self._limit = hook._limit 
     214        self._detail = hook._detail 
     215        self._orderBy = hook._orderBy 
     216        self._descending = hook._descending 
     217        self._fromDT = hook._fromDT 
     218        self._toDT = hook._toDT 
    199219 
    200220    @classmethod 
     
    266286            return tz.localize(value.combine(value.date(), time(23, 59, 59))) 
    267287 
    268     def _getQueryParams(self, qdata): 
    269         fromDT = get_query_parameter(qdata, ['f', 'from']) 
    270         toDT = get_query_parameter(qdata, ['t', 'to']) 
    271         dayDT = get_query_parameter(qdata, ['day']) 
    272  
    273         if (fromDT or toDT) and dayDT: 
    274             raise HTTPAPIError("'day' can only be used without 'from' and 'to'", apache.HTTP_BAD_REQUEST) 
    275         elif dayDT: 
    276             fromDT = toDT = dayDT 
    277  
    278         self._fromDT = ExportInterface._getDateTime('from', fromDT, self._tz) if fromDT else None 
    279         self._toDT = ExportInterface._getDateTime('to', toDT, self._tz, aux=self._fromDT) if toDT else None 
    280  
    281288    def _limitIterator(self, iterator, limit): 
    282289        counter = 0 
     
    349356 
    350357 
    351 @Exporter.register 
    352 class CategoryEventExporter(Exporter): 
     358@HTTPAPIHook.register 
     359class CategoryEventHook(HTTPAPIHook): 
    353360    TYPES = ('event', 'categ') 
    354361    RE = r'(?P<idlist>\w+(?:-\w+)*)' 
     
    362369 
    363370    def _getParams(self): 
    364         super(CategoryEventExporter, self)._getParams() 
    365         self._idList = self._urlParams['idlist'].split('-') 
     371        super(CategoryEventHook, self)._getParams() 
     372        self._idList = self._pathParams['idlist'].split('-') 
     373        self._occurrences = get_query_parameter(self._queryParams, ['occ', 'occurrences'], 'no') == 'yes' 
     374        self._location = get_query_parameter(self._queryParams, ['l', 'location']) 
     375        self._room = get_query_parameter(self._queryParams, ['r', 'room']) 
    366376 
    367377    def export_categ(self, aw): 
    368         expInt = CategoryEventExportInterface(aw, self) 
    369         return expInt.category(self._idList, self._qdata) 
     378        expInt = CategoryEventFetcher(aw, self) 
     379        return expInt.category(self._idList) 
    370380 
    371381    def export_event(self, aw): 
    372         expInt = CategoryEventExportInterface(aw, self) 
    373         return expInt.event(self._idList, self._qdata) 
    374  
    375  
    376 class CategoryEventExportInterface(ExportInterface): 
     382        expInt = CategoryEventFetcher(aw, self) 
     383        return expInt.event(self._idList) 
     384 
     385 
     386class CategoryEventFetcher(DataFetcher): 
    377387    DETAIL_INTERFACES = { 
    378388        'events': IConferenceMetadataFossil, 
     
    382392    } 
    383393 
    384     def _getQueryParams(self, qdata): 
    385         super(CategoryEventExportInterface, self)._getQueryParams(qdata) 
    386         self._occurrences = get_query_parameter(qdata, ['occ', 'occurrences'], 'no') == 'yes' 
     394    def __init__(self, aw, hook): 
     395        super(CategoryEventFetcher, self).__init__(aw, hook) 
     396        self._occurrences = hook._occurrences 
     397        self._location = hook._location 
     398        self._room = hook._room 
    387399 
    388400    def _postprocess(self, obj, fossil, iface): 
     
    412424        return fossil 
    413425 
    414     def category(self, idlist, qdata): 
    415         self._getQueryParams(qdata) 
    416         location = get_query_parameter(qdata, ['l', 'location']) 
    417         room = get_query_parameter(qdata, ['r', 'room']) 
    418  
     426    def category(self, idlist): 
    419427        idx = IndexesHolder().getById('categoryDate') 
    420428 
    421429        filter = None 
    422         if room or location: 
     430        if self._room or self._location: 
    423431            def filter(obj): 
    424                 if location: 
     432                if self._location: 
    425433                    name = obj.getLocation() and obj.getLocation().getName() 
    426                     if not name or not fnmatch.fnmatch(name.lower(), location.lower()): 
     434                    if not name or not fnmatch.fnmatch(name.lower(), self._location.lower()): 
    427435                        return False 
    428                 if room: 
     436                if self._room: 
    429437                    name = obj.getRoom() and obj.getRoom().getName() 
    430                     if not name or not fnmatch.fnmatch(name.lower(), room.lower()): 
     438                    if not name or not fnmatch.fnmatch(name.lower(), self._room.lower()): 
    431439                        return False 
    432440                return True 
     
    436444                yield obj 
    437445 
    438     def event(self, idlist, qdata): 
    439         self._getQueryParams(qdata) 
     446    def event(self, idlist): 
    440447        ch = ConferenceHolder() 
    441448 
  • indico/web/http_api/handlers.py

    r1e4496 rcd4e48  
    3333 
    3434# indico imports 
    35 from indico.web.http_api import Exporter 
     35from indico.web.http_api import HTTPAPIHook 
    3636from indico.web.http_api.auth import APIKeyHolder 
    3737from indico.web.http_api.cache import RequestCache 
     
    6060    Dynamic arguments like signature and timestamp are removed from the query string. 
    6161    """ 
    62     qdata = remove_lists(parse_qs(query)) 
     62    queryParams = remove_lists(parse_qs(query)) 
    6363    if remove: 
    6464        for key in remove: 
    65             qdata.pop(key, None) 
    66     sortedQuery = sorted(qdata.items(), key=lambda x: x[0].lower()) 
     65            queryParams.pop(key, None) 
     66    sortedQuery = sorted(queryParams.items(), key=lambda x: x[0].lower()) 
    6767    if separate: 
    6868        return path, sortedQuery and urllib.urlencode(sortedQuery) 
     
    122122    path, query = req.URLFields['PATH_INFO'], req.URLFields['QUERY_STRING'] 
    123123    # Parse the actual query string 
    124     qdata = parse_qs(query) 
     124    queryParams = parse_qs(query) 
    125125 
    126126    dbi = DBMgr.getInstance() 
     
    129129    cache = RequestCache(HelperMaKaCInfo.getMaKaCInfoInstance().getAPICacheTTL()) 
    130130 
    131     apiKey = get_query_parameter(qdata, ['ak', 'apikey'], None) 
    132     signature = get_query_parameter(qdata, ['signature']) 
    133     timestamp = get_query_parameter(qdata, ['timestamp'], 0, integer=True) 
    134     no_cache = get_query_parameter(qdata, ['nc', 'nocache'], 'no') == 'yes' 
    135     pretty = get_query_parameter(qdata, ['p', 'pretty'], 'no') == 'yes' 
    136     onlyPublic = get_query_parameter(qdata, ['op', 'onlypublic'], 'no') == 'yes' 
     131    apiKey = get_query_parameter(queryParams, ['ak', 'apikey'], None) 
     132    signature = get_query_parameter(queryParams, ['signature']) 
     133    timestamp = get_query_parameter(queryParams, ['timestamp'], 0, integer=True) 
     134    no_cache = get_query_parameter(queryParams, ['nc', 'nocache'], 'no') == 'yes' 
     135    pretty = get_query_parameter(queryParams, ['p', 'pretty'], 'no') == 'yes' 
     136    onlyPublic = get_query_parameter(queryParams, ['op', 'onlypublic'], 'no') == 'yes' 
    137137 
    138138    # Get our handler function and its argument and response type 
    139     func, dformat = Exporter.parseRequest(path, qdata) 
     139    func, dformat = HTTPAPIHook.parseRequest(path, queryParams) 
    140140    if func is None or dformat is None: 
    141141        raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND 
     
    198198 
    199199        serializer = Serializer.create(dformat, pretty=pretty, typeMap=typeMap, 
    200                                        **remove_lists(qdata)) 
     200                                       **remove_lists(queryParams)) 
    201201 
    202202        if error: 
  • indico/web/http_api/util.py

    r774511 rcd4e48  
    2424 
    2525 
    26 def get_query_parameter(qdata, keys, default=None, integer=False): 
     26def get_query_parameter(queryParams, keys, default=None, integer=False): 
    2727    if type(keys) != list: 
    2828        keys = list(keys) 
    2929    for k in keys: 
    30         paramlist = qdata.get(k) 
     30        paramlist = queryParams.get(k) 
    3131        if paramlist: 
    3232            if len(paramlist) == 1: 
     
    3434                if integer: 
    3535                    val = int(val) 
    36                 del qdata[k] 
     36                del queryParams[k] 
    3737                return val 
    3838            else: 
  • setup.py

    r38596a rcd4e48  
    125125    base =  ['ZODB3>=3.8', 'pytz', 'zope.index', 'zope.interface', 
    126126             'lxml', 'cds-indico-extras', 'zc.queue', 'python-dateutil<2.0', 
    127              'pypdf', 'mako>=0.4.1', 'babel'] 
     127             'pypdf', 'mako>=0.4.1', 'babel', 'icalendar', 'pyatom', 'python-memcached'] 
    128128 
    129129    #for Python older than 2.7 
Note: See TracChangeset for help on using the changeset viewer.