1# -*- coding: utf-8 -*- 2from __future__ import absolute_import 3from __future__ import unicode_literals 4 5# python lib: 6from datetime import date, timedelta 7 8# django: 9from django.db.models.functions import ExtractHour 10from django.views.generic import ListView, DetailView 11from django.conf import settings 12from django.shortcuts import get_object_or_404 13from django.utils.dates import MONTHS_ALT 14try: 15 from django.core.urlresolvers import reverse 16except ImportError: 17 from django.urls import reverse 18 19# thirdparties: 20import six 21 22# happenings: 23from .models import Event 24from .utils.displays import month_display, day_display 25from .utils.next_event import get_next_event 26from .utils.mixins import JSONResponseMixin 27from .utils import common as c 28 29 30URLS_NAMESPACE = getattr(settings, "CALENDAR_URLS_NAMESPACE", 'calendar') 31 32 33class GenericEventView(JSONResponseMixin, ListView): 34 model = Event 35 36 def render_to_response(self, context, **kwargs): 37 self.postprocess_context(context) 38 if self.request.is_ajax(): 39 return self.render_to_json_response(context, **kwargs) 40 return super(GenericEventView, self).render_to_response( 41 context, **kwargs 42 ) 43 44 def get_context_data(self, **kwargs): 45 context = super(GenericEventView, self).get_context_data(**kwargs) 46 47 self.net, self.category, self.tag = c.get_net_category_tag( 48 self.request 49 ) 50 51 if self.category is not None: 52 context['cal_category'] = self.category 53 if self.tag is not None: 54 context['cal_tag'] = self.tag 55 return context 56 57 def postprocess_context(self, context, *args, **kwargs): 58 return 59 60 61class EventMonthView(GenericEventView): 62 template_name = 'happenings/event_month_list.html' 63 64 def get_year_and_month(self, net, qs, **kwargs): 65 """ 66 Get the year and month. First tries from kwargs, then from 67 querystrings. If none, or if cal_ignore qs is specified, 68 sets year and month to this year and this month. 69 """ 70 now = c.get_now() 71 year = now.year 72 month = now.month + net 73 month_orig = None 74 75 if 'cal_ignore=true' not in qs: 76 if 'year' and 'month' in self.kwargs: # try kwargs 77 year, month_orig = map( 78 int, (self.kwargs['year'], self.kwargs['month']) 79 ) 80 month = month_orig + net 81 else: 82 try: # try querystring 83 year = int(self.request.GET['cal_year']) 84 month_orig = int(self.request.GET['cal_month']) 85 month = month_orig + net 86 except Exception: 87 pass 88 # return the year and month, and any errors that may have occurred do 89 # to an invalid month/year being given. 90 return c.clean_year_month(year, month, month_orig) 91 92 def get_month_events(self, *args, **kwargs): 93 return Event.objects.all_month_events(*args, **kwargs) 94 95 def get_context_data(self, **kwargs): 96 context = super(EventMonthView, self).get_context_data(**kwargs) 97 98 qs = self.request.META['QUERY_STRING'] 99 100 year, month, error = self.get_year_and_month(self.net, qs) 101 102 # add a dict containing the year, month, and month name to the context 103 current = dict( 104 year=year, month_num=month, month=MONTHS_ALT[month][:3] 105 ) 106 context['current'] = current 107 108 display_month = MONTHS_ALT[month] 109 110 if isinstance(display_month, six.binary_type): 111 display_month = display_month.decode('utf-8') 112 113 context['month_and_year'] = u"%(month)s, %(year)d" % ( 114 {'month': display_month, 'year': year} 115 ) 116 117 if error: # send any year/month errors 118 context['cal_error'] = error 119 120 all_month_events = list( 121 self.get_month_events( 122 year, month, self.category, self.tag, loc=True, cncl=True 123 ).annotate( 124 start_hour=ExtractHour('start_date') 125 ).order_by('start_hour') 126 ) 127 128 context['raw_all_month_events'] = all_month_events 129 130 context['show_events'] = False 131 if getattr(settings, "CALENDAR_SHOW_LIST", False): 132 context['show_events'] = True 133 context['events'] = c.order_events(all_month_events, d=True) \ 134 if self.request.is_ajax() else c.order_events(all_month_events) 135 136 return context 137 138 def postprocess_context(self, context, *args, **kwargs): 139 qs = self.request.META['QUERY_STRING'] 140 141 mini = True if 'cal_mini=true' in qs else False 142 143 start_day = getattr(settings, "CALENDAR_START_DAY", 0) 144 145 # get any querystrings that are not next/prev/year/month 146 if qs: 147 qs = c.get_qs(qs) 148 149 if getattr(settings, "CALENDAR_PASS_VIEW_CONTEXT_TO_DISPLAY_METHOD", False): 150 month_display_base_context = dict(context) 151 month_display_base_context.pop('events', None) 152 else: 153 month_display_base_context = None 154 155 all_month_events = context['raw_all_month_events'] 156 157 context['calendar'] = month_display( 158 context['current']['year'], 159 context['current']['month_num'], 160 all_month_events, 161 start_day, 162 self.net, 163 qs, 164 mini, 165 request=self.request, 166 base_context=month_display_base_context, 167 ) 168 169 170class EventDayView(GenericEventView): 171 template_name = 'happenings/event_day_list.html' 172 173 def get_calendar_back_url(self, year, month_num): 174 self.request.current_app = self.request.resolver_match.namespace 175 if URLS_NAMESPACE: 176 view_name = URLS_NAMESPACE + ':list' 177 else: 178 view_name = 'list' 179 return reverse(view_name, args=(year, month_num), current_app=self.request.current_app) 180 181 def check_for_cancelled_events(self, d): 182 """Check if any events are cancelled on the given date 'd'.""" 183 for event in self.events: 184 for cn in event.cancellations.all(): 185 if cn.date == d: 186 event.title += ' (CANCELLED)' 187 188 def get_month_events(self, *args, **kwargs): 189 return Event.objects.all_month_events(*args, **kwargs) 190 191 def get_context_data(self, **kwargs): 192 context = super(EventDayView, self).get_context_data(**kwargs) 193 194 kw = self.kwargs 195 y, m, d = map(int, (kw['year'], kw['month'], kw['day'])) 196 year, month, day, error = c.clean_year_month_day(y, m, d, self.net) 197 198 if error: 199 context['cal_error'] = error 200 201 # Note that we don't prefetch 'cancellations' because they will be 202 # prefetched later (in day_display in displays.py) 203 all_month_events = self.get_month_events( 204 year, month, self.category, self.tag 205 ) 206 207 self.events = day_display( 208 year, month, all_month_events, day 209 ) 210 211 self.check_for_cancelled_events(d=date(year, month, day)) 212 context['events'] = self.events 213 214 display_month = MONTHS_ALT[month] 215 if isinstance(display_month, six.binary_type): 216 display_month = display_month.decode('utf-8') 217 218 context['month'] = display_month 219 context['month_num'] = month 220 context['year'] = year 221 context['day'] = day 222 context['month_day_year'] = u"%(month)s %(day)d, %(year)d" % ( 223 {'month': display_month, 'day': day, 'year': year} 224 ) 225 context['calendar_back_url'] = self.get_calendar_back_url(year, month) 226 227 # for use in the template to build next & prev querystrings 228 context['next'], context['prev'] = c.get_next_and_prev(self.net) 229 return context 230 231 232class EventDetailView(DetailView): 233 model = Event 234 context_object_name = 'event' 235 236 def get_object(self): 237 return get_object_or_404( 238 Event.objects.prefetch_related( 239 'location', 'categories', 'tags', 'cancellations' 240 ), 241 pk=self.kwargs['pk'] 242 ) 243 244 def get_cncl_days(self): 245 now = c.get_now() 246 cncl = self.object.cancellations.all() 247 return [(x.date, x.reason) for x in cncl if x.date >= now.date()] 248 249 def check_cncl(self, d): 250 cncl = self.object.cancellations.all() 251 return True if [x for x in cncl if x.date == d] else False 252 253 def get_context_data(self, **kwargs): 254 now = c.get_now() 255 context = super(EventDetailView, self).get_context_data(**kwargs) 256 e = self.object 257 258 for choice in Event.REPEAT_CHOICES: 259 if choice[0] == e.repeat: 260 context['repeat'] = choice[1] 261 262 context['cncl_days'] = self.get_cncl_days() 263 264 event = [e] # event needs to be an iterable, see get_next_event() 265 if not e.repeats('NEVER'): # event is ongoing; get next occurrence 266 if e.will_occur(now): 267 year, month, day = get_next_event(event, now) 268 next_event = date(year, month, day) 269 context['next_event'] = date(year, month, day) 270 context['next_or_prev_cncl'] = self.check_cncl(next_event) 271 else: # event is finished repeating; get last occurrence 272 end = e.end_repeat 273 last_event = end 274 if e.repeats('WEEKDAY'): 275 year, month, day = c.check_weekday( 276 end.year, end.month, end.day, reverse=True 277 ) 278 last_event = date(year, month, day) 279 context['last_event'] = last_event 280 context['next_or_prev_cncl'] = self.check_cncl(last_event) 281 else: 282 if e.is_chunk(): 283 # list of days for single-day event chunk 284 context['event_days'] = ( # list comp 285 (e.l_start_date + timedelta(days=x)) 286 for x in range(e.start_end_diff + 1) 287 ) 288 else: 289 # let template know if this single-day, non-repeating event is 290 # cancelled 291 context['this_cncl'] = self.check_cncl(e.l_start_date.date()) 292 return context 293