Projet

Général

Profil

0002-remove-support-for-agenda-events-37967.patch

Frédéric Péters, 26 novembre 2019 14:15

Télécharger (46 ko)

Voir les différences:

Subject: [PATCH 2/6] remove support for agenda/events (#37967)

 auquotidien/auquotidien.py       |   4 -
 auquotidien/modules/admin.py     |  39 +--
 auquotidien/modules/agenda.py    | 307 -----------------------
 auquotidien/modules/events.py    | 234 -----------------
 auquotidien/modules/events_ui.py | 418 -------------------------------
 auquotidien/modules/root.py      |  23 +-
 auquotidien/modules/template.py  |   5 -
 7 files changed, 3 insertions(+), 1027 deletions(-)
 delete mode 100644 auquotidien/modules/agenda.py
 delete mode 100644 auquotidien/modules/events.py
 delete mode 100644 auquotidien/modules/events_ui.py
auquotidien/auquotidien.py
8 8
from modules import backoffice
9 9
from modules import announces_ui
10 10
from modules import categories_admin
11
from modules import events_ui
12 11
from modules import payments_ui
13 12
from modules import strongbox_ui
14 13
from modules import formpage
......
30 29
rdb.register_directory('announces', announces_ui.AnnouncesDirectory())
31 30
rdb.register_menu_item('announces/', _('Announces'))
32 31

  
33
rdb.register_directory('events', events_ui.EventsDirectory())
34
rdb.register_menu_item('events/', _('Events'))
35

  
36 32
rdb.register_directory('payments', payments_ui.PaymentsDirectory())
37 33
rdb.register_menu_item('payments/', _('Payments'))
38 34

  
auquotidien/modules/admin.py
17 17
from wcs.categories import Category
18 18
from wcs.qommon.backoffice.menu import html_top
19 19

  
20
from .events import get_default_event_tags
21 20
import re
22 21
from .abelium_domino_ui import AbeliumDominoDirectory
23 22

  
24 23

  
25 24
class PanelDirectory(Directory):
26
    _q_exports = ['', 'update', 'announces', 'permissions', 'event_keywords',
25
    _q_exports = ['', 'update', 'announces', 'permissions',
27 26
            'announce_themes', 'strongbox', 'clicrdv', 'domino']
28 27
    label = N_('Control Panel')
29 28

  
......
77 76
        form.add(SingleSelectWidget, 'forms', title = _('Admin role for forms'),
78 77
                value = permissions_cfg.get('forms', None),
79 78
                options = [(None, _('Nobody'), None)] + get_user_roles())
80
        if get_publisher().has_site_option('auquotidien-events'):
81
            form.add(SingleSelectWidget, 'events', title = _('Admin role for events'),
82
                    value = permissions_cfg.get('events', None),
83
                    options = [(None, _('Nobody'), None)] + get_user_roles())
84 79
        if get_publisher().has_site_option('auquotidien-announces'):
85 80
            form.add(SingleSelectWidget, 'announces', title = _('Admin role for announces'),
86 81
                    value = permissions_cfg.get('announces', None),
......
109 104
        else:
110 105
            from wcs.admin.settings import cfg_submit
111 106
            cfg_submit(form, 'aq-permissions',
112
                        ('forms', 'events', 'announces', 'payments', 'strongbox'))
113
            return redirect('..')
114

  
115
    def event_keywords(self):
116
        misc_cfg = get_cfg('misc', {})
117
        form = Form(enctype='multipart/form-data')
118
        form.add(WidgetList, 'event_tags', title = _('Event Keywords'),
119
                value = misc_cfg.get('event_tags', get_default_event_tags()),
120
                elemnt_type = StringWidget,
121
                add_element_label = _('Add Keyword'),
122
                element_kwargs = {str('render_br'): False, str('size'): 30})
123

  
124
        form.add_submit('submit', _('Submit'))
125
        form.add_submit('cancel', _('Cancel'))
126

  
127
        if form.get_widget('cancel').parse():
128
            return redirect('..')
129

  
130
        if not form.is_submitted() or form.has_errors():
131
            get_response().breadcrumb.append(('aq/event_keywords', _('Event Keywords')))
132
            html_top('settings', _('Event Keywords'))
133
            r = TemplateIO(html=True)
134
            r += htmltext('<h2>%s</h2>') % _('Event Keywords')
135
            r += form.render()
136
            return r.getvalue()
137
        else:
138
            from wcs.admin.settings import cfg_submit
139
            cfg_submit(form, 'misc', ('event_tags',))
107
                        ('forms', 'announces', 'payments', 'strongbox'))
140 108
            return redirect('..')
141 109

  
142 110
    def announce_themes(self):
......
250 218
class SettingsDirectory(wcs.admin.settings.SettingsDirectory):
251 219
    def _q_index(self):
252 220
        if not (get_publisher().has_site_option('auquotidien-announces') or
253
                get_publisher().has_site_option('auquotidien-events') or
254 221
                get_publisher().has_site_option('auquotidien-payments') or
255 222
                get_publisher().has_site_option('auquotidien-strongvox')):
256 223
            return super(SettingsDirectory, self)._q_index()
......
263 230
        if get_publisher().has_site_option('auquotidien-announces'):
264 231
            r += htmltext('<li><a href="aq/announces">%s</a></li>') % _('Announces Options')
265 232
        r += htmltext('<li><a href="aq/permissions">%s</a></li>') % _('Permissions')
266
        if get_publisher().has_site_option('auquotidien-events'):
267
            r += htmltext('<li><a href="aq/event_keywords">%s</a></li>') % _('Event Keywords')
268 233
        if get_publisher().has_site_option('auquotidien-announces'):
269 234
            r += htmltext('<li><a href="aq/announce_themes">%s</a></li>') % _('Announce Themes')
270 235
        if get_publisher().has_site_option('strongbox'):
auquotidien/modules/agenda.py
1
import time
2
import datetime
3
from sets import Set
4

  
5
from quixote.directory import Directory
6
from quixote import get_publisher, get_request, redirect, get_session, get_response
7
from quixote.html import htmltext, TemplateIO
8

  
9
from wcs.qommon import _
10
from wcs.qommon import misc, template, errors, get_cfg
11
from wcs.qommon.form import *
12

  
13
from .events import Event, RemoteCalendar, get_default_event_tags
14

  
15

  
16
class TagDirectory(Directory):
17
    def _q_lookup(self, component):
18
        events = Event.select()
19
        for remote_calendar in RemoteCalendar.select():
20
            if remote_calendar.events:
21
                events.extend(remote_calendar.events)
22
        self.events = [x for x in events if component in (x.keywords or [])]
23
        self.events.sort(lambda x,y: cmp(x.date_start, y.date_start))
24
        self.tag = component
25
        return self.display_events()
26

  
27
    def display_events(self):
28
        template.html_top(_('Agenda'))
29
        r = TemplateIO(html=True)
30
        if len(self.events) > 1:
31
            r += htmltext('<p id="nb-events">')
32
            r += _('%(nb)d events with %(keyword)s keyword') % {
33
                    'nb': len(self.events),
34
                    'keyword': self.tag
35
            }
36
            r += htmltext('</p>')
37

  
38
        if self.events:
39
            r += htmltext('<dl id="events">')
40
            for ev in self.events:
41
                r += htmltext(ev.as_html_dt_dd())
42
            r += htmltext('</dl>')
43
        else:
44
            r += htmltext('<p id="nb-events">')
45
            r += _('No event registered with the %s keyword.') % self.tag
46
            r += htmltext('</p>')
47
        return r.getvalue()
48

  
49

  
50
class AgendaDirectory(Directory):
51
    _q_exports = ['', 'icalendar', 'tag', 'atom', 'filter']
52

  
53
    year = None
54
    month = None
55

  
56
    tag = TagDirectory()
57

  
58
    def _q_traverse(self, path):
59
        get_response().breadcrumb.append(('agenda/', _('Agenda')))
60
        self.year, self.month = time.localtime()[:2]
61
        if len(path) >= 1 and path[0].isdigit():
62
            self.year, self.month = (None, None)
63
            self.year = int(path[0])
64
            get_response().breadcrumb.append(('%s/' % self.year, self.year))
65
            path = path[1:]
66
            if len(path) >= 1 and path[0] in [str(x) for x in range(1, 13)]:
67
                self.month = int(path[0])
68
                get_response().breadcrumb.append(('%s/' % self.month,
69
                            misc.get_month_name(self.month)))
70
                path = path[1:]
71
            if len(path) == 0:
72
                return redirect(get_request().get_path() + '/')
73
        return Directory._q_traverse(self, path)
74

  
75
    def _q_index(self):
76
        if self.month:
77
            r = TemplateIO(html=True)
78
            r += htmltext(self.display_month_links())
79
            r += htmltext(self.display_month())
80
            return r.getvalue()
81
        else:
82
            return redirect('..')
83

  
84
    def display_month(self):
85
        template.html_top(_('Agenda'))
86
        events = Event.select()
87
        remote_cal = get_request().form.get('cal')
88
        if remote_cal != 'local':
89
            if remote_cal:
90
                try:
91
                    events = RemoteCalendar.get(remote_cal).events
92
                except KeyError:
93
                    raise errors.TraversalError()
94
                if not events:
95
                    events = []
96
            else:
97
                for remote_calendar in RemoteCalendar.select():
98
                    if remote_calendar.events:
99
                        events.extend(remote_calendar.events)
100
        events = [x for x in events if x.in_month(self.year, self.month)]
101
        events.sort(lambda x,y: cmp(x.date_start, y.date_start))
102

  
103
        r = TemplateIO(html=True)
104
        if events:
105
            if len(events) > 1:
106
                r += htmltext('<p id="nb-events">')
107
                r += _('%(nb)d events for %(month_name)s %(year)s') % {
108
                        'nb': len(events),
109
                        'month_name': misc.get_month_name(self.month),
110
                        'year': self.year}
111
                r += htmltext('</p>')
112

  
113
            r += htmltext('<dl id="events">')
114
            for ev in events:
115
                r += htmltext(ev.as_html_dt_dd())
116
            r += htmltext('</dl>')
117
        else:
118
            r += htmltext('<p id="nb-events">')
119
            r += _('No event registered for the month of %s.') % '%s %s' % (
120
                    misc.get_month_name(self.month), self.year)
121
            r += htmltext('</p>')
122

  
123
        root_url = get_publisher().get_root_url()
124
        r += htmltext('<div id="agenda-subs">')
125
        r += htmltext('<p>')
126
        r += _('You can subscribe to this calendar:')
127
        r += htmltext('</p>')
128
        r += htmltext('<ul>')
129
        r += htmltext('  <li><a href="%sagenda/icalendar" id="par_ical">%s</a></li>') % (
130
                root_url, _('iCalendar'))
131
        r += htmltext('  <li><a href="%sagenda/atom" id="par_rss">%s</a></li>') % (
132
                root_url, _('Feed'))
133
        r += htmltext('</ul>')
134
        r += htmltext('</div>')
135
        return r.getvalue()
136

  
137
    def display_month_links(self):
138
        today = datetime.date(*(time.localtime()[:2] + (1,)))
139
        r = TemplateIO(html=True)
140
        r += htmltext('<ul id="month-links">')
141
        for i in range(12):
142
            r += htmltext('<li>')
143
            if (today.year, today.month) == (self.year, self.month):
144
                r += htmltext('<strong>')
145
                r += '%s %s' % (misc.get_month_name(today.month), today.year)
146
                r += htmltext('</strong>')
147
            else:
148
                root_url = get_publisher().get_root_url()
149
                r += htmltext('<a href="%sagenda/%s/%s/">') % (root_url, today.year, today.month)
150
                r += '%s %s' % (misc.get_month_name(today.month), today.year)
151
                r += htmltext('</a>')
152
            r += htmltext('</li>')
153
            today += datetime.timedelta(31)
154
        r += htmltext('</ul>')
155
        return r.getvalue()
156

  
157
    def display_remote_calendars(self):
158
        r = TemplateIO(html=True)
159
        remote_calendars = [x for x in RemoteCalendar.select() if x.label]
160
        if not remote_calendars:
161
            return
162
        remote_calendars.sort(lambda x,y: cmp(x.label, y.label))
163
        r += htmltext('<p class="tags">')
164
        remote_cal = get_request().form.get('cal')
165
        agenda_root_url = get_publisher().get_root_url() + 'agenda/'
166
        if remote_cal:
167
            r += htmltext('<a href="%s">%s</a> ') % (agenda_root_url, _('All'))
168
        else:
169
            r += htmltext('<strong><a href="%s">%s</a></strong> ') % (agenda_root_url, _('All'))
170
        if remote_cal != 'local':
171
            r += htmltext('<a href="%s?cal=local">%s</a> ') % (agenda_root_url, _('Local'))
172
        else:
173
            r += htmltext('<strong><a href="%s?cal=local">%s</a></strong> ') % (agenda_root_url, _('Local'))
174
        for cal in remote_calendars:
175
            if remote_cal == str(cal.id):
176
                r += htmltext('<strong><a href="%s?cal=%s">%s</a></strong> ') % (
177
                        agenda_root_url, cal.id, cal.label)
178
            else:
179
                r += htmltext('<a href="%s?cal=%s">%s</a> ') % (agenda_root_url, cal.id, cal.label)
180
        r += htmltext('</p>')
181
        return r.getvalue()
182

  
183
    def icalendar(self):
184
        if not Event.keys():
185
            raise errors.TraversalError()
186
        response = get_response()
187
        response.set_content_type('text/calendar', 'utf-8')
188
        vcal = Event.as_vcalendar()
189
        if type(vcal) is unicode:
190
            return vcal.encode('utf-8')
191
        else:
192
            return vcal
193

  
194
    def atom(self):
195
        response = get_response()
196
        response.set_content_type('application/atom+xml')
197

  
198
        from pyatom import pyatom
199
        xmldoc = pyatom.XMLDoc()
200
        feed = pyatom.Feed()
201
        xmldoc.root_element = feed
202
        feed.title = get_cfg('misc', {}).get('sitename', 'Publik') + ' - ' + _('Agenda')
203
        feed.id = get_request().get_url()
204

  
205
        author_email = get_cfg('emails', {}).get('reply_to')
206
        if not author_email:
207
            author_email = get_cfg('emails', {}).get('from')
208
        if author_email:
209
            feed.authors.append(pyatom.Author(author_email))
210

  
211
        feed.links.append(pyatom.Link(get_request().get_url(1) + '/'))
212

  
213
        year, month = time.localtime()[:2]
214
        nyear, nmonth = year, month+1
215
        if nmonth > 12:
216
            nyear, nmonth = nyear+1, 1
217

  
218
        events = [x for x in Event.select() if x.in_month(year, month) or x.in_month(nyear, nmonth)]
219
        events.sort(lambda x,y: cmp(x.date_start, y.date_start))
220
        events.reverse()
221

  
222
        for item in events:
223
            entry = item.get_atom_entry()
224
            if entry is not None:
225
                feed.entries.append(entry)
226

  
227
        return str(feed)
228

  
229
    def filter(self, no_event=False):
230
        template.html_top(_('Agenda'))
231
        tags = get_cfg('misc', {}).get('event_tags')
232
        if not tags:
233
            tags = get_default_event_tags()
234
        remote_calendars = [x for x in RemoteCalendar.select() if x.label]
235

  
236
        form = Form(enctype='multipart/form-data')
237
        if tags and remote_calendars:
238
            form.widgets.append(HtmlWidget('<table id="agenda-filter"><tr><td>'))
239
        if tags:
240
            form.add(CheckboxesWidget, 'tags', title=_('Tags'),
241
                    options=[(x,x) for x in tags],
242
                    inline=False)
243
        if tags and remote_calendars:
244
            form.widgets.append(HtmlWidget('</td><td>'))
245
        if remote_calendars:
246
            remote_calendars.sort(lambda x,y: cmp(x.label, y.label))
247
            form.add(CheckboxesWidget, 'calendars', title=_('Calendars'),
248
                    options=[('local', _('Local'))] + [(x.id, x.label) for x in remote_calendars],
249
                    inline=False)
250
        if tags and remote_calendars:
251
            form.widgets.append(HtmlWidget('</td></tr></table>'))
252

  
253
        form.add_submit('submit', _('Submit'))
254
        form.add_submit('cancel', _('Cancel'))
255
        if form.get_widget('cancel').parse():
256
            return redirect('.')
257

  
258
        if no_event or not form.is_submitted():
259
            r = TemplateIO(html=True)
260
            if no_event:
261
                r += htmltext('<p id="nb-events">')
262
                r += _('No events matching the filter.')
263
                r += htmltext('</p>')
264
            r += form.render()
265
            return r.getvalue()
266
        else:
267
            return self.filter_submitted(form, tags, remote_calendars)
268

  
269
    def filter_submitted(self, form, tags, remote_calendars):
270
        if remote_calendars:
271
            selected_remote_calendars = form.get_widget('calendars').parse()
272
            events = []
273
            for remote_calendar in selected_remote_calendars:
274
                if remote_calendar == 'local':
275
                    events.extend(Event.select())
276
                else:
277
                    try:
278
                        events.extend(RemoteCalendar.get(remote_calendar).events)
279
                    except KeyError:
280
                        pass
281
        else:
282
            events = Event.select()
283

  
284
        events = [x for x in events if x.after_today()]
285

  
286
        if tags:
287
            selected_tags = Set(form.get_widget('tags').parse())
288
            if selected_tags and len(selected_tags) != len(tags):
289
                events = [x for x in events if Set(x.keywords).intersection(selected_tags)]
290

  
291
        events.sort(lambda x,y: cmp(x.date_start, y.date_start))
292

  
293
        r = TemplateIO(html=True)
294

  
295
        if len(events) > 1:
296
            r += htmltext('<p id="nb-events">')
297
            r += htmltext(_('%(nb)d events')) % {'nb': len(events)}
298
            r += htmltext('</p>')
299

  
300
        if events:
301
            r += htmltext('<dl id="events">')
302
            for ev in events:
303
                r += htmltext(ev.as_html_dt_dd())
304
            r += htmltext('</dl>')
305
            return r.getvalue()
306
        else:
307
            return self.filter(no_event=True)
auquotidien/modules/events.py
1
import time
2
import datetime
3
import urllib2
4
import vobject
5

  
6
from quixote import get_request, get_publisher, get_response
7
from quixote.html import htmltext, TemplateIO, htmlescape
8

  
9
from wcs.qommon import _
10
from wcs.qommon.publisher import get_publisher_class
11
from wcs.qommon.storage import StorableObject
12
from wcs.qommon.cron import CronJob
13
from wcs.qommon import misc
14

  
15
class Event(StorableObject):
16
    _names = 'events'
17

  
18
    title = None
19
    description = None
20
    url = None
21
    date_start = None
22
    date_end = None
23
    location = None
24
    organizer = None
25
    more_infos = None
26
    keywords = None
27

  
28
    def in_month(self, year, month):
29
        if not self.date_end: # a single date
30
            return tuple(self.date_start[:2]) == (year, month)
31
        else:
32
            # an interval
33
            if tuple(self.date_start[:2]) > (year, month): # start later
34
                return False
35
            if tuple(self.date_end[:2]) < (year, month): # ended before
36
                return False
37
        return True
38

  
39
    def after_today(self):
40
        today = time.localtime()[:3]
41
        if not self.date_end:
42
            return tuple(self.date_start[:3]) > today
43
        return tuple(self.date_end[:3]) > today
44

  
45
    def format_date(self):
46
        d = {
47
            'year_start': self.date_start[0],
48
            'month_start': misc.get_month_name(self.date_start[1]),
49
            'day_start': self.date_start[2]
50
            }
51
        if self.date_end and self.date_start[:3] != self.date_end[:3]:
52
            d.update({
53
                'year_end': self.date_end[0],
54
                'month_end': misc.get_month_name(self.date_end[1]),
55
                'day_end': self.date_end[2]
56
                })
57
            d2 = datetime.date(*self.date_start[:3]) + datetime.timedelta(days=1)
58
            if tuple(self.date_end[:3]) == (d2.year, d2.month, d2.day):
59
                # two consecutive days
60
                if self.date_start[1] == self.date_end[1]:
61
                    return _('On %(month_start)s %(day_start)s and %(day_end)s') % d
62
                else:
63
                    return _('On %(month_start)s %(day_start)s and %(month_end)s %(day_end)s') % d
64
            else:
65
                if self.date_start[0] == self.date_end[0]: # same year
66
                    if self.date_start[1] == self.date_end[1]: # same month
67
                        return _('From %(month_start)s %(day_start)s to %(day_end)s') % d
68
                    else:
69
                        return _('From %(month_start)s %(day_start)s '
70
                                 'to %(month_end)s %(day_end)s') % d
71
                else:
72
                    return _('From %(month_start)s %(day_start)s %(year_start)s '
73
                             'to %(month_end)s %(day_end)s %(year_end)s') % d
74
        else:
75
            return _('On %(month_start)s %(day_start)s') % d
76

  
77
    def as_vevent(self):
78
        vevent = vobject.newFromBehavior('vevent')
79
        site_charset = get_publisher().site_charset
80
        vevent.add('uid').value = '%04d%02d%02d-%s@%s' % (self.date_start[:3] + (self.id,
81
            get_request().get_server().lower().split(':')[0].rstrip('.')))
82
        vevent.add('summary').value = unicode(self.title, site_charset)
83
        vevent.add('dtstart').value = datetime.date(*self.date_start[:3])
84
        vevent.dtstart.value_param = 'DATE'
85
        if self.date_end:
86
            vevent.add('dtend').value = datetime.date(*self.date_end[:3])
87
            vevent.dtend.value_param = 'DATE'
88
        if self.description:
89
            vevent.add('description').value = unicode(self.description.strip(), site_charset)
90
        if self.url:
91
            vevent.add('url').value = unicode(self.url, site_charset)
92
        if self.location:
93
            vevent.add('location').value = unicode(self.location, site_charset)
94
        if self.organizer:
95
            vevent.add('organizer').value = unicode(self.organizer, site_charset)
96
        if self.keywords:
97
            vevent.add('categories').value = [unicode(x, site_charset) for x in self.keywords]
98
        vevent.add('class').value = 'PUBLIC'
99
        return vevent
100

  
101
    def as_vcalendar(cls):
102
        cal = vobject.iCalendar()
103
        cal.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
104
        for x in cls.select():
105
            cal.add(x.as_vevent())
106
        return cal.serialize()
107
    as_vcalendar = classmethod(as_vcalendar)
108
    
109
    def as_html_dt_dd(self):
110
        root_url = get_publisher().get_root_url()
111
        r = TemplateIO(html=True)
112
        r += htmltext('<dt>')
113
        r += self.format_date()
114
        r += htmltext('</dt>')
115
        r += htmltext('<p><dd><strong>%s</strong>') % self.title
116
        if self.description:
117
            r += ' - ' + self.description
118
        r += htmltext('</p>')
119
        if (self.location or self.organizer or self.more_infos or self.keywords):
120
            r += htmltext('<ul>')
121
            if self.location:
122
                r += htmltext('<li>%s: %s</li>') % (_('Location'), self.location)
123
            if self.organizer:
124
                r += htmltext('<li>%s: %s</li>') % (_('Organizer'), self.organizer)
125
            if self.more_infos:
126
                r += htmltext('<li>%s</li>') % self.more_infos
127
            if self.keywords:
128
                r += htmltext('<li>')
129
                for k in self.keywords:
130
                    r += htmltext('<a class="tag" href="%sagenda/tag/%s">%s</a> ') % (root_url, k, k)
131
                r += htmltext('</li>')
132
            r += htmltext('</ul>')
133

  
134
        if self.url:
135
            r += htmltext('<a class="external" href="%s">%s</a>') % (
136
                        self.url, _('More information'))
137
        r += htmltext('</dd>')
138
        return r.getvalue()
139

  
140
    def get_url(self):
141
        return '%s/agenda/events/%s/' % (get_publisher().get_frontoffice_url(), self.id)
142

  
143
    def get_atom_entry(self):
144
        from pyatom import pyatom
145
        entry = pyatom.Entry()
146
        entry.id = self.get_url()
147
        entry.title = self.title
148

  
149
        entry.content.attrs['type'] = 'html'
150
        entry.content.text = str('<p>' + htmlescape(
151
                    unicode(self.description, get_publisher().site_charset).encode('utf-8')) + '</p>')
152

  
153
        return entry
154

  
155

  
156
class RemoteCalendar(StorableObject):
157
    _names = 'remote_calendars'
158

  
159
    label = None
160
    url = None
161
    content = None
162
    events = None
163
    error = None  # (time, string, params)
164

  
165
    def download_and_parse(self, job=None):
166
        old_content = self.content
167

  
168
        try:
169
            self.content = urllib2.urlopen(self.url).read()
170
        except urllib2.HTTPError, e:
171
            self.error = (time.localtime(), N_('HTTP Error %s on download'), (e.code,))
172
            self.store()
173
            return
174
        except urllib2.URLError, e:
175
            self.error = (time.localtime(), N_('Error on download'), ())
176
            self.store()
177
            return
178

  
179
        if self.error:
180
            self.error = None
181
            self.store()
182

  
183
        if self.content == old_content:
184
            return
185

  
186
        self.events = []
187
        try:
188
            parsed_cal = vobject.readOne(self.content)
189
        except vobject.base.ParseError:
190
            self.error = (time.localtime(), N_('Failed to parse file'), ())
191
            self.store()
192
            return
193

  
194
        site_charset = get_publisher().site_charset
195
        for vevent in parsed_cal.vevent_list:
196
            ev = Event()
197
            ev.title = vevent.summary.value.encode(site_charset, 'replace')
198
            try:
199
                ev.url = vevent.url.value.encode(site_charset, 'replace')
200
            except AttributeError:
201
                pass
202
            ev.date_start = vevent.dtstart.value.timetuple()
203
            try:
204
                ev.date_end = vevent.dtend.value.timetuple()
205
            except AttributeError:
206
                pass
207
            try:
208
                ev.description = vevent.description.value.encode(site_charset, 'replace')
209
            except AttributeError:
210
                pass
211
            try:
212
                ev.keywords = [x.encode(site_charset) for x in vevent.categories.value]
213
            except AttributeError:
214
                pass
215
            self.events.append(ev)
216
        self.store()
217

  
218
        
219
    def get_error_message(self):
220
        if not self.error:
221
            return None
222
        return '(%s) %s' % (misc.localstrftime(self.error[0]),
223
                _(self.error[1]) % self.error[2])
224

  
225

  
226
def update_remote_calendars(publisher):
227
    for source in RemoteCalendar.select():
228
        source.download_and_parse()
229

  
230
def get_default_event_tags():
231
    return [_('All Public'), _('Adults'), _('Children'), _('Free')]
232

  
233
get_publisher_class().register_cronjob(CronJob(update_remote_calendars, minutes = [0]))
234

  
auquotidien/modules/events_ui.py
1
import time
2

  
3
from quixote import get_request, get_response, get_session, redirect
4
from quixote.directory import Directory, AccessControlled
5
from quixote.html import TemplateIO, htmltext
6

  
7
import wcs
8
import wcs.admin.root
9

  
10
from wcs.qommon import _
11
from wcs.qommon.backoffice.menu import html_top
12
from wcs.qommon.admin.menu import command_icon
13
from wcs.qommon import get_cfg
14
from wcs.qommon import errors, misc
15
from wcs.qommon.form import *
16
from wcs.qommon.misc import strftime
17

  
18
from .events import Event, RemoteCalendar, get_default_event_tags
19

  
20

  
21

  
22
class RemoteCalendarDirectory(Directory):
23
    _q_exports = ['', 'edit', 'delete', 'update']
24

  
25
    def __init__(self, calendar):
26
        self.calendar = calendar
27

  
28
    def _q_index(self):
29
        form = Form(enctype='multipart/form-data')
30
        form.add_submit('edit', _('Edit'))
31
        form.add_submit('delete', _('Delete'))
32
        form.add_submit('update', _('Update'))
33
        form.add_submit('back', _('Back'))
34

  
35
        if form.get_submit() == 'edit':
36
            return redirect('edit')
37
        if form.get_submit() == 'update':
38
            return redirect('update')
39
        if form.get_submit() == 'delete':
40
            return redirect('delete')
41
        if form.get_submit() == 'back':
42
            return redirect('..')
43

  
44
        html_top('events', title = _('Remote Calendar: %s') % self.calendar.label)
45
        r = TemplateIO(html=True)
46
        r += htmltext('<h2>%s</h2>') % _('Remote Calendar: %s') % self.calendar.label
47

  
48
        r += get_session().display_message()
49

  
50
        r += htmltext('<p>')
51
        self.calendar.url
52
        if self.calendar.error:
53
            r += htmltext(' - <span class="error-message">%s</span>') % self.calendar.get_error_message()
54
        r += htmltext('</p>')
55

  
56
        if not self.calendar.content:
57
            r += htmltext('<p>')
58
            r += _('No content has been retrieved yet.')
59
            r += htmltext('</p>')
60
        else:
61
            r += htmltext('<ul>')
62
            for ev in sorted(self.calendar.events, lambda x,y: cmp(x.date_start, y.date_start)):
63
                r += htmltext('<li>')
64
                if ev.date_start:
65
                    r += strftime(misc.date_format(), ev.date_start)
66
                if ev.date_end and ev.date_start[:3] != ev.date_end[:3]:
67
                    r += ' - '
68
                    r += strftime(misc.date_format(), ev.date_start)
69

  
70
                r += ' : '
71
                if ev.url:
72
                    r += htmltext('<a href="%s">%s</a>') % (ev.url, ev.title)
73
                else:
74
                    r += ev.title
75
                r += htmltext('</li>')
76
            r += htmltext('</ul>')
77

  
78
        r += form.render()
79
        return r.getvalue()
80

  
81
    def edit(self):
82
        form = self.form()
83
        if form.get_submit() == 'cancel':
84
            return redirect('.')
85

  
86
        if form.is_submitted() and not form.has_errors():
87
            self.submit(form)
88
            return redirect('..')
89

  
90
        html_top('events', title = _('Edit Remote Calendar: %s') % self.calendar.label)
91
        r = TemplateIO(html=True)
92
        r += htmltext('<h2>%s</h2>') % _('Edit Remote Calendar: %s') % self.calendar.label
93
        r += form.render()
94
        return r.getvalue()
95

  
96
    def form(self):
97
        form = Form(enctype='multipart/form-data')
98
        form.add(StringWidget, 'label', title = _('Label'), required = True,
99
                value = self.calendar.label)
100
        form.add(StringWidget, 'url', title = _('URL'), required = True,
101
                value = self.calendar.url, size = 40)
102
        form.add_submit('submit', _('Submit'))
103
        form.add_submit('cancel', _('Cancel'))
104
        return form
105

  
106
    def submit(self, form):
107
        for k in ('label', 'url'):
108
            widget = form.get_widget(k)
109
            if widget:
110
                setattr(self.calendar, k, widget.parse())
111
        self.calendar.store()
112

  
113
    def delete(self):
114
        form = Form(enctype='multipart/form-data')
115
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
116
                        'You are about to irrevocably delete this remote calendar.')))
117
        form.add_submit('submit', _('Submit'))
118
        form.add_submit('cancel', _('Cancel'))
119
        if form.get_submit() == 'cancel':
120
            return redirect('..')
121
        if not form.is_submitted() or form.has_errors():
122
            get_response().breadcrumb.append(('delete', _('Delete')))
123
            html_top('events', title = _('Delete Remote Calendar'))
124
            r = TemplateIO(html=True)
125
            r += htmltext('<h2>%s</h2>') % _('Deleting Remote Calendar: %s') % self.calendar.label
126
            r += form.render()
127
            return r.getvalue()
128
        else:
129
            self.calendar.remove_self()
130
            return redirect('..')
131

  
132
    def update(self):
133
        get_session().message = ('info',
134
                _('Calendar update has been requested, reload in a few moments'))
135
        get_response().add_after_job('updating remote calendar',
136
                self.calendar.download_and_parse,
137
                fire_and_forget = True)
138
        return redirect('.')
139

  
140

  
141

  
142
class RemoteCalendarsDirectory(Directory):
143
    _q_exports = ['', 'new']
144

  
145
    def _q_traverse(self, path):
146
        get_response().breadcrumb.append(('remote/', _('Remote Calendars')))
147
        return Directory._q_traverse(self, path)
148

  
149
    def _q_index(self):
150
        return redirect('..')
151

  
152
    def new(self):
153
        calendar_ui = RemoteCalendarDirectory(RemoteCalendar())
154

  
155
        form = calendar_ui.form()
156
        if form.get_submit() == 'cancel':
157
            return redirect('.')
158

  
159
        if form.is_submitted() and not form.has_errors():
160
            calendar_ui.submit(form)
161
            return redirect('%s/' % calendar_ui.calendar.id)
162

  
163
        get_response().breadcrumb.append(('new', _('New Remote Calendar')))
164
        html_top('events', title = _('New Remote Calendar'))
165
        r = TemplateIO(html=True)
166
        r += htmltext('<h2>%s</h2>') % _('New Remote Calendar')
167
        r += form.render()
168
        return r.getvalue()
169

  
170
    def _q_lookup(self, component):
171
        try:
172
            event = RemoteCalendar.get(component)
173
        except KeyError:
174
            raise errors.TraversalError()
175
        get_response().breadcrumb.append((str(event.id), event.label))
176
        return RemoteCalendarDirectory(event)
177

  
178

  
179
class EventDirectory(Directory):
180
    _q_exports = ['', 'edit', 'delete']
181

  
182
    def __init__(self, event):
183
        self.event = event
184

  
185
    def _q_index(self):
186
        form = Form(enctype='multipart/form-data')
187
        form.add_submit('edit', _('Edit'))
188
        form.add_submit('delete', _('Delete'))
189
        form.add_submit('back', _('Back'))
190

  
191
        if form.get_submit() == 'edit':
192
            return redirect('edit')
193
        if form.get_submit() == 'delete':
194
            return redirect('delete')
195
        if form.get_submit() == 'back':
196
            return redirect('..')
197

  
198
        html_top('events', title = _('Event: %s') % self.event.title)
199
        r = TemplateIO(html=True)
200
        r += htmltext('<h2>%s</h2>') % _('Event: %s') % self.event.title
201
        r += htmltext('<p>')
202
        r += self.event.description
203
        r += htmltext('</p>')
204
        r += htmltext('<ul>')
205
        if self.event.location:
206
            r += htmltext('<li>%s: %s</li>') % (_('Location'), self.event.location)
207
        if self.event.organizer:
208
            r += htmltext('<li>%s: %s</li>') % (_('Organizer'), self.event.organizer)
209
        if self.event.url:
210
            r += htmltext('<li>%s: <a href="%s">%s</a></li>') % (_('URL'), self.event.url, self.event.url)
211
        r += htmltext('</ul>')
212

  
213
        if self.event.more_infos:
214
            r += htmltext('<p>')
215
            r += self.event.more_infos
216
            r += htmltext('</p>')
217

  
218
        r += form.render()
219
        return r.getvalue()
220

  
221
    def edit(self):
222
        form = self.form()
223
        if form.get_submit() == 'cancel':
224
            return redirect('.')
225

  
226
        if form.is_submitted() and not form.has_errors():
227
            self.submit(form)
228
            return redirect('..')
229

  
230
        html_top('events', title = _('Edit Event: %s') % self.event.title)
231
        r = TemplateIO(html=True)
232
        r += htmltext('<h2>%s</h2>') % _('Edit Event: %s') % self.event.title
233
        r += form.render()
234
        return r.getvalue()
235

  
236
    def form(self):
237
        form = Form(enctype='multipart/form-data')
238
        form.add(StringWidget, 'title', title = _('Title'), required = True,
239
                value = self.event.title)
240
        form.add(TextWidget, 'description', title = _('Description'),
241
                cols = 70, rows = 10,
242
                required = True, value = self.event.description)
243
        form.add(StringWidget, 'url', title = _('URL'), required = False,
244
                value = self.event.url, size = 40)
245
        form.add(DateWidget, 'date_start', title = _('Start Date'), required = True,
246
                value = strftime(misc.date_format(), self.event.date_start))
247
        form.add(DateWidget, 'date_end', title = _('End Date'), required = False,
248
                value = strftime(misc.date_format(), self.event.date_end))
249
        form.add(TextWidget, 'location', title = _('Location'),
250
                cols = 70, rows = 4,
251
                required = False, value = self.event.location)
252
        form.add(StringWidget, 'organizer', title = _('Organizer'), required = False,
253
                value = self.event.organizer, size = 40)
254
        form.add(TextWidget, 'more_infos', title = _('More informations'),
255
                cols = 70, rows = 10,
256
                required = False, value = self.event.more_infos)
257
        form.add(TagsWidget, 'keywords', title = _('Keywords'),
258
                value = self.event.keywords, size = 50,
259
                known_tags = get_cfg('misc', {}).get('event_tags', get_default_event_tags()))
260
        form.add_submit('submit', _('Submit'))
261
        form.add_submit('cancel', _('Cancel'))
262
        return form
263

  
264
    def submit(self, form):
265
        for k in ('title', 'description', 'url', 'date_start', 'date_end',
266
                    'organizer', 'location', 'more_infos', 'keywords'):
267
            widget = form.get_widget(k)
268
            if widget:
269
                if k in ('date_start', 'date_end'):
270
                    # convert dates to 9-item tuples
271
                    v = widget.parse()
272
                    if v:
273
                        setattr(self.event, k, time.strptime(v, misc.date_format()))
274
                    else:
275
                        setattr(self.event, k, None)
276
                else:
277
                    setattr(self.event, k, widget.parse())
278
        self.event.store()
279

  
280
    def delete(self):
281
        form = Form(enctype='multipart/form-data')
282
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
283
                        'You are about to irrevocably delete this event.')))
284
        form.add_submit('submit', _('Submit'))
285
        form.add_submit('cancel', _('Cancel'))
286
        if form.get_submit() == 'cancel':
287
            return redirect('..')
288
        if not form.is_submitted() or form.has_errors():
289
            get_response().breadcrumb.append(('delete', _('Delete')))
290
            html_top('events', title = _('Delete Event'))
291
            r = TemplateIO(html=True)
292
            r += htmltext('<h2>%s</h2>') % _('Deleting Event: %s') % self.event.title
293
            r += form.render()
294
            return r.getvalue()
295
        else:
296
            self.event.remove_self()
297
            return redirect('..')
298

  
299

  
300

  
301

  
302
class EventsDirectory(AccessControlled, Directory):
303
    _q_exports = ['', 'new', 'listing', 'remote']
304
    label = N_('Events')
305

  
306
    remote = RemoteCalendarsDirectory()
307

  
308
    def is_accessible(self, user):
309
        from .backoffice import check_visibility
310
        return check_visibility('events', user)
311

  
312
    def _q_access(self):
313
        user = get_request().user
314
        if not user:
315
            raise errors.AccessUnauthorizedError()
316

  
317
        if not self.is_accessible(user):
318
            raise errors.AccessForbiddenError(
319
                    public_msg = _('You are not allowed to access Events Management'),
320
                    location_hint = 'backoffice')
321

  
322
        get_response().breadcrumb.append(('events/', _('Events')))
323

  
324

  
325
    def _q_index(self):
326
        html_top('events', _('Events'))
327
        r = TemplateIO(html=True)
328

  
329
        get_response().filter['sidebar'] = self.get_sidebar()
330

  
331
        r += htmltext('<div class="splitcontent-left">')
332

  
333
        r += htmltext('<div class="bo-block">')
334
        events = Event.select()
335
        r += htmltext('<h2>%s</h2>') % _('Events')
336
        if not events:
337
            r += htmltext('<p>')
338
            r += _('There is no event defined at the moment.')
339
            r += htmltext('</p>')
340
        r += htmltext('<ul class="biglist" id="events-list">')
341
        for l in events:
342
            event_id = l.id
343
            r += htmltext('<li class="biglistitem" id="itemId_%s">') % event_id
344
            r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (event_id, l.title)
345
            r += ' - '
346
            r += l.format_date()
347
            r += htmltext('<p class="commands">')
348
            r += command_icon('%s/edit' % event_id, 'edit')
349
            r += command_icon('%s/delete' % event_id, 'remove')
350
            r += htmltext('</p></li>')
351
        r += htmltext('</ul>')
352
        r += htmltext('</div>')
353
        r += htmltext('</div>')
354

  
355
        r += htmltext('<div class="splitcontent-right">')
356
        r += htmltext('<div class="bo-block">')
357
        rcalendars = RemoteCalendar.select()
358
        r += htmltext('<h2>%s</h2>') % _('Remote Calendars')
359
        if not rcalendars:
360
            r += htmltext('<p>')
361
            r += _('There is no remote calendars defined at the moment.')
362
            r += htmltext('</p>')
363

  
364
        r += htmltext('<ul class="biglist" id="events-list">')
365
        for l in rcalendars:
366
            rcal_id = l.id
367
            r += htmltext('<li class="biglistitem" id="itemId_%s">') % rcal_id
368
            r += htmltext('<strong class="label"><a href="remote/%s/">%s</a></strong>') % (rcal_id, l.label)
369
            r += htmltext('<p class="details">')
370
            r += l.url
371
            if l.error:
372
                r += htmltext('<br /><span class="error-message">%s</span>') % l.get_error_message()
373
            r += htmltext('</p>')
374
            r += htmltext('<p class="commands">')
375
            r += command_icon('remote/%s/edit' % rcal_id, 'edit')
376
            r += command_icon('remote/%s/delete' % rcal_id, 'remove')
377
            r += htmltext('</p></li>')
378
        r += htmltext('</ul>')
379
        r += htmltext('</div>')
380
        r += htmltext('</div>')
381
        return r.getvalue()
382

  
383
    def get_sidebar(self):
384
        r = TemplateIO(html=True)
385
        r += htmltext('<ul id="sidebar-actions">')
386
        r += htmltext('  <li><a class="new-item" href="new">%s</a></li>') % _('New Event')
387
        r += htmltext('  <li><a class="new-item" href="remote/new">%s</a></li>') % _('New Remote Calendar')
388
        r += htmltext('</ul>')
389
        return r.getvalue()
390

  
391
    def new(self):
392
        event_ui = EventDirectory(Event())
393

  
394
        form = event_ui.form()
395
        if form.get_submit() == 'cancel':
396
            return redirect('.')
397

  
398
        if form.is_submitted() and not form.has_errors():
399
            event_ui.submit(form)
400
            return redirect('%s/' % event_ui.event.id)
401

  
402
        get_response().breadcrumb.append(('new', _('New Event')))
403
        html_top('events', title = _('New Event'))
404
        r = TemplateIO(html=True)
405
        r += htmltext('<h2>%s</h2>') % _('New Event')
406
        r += form.render()
407
        return r.getvalue()
408

  
409
    def _q_lookup(self, component):
410
        try:
411
            event = Event.get(component)
412
        except KeyError:
413
            raise errors.TraversalError()
414
        get_response().breadcrumb.append((str(event.id), event.title))
415
        return EventDirectory(event)
416

  
417
    def listing(self):
418
        return redirect('.')
auquotidien/modules/root.py
36 36

  
37 37
from .announces import Announce, AnnounceSubscription
38 38
from .myspace import MyspaceDirectory
39
from .agenda import AgendaDirectory
40
from .events import Event, get_default_event_tags
41 39
from .payments import PublicPaymentDirectory
42 40
from .payments_ui import InvoicesDirectory
43 41

  
......
760 758
            'saml', 'register', 'ident', 'afterjobs',
761 759
            ('informations-editeur', 'informations_editeur'),
762 760
            ('announces', 'announces_dir'),
763
            'myspace', 'services', 'agenda', 'categories', 'user',
761
            'myspace', 'services', 'categories', 'user',
764 762
            ('tmp-upload', 'tmp_upload'), 'json', '__version__',
765 763
            'themes', 'pages', 'payment', 'invoices', 'roles',
766 764
            'api', 'code', 'fargo', 'tryauth', 'auth', 'preview',
......
772 770
    login = AlternateLoginDirectory()
773 771
    ident = AlternateIdentDirectory()
774 772
    myspace = MyspaceDirectory()
775
    agenda = AgendaDirectory()
776 773
    saml = Saml2Directory()
777 774
    payment = PublicPaymentDirectory()
778 775
    invoices = InvoicesDirectory()
......
1095 1092
            r += htmltext('</ul>')
1096 1093
            r += htmltext('</div>')
1097 1094

  
1098
        if get_cfg('aq-permissions', {}).get('events') and Event.keys():
1099
            # if there are events, add a link to the agenda
1100
            tags = get_cfg('misc', {}).get('event_tags')
1101
            if not tags:
1102
                tags = get_default_event_tags()
1103
            r += htmltext('<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>') % (root_url, _('Agenda'))
1104

  
1105
            if path and path[0] == 'agenda':
1106
                r += htmltext('<p class="tags">')
1107
                for tag in tags:
1108
                    r += htmltext('<a href="%sagenda/tag/%s">%s</a> ') % (root_url, tag, tag)
1109
                r += htmltext('</p>')
1110
                r += self.agenda.display_remote_calendars()
1111

  
1112
                r += htmltext('<p>')
1113
                r += htmltext('  <a href="%sagenda/filter">%s</a>') % (root_url, _('Advanced Filter'))
1114
                r += htmltext('</p>')
1115

  
1116 1095
        v = r.getvalue()
1117 1096
        if v:
1118 1097
            r = TemplateIO(html=True)
auquotidien/modules/template.py
48 48
        section_title = '<h2 id="announces">%s</h2>\n' % _('Announces to citizens')
49 49
        if page_title == _('Announces to citizens'):
50 50
            page_title = ''
51
    elif section == 'agenda':
52
        response.filter['bigdiv'] = 'rub_agenda'
53
        section_title = '<h2 id="agenda">%s</h2>\n' % _('Agenda')
54
        if page_title == _('Agenda'):
55
            page_title = ''
56 51
    elif section and len(section) > 1:
57 52
        # XXX: this works but is not efficient
58 53
        if Category.has_urlname(section):
59
-