Skip to content

Commit

Permalink
feat(signage): custom switch page and enhanced admin dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
alanzhu0 committed Apr 23, 2023
1 parent bacb4f8 commit 9cd12b8
Show file tree
Hide file tree
Showing 12 changed files with 330 additions and 14 deletions.
5 changes: 5 additions & 0 deletions Ion.egg-info/SOURCES.txt
Expand Up @@ -849,6 +849,7 @@ intranet/apps/sessionmgmt/migrations/__init__.py
intranet/apps/signage/__init__.py
intranet/apps/signage/admin.py
intranet/apps/signage/consumers.py
intranet/apps/signage/forms.py
intranet/apps/signage/models.py
intranet/apps/signage/pages.py
intranet/apps/signage/tests.py
Expand All @@ -871,6 +872,9 @@ intranet/apps/signage/migrations/0014_page_strip_links.py
intranet/apps/signage/migrations/0015_auto_20180422_1714.py
intranet/apps/signage/migrations/0016_auto_20191210_1520.py
intranet/apps/signage/migrations/0017_sign_latest_heartbeat_time.py
intranet/apps/signage/migrations/0018_auto_20230422_2219.py
intranet/apps/signage/migrations/0019_alter_sign_custom_switch_time.py
intranet/apps/signage/migrations/0020_sign_custom_switch_page_lock.py
intranet/apps/signage/migrations/__init__.py
intranet/apps/signage/templatetags/__init__.py
intranet/apps/signage/templatetags/signage.py
Expand Down Expand Up @@ -3561,6 +3565,7 @@ intranet/templates/seniors/add.html
intranet/templates/seniors/home.html
intranet/templates/sessionmgmt/index.html
intranet/templates/signage/base.html
intranet/templates/signage/admin/set_custom_switch_time_for_all_signs.html
intranet/templates/signage/pages/announcements.html
intranet/templates/signage/pages/bus.html
intranet/templates/signage/pages/eighth.html
Expand Down
8 changes: 8 additions & 0 deletions docs/sourcedoc/intranet.apps.signage.rst
Expand Up @@ -28,6 +28,14 @@ intranet.apps.signage.consumers module
:undoc-members:
:show-inheritance:

intranet.apps.signage.forms module
----------------------------------

.. automodule:: intranet.apps.signage.forms
:members:
:undoc-members:
:show-inheritance:

intranet.apps.signage.models module
-----------------------------------

Expand Down
146 changes: 144 additions & 2 deletions intranet/apps/signage/admin.py
@@ -1,14 +1,156 @@
from django.contrib import admin
from django.shortcuts import redirect, render
from django.urls import reverse

from ...utils.helpers import join_nicely
from .forms import SetSignCustomSwitchTimeForm
from .models import Page, Sign


class SignAdmin(admin.ModelAdmin):
list_display = ("name", "display")
list_display = (
"name",
"display",
"lock_page",
"default_page",
"custom_switch_page",
"custom_switch_time",
"custom_switch_page_lock",
"latest_heartbeat_time",
"page_list",
)

def page_list(self, obj):
return ", ".join(str(p) for p in obj.pages.all())


class PageAdmin(admin.ModelAdmin):
list_display = ("name",)
list_display = (
"name",
"url",
"template",
"order",
"sign_list",
"lock_page_on",
"custom_page_on",
)

def sign_list(self, obj):
return ", ".join(str(s).replace(" Commons", "") for s in obj.signs.all())

def lock_page_on(self, obj):
return ", ".join(str(s).replace(" Commons", "") for s in Sign.objects.filter(lock_page=obj))

def custom_page_on(self, obj):
return ", ".join(str(s).replace(" Commons", "") for s in Sign.objects.filter(custom_switch_page=obj))

actions = [
"add_page_to_all_signs",
"remove_page_from_all_signs",
"lock_page_to_all_signs",
"unlock_all_signs_from_selected_pages",
"set_custom_switch_page_for_all_signs",
"set_locked_custom_switch_page_for_all_signs",
"remove_custom_switch_page_from_all_signs",
"set_custom_switch_time_for_all_signs",
]

@admin.action(description="Add selected Page(s) to all Signs")
def add_page_to_all_signs(self, request, queryset):
for page in queryset:
page.deploy_to()
self.message_user(request, f"Successfully added {join_nicely(queryset)} to all Signs.")

@admin.action(description="Remove selected Page(s) from all Signs")
def remove_page_from_all_signs(self, request, queryset):
for page in queryset:
page.signs.clear()
self.message_user(request, f"Successfully removed {join_nicely(queryset)} from all Signs.")

@admin.action(description="Lock all Signs to selected Page")
def lock_page_to_all_signs(self, request, queryset):
if queryset.count() > 1:
self.message_user(request, "Please select only one Page to lock to all Signs.", level="ERROR")
return
page = queryset.first()
page.deploy_to(lock=True)
self.message_user(request, f"Successfully locked all Signs to {page.name}.")

@admin.action(description="Unlock all Signs from selected Page(s)")
def unlock_all_signs_from_selected_pages(self, request, queryset):
for page in queryset:
locked_signs = page.locked_signs.all()
if not locked_signs:
self.message_user(request, f"No Signs are locked to {page.name}.", level="ERROR")
else:
page.locked_signs.clear()
self.message_user(request, f"Successfully unlocked {join_nicely(locked_signs)} from {page.name}.")

@admin.action(description="Set selected Page as custom switch page for all Signs")
def set_custom_switch_page_for_all_signs(self, request, queryset):
if queryset.count() > 1:
self.message_user(request, "Please select only one Page to set as custom switch page for all Signs.", level="ERROR")
return
page = queryset.first()
for sign in Sign.objects.all():
sign.pages.add(page)
sign.custom_switch_page = page
sign.custom_switch_page_lock = False
sign.save()
self.message_user(
request, f"Successfully set {page.name} as custom switch page for all Signs. Set a custom switch time to activate this page."
)

@admin.action(description="Set selected Page as locked custom switch page for all Signs")
def set_locked_custom_switch_page_for_all_signs(self, request, queryset):
if queryset.count() > 1:
self.message_user(request, "Please select only one Page to set as locked custom switch page for all Signs.", level="ERROR")
return
page = queryset.first()
for sign in Sign.objects.all():
sign.pages.add(page)
sign.custom_switch_page = page
sign.custom_switch_page_lock = True
sign.save()
self.message_user(
request, f"Successfully set {page.name} as locked custom switch page for all Signs. Set a custom switch time to activate this page."
)

@admin.action(description="Remove selected Page(s) as custom switch page from all Signs")
def remove_custom_switch_page_from_all_signs(self, request, queryset):
for page in queryset:
custom_page_signs = page.custom_page_signs.all()
if not custom_page_signs:
self.message_user(request, f"No Signs have their custom switch page set to {page.name}.", level="ERROR")
else:
page.signs.clear()
page.custom_page_signs.clear()
self.message_user(request, f"Successfully removed {page.name} as custom switch page from {join_nicely(custom_page_signs)}.")
for sign in Sign.objects.all():
sign.custom_switch_page_lock = False
sign.save()

@admin.action(description="Set custom switch time for all Signs")
def set_custom_switch_time_for_all_signs(self, request, queryset):
if "apply" in request.POST:
form = SetSignCustomSwitchTimeForm(request.POST)
if form.is_valid():
time = form.cleaned_data["time"]
for sign in Sign.objects.all():
sign.custom_switch_time = time
sign.save()
self.message_user(request, f"Successfully set custom switch time for all Signs to {time}.")
return redirect(reverse("admin:signage_sign_changelist"))
else:
self.message_user(request, "Invalid form data.", level="ERROR")
return render(request, "signage/admin/set_custom_switch_time_for_all_signs.html", {"form": form})
form = SetSignCustomSwitchTimeForm(
initial={
"_selected_action": queryset.values_list("pk", flat=True),
"time": Sign.objects.first().custom_switch_time,
}
)
return render(request, "signage/admin/set_custom_switch_time_for_all_signs.html", {"form": form})


admin.site.register(Sign, SignAdmin)
Expand Down
12 changes: 12 additions & 0 deletions intranet/apps/signage/forms.py
@@ -0,0 +1,12 @@
from django import forms


class SetSignCustomSwitchTimeForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
action = forms.CharField(widget=forms.HiddenInput, initial="set_custom_switch_time_for_all_signs")
time = forms.TimeField(
widget=forms.TimeInput(format="%H:%M"),
input_formats=("%H:%M",),
help_text="Time at which to switch to the custom page for the Sign. Leave blank to clear. Example: 16:30",
required=False,
)
38 changes: 38 additions & 0 deletions intranet/apps/signage/migrations/0018_auto_20230422_2219.py
@@ -0,0 +1,38 @@
# Generated by Django 3.2.17 on 2023-04-23 02:19

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('signage', '0017_sign_latest_heartbeat_time'),
]

operations = [
migrations.AlterModelOptions(
name='page',
options={'ordering': ['order', 'name']},
),
migrations.AddField(
model_name='sign',
name='custom_switch_page',
field=models.ForeignKey(blank=True, help_text='Switch to this page at the custom switch time', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='custom_page_signs', to='signage.page'),
),
migrations.AddField(
model_name='sign',
name='custom_switch_time',
field=models.DateTimeField(blank=True, help_text='Switch to a custom page at this time', null=True),
),
migrations.AlterField(
model_name='sign',
name='default_page',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_signs', to='signage.page'),
),
migrations.AlterField(
model_name='sign',
name='lock_page',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locked_signs', to='signage.page'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-04-23 02:44

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('signage', '0018_auto_20230422_2219'),
]

operations = [
migrations.AlterField(
model_name='sign',
name='custom_switch_time',
field=models.TimeField(blank=True, help_text='Switch to the custom page at this time', null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-04-23 18:12

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('signage', '0019_alter_sign_custom_switch_time'),
]

operations = [
migrations.AddField(
model_name='sign',
name='custom_switch_page_lock',
field=models.BooleanField(default=False, help_text='Lock the custom page when switching to it'),
),
]
37 changes: 26 additions & 11 deletions intranet/apps/signage/models.py
@@ -1,9 +1,12 @@
import logging
from datetime import timedelta

from django.conf import settings
from django.db import models
from django.utils import timezone

logger = logging.getLogger(__name__)


class PageQuerySet(models.query.QuerySet):
def order_properly(self) -> "models.query.QuerySet[Page]":
Expand Down Expand Up @@ -50,7 +53,7 @@ class Page(models.Model):

strip_links = models.BooleanField(default=True)

def deploy_to(self, displays=None, exclude=None):
def deploy_to(self, displays=None, exclude=None, lock=False):
"""
Deploys page to listed display (specify with display). If display is None,
deploy to all display. Can specify exclude for which display to exclude.
Expand All @@ -64,16 +67,17 @@ def deploy_to(self, displays=None, exclude=None):
else:
signs = Sign.objects.filter(display__in=displays)
for sign in signs.exclude(display__in=exclude):
sign.pages.add(self)
if lock:
sign.lock_page = self
else:
sign.pages.add(self)
sign.save()

def __str__(self):
if self.iframe:
url = self.url[:10]
url = url + "..." if len(self.url) > 10 else url
return "{} ({})".format(self.name, url)
else:
return "{}".format(self.name)
return self.name

class Meta:
ordering = ["order", "name"]


class SignQuerySet(models.query.QuerySet):
Expand Down Expand Up @@ -121,8 +125,8 @@ class Sign(models.Model):
map_location = models.CharField(max_length=20, null=True, blank=True)
img_path = models.CharField(max_length=250, default="https://c1.staticflickr.com/5/4331/36927945575_c2c09e44db_k.jpg")

lock_page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True, related_name="_unused_1")
default_page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True, related_name="_unused_2")
lock_page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True, related_name="locked_signs")
default_page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True, related_name="default_signs")
pages = models.ManyToManyField(Page, related_name="signs")

day_end_switch_page = models.ForeignKey(
Expand All @@ -132,6 +136,17 @@ class Sign(models.Model):
default=5, null=False, blank=False, help_text="Switch pages this many minutes before the end of the day"
)

custom_switch_page = models.ForeignKey(
Page,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="custom_page_signs",
help_text="Switch to this page at the custom switch time",
)
custom_switch_time = models.TimeField(null=True, blank=True, help_text="Switch to the custom page at this time")
custom_switch_page_lock = models.BooleanField(default=False, help_text="Lock the custom page when switching to it")

# This can be set to None at any time if the
latest_heartbeat_time = models.DateTimeField(null=True, default=None)

Expand All @@ -142,4 +157,4 @@ def is_offline(self) -> bool:
)

def __str__(self):
return "{} ({})".format(self.name, self.display)
return self.name
1 change: 1 addition & 0 deletions intranet/apps/signage/views.py
Expand Up @@ -54,6 +54,7 @@ def signage_display(request, display_id: str) -> HttpResponse:
context["sign"] = sign
context["page_args"] = (sign, request)
context["end_switch_page_time"] = end_of_day - datetime.timedelta(minutes=sign.day_end_switch_minutes)
context["custom_switch_time"] = sign.custom_switch_time
context["senior_graduation_year"] = get_senior_graduation_year()
return render(request, "signage/base.html", context)

Expand Down
12 changes: 12 additions & 0 deletions intranet/static/js/signage.js
Expand Up @@ -202,4 +202,16 @@ window.onload = function () {
setActive(window.endSwitchPage);
}, ((endSwitchHour - now.getHours()) * 3600 + (endSwitchMinute - now.getMinutes()) * 60 - now.getSeconds()) * 1000 - now.getMilliseconds());
}

if(window.customSwitchPage && window.customSwitchHour && window.customSwitchMinute) {
var now = new Date();
setTimeout(function() {
setActive(window.customSwitchPage);
if(window.customSwitchPageLock) {
window.lockPage = window.customSwitchPage;
$('.signage-nav').addClass('lock');
$('section.signage-section iframe.signage-iframe').css('width', '100vw');
}
}, Math.max(((customSwitchHour - now.getHours()) * 3600 + (customSwitchMinute - now.getMinutes()) * 60 - now.getSeconds()) * 1000 - now.getMilliseconds()), 0);
}
};

0 comments on commit 9cd12b8

Please sign in to comment.