• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tjcsl / ion / 9047122193

11 May 2024 11:26PM UTC coverage: 79.447% (-0.3%) from 79.718%
9047122193

push

github

alanzhu0
ci(scripts): make ci.yml easier to read

3079 of 5838 branches covered (52.74%)

15790 of 19875 relevant lines covered (79.45%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.46
/intranet/apps/dashboard/views.py
1
import logging
1✔
2
from datetime import datetime, time, timedelta
1✔
3
from itertools import chain
1✔
4

5
from django.conf import settings
1✔
6
from django.contrib.auth.decorators import login_required
1✔
7
from django.core.paginator import Paginator
1✔
8
from django.shortcuts import redirect, render
1✔
9
from django.urls import reverse
1✔
10
from django.utils import timezone
1✔
11
from django.utils.timezone import make_aware
1✔
12

13
from ...utils.date import get_senior_graduation_date, get_senior_graduation_year
1✔
14
from ...utils.helpers import get_ap_week_warning, get_fcps_emerg, get_warning_html
1✔
15
from ..announcements.models import Announcement, AnnouncementRequest, WarningAnnouncement
1✔
16
from ..eighth.models import EighthBlock, EighthScheduledActivity, EighthSignup
1✔
17
from ..enrichment.models import EnrichmentActivity
1✔
18
from ..events.models import Event, TJStarUUIDMap
1✔
19
from ..schedule.models import Day
1✔
20
from ..schedule.views import decode_date, schedule_context
1✔
21
from ..seniors.models import Senior
1✔
22

23
logger = logging.getLogger(__name__)
1✔
24

25

26
def gen_schedule(user, num_blocks=6, surrounding_blocks=None):
1✔
27
    """Generate a list of information about a block and a student's current activity signup.
28

29
    Returns:
30
        schedule
31
        no_signup_today
32

33
    """
34
    no_signup_today = None
1✔
35
    schedule = []
1✔
36

37
    if surrounding_blocks is None:
1✔
38
        #######
39
        if settings.ENABLE_HYBRID_EIGHTH:
1!
40
            now = timezone.localtime()
×
41
            if now.hour < 17:
×
42
                now = now.replace(hour=0, minute=0, second=0, microsecond=0)
×
43
            surrounding_blocks = (
×
44
                EighthBlock.objects.exclude(
45
                    eighthscheduledactivity__in=EighthScheduledActivity.objects.filter(activity__name="z - Hybrid Sticky", members__in=[user])
46
                )
47
                .order_by("date", "block_letter")
48
                .filter(date__gte=now)
49
            )[:num_blocks]
50
        else:
51
            #######
52
            surrounding_blocks = EighthBlock.objects.get_upcoming_blocks(num_blocks)
1✔
53

54
    if not surrounding_blocks:
1✔
55
        return None, False
1✔
56

57
    # Use select_related to reduce query count
58
    signups = EighthSignup.objects.filter(user=user, scheduled_activity__block__in=surrounding_blocks).select_related(
1✔
59
        "scheduled_activity", "scheduled_activity__block", "scheduled_activity__activity"
60
    )
61
    block_signup_map = {s.scheduled_activity.block.id: s.scheduled_activity for s in signups}
1✔
62

63
    for b in surrounding_blocks:
1✔
64
        current_sched_act = block_signup_map.get(b.id, None)
1✔
65
        if current_sched_act:
1✔
66
            current_signup = current_sched_act.title_with_flags
1✔
67
            current_signup_cancelled = current_sched_act.cancelled
1✔
68
            current_signup_sticky = current_sched_act.activity.sticky
1✔
69
            rooms = current_sched_act.get_true_rooms()
1✔
70
        else:
71
            current_signup = None
1✔
72
            current_signup_cancelled = False
1✔
73
            current_signup_sticky = False
1✔
74
            rooms = None
1✔
75

76
        # warning flag (red block text and signup link) if no signup today
77
        # cancelled flag (red activity text) if cancelled
78
        flags = "locked" if b.locked else "open"
1✔
79
        blk_today = b.is_today()
1✔
80
        if blk_today and not current_signup:
1✔
81
            flags += " warning"
1✔
82
        if current_signup_cancelled:
1✔
83
            flags += " cancelled warning"
1✔
84

85
        if current_signup_cancelled:
1✔
86
            # don't duplicate this info; already caught
87
            current_signup = current_signup.replace(" (Cancelled)", "")
1✔
88

89
        info = {
1✔
90
            "id": b.id,
91
            "block": b,
92
            "block_letter": b.block_letter,
93
            "current_signup": current_signup,
94
            "current_signup_cancelled": current_signup_cancelled,
95
            "current_signup_sticky": current_signup_sticky,
96
            "locked": b.locked,
97
            "date": b.date,
98
            "flags": flags,
99
            "is_today": blk_today,
100
            "signup_time": b.signup_time,
101
            "signup_time_future": b.signup_time_future(),
102
            "rooms": rooms,
103
        }
104
        schedule.append(info)
1✔
105

106
        if blk_today and not current_signup:
1✔
107
            no_signup_today = True
1✔
108

109
    return schedule, no_signup_today
1✔
110

111

112
def gen_sponsor_schedule(user, sponsor=None, num_blocks=6, surrounding_blocks=None, given_date=None):
1✔
113
    r"""Return a list of :class:`EighthScheduledActivity`\s in which the
114
    given user is sponsoring.
115

116
    Returns:
117
        Dictionary with:
118
            activities
119
            no_attendance_today
120
            num_acts
121
    """
122

123
    no_attendance_today = None
1✔
124
    acts = []
1✔
125

126
    if sponsor is None:
1✔
127
        sponsor = user.get_eighth_sponsor()
1✔
128

129
    if surrounding_blocks is None:
1✔
130
        surrounding_blocks = EighthBlock.objects.get_upcoming_blocks(num_blocks)
1✔
131

132
    activities_sponsoring = EighthScheduledActivity.objects.for_sponsor(sponsor).select_related("block").filter(block__in=surrounding_blocks)
1✔
133
    sponsoring_block_map = {}
1✔
134
    for sa in activities_sponsoring:
1✔
135
        bid = sa.block.id
1✔
136
        if bid in sponsoring_block_map:
1!
137
            sponsoring_block_map[bid] += [sa]
×
138
        else:
139
            sponsoring_block_map[bid] = [sa]
1✔
140

141
    num_acts = 0
1✔
142

143
    for b in surrounding_blocks:
1✔
144
        num_added = 0
1✔
145
        sponsored_for_block = sponsoring_block_map.get(b.id, [])
1✔
146

147
        for schact in sponsored_for_block:
1✔
148
            acts.append(schact)
1✔
149
            if schact.block.is_today():
1!
150
                if not schact.attendance_taken and schact.block.locked:
1✔
151
                    no_attendance_today = True
1✔
152

153
            num_added += 1
1✔
154

155
        if num_added == 0:
1!
156
            # fake an entry for a block where there is no sponsorship
157
            acts.append({"block": b, "id": None, "fake": True})
×
158
        else:
159
            num_acts += 1
1✔
160

161
    cur_date = surrounding_blocks[0].date if acts else given_date if given_date else timezone.localdate()
1✔
162

163
    last_block = surrounding_blocks[len(surrounding_blocks) - 1] if surrounding_blocks else None
1✔
164
    last_block_date = last_block.date + timedelta(days=1) if last_block else cur_date
1✔
165
    next_blocks = list(last_block.next_blocks(1)) if last_block else None
1✔
166
    next_date = next_blocks[0].date if next_blocks else last_block_date  # pylint: disable=unsubscriptable-object
1✔
167

168
    first_block = surrounding_blocks[0] if surrounding_blocks else None
1✔
169
    if cur_date and not first_block:
1✔
170
        first_block = EighthBlock.objects.filter(date__lte=cur_date).last()
1✔
171
    first_block_date = first_block.date + timedelta(days=-7) if first_block else cur_date
1✔
172
    prev_blocks = list(first_block.previous_blocks(num_blocks - 1)) if first_block else None
1✔
173
    prev_date = prev_blocks[0].date if prev_blocks else first_block_date  # pylint: disable=unsubscriptable-object
1✔
174
    return {
1✔
175
        "sponsor_schedule": acts,
176
        "no_attendance_today": no_attendance_today,
177
        "num_attendance_acts": num_acts,
178
        "sponsor_schedule_cur_date": cur_date,
179
        "sponsor_schedule_next_date": next_date,
180
        "sponsor_schedule_prev_date": prev_date,
181
    }
182

183

184
def get_prerender_url(request):
1✔
185
    if request.user.is_eighth_admin:
1✔
186
        if request.user.is_student:
1!
187
            view = "eighth_signup"
1✔
188
        else:
189
            view = "eighth_admin_dashboard"
×
190
    else:
191
        view = "eighth_redirect"
1✔
192

193
    return request.build_absolute_uri(reverse(view))
1✔
194

195

196
def get_announcements_list(request, context):
1✔
197
    """
198
    An announcement will be shown if:
199
    * It is not expired
200

201
      * unless ?show_expired=1
202

203
    * It is visible to the user
204

205
      * There are no groups on the announcement (so it is public)
206
      * The user's groups are in union with the groups on the
207
        announcement (at least one matches)
208
      * The user submitted the announcement directly
209
      * The user submitted the announcement through a request
210
      * The user approved the announcement through a request
211
      * ...unless ?show_all=1
212

213
    An event will be shown if:
214
    * It is not expired
215

216
      * unless ?show_expired=1
217

218
    * It is approved
219

220
      * unless an events admin
221

222
    * It is visible to the user
223

224
      * There are no groups
225
      * The groups are in union
226

227
    """
228
    user = context["user"]
1✔
229

230
    if context["announcements_admin"] and context["show_all"]:
1!
231
        # Show all announcements if user has admin permissions and the
232
        # show_all GET argument is given.
233
        announcements = Announcement.objects.all()
×
234
    else:
235
        # Only show announcements for groups that the user is enrolled in.
236
        if context["show_expired"]:
1✔
237
            announcements = Announcement.objects.visible_to_user(user)
1✔
238
        else:
239
            announcements = Announcement.objects.visible_to_user(user).filter(expiration_date__gt=timezone.now())
1✔
240

241
    # Load information on the user who posted the announcement
242
    # Unless the announcement has a custom author (some do, but not all), we will need the user information to construct the byline,
243
    announcements = announcements.select_related("user")
1✔
244

245
    # We may query the announcement request multiple times while checking if the user submitted or approved the announcement.
246
    # prefetch_related() will still make a separate query for each request, but the results are cached if we check them multiple times
247
    announcements = announcements.prefetch_related("announcementrequest_set")
1✔
248

249
    if context["events_admin"] and context["show_all"]:
1!
250
        events = Event.objects.all()
×
251
    else:
252
        if context["show_expired"]:
1✔
253
            events = Event.objects.visible_to_user(user)
1✔
254
        else:
255
            # Unlike announcements, show events for the rest of the day after they occur.
256
            midnight = timezone.localtime().replace(hour=0, minute=0, second=0, microsecond=0)
1✔
257
            events = Event.objects.visible_to_user(user).filter(time__gte=midnight, show_on_dashboard=True)
1✔
258

259
    def announcements_sorting_key(item):
1✔
260
        if context["show_expired"] or context["show_all"]:
1!
261
            return item.added
×
262
        # otherwise sort by pinned and then added date
263
        return (item.pinned, item.added)
1✔
264

265
    items = sorted(chain(announcements, events), key=announcements_sorting_key, reverse=True)
1✔
266

267
    return items
1✔
268

269

270
def paginate_announcements_list(request, context, items):
1✔
271
    """
272
    Paginate ``items`` in groups of 15
273

274
    """
275
    DEFAULT_PAGE_NUM = 1
1✔
276

277
    if request.GET.get("page", "INVALID").isdigit():
1!
278
        page_num = int(request.GET["page"])
×
279
    else:
280
        page_num = DEFAULT_PAGE_NUM
1✔
281

282
    paginator = Paginator(items, 15)
1✔
283
    if page_num not in paginator.page_range:
1!
284
        page_num = DEFAULT_PAGE_NUM
×
285

286
    items = paginator.page(page_num)
1✔
287

288
    more_items = items.has_next()
1✔
289
    prev_page = items.previous_page_number() if items.has_previous() else 0
1✔
290
    next_page = items.next_page_number() if more_items else 0
1✔
291

292
    context.update(
1✔
293
        {"items": items, "page_num": page_num, "prev_page": prev_page, "next_page": next_page, "more_items": more_items, "page_obj": paginator}
294
    )
295

296
    return context, items
1✔
297

298

299
def get_tjstar_mapping(user):
1✔
300
    m = TJStarUUIDMap.objects.filter(user=user)
×
301
    if m:
×
302
        return {"tjstar_uuid": m.first().uuid}
×
303

304
    return {}
×
305

306

307
def add_widgets_context(request, context):
1✔
308
    """
309
    WIDGETS:
310
    * Eighth signup (STUDENT)
311
    * Eighth attendance (TEACHER or ADMIN)
312
    * Enrichment activities (ALL if enrichment activity today)
313
    * Bell schedule (ALL)
314
    * Administration (ADMIN)
315
    * Links (ALL)
316
    * Seniors (STUDENT; graduation countdown if senior, link to destinations otherwise) if settings.ENABLE_SENIOR_DESTINATIONS
317
    """
318

319
    user = context["user"]
1✔
320
    if context["is_student"] or context["eighth_sponsor"]:
1✔
321
        num_blocks = 6
1✔
322
        surrounding_blocks = EighthBlock.objects.get_upcoming_blocks(num_blocks)
1✔
323

324
    if context["is_student"]:
1✔
325
        #######
326
        if settings.ENABLE_HYBRID_EIGHTH:
1!
327
            if surrounding_blocks is not None:
×
328
                now = timezone.localtime()
×
329
                if now.hour < 17:
×
330
                    now = now.replace(hour=0, minute=0, second=0, microsecond=0)
×
331
                surrounding_blocks = (
×
332
                    EighthBlock.objects.exclude(
333
                        eighthscheduledactivity__in=EighthScheduledActivity.objects.filter(
334
                            activity__name="z - Hybrid Sticky", members__in=[request.user]
335
                        )
336
                    )
337
                    .order_by("date", "block_letter")
338
                    .filter(date__gte=now)
339
                )[:num_blocks]
340
        #######
341
        schedule, no_signup_today = gen_schedule(user, num_blocks, surrounding_blocks)
1✔
342
        context.update(
1✔
343
            {
344
                "schedule": schedule,
345
                "last_displayed_block": schedule[-1] if schedule else None,
346
                "no_signup_today": no_signup_today,
347
                "senior_graduation": get_senior_graduation_date().strftime("%B %d %Y %H:%M:%S"),
348
            }
349
        )
350

351
    if context["eighth_sponsor"]:
1!
352
        sponsor_date = request.GET.get("sponsor_date", None)
×
353
        if sponsor_date:
×
354
            sponsor_date = decode_date(sponsor_date)
×
355
            if sponsor_date:
×
356
                block = EighthBlock.objects.filter(date__gte=sponsor_date).first()
×
357
                if block:
×
358
                    surrounding_blocks = [block] + list(block.next_blocks(num_blocks - 1))
×
359
                else:
360
                    surrounding_blocks = []
×
361

362
        sponsor_sch = gen_sponsor_schedule(user, context["eighth_sponsor"], num_blocks, surrounding_blocks, sponsor_date)
×
363
        context.update(sponsor_sch)
×
364
        # "sponsor_schedule", "no_attendance_today", "num_attendance_acts",
365
        # "sponsor_schedule_cur_date", "sponsor_schedule_prev_date", "sponsor_schedule_next_date"
366

367
    sched_ctx = schedule_context(request)
1✔
368
    context.update(sched_ctx)
1✔
369

370
    today_midnight = timezone.localtime().replace(hour=0, minute=0, second=0, microsecond=0)
1✔
371
    context.update(
1✔
372
        {
373
            "enrichments": (
374
                EnrichmentActivity.objects.visible_to_user(user).filter(
375
                    time__gte=today_midnight,
376
                    time__lte=today_midnight + timedelta(days=1),
377
                )
378
                if settings.ENABLE_ENRICHMENT_APP
379
                else []
380
            ),
381
            "senior_graduation_year": get_senior_graduation_year(),
382
        }
383
    )
384

385
    return context
1✔
386

387

388
@login_required
1✔
389
def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashboard_types=None, show_welcome=False):
1!
390
    """Process and show the dashboard, which includes activities, events, and widgets."""
391

392
    user = request.user
1✔
393
    now = timezone.localtime()
1✔
394

395
    if user.is_student and settings.ENABLE_PRE_EIGHTH_LOCATION_REDIRECT and request.COOKIES.get("seen_eighth_location", "") != "1":
1✔
396
        try:
1✔
397
            today_8 = Day.objects.today().day_type.blocks.filter(name__contains="8")
1✔
398
            if today_8:
1!
399
                first_start_time = time(today_8[0].start.hour, today_8[0].start.minute)
1✔
400
                last_start_time = time(today_8.last().start.hour, today_8.last().start.minute)
1✔
401
                first_start_date = datetime.combine(now.today(), first_start_time)
1✔
402
                last_start_date = datetime.combine(now.today(), last_start_time)
1✔
403
                if first_start_date - timedelta(minutes=30) < datetime.combine(now.today(), now.time()) < last_start_date + timedelta(minutes=20):
1!
404
                    return redirect(reverse("eighth_location"))
1✔
405
        except AttributeError:
1✔
406
            pass
1✔
407

408
    if user.is_student and settings.ENABLE_PRE_DISMISSAL_BUS_REDIRECT and request.COOKIES.get("seen_bus_redirect", "") != "1":
1✔
409
        try:
1✔
410
            day = Day.objects.today()
1✔
411
            if day is not None and day.end_time is not None:
1✔
412
                end_of_day = make_aware(day.end_time.date_obj(now.date()))
1✔
413
                if end_of_day - timedelta(minutes=5) <= now <= end_of_day + timedelta(minutes=20):
1!
414
                    response = redirect(reverse("afternoon_bus"))
×
415
                    response.set_cookie("seen_bus_redirect", "1", max_age=60 * 60)
×
416
                    return response
×
417
            elif settings.IS_SUMMER_SCHOOL:
1!
418
                end_of_day = datetime.datetime(now.year, now.month, now.day, settings.SCHOOL_END_HOUR, settings.SCHOOL_END_MINUTE)
×
419
                if end_of_day - timedelta(minutes=5) <= now <= end_of_day + timedelta(minutes=20):
×
420
                    response = redirect(reverse("afternoon_bus"))
×
421
                    response.set_cookie("seen_bus_redirect", "1", max_age=60 * 60)
×
422
                    return response
×
423

424
        except AttributeError:
×
425
            pass
×
426

427
    announcements_admin = user.has_admin_permission("announcements")
1✔
428
    events_admin = user.has_admin_permission("events")
1✔
429

430
    if not show_expired:
1✔
431
        show_expired = "show_expired" in request.GET
1✔
432

433
    show_all = request.GET.get("show_all", "0") != "0"
1✔
434
    if "show_all" not in request.GET and request.user.is_eighthoffice:
1!
435
        # Show all by default to 8th period office
436
        show_all = True
×
437

438
    is_index_page = request.path_info in ["/", ""]
1✔
439

440
    context = {
1✔
441
        "prerender_url": get_prerender_url(request),
442
        "user": user,
443
        "announcements_admin": announcements_admin,
444
        "events_admin": events_admin,
445
        "is_index_page": is_index_page,
446
        "show_all": show_all,
447
        "show_expired": show_expired,
448
        "show_tjstar": settings.TJSTAR_BANNER_START_DATE <= now.date() <= settings.TJSTAR_DATE,
449
    }
450

451
    # Get list of announcements
452
    items = get_announcements_list(request, context)
1✔
453

454
    # Paginate announcements list
455
    context, items = paginate_announcements_list(request, context, items)
1✔
456

457
    user_hidden_announcements = Announcement.objects.hidden_announcements(user).values_list("id", flat=True)
1✔
458
    user_hidden_events = Event.objects.hidden_events(user).values_list("id", flat=True)
1✔
459

460
    if ignore_dashboard_types is None:
1✔
461
        ignore_dashboard_types = []
1✔
462

463
    context.update(
1✔
464
        {
465
            "hide_announcements": True,
466
            "hide_events": True,
467
            "user_hidden_announcements": user_hidden_announcements,
468
            "user_hidden_events": user_hidden_events,
469
            "ignore_dashboard_types": ignore_dashboard_types,
470
        }
471
    )
472

473
    is_student = user.is_student
1✔
474
    is_teacher = user.is_teacher
1✔
475
    is_senior = user.is_senior
1✔
476
    show_admin_widget = user.is_global_admin or announcements_admin or user.is_eighth_admin
1✔
477
    eighth_sponsor = user.get_eighth_sponsor()
1✔
478

479
    # the URL path for forward/back buttons
480
    view_announcements_url = "index"
1✔
481

482
    if show_widgets:
1✔
483
        dashboard_title = "Dashboard"
1✔
484
        dashboard_header = "Dashboard"
1✔
485
    elif show_expired:
1✔
486
        dashboard_title = dashboard_header = "Announcement Archive"
1✔
487
        view_announcements_url = "announcements_archive"
1✔
488
    else:
489
        dashboard_title = dashboard_header = "Announcements"
1✔
490

491
    num_senior_destinations = len(Senior.objects.filled())
1✔
492

493
    try:
1✔
494
        dash_warning = settings.DASH_WARNING
1✔
495
    except Exception:
1✔
496
        dash_warning = None
1✔
497

498
    fcps_emerg = get_fcps_emerg(request)
1✔
499
    ap_week = get_ap_week_warning(request)
1✔
500
    if fcps_emerg:
1!
501
        dash_warning = fcps_emerg
×
502
    elif ap_week:
1!
503
        dash_warning = ap_week
×
504

505
    warnings = WarningAnnouncement.objects.filter(active=True)
1✔
506
    html = get_warning_html(warnings, dashboard=True)
1✔
507
    if html:
1!
508
        dash_warning = html
×
509

510
    context.update(
1✔
511
        {
512
            "dash_warning": dash_warning,
513
            "show_widgets": show_widgets,
514
            "show_expired": show_expired,
515
            "show_near_graduation_message": is_senior
516
            and (timezone.now().date() + timedelta(days=settings.NEAR_GRADUATION_DAYS) >= get_senior_graduation_date().date()),
517
            "view_announcements_url": view_announcements_url,
518
            "dashboard_title": dashboard_title,
519
            "dashboard_header": dashboard_header,
520
            "is_student": is_student,
521
            "is_teacher": is_teacher,
522
            "is_senior": is_senior,
523
            "show_admin_widget": show_admin_widget,
524
            "eighth_sponsor": eighth_sponsor,
525
            "num_senior_destinations": num_senior_destinations,
526
        }
527
    )
528

529
    if settings.TJSTAR_MAP:
1!
530
        context.update(get_tjstar_mapping(request.user))
×
531

532
    #######
533
    if settings.ENABLE_HYBRID_EIGHTH:
1!
534
        context.update({"hybrid": True})
×
535
    #######
536

537
    if show_widgets:
1✔
538
        context = add_widgets_context(request, context)
1✔
539

540
    if announcements_admin:
1✔
541
        all_waiting = AnnouncementRequest.objects.filter(posted=None, rejected=False).this_year()
1✔
542
        awaiting_teacher = all_waiting.filter(teachers_approved__isnull=True)
1✔
543
        awaiting_approval = all_waiting.filter(teachers_approved__isnull=False)
1✔
544

545
        context.update({"awaiting_teacher": awaiting_teacher, "awaiting_approval": awaiting_approval})
1✔
546

547
    self_awaiting_teacher = AnnouncementRequest.objects.filter(posted=None, rejected=False, teachers_requested=request.user).this_year()
1✔
548
    context.update({"self_awaiting_teacher": self_awaiting_teacher})
1✔
549

550
    if show_welcome:
1✔
551
        return render(request, "welcome/student.html", context)
1✔
552
    else:
553
        return render(request, "dashboard/dashboard.html", context)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc