diff --git a/AKModel/admin.py b/AKModel/admin.py index 8d60bc7e64647b1fe450a4832212bc898d11e8a2..8446259d31b0a3fad0a8b1c981d7151e4a578b2e 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -74,6 +74,8 @@ admin.site.register(Room) @admin.register(AKSlot) class AKSlotAdmin(admin.ModelAdmin): + readonly_fields = ['updated'] + def get_form(self, request, obj=None, change=False, **kwargs): # Use timezone of associated event if obj is not None and obj.event.timezone: diff --git a/AKModel/models.py b/AKModel/models.py index 997e85e22c3ef05676afbcda82e540e991c94b19..24ceb4d7e7a1771a34b293b549f41f57dd7d2562 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -1,8 +1,8 @@ -# Create your models here. import datetime import itertools from django.db import models +from django.utils import timezone from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from timezone_field import TimeZoneField @@ -301,3 +301,12 @@ class AKSlot(models.Model): Retrieve end time of the AK slot """ return self.start + datetime.timedelta(hours=float(self.duration)) + + @property + def seconds_since_last_update(self): + """ + Return minutes since last update + :return: minutes since last update + :rtype: float + """ + return (timezone.now() - self.updated).total_seconds() diff --git a/AKPlan/templates/AKPlan/encode_events.html b/AKPlan/templates/AKPlan/encode_events.html index 2cb97b193e59fa910a0f4e9ea187079fcf8a0d51..c38a4652b3ebfdd164362309e3dc645d617ad1a2 100644 --- a/AKPlan/templates/AKPlan/encode_events.html +++ b/AKPlan/templates/AKPlan/encode_events.html @@ -1,4 +1,5 @@ {% load tz %} +{% load tags_AKPlan %} [ {% for slot in slots %} @@ -8,8 +9,8 @@ 'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', 'end': '{{ slot.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}', 'resourceId': '{{ slot.room.title }}', - 'backgroundColor': '{{ slot.ak.category.color }}', - 'borderColor': '{{ slot.ak.track.color }}', + 'backgroundColor': '{{ slot|highlight_change_colors }}', + 'borderColor': '{{ slot.ak.category.color }}', 'url': '{% url 'submit:ak_detail' event_slug=event.slug pk=slot.ak.pk %}' }, {% endif %} diff --git a/AKPlan/templatetags/__init__.py b/AKPlan/templatetags/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/AKPlan/templatetags/color_gradients.py b/AKPlan/templatetags/color_gradients.py new file mode 100644 index 0000000000000000000000000000000000000000..1a9f96fd87eda8e405894eef648e048d81e87d32 --- /dev/null +++ b/AKPlan/templatetags/color_gradients.py @@ -0,0 +1,61 @@ +# gradients based on http://bsou.io/posts/color-gradients-with-python + + +def hex_to_rgb(hex): + """ + Convert hex color to RGB color code + :param hex: hex encoded color + :type hex: str + :return: rgb encoded version of given color + :rtype: list[int] + """ + # Pass 16 to the integer function for change of base + return [int(hex[i:i+2], 16) for i in range(1,6,2)] + + +def rgb_to_hex(rgb): + """ + Convert rgb color (list) to hex encoding (str) + :param rgb: rgb encoded color + :type rgb: list[int] + :return: hex encoded version of given color + :rtype: str + """ + # Components need to be integers for hex to make sense + rgb = [int(x) for x in rgb] + return "#"+"".join(["0{0:x}".format(v) if v < 16 else + "{0:x}".format(v) for v in rgb]) + + +def linear_blend(start_hex, end_hex, position): + """ + Create a linear blend between two colors and return color code on given position of the range from 0 to 1 + :param start_hex: hex representation of start color + :type start_hex: str + :param end_hex: hex representation of end color + :type end_hex: str + :param position: position in range from 0 to 1 + :type position: float + :return: hex encoded interpolated color + :rtype: str + """ + s = hex_to_rgb(start_hex) + f = hex_to_rgb(end_hex) + blended = [int(s[j] + position * (f[j] - s[j])) for j in range(3)] + return rgb_to_hex(blended) + + +def darken(start_hex, amount): + """ + Darken the given color by the given amount (sensitivity will be cut in half) + + :param start_hex: original color + :type start_hex: str + :param amount: how much to darken (1.0 -> 50% darker) + :type amount: float + :return: darker version of color + :rtype: str + """ + start_rbg = hex_to_rgb(start_hex) + darker = [int(s * (1 - amount * .5)) for s in start_rbg] + return rgb_to_hex(darker) diff --git a/AKPlan/templatetags/tags_AKPlan.py b/AKPlan/templatetags/tags_AKPlan.py new file mode 100644 index 0000000000000000000000000000000000000000..5618fd57694fbfe75988bed3e9f3341f8a6c8818 --- /dev/null +++ b/AKPlan/templatetags/tags_AKPlan.py @@ -0,0 +1,20 @@ +from django import template + +from AKPlan.templatetags.color_gradients import darken +from AKPlanning import settings + +register = template.Library() + + +@register.filter +def highlight_change_colors(akslot): + seconds_since_update = akslot.seconds_since_last_update + + # Last change long ago? Use default color + if seconds_since_update > settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS: + return akslot.ak.category.color + + # Recent change? Calculate gradient blend between red and + recentness = seconds_since_update / settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS + return darken("#b71540", recentness) + # return linear_blend("#b71540", "#000000", recentness) diff --git a/AKPlanning/settings.py b/AKPlanning/settings.py index fc67a5424a76e10519fbbe1a77bb1dfdd4c0027c..32eb423d6303d0afca26bba36142b8a565df9281 100644 --- a/AKPlanning/settings.py +++ b/AKPlanning/settings.py @@ -164,5 +164,7 @@ PLAN_WALL_HOURS_RETROSPECT = 3 PLAN_WALL_HOURS_FUTURE = 18 # Should the plan use a hierarchy of buildings and rooms? PLAN_SHOW_HIERARCHY = True +# For which time (in seconds) should changes of akslots be highlighted in plan? +PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS = 2 * 60 * 60 include(optional("settings/*.py"))