add date into diffusion menu in wagtail nav; get larger menus in back-office

This commit is contained in:
bkfox 2017-05-14 22:33:11 +02:00
parent 3cceb65121
commit a599f133a1
9 changed files with 121 additions and 108 deletions

View File

@ -101,9 +101,9 @@ class DiffusionAdmin(admin.ModelAdmin):
sounds = [ str(s) for s in obj.get_archives()] sounds = [ str(s) for s in obj.get_archives()]
return ', '.join(sounds) if sounds else '' return ', '.join(sounds) if sounds else ''
def conflicts(self, obj): def conflicts_(self, obj):
if obj.type == Diffusion.Type.unconfirmed: if obj.conflicts.count():
return ', '.join([ str(d) for d in obj.get_conflicts()]) return obj.conflicts.count()
return '' return ''
def end_time(self, obj): def end_time(self, obj):
@ -113,12 +113,13 @@ class DiffusionAdmin(admin.ModelAdmin):
def first(self, obj): def first(self, obj):
return obj.initial.start if obj.initial else '' return obj.initial.start if obj.initial else ''
list_display = ('id', 'program', 'start', 'end_time', 'type', 'first', 'archives', 'conflicts') list_display = ('id', 'program', 'start', 'end_time', 'type', 'first', 'archives', 'conflicts_')
list_filter = ('type', 'start', 'program') list_filter = ('type', 'start', 'program')
list_editable = ('type',) list_editable = ('type',)
ordering = ('-start', 'id') ordering = ('-start', 'id')
fields = ['type', 'start', 'end', 'initial', 'program'] fields = ['type', 'start', 'end', 'initial', 'program', 'conflicts']
readonly_fields = ('conflicts',)
inlines = [ DiffusionInline, SoundInline ] inlines = [ DiffusionInline, SoundInline ]

View File

@ -25,43 +25,12 @@ from aircox.models import *
logger = logging.getLogger('aircox.tools') logger = logging.getLogger('aircox.tools')
import time
class Actions: class Actions:
@staticmethod
def __check_conflicts (item, saved_items):
"""
Check for conflicts, and update conflictual
items if they have been generated during this
update.
It set an attribute 'do_not_save' if the item should not
be saved. FIXME: find proper way
Return the number of conflicts
"""
conflicts = list(item.get_conflicts())
for i, conflict in enumerate(conflicts):
if conflict.program == item.program:
item.do_not_save = True
del conflicts[i]
continue
if conflict.pk in saved_items and \
conflict.type != Diffusion.Type.unconfirmed:
conflict.type = Diffusion.Type.unconfirmed
conflict.save()
if not conflicts:
item.type = Diffusion.Type.normal
return 0
item.type = Diffusion.Type.unconfirmed
return len(conflicts)
@classmethod @classmethod
def update (cl, date, mode): def update (cl, date, mode):
manual = (mode == 'manual') manual = (mode == 'manual')
if not manual:
saved_items = set()
count = [0, 0] count = [0, 0]
for schedule in Schedule.objects.filter(program__active = True) \ for schedule in Schedule.objects.filter(program__active = True) \
@ -71,16 +40,15 @@ class Actions:
items = schedule.diffusions_of_month(date, exclude_saved = True) items = schedule.diffusions_of_month(date, exclude_saved = True)
count[0] += len(items) count[0] += len(items)
if manual: # we can't bulk create because we ned signal processing
Diffusion.objects.bulk_create(items) for item in items:
else: conflicts = item.get_conflicts()
for item in items: item.type = Diffusion.Type.unconfirmed \
count[1] += cl.__check_conflicts(item, saved_items) if manual or conflicts.count() else \
if hasattr(item, 'do_not_save'): Diffusion.Type.normal
count[0] -= 1 item.save(no_check = True)
continue if conflicts.count():
item.save() item.conflicts.set(conflicts.all())
saved_items.add(item)
logger.info('[update] schedule %s: %d new diffusions', logger.info('[update] schedule %s: %d new diffusions',
str(schedule), len(items), str(schedule), len(items),

View File

@ -720,19 +720,6 @@ class DiffusionManager(models.Manager):
qs = self if qs is None else qs qs = self if qs is None else qs
return qs.filter(program__station = station) return qs.filter(program__station = station)
@staticmethod
def __in_range(field, range, field_ = None, reversed = False):
"""
Return a kwargs to catch diffusions based on the given field name
and datetime range.
"""
if reversed:
return { field + "__lte": range[1],
(field_ or field) + "__gte": range[0] }
return { field + "__gte" : range[0],
(field_ or field) + "__lte" : range[1] }
def at(self, station, date = None, next = False, qs = None): def at(self, station, date = None, next = False, qs = None):
""" """
Return diffusions occuring at the given date, ordered by +start Return diffusions occuring at the given date, ordered by +start
@ -754,22 +741,21 @@ class DiffusionManager(models.Manager):
if isinstance(date, datetime.datetime): if isinstance(date, datetime.datetime):
# use datetime: we want diffusion that occurs around this # use datetime: we want diffusion that occurs around this
# range # range
range = date, date filters = { 'start__lte': date, 'end__gte': date }
filters = self.__in_range('start', range, 'end', True)
if next: if next:
qs = qs.filter( qs = qs.filter(
models.Q(date__gte = date) | models.Q(**filters) models.Q(start__gte = date) | models.Q(**filters)
) )
else: else:
qs = qs.filter(**filters) qs = qs.filter(**filters)
else: else:
# use date: we want diffusions that occurs this day # use date: we want diffusions that occurs this day
range = utils.date_range(date) start, end = utils.date_range(date)
filters = models.Q(**self.__in_range('start', range)) | \ filters = models.Q(start__gte = start, start__lte = end) | \
models.Q(**self.__in_range('end', range)) models.Q(end__gt = start, end__lt = end)
if next: if next:
# include also diffusions of the next day # include also diffusions of the next day
filters |= models.Q(start__gte = range[0]) filters |= models.Q(start__gte = start)
qs = qs.filter(filters) qs = qs.filter(filters)
return self.station(station, qs).order_by('start').distinct() return self.station(station, qs).order_by('start').distinct()
@ -842,6 +828,12 @@ class Diffusion(models.Model):
# blank = True, null = True, # blank = True, null = True,
# help_text = _('use this input port'), # help_text = _('use this input port'),
# ) # )
conflicts = models.ManyToManyField(
'self',
verbose_name = _('conflicts'),
blank = True,
help_text = _('conflicts'),
)
start = models.DateTimeField( _('start of the diffusion') ) start = models.DateTimeField( _('start of the diffusion') )
end = models.DateTimeField( _('end of the diffusion') ) end = models.DateTimeField( _('end of the diffusion') )
@ -891,22 +883,44 @@ class Diffusion(models.Model):
""" """
Return a list of conflictual diffusions, based on the scheduled duration. Return a list of conflictual diffusions, based on the scheduled duration.
""" """
r = Diffusion.objects.filter( return Diffusion.objects.filter(
models.Q(start__lt = self.start, models.Q(start__lt = self.start,
end__gt = self.start) | end__gt = self.start) |
models.Q(start__gt = self.start, models.Q(start__gt = self.start,
start__lt = self.end) start__lt = self.end)
) )
return r
def save(self, *args, **kwargs): def check_conflicts(self):
conflicts = self.get_conflicts()
self.conflicts.set(conflicts)
__initial = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__initial = {
'start': self.start,
'end': self.end,
}
def save(self, no_check = False, *args, **kwargs):
if no_check:
return super().save(*args, **kwargs)
if self.initial: if self.initial:
# force link to the top initial diffusion # force link to the top initial diffusion
if self.initial.initial: if self.initial.initial:
self.initial = self.initial.initial self.initial = self.initial.initial
self.program = self.initial.program self.program = self.initial.program
super().save(*args, **kwargs) super().save(*args, **kwargs)
if self.__initial:
if self.start != self.__initial['start'] or \
self.end != self.__initial['end']:
self.check_conflicts()
def __str__(self): def __str__(self):
return '{self.program.name} {date} #{self.pk}'.format( return '{self.program.name} {date} #{self.pk}'.format(
self=self, date=self.date.strftime('%Y-%m-%d %H:%M') self=self, date=self.date.strftime('%Y-%m-%d %H:%M')

View File

@ -1,3 +1,6 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. # Register your models here.

View File

@ -150,7 +150,7 @@
@-webkit-keyframes MenuAnimOut1 { @-webkit-keyframes MenuAnimOut1 {
50% { 50% {
-webkit-transform: translateZ(-250px) rotateY(30deg); -webkit-transform: translateZ(-220px) rotateY(30deg);
} }
75% { 75% {
-webkit-transform: translateZ(-372.5px) rotateY(15deg); -webkit-transform: translateZ(-372.5px) rotateY(15deg);
@ -192,8 +192,8 @@
@keyframes MenuAnimOut1 { @keyframes MenuAnimOut1 {
50% { 50% {
-webkit-transform: translateZ(-250px) rotateY(30deg); -webkit-transform: translateZ(-220px) rotateY(30deg);
transform: translateZ(-250px) rotateY(30deg); transform: translateZ(-220px) rotateY(30deg);
} }
75% { 75% {
-webkit-transform: translateZ(-372.5px) rotateY(15deg); -webkit-transform: translateZ(-372.5px) rotateY(15deg);
@ -270,7 +270,7 @@
opacity: 0; opacity: 0;
} }
20% { 20% {
-webkit-transform: translateZ(-250px) rotateY(30deg); -webkit-transform: translateZ(-220px) rotateY(30deg);
opacity: 0.5; opacity: 0.5;
} }
100% { 100% {
@ -330,8 +330,8 @@
opacity: 0; opacity: 0;
} }
20% { 20% {
-webkit-transform: translateZ(-250px) rotateY(30deg); -webkit-transform: translateZ(-220px) rotateY(30deg);
transform: translateZ(-250px) rotateY(30deg); transform: translateZ(-220px) rotateY(30deg);
opacity: 0.5; opacity: 0.5;
} }
100% { 100% {
@ -1932,7 +1932,7 @@ body.ready .fade {
@media screen and (min-width: 50em) { @media screen and (min-width: 50em) {
.modal-dialog { .modal-dialog {
padding: 0 0 2em 180px; padding: 0 0 2em 220px;
} }
} }
@ -3846,7 +3846,7 @@ table.listing {
} }
.listing.images > li .image { .listing.images > li .image {
text-align: center; text-align: center;
height: 180px; height: 220px;
} }
.listing.images > li .image:before { .listing.images > li .image:before {
content: ''; content: '';
@ -4881,8 +4881,8 @@ body.ready .progress .bar {
.nav-wrapper { .nav-wrapper {
position: relative; position: relative;
margin-left: -180px; margin-left: -220px;
width: 180px; width: 220px;
float: left; float: left;
height: 100%; height: 100%;
min-height: 800px; min-height: 800px;
@ -5005,13 +5005,13 @@ body.ready .nav-main a {
display: none; display: none;
} }
.nav-submenu .menu-item a { .nav-submenu .menu-item .info {
white-space: normal; margin-right: 1em;
padding: 0.9em 1.7em 0.9em 4.5em;
} }
.nav-submenu .menu-item a:before { .nav-submenu .menu-item a {
margin-left: -1.5em; white-space: normal;
padding: 0.9em 1em 0.9em 1em;
} }
.nav-submenu .menu-item a:hover { .nav-submenu .menu-item a:hover {
@ -5106,8 +5106,8 @@ body.ready .nav-main a {
} }
body.nav-open .wrapper { body.nav-open .wrapper {
transform: translate3d(180px, 0, 0); transform: translate3d(220px, 0, 0);
-webkit-transform: translate3d(180px, 0, 0); -webkit-transform: translate3d(220px, 0, 0);
} }
body.nav-open .content-wrapper { body.nav-open .content-wrapper {
@ -5163,7 +5163,7 @@ body.explorer-open .explorer-close {
-webkit-transform: none; -webkit-transform: none;
-ms-transform: none; -ms-transform: none;
transform: none; transform: none;
padding-left: 180px; padding-left: 220px;
} }
.nav-wrapper { .nav-wrapper {
position: absolute; position: absolute;
@ -5174,7 +5174,7 @@ body.explorer-open .explorer-close {
.nav-wrapper .inner { .nav-wrapper .inner {
height: 100%; height: 100%;
position: fixed; position: fixed;
width: 180px; width: 220px;
} }
.nav-toggle { .nav-toggle {
display: none; display: none;
@ -5186,7 +5186,7 @@ body.explorer-open .explorer-close {
} }
.nav-main .footer { .nav-main .footer {
position: fixed; position: fixed;
width: 180px; width: 220px;
bottom: 0; bottom: 0;
} }
.nav-main .footer-open .footer-submenu { .nav-main .footer-open .footer-submenu {
@ -5256,14 +5256,14 @@ body.explorer-open .explorer-close {
width: 0; width: 0;
padding: 0; padding: 0;
top: 0; top: 0;
left: 180px; left: 220px;
overflow: auto; overflow: auto;
max-height: 100%; max-height: 100%;
} }
.nav-submenu h2, .nav-submenu h2,
.nav-submenu ul { .nav-submenu ul {
float: right; float: right;
width: 180px; width: 220px;
} }
.nav-submenu h2 { .nav-submenu h2 {
display: block; display: block;
@ -5296,14 +5296,14 @@ body.explorer-open .explorer-close {
} }
li.submenu-active .nav-submenu { li.submenu-active .nav-submenu {
box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35);
width: 180px; width: 220px;
padding: 0 0 1.5em; padding: 0 0 0.5em;
} }
body.ready li.submenu-active .nav-submenu { body.ready li.submenu-active .nav-submenu {
transition: width 0.2s ease; transition: width 0.2s ease;
} }
li.submenu-active .nav-submenu a { li.submenu-active .nav-submenu a {
padding-left: 3.5em; padding-left: 1em;
} }
.explorer { .explorer {
width: 400px; width: 400px;
@ -5325,7 +5325,7 @@ body.explorer-open .explorer-close {
} }
body.explorer-open .nav-wrapper { body.explorer-open .nav-wrapper {
margin-left: 0; margin-left: 0;
width: 180px; width: 220px;
} }
body.explorer-open .explorer:before { body.explorer-open .explorer:before {
display: none; display: none;
@ -5356,7 +5356,7 @@ body.explorer-open .explorer-close {
-webkit-transform: none; -webkit-transform: none;
-ms-transform: none; -ms-transform: none;
transform: none; transform: none;
left: 180px; left: 220px;
position: relative; position: relative;
} }
body.explorer-open .wrapper { body.explorer-open .wrapper {
@ -5375,7 +5375,7 @@ body.explorer-open .explorer-close {
left: 0; left: 0;
} }
body.explorer-open .nav-wrapper { body.explorer-open .nav-wrapper {
width: 180px; width: 220px;
} }
} }
@ -5603,7 +5603,7 @@ body.explorer-open .explorer-close {
position: absolute; position: absolute;
z-index: 25; z-index: 25;
top: 0; top: 0;
left: 180px; left: 220px;
} }
#wagtail .c-state-indicator { #wagtail .c-state-indicator {
@ -5811,7 +5811,7 @@ footer li {
} }
footer .actions { footer .actions {
width: 250px; width: 220px;
margin-right: 1.5%; margin-right: 1.5%;
} }

View File

@ -18,6 +18,7 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% if diffusion.diffusion_set.count %}
<section class="dates"> <section class="dates">
<h2>{% trans "Dates of diffusion" %}</h2> <h2>{% trans "Dates of diffusion" %}</h2>
<ul> <ul>
@ -29,6 +30,7 @@
{% endwith %} {% endwith %}
</ul> </ul>
</section> </section>
{% endif %}
{% with podcasts=self.get_podcasts %} {% with podcasts=self.get_podcasts %}
{% if podcasts %} {% if podcasts %}

View File

@ -8,7 +8,7 @@
{% block content_extras %} {% block content_extras %}
<section class="schedule"> <section class="schedule">
{% if page.program.active %} {% if page.program.active and page.program.schedule_set.count %}
<h2>{% trans "Schedule" %}</h2> <h2>{% trans "Schedule" %}</h2>
<ul> <ul>
{% for schedule in page.program.schedule_set.all %} {% for schedule in page.program.schedule_set.all %}

View File

@ -29,6 +29,7 @@ class DiffusionAdmin(ModelAdmin):
menu_order = 200 menu_order = 200
list_display = ('program', 'start', 'end', 'type', 'initial') list_display = ('program', 'start', 'end', 'type', 'initial')
list_filter = ('program', 'start', 'type') list_filter = ('program', 'start', 'type')
readonly_fields = ('conflicts',)
search_fields = ('program__name', 'start') search_fields = ('program__name', 'start')
class ScheduleAdmin(ModelAdmin): class ScheduleAdmin(ModelAdmin):
@ -99,12 +100,21 @@ class GenericMenu(Menu):
super().__init__('') super().__init__('')
def get_queryset(self): def get_queryset(self):
"""
Return a queryset of items used to display menu
"""
pass pass
def get_title(self, item): def get_title(self, item):
"""
Return the title of a menu-item for the given item
"""
pass pass
def get_parent(self, item): def get_parent(self, item):
"""
Return id of the parent page for the given item
"""
pass pass
def get_page_url(self, page_model, item): def get_page_url(self, page_model, item):
@ -150,7 +160,12 @@ class DiffusionsMenu(GenericMenu):
).order_by('start') ).order_by('start')
def get_title(self, item): def get_title(self, item):
return item.program.name from django.utils.safestring import mark_safe
title = '<i class="info">{}</i> {}'.format(
item.start.strftime('%H:%M'),
item.program.name
)
return mark_safe(title)
def get_parent(self, item): def get_parent(self, item):
return item.program.page.first() return item.program.page.first()

View File

@ -1,6 +1,21 @@
This file is used as a reminder, can be used as crappy documentation too. This file is used as a reminder, can be used as crappy documentation too.
# for the 1.0
- sounds:
- detect id3 tags
- run tests:
- streamer: dealer & streams hours (to localtime)
x diffusions: update & check algorithms
x check in templates
x diffusion page date info
- streamer:
- restart running streamer on demand
- add restart button for the streamer
- cms:
- button to select the current station
# conventions # conventions
## coding style ## coding style
* name of classes relative to a class: * name of classes relative to a class:
@ -40,11 +55,6 @@ cms:
- comments -> remove/edit by the author - comments -> remove/edit by the author
# Timezone shit: # Timezone shit:
- run tests:
- streamer: dealer & streams hours (to localtime)
- diffusions: update & check algorithms
x check in templates
x diffusion page date info
# Instance's TODO # Instance's TODO
- menu_top .sections: - menu_top .sections: