| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | ## |
|---|
| 3 | ## |
|---|
| 4 | ## This file is part of CDS Indico. |
|---|
| 5 | ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. |
|---|
| 6 | ## |
|---|
| 7 | ## CDS Indico is free software; you can redistribute it and/or |
|---|
| 8 | ## modify it under the terms of the GNU General Public License as |
|---|
| 9 | ## published by the Free Software Foundation; either version 2 of the |
|---|
| 10 | ## License, or (at your option) any later version. |
|---|
| 11 | ## |
|---|
| 12 | ## CDS Indico is distributed in the hope that it will be useful, but |
|---|
| 13 | ## WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 15 | ## General Public License for more details. |
|---|
| 16 | ## |
|---|
| 17 | ## You should have received a copy of the GNU General Public License |
|---|
| 18 | ## along with CDS Indico; if not, write to the Free Software Foundation, Inc., |
|---|
| 19 | ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 20 | |
|---|
| 21 | """ |
|---|
| 22 | Main export interface |
|---|
| 23 | """ |
|---|
| 24 | |
|---|
| 25 | # python stdlib imports |
|---|
| 26 | import re |
|---|
| 27 | from zope.interface import Interface, implements |
|---|
| 28 | from datetime import datetime, timedelta, date, time |
|---|
| 29 | |
|---|
| 30 | # external lib imports |
|---|
| 31 | import pytz |
|---|
| 32 | from simplejson import dumps |
|---|
| 33 | |
|---|
| 34 | # indico imports |
|---|
| 35 | from indico.util.date_time import nowutc |
|---|
| 36 | |
|---|
| 37 | # indico legacy imports |
|---|
| 38 | from MaKaC.common.indexes import IndexesHolder |
|---|
| 39 | from MaKaC.common.info import HelperMaKaCInfo |
|---|
| 40 | |
|---|
| 41 | |
|---|
| 42 | class ArgumentParseError(Exception): |
|---|
| 43 | pass |
|---|
| 44 | |
|---|
| 45 | class ArgumentValueError(Exception): |
|---|
| 46 | pass |
|---|
| 47 | |
|---|
| 48 | class IExport(Interface): |
|---|
| 49 | |
|---|
| 50 | def category(cls, idlist, dformat, fromDT, toDT, location=None, limit=None, |
|---|
| 51 | orderBy=None, descending=False, detail="events"): |
|---|
| 52 | """ |
|---|
| 53 | TODO: Document this |
|---|
| 54 | """ |
|---|
| 55 | |
|---|
| 56 | def event(cls, idlist, dformat, orderBy=None, descending=False, detail="events"): |
|---|
| 57 | """ |
|---|
| 58 | TODO: Document this |
|---|
| 59 | """ |
|---|
| 60 | |
|---|
| 61 | |
|---|
| 62 | class ExportInterface(object): |
|---|
| 63 | implements(IExport) |
|---|
| 64 | |
|---|
| 65 | _deltas = {'yesterday': timedelta(-1), |
|---|
| 66 | 'tomorrow': timedelta(1)} |
|---|
| 67 | |
|---|
| 68 | def __init__(self, dbi): |
|---|
| 69 | self._dbi = dbi |
|---|
| 70 | |
|---|
| 71 | @classmethod |
|---|
| 72 | def _parseDateTime(cls, dateTime): |
|---|
| 73 | """ |
|---|
| 74 | Accepted formats: |
|---|
| 75 | * ISO 8601 subset - YYYY-MM-DD[THH:MM] |
|---|
| 76 | * 'today', 'yesterday', 'tomorrow' and 'now' |
|---|
| 77 | * days in the future/past: '[+/-]DdHHhMMm' |
|---|
| 78 | |
|---|
| 79 | 'ctx' means that the date will change according to its function |
|---|
| 80 | ('from' or 'to') |
|---|
| 81 | """ |
|---|
| 82 | |
|---|
| 83 | # if it's a an "alias", return immediately |
|---|
| 84 | now = nowutc() |
|---|
| 85 | if dateTime in cls._deltas: |
|---|
| 86 | return ('ctx', now + cls._deltas[dateTime]) |
|---|
| 87 | elif dateTime == 'now': |
|---|
| 88 | return ('abs', now) |
|---|
| 89 | elif dateTime == 'today': |
|---|
| 90 | return ('ctx', now) |
|---|
| 91 | |
|---|
| 92 | m = re.match(r'^(?:(\d{1,3})d)?(?:(\d{1,2})h)?(?:(\d{1,2})m)?$', dateTime) |
|---|
| 93 | if m: |
|---|
| 94 | atoms = list(0 if a == None else int(a) for a in m.groups()) |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | |
|---|
| 98 | if atoms[1] > 23 or atoms[2] > 59: |
|---|
| 99 | raise ArgumentParseError("Invalid time!") |
|---|
| 100 | return ('ctx', timedelta(days=atoms[0], hours=atoms[1], minutes=atoms[2])) |
|---|
| 101 | else: |
|---|
| 102 | # iso 8601 subset |
|---|
| 103 | try: |
|---|
| 104 | return ('abs', datetime.strptime(dateTime, "%Y-%m-%dT%H:%M")) |
|---|
| 105 | except ValueError: |
|---|
| 106 | pass |
|---|
| 107 | try: |
|---|
| 108 | return ('ctx', datetime.strptime(dateTime, "%Y-%m-%d")) |
|---|
| 109 | except ValueError: |
|---|
| 110 | raise ArgumentParseError("Impossible to parse '%s'" % dateTime) |
|---|
| 111 | |
|---|
| 112 | @classmethod |
|---|
| 113 | def _getDateTime(cls, ctx, dateTime, tz, aux=None): |
|---|
| 114 | |
|---|
| 115 | rel, value = cls._parseDateTime(dateTime) |
|---|
| 116 | |
|---|
| 117 | if rel == 'abs': |
|---|
| 118 | return tz.localize(value) |
|---|
| 119 | elif rel == 'ctx' and type(value) == timedelta: |
|---|
| 120 | if ctx == 'from': |
|---|
| 121 | raise ArgumentValueError("Only 'to' accepts relative times") |
|---|
| 122 | else: |
|---|
| 123 | value = aux + value |
|---|
| 124 | |
|---|
| 125 | # from here on, 'value' has to be a datetime |
|---|
| 126 | if ctx == 'from': |
|---|
| 127 | return tz.localize(value.combine(value.date(), time(0, 0, 0))) |
|---|
| 128 | else: |
|---|
| 129 | return tz.localize(value.combine(value.date(), time(23, 59, 59))) |
|---|
| 130 | |
|---|
| 131 | def category(self, idlist, dformat, fromDT, toDT, location=None, limit=None, |
|---|
| 132 | orderBy=None, descending=False, detail="events", tz=None): |
|---|
| 133 | |
|---|
| 134 | self._dbi.startRequest() |
|---|
| 135 | |
|---|
| 136 | if tz == None: |
|---|
| 137 | info = HelperMaKaCInfo.getMaKaCInfoInstance() |
|---|
| 138 | tz = pytz.timezone(info.getTimezone()) |
|---|
| 139 | |
|---|
| 140 | fromDT = ExportInterface._getDateTime('from', fromDT, tz) if fromDT != None else None |
|---|
| 141 | toDT = ExportInterface._getDateTime('to', toDT, tz, aux=fromDT) if toDT != None else None |
|---|
| 142 | |
|---|
| 143 | idx = IndexesHolder().getById('categoryDate') |
|---|
| 144 | |
|---|
| 145 | results = [] |
|---|
| 146 | counter = 0 |
|---|
| 147 | terminate = False |
|---|
| 148 | # this set acts as a checklist to know if a record has already been sent |
|---|
| 149 | exclude = set() |
|---|
| 150 | |
|---|
| 151 | for catId in idlist: |
|---|
| 152 | for obj in idx.iterateObjectsIn(catId, fromDT, toDT): |
|---|
| 153 | # TODO: hard limit |
|---|
| 154 | if limit and counter >= limit: |
|---|
| 155 | terminate = True |
|---|
| 156 | break |
|---|
| 157 | if obj not in exclude: |
|---|
| 158 | results.append(obj.id) |
|---|
| 159 | exclude.add(obj) |
|---|
| 160 | counter += 1 |
|---|
| 161 | if terminate: |
|---|
| 162 | break |
|---|
| 163 | |
|---|
| 164 | self._dbi.endRequest(False) |
|---|
| 165 | |
|---|
| 166 | return dumps(results) |
|---|
| 167 | |
|---|
| 168 | def event(self, idlist, dformat, orderBy=None, descending=False, detail="events"): |
|---|
| 169 | """ |
|---|
| 170 | TODO: Document this |
|---|
| 171 | """ |
|---|