Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100755 1290 lines (1100 sloc) 51.642 kB
8414350 @billsaysthis multiline for details and notes fields
billsaysthis authored
1 import cgi
293066a @djpetti Switch to webapp2.
djpetti authored
2 from google.appengine.ext import db
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
3 from google.appengine.ext.webapp import util, template
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
4 from google.appengine.api import urlfetch, memcache, users, mail
5
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
6 import json
7 import unicodedata
841fefb added meaningful descriptions to ical events.
Stig Hackvan authored
8 from icalendar import Calendar, Event as CalendarEvent
b4ea292 Handler to bug pending events
Brian Klug authored
9 import logging, urllib, os
7bdb866 Let all members staff events.
Brian Klug authored
10 from pprint import pprint
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
11 import cPickle as pickle
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
12 from datetime import datetime, timedelta
fcf7602 @progrium working approval process and ical view of approved events
progrium authored
13
2ad290e @billsaysthis add logging model and obscure list view
billsaysthis authored
14 from models import Event, Feedback, HDLog, ROOM_OPTIONS, PENDING_LIFETIME
c97598b @dustball Room Conflict Detection
dustball authored
15 from utils import username, human_username, set_cookie, local_today, is_phone_valid, UserRights, dojo
81a81e1 @progrium adding errors for form validation, full notifications, and minor edit…
progrium authored
16 from notices import *
7742592 @progrium start of maintaining form state on errors
progrium authored
17
9f06c39 @casey Added RSS for upcoming events.
casey authored
18 import PyRSS2Gen
7787223 restored an inscrutible but necessary pytz tzinfo quirk to the start/…
Stig Hackvan authored
19 import pytz
4b99577 @djpetti Add some unit tests for main.py.
djpetti authored
20
293066a @djpetti Switch to webapp2.
djpetti authored
21 import webapp2
22
4b99577 @djpetti Add some unit tests for main.py.
djpetti authored
23 from config import Config
24 import re
fdcb013 @dustball HVAC auto-pilot
dustball authored
25 import keymaster
35f68f4 clean up extraneous whitespace
daniel watson authored
26
293066a @djpetti Switch to webapp2.
djpetti authored
27 template.register_template_library("templatefilters.templatefilters")
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
28
29 def slugify(str):
30 str = unicodedata.normalize('NFKD', str.lower()).encode('ascii','ignore')
31 return re.sub(r'\W+','-',str)
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
32
9f06c39 @casey Added RSS for upcoming events.
casey authored
33 def event_path(event):
34 return '/event/%s-%s' % (event.key().id(), slugify(event.name))
35
d570ec4 @djpetti Enforce a rule about specifying a second member.
djpetti authored
36
37 """ Checks if a user needs to enter contact info for another member.
38 handler: The handler to read request parameters from.
39 start_time: Start time of the event. (datetime)
40 end_time: End time of the event. (datetime)
41 Returns: Either the member email that they entered, or an empty string if they
42 don't need to enter one. """
43 def _get_other_member(handler, start_time, end_time):
44 if end_time - start_time < timedelta(hours=24):
45 # No need to do this.
46 return ""
47
48 # If the event lasts for 24 hours or more, they must specify a
49 # second person to be in charge of it.
50 member = handler.request.get('other_member')
51 if not member:
52 raise ValueError('Need to specify second responsible member' \
53 ' for multi-day event.')
54
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
55 conf = Config()
56 if not conf.is_testing:
4b99577 @djpetti Add some unit tests for main.py.
djpetti authored
57 # Make sure this person is a member.
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
58 base_url = conf.SIGNUP_URL + '/api/v1/user'
4b99577 @djpetti Add some unit tests for main.py.
djpetti authored
59 query = urllib.urlencode({'email': member, 'properties[]': ''})
60 result = urlfetch.fetch("%s?%s" % (base_url, query),
61 follow_redirects=False)
62 logging.debug("Got response: %s" % (result.content))
63
64 if result.status_code != 200:
65 # The API call failed.
66 if result.status_code == 422:
67 raise ValueError('\'%s\' is not the email of a member.' % \
68 (member))
69 raise ValueError('Backend API call failed. Please try again' \
70 ' later.')
d570ec4 @djpetti Enforce a rule about specifying a second member.
djpetti authored
71
72 return member
73
74
a2d5558 @djpetti Enforce limit of 10 future events per user.
djpetti authored
75 """ Checks that this particular user is clear to create an event. Mainly, this
76 means that they don't have too many future events already scheduled.
77 user: The user we are checking for. (GAE User() object.)
d9131d0 @djpetti Add support for recurring events.
djpetti authored
78 event_times: A list of tuples, with each tuple containing the start and end
79 times for a particular event. These are the events that we are checking if we
80 can create.
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
81 ignore_admin: Forces it to always perform the check as if the user were a
82 regular user. Defaults to False.
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
83 editing: The event that we are editing, if we are editing one. Defaults to
84 None. """
85 def _check_user_can_create(user, event_times, ignore_admin=False,
86 editing=None):
d9131d0 @djpetti Add support for recurring events.
djpetti authored
87 logging.debug("User wants to add %d events." % (len(event_times)))
88
a315351 @djpetti Allow admin to override some rules.
djpetti authored
89 # If they are an admin, they can do whatever they want.
90 user_status = UserRights(user)
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
91 if (not ignore_admin and user_status.is_admin):
a315351 @djpetti Allow admin to override some rules.
djpetti authored
92 logging.info("User %s is admin, not performing checks." % (user.email()))
93 return
94
a2d5558 @djpetti Enforce limit of 10 future events per user.
djpetti authored
95 events_query = db.GqlQuery("SELECT * FROM Event WHERE member = :1 AND" \
96 " start_time > :2 AND status IN :3", user,
97 datetime.now(), ["approved", "pending", "on_hold"])
fa5c9ac @djpetti Enforce 4-week event limit rule.
djpetti authored
98
d9131d0 @djpetti Add support for recurring events.
djpetti authored
99 num_events = events_query.count()
100 logging.debug("User has %d events." % (num_events))
101 num_events += len(event_times)
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
102 # If we're editing events, subtract one so that we don't count the same event
103 # twice.
104 if editing:
105 num_events -= 1
4b2a121 @djpetti Add test for the issue Bill described.
djpetti authored
106
fa5c9ac @djpetti Enforce 4-week event limit rule.
djpetti authored
107 conf = Config()
d9131d0 @djpetti Add support for recurring events.
djpetti authored
108 if num_events > conf.USER_MAX_FUTURE_EVENTS:
fa5c9ac @djpetti Enforce 4-week event limit rule.
djpetti authored
109 raise ValueError("You may only have %d future events." % \
110 (conf.USER_MAX_FUTURE_EVENTS))
111
112 # We have a limit on how many events we can have within a four-week period
113 # too.
d9131d0 @djpetti Add support for recurring events.
djpetti authored
114 for start_time, end_time in event_times:
115 # Find the subset of events that this event could possibly cause to be in
116 # violation of this rule.
117 earliest_start = start_time - timedelta(days=28)
118 latest_start = start_time + timedelta(days=28)
119 possible_violators = db.GqlQuery("SELECT * FROM Event WHERE member = :1 AND" \
120 " start_time >= :2 AND" \
121 " start_time <= :3 AND status IN :4" \
122 " ORDER BY start_time",
123 user, earliest_start, latest_start,
124 ["approved", "pending", "on_hold"])
125
126 # Find the subset of events we want to add that could be in violation of
127 # this rule.
128 possible_pending_violators = []
129 for event in event_times:
130 if (event[0] >= earliest_start and event[0] <= latest_start):
131 possible_pending_violators.append(event[0])
132
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
133 logging.debug("Have %d possible violators." % (possible_violators.count()))
134 logging.debug("Have %d possible pending violators." % \
135 (len(possible_pending_violators)))
136
d9131d0 @djpetti Add support for recurring events.
djpetti authored
137 if (possible_violators.count() + len(possible_pending_violators)) <= \
138 conf.USER_MAX_FOUR_WEEKS:
139 # There's no way we could be violating this rule.
140 return
141
142 # Group the possible violators into those before and after the event.
143 before_event = []
144 after_event = []
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
145 for event in possible_violators:
146 # If we are editing an event, ignore it, so that it doesn't get
147 # double-counted.
148 if (editing and event.key().id() == editing.key().id()):
149 continue
150
d9131d0 @djpetti Add support for recurring events.
djpetti authored
151 # Split it into groups of events that happen before and after our proposed
152 # event.
153 if event.start_time < start_time:
154 before_event.append(event.start_time)
155 else:
156 after_event.append(event.start_time)
157
158 # Do the same with the pending events we want to add.
159 for pending_start in possible_pending_violators:
160 if pending_start < start_time:
161 before_event.append(pending_start)
162 else:
163 after_event.append(pending_start)
164
165 # If we have extraneous events, it means that the rule was already violated,
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
166 # or that it will be violated just by adding recurring events.
d9131d0 @djpetti Add support for recurring events.
djpetti authored
167 if (len(before_event) > conf.USER_MAX_FOUR_WEEKS or \
168 len(after_event) > conf.USER_MAX_FOUR_WEEKS):
169 raise ValueError("You may only have %d events within a 4-week period." % \
170 (conf.USER_MAX_FOUR_WEEKS))
171
172 # Recombine the lists.
173 possible_violators = before_event
174 # Sandwhich in the time of the event we want to create.
175 possible_violators.append(start_time)
176 possible_violators.extend(after_event)
177
178 # Now look through every possible combination of USER_MAX_FOUR_WEEKS + 1
179 # consecutive events and see if it violates our rule. (The + 1 is so that
180 # every group we come up with will contain the event we are trying to add.)
181 event_group = []
182 for event_time in possible_violators:
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
183 if len(event_group) < (conf.USER_MAX_FOUR_WEEKS + 1):
d9131d0 @djpetti Add support for recurring events.
djpetti authored
184 # We don't have enough events yet to do anything.
185 event_group.append(event_time)
186 continue
187
188 # On to the next group...
189 event_group.pop(0)
fa5c9ac @djpetti Enforce 4-week event limit rule.
djpetti authored
190 event_group.append(event_time)
191
d9131d0 @djpetti Add support for recurring events.
djpetti authored
192 # Check that our current event group is valid.
193 if event_group[len(event_group) - 1] - event_group[0] <= \
194 timedelta(days=28):
195 # Now if we were to create that event, we would have one too many events
196 # in a four week period, meaning that this event violates the rule.
197 raise ValueError("You may only have %d events within a 4-week period." % \
198 (conf.USER_MAX_FOUR_WEEKS))
a2d5558 @djpetti Enforce limit of 10 future events per user.
djpetti authored
199
200
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
201 """ Makes sure that a proposed event is valid.
202 handler: The handler handling the users request to create/change an event.
203 editing_event_id: The id of the event we are editing. We use this so that we can
204 ignore it when detecting conflicts.
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
205 ignore_admin: If true, it will ignore the user's possible admin status and
206 perform all checks as if they were a normal user. Defaults to False.
d9131d0 @djpetti Add support for recurring events.
djpetti authored
207 recurring: Whether this is a recurring event or not. Defaults to False.
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
208 Raises a ValueError if it detects a problem.
d9131d0 @djpetti Add support for recurring events.
djpetti authored
209 Returns: A list of tuples, one for each individual event. Each tuple
210 contains the event start time and event end time. """
211 def _validate_event(handler, editing_event_id=0, ignore_admin=False,
212 recurring=False):
213 """ Find the next weekday after a given date.
214 date: The date to look for a weekday after.
215 weekday: The day of the week to look for. (The name of the day, e.g.
216 'monday'.) """
217 def find_next_weekday(date, weekday):
218 # Convert the string weekday to a number.
219 day_conversion = {"monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3,
220 "friday": 4, "saturday": 5, "sunday": 6}
221 weekday = weekday.lower()
222 weekday = day_conversion[weekday]
223
224 # Calculate the next weekday.
225 days_ahead = weekday - date.weekday()
226 if days_ahead <= 0:
227 # Target day already happened this week.
228 days_ahead += 7
229 return date + timedelta(days_ahead)
230
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
231 user = users.get_current_user()
232 start_time = datetime.strptime('%s %s:%s %s' % (
233 handler.request.get('start_date'),
234 handler.request.get('start_time_hour'),
235 handler.request.get('start_time_minute'),
236 handler.request.get('start_time_ampm')), '%m/%d/%Y %I:%M %p')
237 end_time = datetime.strptime('%s %s:%s %s' % (
238 handler.request.get('end_date'),
239 handler.request.get('end_time_hour'),
240 handler.request.get('end_time_minute'),
241 handler.request.get('end_time_ampm')), '%m/%d/%Y %I:%M %p')
242 conflicts = Event.check_conflict(
243 start_time,end_time,
244 handler.request.get('setup'),
245 handler.request.get('teardown'),
246 handler.request.get_all('rooms'),
247 optional_existing_event_id=editing_event_id
248 )
249
d9131d0 @djpetti Add support for recurring events.
djpetti authored
250 # Get the number of times it repeats.
251 if recurring:
252 recurrence_data = handler.request.get("recurring-data")
253 recurrence_data = json.loads(recurrence_data)
254
255 repetitions = recurrence_data["repetitions"]
256 else:
257 repetitions = 1
258
259 event_times = [(start_time, end_time)]
260 event_length = end_time - start_time
261 logging.debug("Length of event: %s" % (event_length))
262
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
263 editing_event = None
264 if editing_event_id:
265 editing_event = Event.get_by_id(editing_event_id)
266
d9131d0 @djpetti Add support for recurring events.
djpetti authored
267 for i in range(0, repetitions):
268 logging.debug("Next event starting at: %s" % (start_time))
269
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
270 _check_one_event_per_day(user, start_time, editing=editing_event,
271 ignore_admin=ignore_admin)
d9131d0 @djpetti Add support for recurring events.
djpetti authored
272
273 if conflicts:
274 if ("Deck" in handler.request.get_all('rooms') or \
275 "Savanna" in handler.request.get_all('rooms')):
276 raise ValueError('Room conflict detected <small>(Note: Deck &amp;' \
277 ' Savanna share the same area, two events cannot take' \
278 ' place at the same time in these rooms.)</small>')
279 else:
280 raise ValueError('Room conflict detected')
281 if not handler.request.get('details'):
282 raise ValueError('You must provide a description of the event')
283 if not handler.request.get('estimated_size').isdigit():
284 raise ValueError('Estimated number of people must be a number')
285 if not int(handler.request.get('estimated_size')) > 0:
286 raise ValueError('Estimated number of people must be greater then zero')
287 if (end_time-start_time).days < 0:
288 raise ValueError('End time must be after start time')
289 if (handler.request.get('contact_phone') and not is_phone_valid(handler.request.get('contact_phone'))):
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
290 raise ValueError('Phone number does not appear to be valid')
d9131d0 @djpetti Add support for recurring events.
djpetti authored
291 if not handler.request.get_all('rooms'):
292 raise ValueError('You must select a room to reserve.')
293
294 # Figure out the start and end time of the next event.
295 if recurring:
296 # After we validate the last event, we're done.
297 if i == (repetitions - 1):
298 break
299
300 frequency = recurrence_data["frequency"]
301 if frequency == "monthly":
302 day_number = recurrence_data["dayNumber"]
303 day_name = recurrence_data["monthDay"]
304
305 # Now we can figure out when the next one is. Start by extracting only
306 # the number.
307 day_number = int(day_number[0])
308 # Get a date for the start of the correct week.
309 # Annoyingly, timedelta doesn't support months so we have to add a month
310 # the hard way.
311 months = start_time.month + 1
312 years = start_time.year
313 if months > 12:
314 months = months % 12
315 years += 1
316
317 next_month = start_time.replace(year=years, month=months,
318 day=(day_number - 1) * 7)
319 # Find the specified weekday.
320 start_time = find_next_weekday(next_month, day_name)
321
322 elif frequency == "weekly":
323 # Just add a week to our current time.
324 start_time += timedelta(days=7)
325
326 elif frequency == "daily":
327 # Add a day.
328 start_time += timedelta(days=1)
329
330 if recurrence_data["weekdaysOnly"]:
331 # Add days until we get past the weekend.
332 while start_time.weekday() >= 5:
333 start_time += timedelta(days=1)
334
335 else:
336 # This is almost certainly a programming error.
337 error = "Got unknown frequency for recurring event."
338 logging.critical(error)
339 raise RuntimeError(error)
340
341 end_time = start_time + event_length
342 event_times.append((start_time, end_time))
343
344
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
345 _check_user_can_create(user, event_times, ignore_admin=ignore_admin,
346 editing=editing_event)
d9131d0 @djpetti Add support for recurring events.
djpetti authored
347
348 return event_times
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
349
350
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
351 """ Makes sure that adding this event won't violate a rule against having more
352 than one event per day during Dojo hours. There are, of course, exceptions to
353 this rule for anyone on the @events team.
354 user: The user that is creating this event.
355 start_time: The proposed start time of the event.
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
356 ignore_admin: Forces it to always perform the check as if the user were a normal
357 user. Defaults to False.
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
358 editing: The event we are editing, if we are editing. """
359 def _check_one_event_per_day(user, start_time, editing=None,
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
360 ignore_admin=False):
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
361 # If we're an admin, we can do anything we want.
362 user_status = UserRights(user)
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
363 if (not ignore_admin and user_status.is_admin):
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
364 logging.info("User %s is admin, not performing check." % (user.email()))
365 return
366
f790c54 @djpetti Add exception to one event rule for weekends.
djpetti authored
367 # If it is on a weekend, we shouldn't check either.
368 if start_time.weekday() > 4:
369 logging.info("Not performing check because event is on the weekend.")
370 return
371
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
372 conf = Config()
373 # The earliest and latest that other events during Dojo hours this day might
374 # start.
abf7bb4 @djpetti Change DOJO_HOURS name to something more accurate.
djpetti authored
375 earliest_start = start_time.replace(hour=conf.EVENT_HOURS[0], minute=0,
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
376 second=0, microsecond=0)
abf7bb4 @djpetti Change DOJO_HOURS name to something more accurate.
djpetti authored
377 latest_start = start_time.replace(hour=conf.EVENT_HOURS[1], minute=0, second=0,
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
378 microsecond=0)
e0f7d69 @djpetti Refine the "one event" check.
djpetti authored
379 logging.debug("earliest start: %s, latest start: %s" % \
380 (earliest_start, latest_start))
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
381
923c1ab @djpetti Fix bug in one event per day limit check.
djpetti authored
382 # Check that we are trying to make this event during coworking hours.
383 if (start_time < earliest_start or start_time > latest_start):
384 logging.debug("Event is not during coworking hours.")
385 return
386
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
387 event_query = db.GqlQuery("SELECT * FROM Event WHERE start_time >= :1 AND" \
e0f7d69 @djpetti Refine the "one event" check.
djpetti authored
388 " start_time < :2 AND status IN :3",
5835a95 @djpetti Make "one event from 9-5" ignore certain events.
djpetti authored
389 earliest_start, latest_start,
390 ["pending", "approved"])
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
391 found_events = event_query.count()
e0f7d69 @djpetti Refine the "one event" check.
djpetti authored
392 logging.debug("Found %d events." % (found_events))
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
393
f8fa3a5 @djpetti Fix a bug where edited events got double-counted.
djpetti authored
394 if editing:
395 if (editing.start_time >= earliest_start and \
396 editing.start_time <= latest_start):
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
397 # In this case, our old event is going to show up in the query and cause
398 # it to register one too many events.
399 logging.debug("Removing old event from event count.")
400 found_events -= 1
401
402 if found_events >= 1:
403 # We can't have another event that starts today.
299795c @djpetti Change new event error message.
djpetti authored
404 raise ValueError("Hacker Dojo does not have enough space for all of our" \
405 " events+meetings+startups. As a result, we have to" \
406 " limit events during coworking hours (Monday through" \
407 " Friday, 9AM-5PM). There is already an event booked" \
408 " for this date. Please try another date. Sorry about" \
409 " any inconvenience.")
b2802fa @djpetti Enforce the "one event per day" rule.
djpetti authored
410
411
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
412 """ Figure out how many days a user must wait before they can create an event.
413 user: The user we are getting information for.
414 Returns: How many more days the user must wait to create an event. """
415 def _get_user_wait_time(user):
416 conf = Config()
417 if not conf.is_prod:
418 # Don't do this check if we're not on the production server.
419 return 0
420
421 if not user:
422 # We'll perform the check when they are logged in.
423 return 0
424
425 # Make an API request to the signup app to get this information about the
426 # user.
427 base_url = conf.SIGNUP_URL + "/api/v1/user"
428 query_str = urllib.urlencode({"email": user.email(), "properties": "created"})
429 response = urlfetch.fetch("%s?%s" % (base_url, query_str),
430 follow_redirects=False)
431 logging.debug("Got response from signup app: %s" % (response.content))
432
433 if response.status_code != 200:
434 logging.error("Failed to fetch user data, status %d." % \
435 (response.status_code))
436 # Disable it to be safe.
437 return conf.NEW_EVENT_WAIT_PERIOD
438
439 result = json.loads(response.content)
440 created = pickle.loads(str(result["created"]))
441 logging.debug("User created at %s." % (created))
442
443 # Check to see how long we have left.
444 since_creation = datetime.now() - created
445 to_wait = max(0, conf.NEW_EVENT_WAIT_PERIOD - since_creation.days)
446 logging.debug("Days to wait: %d" % (to_wait))
447
448 return to_wait
449
450
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
451 """ Performs an action on a single event.
452 event: The event object that we are working with.
453 action: A string specifying the action to perform.
454 user: The user who is performing this action. (User() object.)
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
455 check: If True, the it will check whether the action can be run, but won't
456 actually run it.
457 Returns: True if the action is performed or can be performed, False otherwise.
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
458 """
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
459 def _do_event_action(event, action, user, check=False):
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
460 access_rights = UserRights(user, event)
461
462 desc = ''
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
463 todo = None
464 args = []
465 if action.lower() == 'approve':
466 if not access_rights.can_approve:
467 return False
5de5439 @djpetti Fix event approval notification.
djpetti authored
468 if not check:
469 notify_owner_approved(event)
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
470 todo = event.approve
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
471 desc = 'Approved event'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
472
473 elif action.lower() == 'notapproved':
474 if not access_rights.can_not_approve:
475 return False
476 todo = event.not_approved
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
477 desc = 'Event marked not approved'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
478
479 elif action.lower() == 'rsvp':
480 if not user:
481 return False
482 todo = event.rsvp
483 if not check:
484 notify_owner_rsvp(event,user)
485
486 elif action.lower() == 'staff':
487 if not access_rights.can_staff:
488 return False
489 todo = event.add_staff
490 args.append(user)
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
491 desc = 'added self as staff'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
492
493 elif action.lower() == 'unstaff':
494 if not access_rights.can_unstaff:
495 return False
496 todo = event.remove_staff
497 args.append(user)
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
498 desc = 'Removed self as staff'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
499
500 elif action.lower() == 'onhold':
501 if not access_rights.can_cancel:
502 return False
503 todo = event.on_hold
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
504 desc = 'Put event on hold'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
505
506 elif action.lower() == 'cancel':
507 if not access_rights.can_cancel:
508 return False
509 todo = event.cancel
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
510 desc = 'Cancelled event'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
511
512 elif action.lower() == 'delete':
513 if not access_rights.can_delete:
514 return False
515 todo = event.delete
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
516 desc = 'Deleted event'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
517 if not check:
518 notify_deletion(event,user)
519
520 elif action.lower() == 'undelete':
521 if not access_rights.can_undelete:
522 return False
523 todo = event.undelete
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
524 desc = 'Undeleted event'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
525
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
526 elif action.lower() == 'expire' and access_rights.is_admin:
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
527 todo = event.expire
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
528 desc = 'Expired event'
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
529
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
530 else:
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
531 logging.warning("Action '%s' was not recognized." % (action))
532
533 if check:
534 return True
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
535
536 if desc != '':
537 log = HDLog(event=event,description=desc)
538 log.put()
539
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
540 if todo:
541 todo(*args)
542
543 return True
544
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
545
293066a @djpetti Switch to webapp2.
djpetti authored
546 class DomainCacheCron(webapp2.RequestHandler):
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
547 def get(self):
219203b @dustball Cache better
dustball authored
548 noop = dojo('/groups/events',force=True)
549
550
293066a @djpetti Switch to webapp2.
djpetti authored
551 class ReminderCron(webapp2.RequestHandler):
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
552 def get(self):
f49797a @dustball Reminders 2.0
dustball authored
553 self.response.out.write("REMINDERS")
554 today = local_today()
555 # remind everyone 3 days in advance they need to show up
556 events = Event.all() \
557 .filter('status IN', ['approved']) \
558 .filter('reminded =', False) \
559 .filter('start_time <', today + timedelta(days=3))
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
560 for event in events:
f49797a @dustball Reminders 2.0
dustball authored
561 self.response.out.write(event.name)
562 # only mail them if they created the event 2+ days ago
563 if event.created < today - timedelta(days=2):
564 schedule_reminder_email(event)
565 event.reminded = True
566 event.put()
567
568
293066a @djpetti Switch to webapp2.
djpetti authored
569 class ExpireCron(webapp2.RequestHandler):
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
570 def post(self):
571 # Expire events marked to expire today
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
572 today = local_today()
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
573 events = Event.all() \
574 .filter('status IN', ['pending', 'understaffed']) \
575 .filter('expired >=', today) \
576 .filter('expired <', today + timedelta(days=1))
577 for event in events:
578 event.expire()
579 notify_owner_expired(event)
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
580
f1997e4 @christopherb Made misc style fixes
christopherb authored
581
293066a @djpetti Switch to webapp2.
djpetti authored
582 class ExpireReminderCron(webapp2.RequestHandler):
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
583 def post(self):
584 # Find events expiring in 10 days to warn owner
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
585 ten_days = local_today() + timedelta(days=10)
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
586 events = Event.all() \
587 .filter('status IN', ['pending', 'understaffed']) \
588 .filter('expired >=', ten_days) \
589 .filter('expired <', ten_days + timedelta(days=1))
590 for event in events:
591 notify_owner_expiring(event)
592
293066a @djpetti Switch to webapp2.
djpetti authored
593 class ExportHandler(webapp2.RequestHandler):
fcf7602 @progrium working approval process and ical view of approved events
progrium authored
594 def get(self, format):
9c331d0 @progrium refactoring the export handler a bit
progrium authored
595 content_type, body = getattr(self, 'export_%s' % format)()
596 self.response.headers['content-type'] = content_type
597 self.response.out.write(body)
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
598
9c331d0 @progrium refactoring the export handler a bit
progrium authored
599 def export_json(self):
20a578c @dustball Reasonable limits
dustball authored
600 events = Event.get_recent_past_and_future()
9c331d0 @progrium refactoring the export handler a bit
progrium authored
601 for k in self.request.GET:
602 if self.request.GET[k] and k in ['member']:
603 value = users.User(urllib.unquote(self.request.GET[k]))
604 else:
605 value = urllib.unquote(self.request.GET[k])
606 events = events.filter('%s =' % k, value)
607 events = map(lambda x: x.to_dict(summarize=True), events)
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
608 return 'application/json', json.dumps(events)
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
609
9c331d0 @progrium refactoring the export handler a bit
progrium authored
610 def export_ics(self):
20a578c @dustball Reasonable limits
dustball authored
611 events = Event.get_recent_past_and_future()
841fefb added meaningful descriptions to ical events.
Stig Hackvan authored
612 url_base = 'http://' + self.request.headers.get('host', 'events.hackerdojo.com')
9c331d0 @progrium refactoring the export handler a bit
progrium authored
613 cal = Calendar()
614 for event in events:
615 iev = CalendarEvent()
616 iev.add('summary', event.name if event.status == 'approved' else event.name + ' (%s)' % event.status.upper())
617 # make verbose description with empty fields where information is missing
618 ev_desc = '__Status: %s\n__Member: %s\n__Type: %s\n__Estimated size: %s\n__Info URL: %s\n__Fee: %s\n__Contact: %s, %s\n__Rooms: %s\n\n__Details: %s\n\n__Notes: %s' % (
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
619 event.status,
620 event.owner(),
621 event.type,
622 event.estimated_size,
623 event.url,
624 event.fee,
625 event.contact_name,
626 event.contact_phone,
627 event.roomlist(),
628 event.details,
9c331d0 @progrium refactoring the export handler a bit
progrium authored
629 event.notes)
630 # then delete the empty fields with a regex
631 ev_desc = re.sub(re.compile(r'^__.*?:[ ,]*$\n*',re.M),'',ev_desc)
632 ev_desc = re.sub(re.compile(r'^__',re.M),'',ev_desc)
633 ev_url = url_base + event_path(event)
634 iev.add('description', ev_desc + '\n--\n' + ev_url)
635 iev.add('url', ev_url)
636 if event.start_time:
637 iev.add('dtstart', event.start_time.replace(tzinfo=pytz.timezone('US/Pacific')))
638 if event.end_time:
639 iev.add('dtend', event.end_time.replace(tzinfo=pytz.timezone('US/Pacific')))
640 cal.add_component(iev)
641 return 'text/calendar', cal.as_string()
85d17af @dustball "Large Events" page and iCal feed
dustball authored
642
643 def export_large_ics(self):
20a578c @dustball Reasonable limits
dustball authored
644 events = Event.get_recent_past_and_future()
85d17af @dustball "Large Events" page and iCal feed
dustball authored
645 url_base = 'http://' + self.request.headers.get('host', 'events.hackerdojo.com')
646 cal = Calendar()
647 for event in events:
648 iev = CalendarEvent()
649 iev.add('summary', event.name + ' (%s)' % event.estimated_size)
650 # make verbose description with empty fields where information is missing
651 ev_desc = '__Status: %s\n__Member: %s\n__Type: %s\n__Estimated size: %s\n__Info URL: %s\n__Fee: %s\n__Contact: %s, %s\n__Rooms: %s\n\n__Details: %s\n\n__Notes: %s' % (
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
652 event.status,
653 event.owner(),
654 event.type,
655 event.estimated_size,
656 event.url,
657 event.fee,
658 event.contact_name,
659 event.contact_phone,
660 event.roomlist(),
661 event.details,
85d17af @dustball "Large Events" page and iCal feed
dustball authored
662 event.notes)
663 # then delete the empty fields with a regex
664 ev_desc = re.sub(re.compile(r'^__.*?:[ ,]*$\n*',re.M),'',ev_desc)
665 ev_desc = re.sub(re.compile(r'^__',re.M),'',ev_desc)
666 ev_url = url_base + event_path(event)
667 iev.add('description', ev_desc + '\n--\n' + ev_url)
668 iev.add('url', ev_url)
669 if event.start_time:
670 iev.add('dtstart', event.start_time.replace(tzinfo=pytz.timezone('US/Pacific')))
671 if event.end_time:
672 iev.add('dtend', event.end_time.replace(tzinfo=pytz.timezone('US/Pacific')))
673 cal.add_component(iev)
674 return 'text/calendar', cal.as_string()
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
675
9c331d0 @progrium refactoring the export handler a bit
progrium authored
676 def export_rss(self):
677 url_base = 'http://' + self.request.headers.get('host', 'events.hackerdojo.com')
0fef364 @Yuffster Removing events from RSS if they're cancelled (#51).
Yuffster authored
678 events = Event.get_recent_past_and_future_approved()
9c331d0 @progrium refactoring the export handler a bit
progrium authored
679 rss = PyRSS2Gen.RSS2(
680 title = "Hacker Dojo Events Feed",
681 link = url_base,
682 description = "Upcoming events at the Hacker Dojo in Mountain View, CA",
683 lastBuildDate = datetime.now(),
684 items = [PyRSS2Gen.RSSItem(
15786c3 @mikeharris100 Include event date on RSS feed (Fixes #39)
mikeharris100 authored
685 title = "%s @ %s: %s" % (
686 event.start_time.strftime("%A, %B %d"),
35f68f4 clean up extraneous whitespace
daniel watson authored
687 event.start_time.strftime("%I:%M%p").lstrip("0"),
15786c3 @mikeharris100 Include event date on RSS feed (Fixes #39)
mikeharris100 authored
688 event.name),
9c331d0 @progrium refactoring the export handler a bit
progrium authored
689 link = url_base + event_path(event),
690 description = event.details,
691 guid = url_base + event_path(event),
692 pubDate = event.updated,
693 ) for event in events]
694 )
695 return 'application/xml', rss.to_xml()
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
696
f1997e4 @christopherb Made misc style fixes
christopherb authored
697
293066a @djpetti Switch to webapp2.
djpetti authored
698 class EditHandler(webapp2.RequestHandler):
ca8c7f2 @dustball Let edit events and refactor error handling
dustball authored
699 def get(self, id):
700 event = Event.get_by_id(int(id))
701 user = users.get_current_user()
736390e @dustball Bug fixes & UI
dustball authored
702 show_all_nav = user
ca8c7f2 @dustball Let edit events and refactor error handling
dustball authored
703 access_rights = UserRights(user, event)
704 if access_rights.can_edit:
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
705 logout_url = users.create_logout_url('/')
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
706 rooms = ROOM_OPTIONS
707 hours = [1,2,3,4,5,6,7,8,9,10,11,12]
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
708
709 wait_days = _get_user_wait_time(user)
710
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
711 self.response.out.write(template.render('templates/edit.html', locals()))
ca8c7f2 @dustball Let edit events and refactor error handling
dustball authored
712 else:
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
713 self.response.out.write("Access denied")
ca8c7f2 @dustball Let edit events and refactor error handling
dustball authored
714
715 def post(self, id):
716 event = Event.get_by_id(int(id))
717 user = users.get_current_user()
718 access_rights = UserRights(user, event)
719 if access_rights.can_edit:
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
720 try:
d9131d0 @djpetti Add support for recurring events.
djpetti authored
721 event_times = _validate_event(self, editing_event_id=int(id))
722 start_time, end_time = event_times[0]
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
723
724 other_member = _get_other_member(self, start_time, end_time)
725 except ValueError, e:
726 error = str(e)
4b2a121 @djpetti Add test for the issue Bill described.
djpetti authored
727 logging.warning(error)
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
728 self.response.set_status(400)
729 self.response.out.write(template.render('templates/error.html', locals()))
730 return
731
732 log_desc = ""
733 previous_object = Event.get_by_id(int(id))
734 event.status = 'pending'
735 event.name = self.request.get('name')
736 if (previous_object.name != event.name):
737 log_desc += "<strong>Title:</strong> " + previous_object.name + " to " + event.name + "<br />"
738 event.start_time = start_time
739 if (previous_object.start_time != event.start_time):
740 log_desc += "<strong>Start time:</strong> " + str(previous_object.start_time) + " to " + str(event.start_time) + "<br />"
741 event.end_time = end_time
742 if (previous_object.end_time != event.end_time):
743 log_desc += "<strong>End time:</strong> " + str(previous_object.end_time) + " to " + str(event.end_time) + "<br />"
744 event.estimated_size = cgi.escape(self.request.get('estimated_size'))
745 if (previous_object.estimated_size != event.estimated_size):
746 log_desc += "<strong>Est. size:</strong> " + previous_object.estimated_size + " to " + event.estimated_size + "<br />"
747 event.contact_name = cgi.escape(self.request.get('contact_name'))
748 if (previous_object.contact_name != event.contact_name):
749 log_desc += "<strong>Contact:</strong> " + previous_object.contact_name + " to " + event.contact_name + "<br />"
750 event.contact_phone = cgi.escape(self.request.get('contact_phone'))
751 if (previous_object.contact_phone != event.contact_phone):
752 log_desc += "<strong>Contact phone:</strong> " + previous_object.contact_phone + " to " + event.contact_phone + "<br />"
753 event.details = cgi.escape(self.request.get('details'))
754 if (previous_object.details != event.details):
755 log_desc += "<strong>Details:</strong> " + previous_object.details + " to " + event.details + "<br />"
756 event.url = cgi.escape(self.request.get('url'))
757 if (previous_object.url != event.url):
758 log_desc += "<strong>Url:</strong> " + previous_object.url + " to " + event.url + "<br />"
759 event.fee = cgi.escape(self.request.get('fee'))
760 if (previous_object.fee != event.fee):
761 log_desc += "<strong>Fee:</strong> " + previous_object.fee + " to " + event.fee + "<br />"
762 event.notes = cgi.escape(self.request.get('notes'))
763 if (previous_object.notes != event.notes):
764 log_desc += "<strong>Notes:</strong> " + previous_object.notes + " to " + event.notes + "<br />"
a8b3d0b @djpetti Add "Admin Notes" box.
djpetti authored
765 event.admin_notes = cgi.escape(self.request.get("admin_notes"))
766 if (previous_object.admin_notes != event.admin_notes):
767 log_desc += "<strong>Admin Notes:</strong> " + \
768 previous_object.admin_notes + " to " + event.admin_notes + "<br />"
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
769 event.rooms = self.request.get_all('rooms')
770 if (previous_object.rooms != event.rooms):
771 log_desc += "<strong>Rooms changed</strong><br />"
772 log_desc += "<strong>Old room:</strong> " + previous_object.roomlist() + "<br />"
773 log_desc += "<strong>New room:</strong> " + event.roomlist() + "<br />"
774 setup = cgi.escape(self.request.get('setup')) or 0
775 event.setup = int(setup)
776 if (previous_object.setup != event.setup):
777 log_desc += "<strong>Setup time changed</strong><br />"
778 log_desc += "<strong>Old time:</strong> %s minutes<br/>" % previous_object.setup
779 log_desc += "<strong>New time:</strong> %s minutes<br/>" % event.setup
780 teardown = cgi.escape(self.request.get('teardown')) or 0
781 event.teardown = int(teardown)
782 if (previous_object.teardown != event.teardown):
783 log_desc += "<strong>Teardown time changed</strong><br />"
784 log_desc += "<strong>Old time:</strong> %s minutes<br/>" % previous_object.teardown
785 log_desc += "<strong>New time:</strong> %s minutes<br/>" % event.teardown
786 event.other_member = other_member
787 if (previous_object.other_member != event.other_member):
788 log_desc += "<strong>Other member changed</strong><br />"
789 log_desc += "<strong>Old:</strong> %s<br />" % \
790 (previous_object.other_member)
791 log_desc += "<strong>New:</strong> %s<br />" % \
792 (event.other_member)
793 log = HDLog(event=event,description="Event edited<br />"+log_desc)
794 log.put()
795 show_all_nav = user
796 access_rights = UserRights(user, event)
797 if access_rights.can_edit:
798 logout_url = users.create_logout_url('/')
799 rooms = ROOM_OPTIONS
800 hours = [1,2,3,4,5,6,7,8,9,10,11,12]
801 if log_desc:
802 edited = "<u>Saved changes:</u><br>"+log_desc
803 notify_event_change(event=event,modification=1)
804 event.put()
805 self.response.out.write(template.render('templates/edit.html', locals()))
806 else:
4b99577 @djpetti Add some unit tests for main.py.
djpetti authored
807 self.response.set_status(401)
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
808 self.response.out.write("Access denied")
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
809 else:
810 self.response.set_status(401)
811 self.response.out.write("Access denied")
ca8c7f2 @dustball Let edit events and refactor error handling
dustball authored
812
813
293066a @djpetti Switch to webapp2.
djpetti authored
814 class EventHandler(webapp2.RequestHandler):
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
815 def get(self, id):
816 event = Event.get_by_id(int(id))
2e08a66 @progrium cleaning things up and adding json representations for jon
progrium authored
817 if self.request.path.endswith('json'):
818 self.response.headers['content-type'] = 'application/json'
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
819 self.response.out.write(json.dumps(event.to_dict()))
fcf7602 @progrium working approval process and ical view of approved events
progrium authored
820 else:
2e08a66 @progrium cleaning things up and adding json representations for jon
progrium authored
821 user = users.get_current_user()
822 if user:
766e87c @christopherb Added UserRights class.
christopherb authored
823 access_rights = UserRights(user, event)
2e08a66 @progrium cleaning things up and adding json representations for jon
progrium authored
824 logout_url = users.create_logout_url('/')
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
825
2e08a66 @progrium cleaning things up and adding json representations for jon
progrium authored
826 else:
827 login_url = users.create_login_url('/')
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
828 event.details = db.Text(event.details.replace('\n','<br/>'))
736390e @dustball Bug fixes & UI
dustball authored
829 show_all_nav = user
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
830 event.notes = db.Text(event.notes.replace('\n','<br/>'))
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
831
832 wait_days = _get_user_wait_time(user)
833
2e08a66 @progrium cleaning things up and adding json representations for jon
progrium authored
834 self.response.out.write(template.render('templates/event.html', locals()))
e2b9558 @btubbs Fixes issue 4. Changed to jqueryUI datepicker. Replaced jquery
btubbs authored
835
fcf7602 @progrium working approval process and ical view of approved events
progrium authored
836 def post(self, id):
837 event = Event.get_by_id(int(id))
838 user = users.get_current_user()
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
839 action = self.request.get('state')
840
841 _do_event_action(event, action, user)
766e87c @christopherb Added UserRights class.
christopherb authored
842
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
843 event.details = db.Text(event.details.replace('\n','<br/>'))
844 show_all_nav = user
845 event.notes = db.Text(event.notes.replace('\n','<br/>'))
846 self.response.out.write(template.render('templates/event.html', locals()))
f1997e4 @christopherb Made misc style fixes
christopherb authored
847
293066a @djpetti Switch to webapp2.
djpetti authored
848 class ApprovedHandler(webapp2.RequestHandler):
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
849 def get(self):
850 user = users.get_current_user()
851 if user:
852 logout_url = users.create_logout_url('/')
853 else:
854 login_url = users.create_login_url('/')
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
855 today = local_today()
736390e @dustball Bug fixes & UI
dustball authored
856 show_all_nav = user
411c89f @mikeharris100 (For #12) Show every day of multiple day events
mikeharris100 authored
857 events = Event.get_approved_list_with_multiday()
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
858 tomorrow = today + timedelta(days=1)
d63161a Add widget mode
Brian Klug authored
859 whichbase = 'base.html'
860 if self.request.get('base'):
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
861 whichbase = self.request.get('base') + '.html'
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
862
863 wait_days = _get_user_wait_time(user)
864
636a975 @djpetti Add bulk action bar to other views.
djpetti authored
865 user_rights = UserRights(user)
866 is_admin = user_rights.is_admin
567d748 @djpetti Disable bulk actions for approved view.
djpetti authored
867 hide_checkboxes = True
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
868 self.response.out.write(template.render('templates/approved.html', locals()))
869
f1997e4 @christopherb Made misc style fixes
christopherb authored
870
293066a @djpetti Switch to webapp2.
djpetti authored
871 class MyEventsHandler(webapp2.RequestHandler):
a82b43e @billsaysthis add myevents, pastevents code, refined list display on event page
billsaysthis authored
872 @util.login_required
873 def get(self):
874 user = users.get_current_user()
875 if user:
876 logout_url = users.create_logout_url('/')
877 else:
878 login_url = users.create_login_url('/')
879 events = Event.all().filter('member = ', user).order('start_time')
736390e @dustball Bug fixes & UI
dustball authored
880 show_all_nav = user
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
881 today = local_today()
a82b43e @billsaysthis add myevents, pastevents code, refined list display on event page
billsaysthis authored
882 tomorrow = today + timedelta(days=1)
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
883
884 wait_days = _get_user_wait_time(user)
885
567d748 @djpetti Disable bulk actions for approved view.
djpetti authored
886 hide_checkboxes = True
a82b43e @billsaysthis add myevents, pastevents code, refined list display on event page
billsaysthis authored
887 self.response.out.write(template.render('templates/myevents.html', locals()))
888
f1997e4 @christopherb Made misc style fixes
christopherb authored
889
293066a @djpetti Switch to webapp2.
djpetti authored
890 class PastHandler(webapp2.RequestHandler):
a82b43e @billsaysthis add myevents, pastevents code, refined list display on event page
billsaysthis authored
891 def get(self):
892 user = users.get_current_user()
893 if user:
894 logout_url = users.create_logout_url('/')
895 else:
896 login_url = users.create_login_url('/')
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
897 today = local_today()
736390e @dustball Bug fixes & UI
dustball authored
898 show_all_nav = user
cfab83b @djpetti Make past event list a little more reasonable.
djpetti authored
899 events = db.GqlQuery("SELECT * FROM Event WHERE start_time < :1 ORDER" \
900 " BY start_time DESC LIMIT 100", today)
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
901
902 wait_days = _get_user_wait_time(user)
903
a82b43e @billsaysthis add myevents, pastevents code, refined list display on event page
billsaysthis authored
904 self.response.out.write(template.render('templates/past.html', locals()))
905
f1997e4 @christopherb Made misc style fixes
christopherb authored
906
293066a @djpetti Switch to webapp2.
djpetti authored
907 class NotApprovedHandler(webapp2.RequestHandler):
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
908 def get(self):
909 user = users.get_current_user()
910 if user:
911 logout_url = users.create_logout_url('/')
912 else:
913 login_url = users.create_login_url('/')
914 today = local_today()
71228b2 @djpetti Add back in event grouping by date.
djpetti authored
915 tomorrow = today + timedelta(days=1)
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
916 show_all_nav = user
917 events = Event.get_recent_not_approved_list()
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
918
919 wait_days = _get_user_wait_time(user)
920
636a975 @djpetti Add bulk action bar to other views.
djpetti authored
921 user_rights = UserRights(user)
922 is_admin = user_rights.is_admin
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
923 self.response.out.write(template.render('templates/not_approved.html', locals()))
924
925
293066a @djpetti Switch to webapp2.
djpetti authored
926 class CronBugOwnersHandler(webapp2.RequestHandler):
b4ea292 Handler to bug pending events
Brian Klug authored
927 def get(self):
928 events = Event.get_pending_list()
929 for e in events:
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
930 bug_owner_pending(e)
931
932
293066a @djpetti Switch to webapp2.
djpetti authored
933 class AllFutureHandler(webapp2.RequestHandler):
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
934 def get(self):
935 user = users.get_current_user()
936 if user:
937 logout_url = users.create_logout_url('/')
938 else:
939 login_url = users.create_login_url('/')
736390e @dustball Bug fixes & UI
dustball authored
940 show_all_nav = user
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
941 events = Event.get_all_future_list()
942 today = local_today()
943 tomorrow = today + timedelta(days=1)
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
944
945 wait_days = _get_user_wait_time(user)
946
636a975 @djpetti Add bulk action bar to other views.
djpetti authored
947 user_rights = UserRights(user)
948 is_admin = user_rights.is_admin
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
949 self.response.out.write(template.render('templates/all_future.html', locals()))
b4ea292 Handler to bug pending events
Brian Klug authored
950
293066a @djpetti Switch to webapp2.
djpetti authored
951 class LargeHandler(webapp2.RequestHandler):
85d17af @dustball "Large Events" page and iCal feed
dustball authored
952 def get(self):
953 user = users.get_current_user()
954 if user:
955 logout_url = users.create_logout_url('/')
956 else:
957 login_url = users.create_login_url('/')
958 show_all_nav = user
959 events = Event.get_large_list()
960 today = local_today()
961 tomorrow = today + timedelta(days=1)
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
962
963 wait_days = _get_user_wait_time(user)
964
85d17af @dustball "Large Events" page and iCal feed
dustball authored
965 self.response.out.write(template.render('templates/large.html', locals()))
966
f1997e4 @christopherb Made misc style fixes
christopherb authored
967
293066a @djpetti Switch to webapp2.
djpetti authored
968 class PendingHandler(webapp2.RequestHandler):
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
969 def get(self):
970 user = users.get_current_user()
971 if user:
972 logout_url = users.create_logout_url('/')
973 else:
974 login_url = users.create_login_url('/')
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
975 events = Event.get_pending_list()
736390e @dustball Bug fixes & UI
dustball authored
976 show_all_nav = user
b879063 @mdhancher Respect the local timezone (Pacific), and tidy up related imports.
mdhancher authored
977 today = local_today()
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
978 tomorrow = today + timedelta(days=1)
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
979
980 wait_days = _get_user_wait_time(user)
981
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
982 user_rights = UserRights(user)
983 is_admin = user_rights.is_admin
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
984 self.response.out.write(template.render('templates/pending.html', locals()))
985
f1997e4 @christopherb Made misc style fixes
christopherb authored
986
293066a @djpetti Switch to webapp2.
djpetti authored
987 class NewHandler(webapp2.RequestHandler):
fcf7602 @progrium working approval process and ical view of approved events
progrium authored
988 @util.login_required
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
989 def get(self):
990 user = users.get_current_user()
4d85afa @dustball Remove staffing requirement and make members more responsible
dustball authored
991 human = human_username(user)
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
992 if user:
993 logout_url = users.create_logout_url('/')
994 else:
995 login_url = users.create_login_url('/')
996 rooms = ROOM_OPTIONS
cb7a9d1 pull in the rules live and memcache them for 1 day on new and confirm…
jonathan authored
997 rules = memcache.get("rules")
998 if(rules is None):
f434ff5 @dustball Send e-mails to a better place on dev
dustball authored
999 try:
1000 rules = urlfetch.fetch("http://wiki.hackerdojo.com/api_v2/op/GetPage/page/Event+Policies/_type/html", "GET").content
1001 memcache.add("rules", rules, 86400)
1002 except Exception, e:
1003 rules = "Error fetching rules. Please report this error to internal-dev@hackerdojo.com."
e2b9558 @btubbs Fixes issue 4. Changed to jqueryUI datepicker. Replaced jquery
btubbs authored
1004
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
1005 to_wait = _get_user_wait_time(user)
1006 if to_wait:
1007 # They can't create an event yet.
1008 error = "You must wait %d days before creating an event." % \
1009 (to_wait)
1010 logging.warning(error)
1011 self.response.set_status(401)
1012 self.response.out.write(template.render('templates/error.html', locals()))
1013 return
1014
a8b3d0b @djpetti Add "Admin Notes" box.
djpetti authored
1015 is_admin = UserRights(user).is_admin
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
1016 self.response.out.write(template.render('templates/new.html', locals()))
f1997e4 @christopherb Made misc style fixes
christopherb authored
1017
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
1018 def post(self):
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
1019 # Whether we want to submit the event as a regular member.
1020 ignore_admin = self.request.get("regular_user", None)
1021 if ignore_admin:
1022 logging.info("Validating as regular member.")
1023
d9131d0 @djpetti Add support for recurring events.
djpetti authored
1024 recurring = self.request.get("recurring", None)
1025 if recurring:
1026 logging.debug("Submitting recurring event.")
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
1027
d9131d0 @djpetti Add support for recurring events.
djpetti authored
1028 try:
1029 event_times = _validate_event(self, ignore_admin=ignore_admin,
1030 recurring=recurring)
1031
1032 # Since this check is just based on the duration, it doesn't really
1033 # matter which start and end times we use.
1034 first_start = event_times[0][0]
1035 first_end = event_times[0][1]
1036 other_member = _get_other_member(self, first_start, first_end)
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
1037 except ValueError, e:
1038 error = str(e)
4b2a121 @djpetti Add test for the issue Bill described.
djpetti authored
1039 logging.warning(error)
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
1040 self.response.set_status(400)
1041 self.response.out.write(template.render('templates/error.html', locals()))
1042 return
1043
9357808 @djpetti Add box to /new page to simulate normal user.
djpetti authored
1044 # If we are ignoring our admin status, we are testing, so don't save it.
1045 if not ignore_admin:
d9131d0 @djpetti Add support for recurring events.
djpetti authored
1046 first_event = None
1047 for start_time, end_time in event_times:
1048 event = Event(
1049 name=cgi.escape(self.request.get('name')),
1050 start_time=start_time,
1051 end_time=end_time,
1052 type=cgi.escape(self.request.get('type')),
1053 estimated_size=cgi.escape(self.request.get('estimated_size')),
1054 contact_name=cgi.escape(self.request.get('contact_name')),
1055 contact_phone=cgi.escape(self.request.get('contact_phone')),
1056 details=cgi.escape(self.request.get('details')),
1057 url=cgi.escape(self.request.get('url')),
1058 fee=cgi.escape(self.request.get('fee')),
1059 notes=cgi.escape(self.request.get('notes')),
1060 rooms=self.request.get_all('rooms'),
1061 expired=local_today() + timedelta(days=PENDING_LIFETIME), # Set expected expiration date
1062 setup=int(self.request.get('setup') or 0),
1063 teardown=int(self.request.get('teardown') or 0),
1064 other_member=other_member,
1065 admin_notes=self.request.get('admin_notes')
1066 )
1067
1068 if not first_event:
1069 first_event = event
1070
1071 event.put()
1072 log = HDLog(event=event,description="Created new event")
1073 log.put()
1074
1075 # For obvious reasons, we only notify people about the first event in a
1076 # recurring series.
1077 notify_owner_confirmation(first_event)
1078 notify_event_change(first_event)
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
1079 set_cookie(self.response.headers, 'formvalues', None)
d570ec4 @djpetti Enforce a rule about specifying a second member.
djpetti authored
1080
ba2f0bb @djpetti Split out validation code into separate function.
djpetti authored
1081 rules = memcache.get("rules")
1082 if(rules is None):
1083 try:
1084 rules = urlfetch.fetch("http://wiki.hackerdojo.com/api_v2/op/GetPage/page/Event+Policies/_type/html", "GET").content
1085 memcache.add("rules", rules, 86400)
1086 except Exception, e:
1087 rules = "Error fetching rules. Please report this error to internal-dev@hackerdojo.com."
1088 self.response.out.write(template.render('templates/confirmation.html', locals()))
d570ec4 @djpetti Enforce a rule about specifying a second member.
djpetti authored
1089
87868cf issue 11 add a confirmation page
jonathan authored
1090
293066a @djpetti Switch to webapp2.
djpetti authored
1091 class ConfirmationHandler(webapp2.RequestHandler):
87868cf issue 11 add a confirmation page
jonathan authored
1092 def get(self, id):
1093 event = Event.get_by_id(int(id))
cb7a9d1 pull in the rules live and memcache them for 1 day on new and confirm…
jonathan authored
1094 rules = memcache.get("rules")
1095 if(rules is None):
f434ff5 @dustball Send e-mails to a better place on dev
dustball authored
1096 try:
1097 rules = urlfetch.fetch("http://wiki.hackerdojo.com/api_v2/op/GetPage/page/Event+Policies/_type/html", "GET").content
1098 memcache.add("rules", rules, 86400)
1099 except Exception, e:
1100 rules = "Error fetching rules. Please report this error to internal-dev@hackerdojo.com."
b47158d Issue 36 Need to use deffered mail sends everywhere
Abraham Erki authored
1101 user = users.get_current_user()
1102 logout_url = users.create_logout_url('/')
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
1103
1104 wait_days = _get_user_wait_time(user)
1105
87868cf issue 11 add a confirmation page
jonathan authored
1106 self.response.out.write(template.render('templates/confirmation.html', locals()))
1107
293066a @djpetti Switch to webapp2.
djpetti authored
1108 class LogsHandler(webapp2.RequestHandler):
2ad290e @billsaysthis add logging model and obscure list view
billsaysthis authored
1109 @util.login_required
1110 def get(self):
1111 user = users.get_current_user()
1112 logs = HDLog.get_logs_list()
1113 if user:
1114 logout_url = users.create_logout_url('/')
1115 else:
1116 login_url = users.create_login_url('/')
736390e @dustball Bug fixes & UI
dustball authored
1117 show_all_nav = user
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
1118
1119 wait_days = _get_user_wait_time(user)
1120
2ad290e @billsaysthis add logging model and obscure list view
billsaysthis authored
1121 self.response.out.write(template.render('templates/logs.html', locals()))
1122
293066a @djpetti Switch to webapp2.
djpetti authored
1123 class FeedbackHandler(webapp2.RequestHandler):
d2c3dc7 @billsaysthis moved app-specific js to separate file
billsaysthis authored
1124 @util.login_required
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
1125 def get(self, id):
d2c3dc7 @billsaysthis moved app-specific js to separate file
billsaysthis authored
1126 user = users.get_current_user()
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
1127 event = Event.get_by_id(int(id))
d2c3dc7 @billsaysthis moved app-specific js to separate file
billsaysthis authored
1128 if user:
1129 logout_url = users.create_logout_url('/')
1130 else:
1131 login_url = users.create_login_url('/')
3249897 @djpetti Enforce wait between signup and event creation.
djpetti authored
1132
1133 wait_days = _get_user_wait_time(user)
1134
d2c3dc7 @billsaysthis moved app-specific js to separate file
billsaysthis authored
1135 self.response.out.write(template.render('templates/feedback.html', locals()))
1136
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
1137 def post(self, id):
d2c3dc7 @billsaysthis moved app-specific js to separate file
billsaysthis authored
1138 user = users.get_current_user()
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
1139 event = Event.get_by_id(int(id))
8c52522 @billsaysthis small tweak to how feedbacks displayed in event page
billsaysthis authored
1140 try:
1141 if self.request.get('rating'):
1142 feedback = Feedback(
1143 event = event,
1144 rating = int(self.request.get('rating')),
ca3c6dd Escaped additional fields that could be used for XSS
Christopher Biettchert authored
1145 comment = cgi.escape(self.request.get('comment')))
8c52522 @billsaysthis small tweak to how feedbacks displayed in event page
billsaysthis authored
1146 feedback.put()
0eb5a92 @billsaysthis basic logging for all user-initiated post methods in place
billsaysthis authored
1147 log = HDLog(event=event,description="Posted feedback")
1148 log.put()
8c52522 @billsaysthis small tweak to how feedbacks displayed in event page
billsaysthis authored
1149 self.redirect('/event/%s-%s' % (event.key().id(), slugify(event.name)))
1150 else:
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
1151 raise ValueError('Please select a rating')
8c52522 @billsaysthis small tweak to how feedbacks displayed in event page
billsaysthis authored
1152 except Exception:
1153 set_cookie(self.response.headers, 'formvalues', dict(self.request.POST))
7ebb2cd @christopherb Cleaned up the style of the files some. Mostly standardizing on singl…
christopherb authored
1154 self.redirect('/feedback/new/' + id)
d2c3dc7 @billsaysthis moved app-specific js to separate file
billsaysthis authored
1155
293066a @djpetti Switch to webapp2.
djpetti authored
1156 class TempHandler(webapp2.RequestHandler):
fdcb013 @dustball HVAC auto-pilot
dustball authored
1157 def get(self):
35f68f4 clean up extraneous whitespace
daniel watson authored
1158 units = {"AC1":"EDD9A758", "AC2":"B65D8121", "AC3":"0BA20EDC", "AC5":"47718E38"}
8fd9cd7 @dustball Bug fixes & code readability
dustball authored
1159 modes = ["Off","Heat","Cool"]
1160 master = units["AC3"]
fdcb013 @dustball HVAC auto-pilot
dustball authored
1161 key = keymaster.get('thermkey')
1162 url = "https://api.bayweb.com/v2/?id="+master+"&key="+key+"&action=data"
1163 result = urlfetch.fetch(url)
1164 if result.status_code == 200:
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
1165 thdata = json.loads(result.content)
8fd9cd7 @dustball Bug fixes & code readability
dustball authored
1166 inside_air_temp = thdata['iat']
1167 mode = thdata['mode']
1168 if inside_air_temp <= 66 and modes[mode] == "Cool":
1169 for thermostat in units:
1170 url = "https://api.bayweb.com/v2/?id="+units[thermostat]+"&key="+key+"&action=set&heat_sp=69&mode="+str(modes.index("Heat"))
1171 result = urlfetch.fetch(url)
1172 notify_hvac_change(inside_air_temp,"Heat")
1173 if inside_air_temp >= 75 and modes[mode] == "Heat":
1174 for thermostat in units:
1175 url = "https://api.bayweb.com/v2/?id="+units[thermostat]+"&key="+key+"&action=set&cool_sp=71&mode="+str(modes.index("Cool"))
1176 result = urlfetch.fetch(url)
1177 notify_hvac_change(inside_air_temp,"Cold")
1178 self.response.out.write("200 OK")
1179 else:
1180 notify_hvac_change(result.status_code,"ERROR connecting to BayWeb API")
1181 self.response.out.write("500 Internal Server Error")
35f68f4 clean up extraneous whitespace
daniel watson authored
1182
fdcb013 @dustball HVAC auto-pilot
dustball authored
1183
70dfdf2 @djpetti Add cron job for expiring events.
djpetti authored
1184 """ Expires events that were put on hold when users were suspended. """
293066a @djpetti Switch to webapp2.
djpetti authored
1185 class ExpireSuspendedCronHandler(webapp2.RequestHandler):
70dfdf2 @djpetti Add cron job for expiring events.
djpetti authored
1186 def get(self):
1187 events_query = db.GqlQuery("SELECT * FROM Event WHERE" \
1188 " owner_suspended_time != NULL and status = :1",
1189 "onhold")
1190
1191 for event in events_query.run():
1192 # Check if it's been enough time to expire them.
1193 expire_period = timedelta(days=Config().SUSPENDED_EVENT_EXPIRY)
1194 if datetime.now() - event.owner_suspended_time >= expire_period:
1195 logging.info("Expiring event from suspended user: %s" % (event.name))
1196 event.expire()
1197
1198
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1199 """ Stuff that the bulk action handlers have in common. """
293066a @djpetti Switch to webapp2.
djpetti authored
1200 class BulkActionCommon(webapp2.RequestHandler):
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1201 """ Reads the event ids given and produces a list of Event objects.
1202 Returns: A list of Event objects corresponding to the event ids specified. """
1203 def _get_events(self):
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
1204 event_ids = self.request.get("events")
1205 event_ids = json.loads(event_ids)
1206
1207 # Get the actual events.
1208 events = []
1209 for event in event_ids:
1210 events.append(Event.get_by_id(int(event)))
1211
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1212 return events
1213
1214
1215 """ Performs bulk actions on a set of events. """
1216 class BulkActionHandler(BulkActionCommon):
1217 def post(self):
1218 action = self.request.get("action")
1219
1220 events = self._get_events()
1221
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
1222 user = users.get_current_user()
1223
1224 # Perform the action on all the events.
1225 logging.debug("Performing bulk action: %s" % (action))
1226 for event in events:
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1227 if not _do_event_action(event, action, user):
1228 logging.warning("Performing action '%s' failed." % (action))
1229 self.response.set_status(400)
1230 return
1231
1232
1233 """ Checks which bulk actions can be performed on a set of events. """
1234 class BulkActionCheckHandler(BulkActionCommon):
1235 """ Gets a list of bulk actions that can be performed on a set of events.
cb8290c @djpetti Switch to POST request for bulk_action_check.
djpetti authored
1236 Even though this is "safe", it is a POST request because the list of events
1237 can be extremely long.
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1238 Request parameters:
1239 events: The list of event ids to check.
1240 Response: JSON-formatted dictionary containing two lists: A "valid" list of
1241 valid actions, and an "invalid" list of invalid actions. """
cb8290c @djpetti Switch to POST request for bulk_action_check.
djpetti authored
1242 def post(self):
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1243 events = self._get_events()
1244
1245 user = users.get_current_user()
1246
1247 # See what actions can be performed on all the events.
1248 possible_actions = ["approve", "notapproved", "onhold", "delete"]
1249 bad_actions = []
1250 for event in events:
1251 to_remove = []
1252 for action in possible_actions:
1253 if not _do_event_action(event, action, user, check=True):
1254 # This action cannot be performed.
1255 bad_actions.append(action)
1256 to_remove.append(action)
1257
1258 for action in to_remove:
1259 possible_actions.remove(action)
1260
1261 response = {"valid": possible_actions, "invalid": bad_actions}
1262 self.response.out.write(json.dumps(response))
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
1263
1264
293066a @djpetti Switch to webapp2.
djpetti authored
1265 app = webapp2.WSGIApplication([
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
1266 ('/', ApprovedHandler),
4c7da5d @billsaysthis added func to ensure word 'in' only shown when rooms are assigned
billsaysthis authored
1267 ('/all_future', AllFutureHandler),
85d17af @dustball "Large Events" page and iCal feed
dustball authored
1268 ('/large', LargeHandler),
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
1269 ('/pending', PendingHandler),
841fefb added meaningful descriptions to ical events.
Stig Hackvan authored
1270 ('/past', PastHandler),
fdcb013 @dustball HVAC auto-pilot
dustball authored
1271 ('/temperature', TempHandler),
c4d250e @dustball Python 2.7 upgrade & HRD compatibility
dustball authored
1272 #('/cronbugowners', CronBugOwnersHandler),
a82b43e @billsaysthis add myevents, pastevents code, refined list display on event page
billsaysthis authored
1273 ('/myevents', MyEventsHandler),
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
1274 ('/not_approved', NotApprovedHandler),
9b632dd @progrium initial commit -- basic form, rough listing
progrium authored
1275 ('/new', NewHandler),
87868cf issue 11 add a confirmation page
jonathan authored
1276 ('/confirm/(\d+).*', ConfirmationHandler),
ca8c7f2 @dustball Let edit events and refactor error handling
dustball authored
1277 ('/edit/(\d+).*', EditHandler),
841fefb added meaningful descriptions to ical events.
Stig Hackvan authored
1278 # single event views
47a7f7a @progrium basic form validation, added end time, start of notifications, some f…
progrium authored
1279 ('/event/(\d+).*', EventHandler),
2e08a66 @progrium cleaning things up and adding json representations for jon
progrium authored
1280 ('/event/(\d+)\.json', EventHandler),
841fefb added meaningful descriptions to ical events.
Stig Hackvan authored
1281 # various export methods -- events.{json,rss,ics}
1282 ('/events\.(.+)', ExportHandler),
5459516 @billsaysthis [#40] add not approved status and list view
billsaysthis authored
1283 ('/domaincache', DomainCacheCron),
2ad290e @billsaysthis add logging model and obscure list view
billsaysthis authored
1284 ('/logs', LogsHandler),
70dfdf2 @djpetti Add cron job for expiring events.
djpetti authored
1285 ('/feedback/new/(\d+).*', FeedbackHandler),
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
1286 ('/expire_suspended', ExpireSuspendedCronHandler),
1287 ('/bulk_action', BulkActionHandler),
d96d11c @djpetti Don't allow self-approval with bulk actions.
djpetti authored
1288 ('/bulk_action_check', BulkActionCheckHandler),
31d2800 @djpetti Add preliminary code for performing bulk actions.
djpetti authored
1289 ],debug=True)
Something went wrong with that request. Please try again.