forked from rc/aircox
		
	work on pages, filters, lists
This commit is contained in:
		@ -11,11 +11,8 @@ __all__ = ['ArticleAdmin']
 | 
			
		||||
 | 
			
		||||
@admin.register(Article)
 | 
			
		||||
class ArticleAdmin(PageAdmin):
 | 
			
		||||
    list_display = PageAdmin.list_display + ('program',)
 | 
			
		||||
    list_filter = PageAdmin.list_filter + ('program',)
 | 
			
		||||
    search_fields = PageAdmin.search_fields + ['program__title']
 | 
			
		||||
    list_filter = PageAdmin.list_filter
 | 
			
		||||
    search_fields = PageAdmin.search_fields + ['parent__title']
 | 
			
		||||
    # TODO: readonly field
 | 
			
		||||
 | 
			
		||||
    fieldsets = copy.deepcopy(PageAdmin.fieldsets)
 | 
			
		||||
    fieldsets[1][1]['fields'].insert(0, 'program')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,13 +48,11 @@ class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline):
 | 
			
		||||
 | 
			
		||||
@admin.register(Episode)
 | 
			
		||||
class EpisodeAdmin(PageAdmin):
 | 
			
		||||
    list_display = PageAdmin.list_display + ('program',)
 | 
			
		||||
    list_filter = PageAdmin.list_filter + ('program',)
 | 
			
		||||
    search_fields = PageAdmin.search_fields + ['program__title']
 | 
			
		||||
    readonly_fields = ('program',)
 | 
			
		||||
    list_display = PageAdmin.list_display
 | 
			
		||||
    list_filter = PageAdmin.list_filter
 | 
			
		||||
    search_fields = PageAdmin.search_fields + ['parent__title']
 | 
			
		||||
    # readonly_fields = ('parent',)
 | 
			
		||||
 | 
			
		||||
    fieldsets = copy.deepcopy(PageAdmin.fieldsets)
 | 
			
		||||
    fieldsets[1][1]['fields'].insert(0, 'program')
 | 
			
		||||
    inlines = [TracksInline, SoundInline, DiffusionInline]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ class CategoryAdmin(admin.ModelAdmin):
 | 
			
		||||
 | 
			
		||||
# limit category choice
 | 
			
		||||
class PageAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('cover_thumb', 'title', 'status', 'category')
 | 
			
		||||
    list_display = ('cover_thumb', 'title', 'status', 'category', 'parent')
 | 
			
		||||
    list_display_links = ('cover_thumb', 'title')
 | 
			
		||||
    list_editable = ('status', 'category')
 | 
			
		||||
    list_filter = ('status', 'category')
 | 
			
		||||
@ -33,7 +33,7 @@ class PageAdmin(admin.ModelAdmin):
 | 
			
		||||
            'fields': ['title', 'slug', 'category', 'cover', 'content'],
 | 
			
		||||
        }),
 | 
			
		||||
        (_('Publication Settings'), {
 | 
			
		||||
            'fields': ['featured', 'allow_comments', 'status'],
 | 
			
		||||
            'fields': ['featured', 'allow_comments', 'status', 'parent'],
 | 
			
		||||
            'classes': ('collapse',),
 | 
			
		||||
        }),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,8 @@ class WeekConverter:
 | 
			
		||||
        return datetime.datetime.strptime(value + '/1', '%G/%V/%u').date()
 | 
			
		||||
 | 
			
		||||
    def to_url(self, value):
 | 
			
		||||
        return '{:04d}/{:02d}'.format(*value.isocalendar())
 | 
			
		||||
        return value if isinstance(value, str) else \
 | 
			
		||||
            '{:04d}/{:02d}'.format(*value.isocalendar())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DateConverter:
 | 
			
		||||
@ -41,8 +42,9 @@ class DateConverter:
 | 
			
		||||
    regex = r'[0-9]{4}/[0-9]{2}/[0-9]{2}'
 | 
			
		||||
 | 
			
		||||
    def to_python(self, value):
 | 
			
		||||
        return str_to_date(value)
 | 
			
		||||
        value = value.split('/')[:3]
 | 
			
		||||
        return datetime.date(int(value[0]), int(value[1]), int(value[2]))
 | 
			
		||||
 | 
			
		||||
    def to_url(self, value):
 | 
			
		||||
        return '{:04d}/{:02d}/{:02d}'.format(value.year, value.month,
 | 
			
		||||
                                             value.day)
 | 
			
		||||
        return value if isinstance(value, str) else \
 | 
			
		||||
            '{:04d}/{:02d}/{:02d}'.format(value.year, value.month, value.day)
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@ -2,28 +2,20 @@ from django.db import models
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from .page import Page, PageQuerySet
 | 
			
		||||
from .program import Program, InProgramQuerySet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ArticleQuerySet(InProgramQuerySet, PageQuerySet):
 | 
			
		||||
    pass
 | 
			
		||||
from .program import Program, ProgramChildQuerySet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Article(Page):
 | 
			
		||||
    detail_url_name = 'article-detail'
 | 
			
		||||
 | 
			
		||||
    program = models.ForeignKey(
 | 
			
		||||
        Program, models.SET_NULL,
 | 
			
		||||
        verbose_name=_('program'), blank=True, null=True,
 | 
			
		||||
        help_text=_("publish as this program's article"),
 | 
			
		||||
    )
 | 
			
		||||
    is_static = models.BooleanField(
 | 
			
		||||
        _('is static'), default=False,
 | 
			
		||||
        help_text=_('Should this article be considered as a page '
 | 
			
		||||
                    'instead of a blog article'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    objects = ArticleQuerySet.as_manager()
 | 
			
		||||
    objects = ProgramChildQuerySet.as_manager()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _('Article')
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ from django.utils.functional import cached_property
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from aircox import settings, utils
 | 
			
		||||
from .program import Program, InProgramQuerySet, \
 | 
			
		||||
from .program import Program, ProgramChildQuerySet, \
 | 
			
		||||
        BaseRerun, BaseRerunQuerySet
 | 
			
		||||
from .page import Page, PageQuerySet
 | 
			
		||||
 | 
			
		||||
@ -16,18 +16,18 @@ from .page import Page, PageQuerySet
 | 
			
		||||
__all__ = ['Episode', 'Diffusion', 'DiffusionQuerySet']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EpisodeQuerySet(PageQuerySet, InProgramQuerySet):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Episode(Page):
 | 
			
		||||
    program = models.ForeignKey(
 | 
			
		||||
        Program, models.CASCADE,
 | 
			
		||||
        verbose_name=_('program'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    objects = EpisodeQuerySet.as_manager()
 | 
			
		||||
    objects = ProgramChildQuerySet.as_manager()
 | 
			
		||||
    detail_url_name = 'episode-detail'
 | 
			
		||||
    item_template_name = 'aircox/episode_item.html'
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def program(self):
 | 
			
		||||
        return getattr(self.parent, 'program', None)
 | 
			
		||||
 | 
			
		||||
    @program.setter
 | 
			
		||||
    def program(self, value):
 | 
			
		||||
        self.parent = value
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _('Episode')
 | 
			
		||||
@ -41,6 +41,8 @@ class Episode(Page):
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        if self.cover is None:
 | 
			
		||||
            self.cover = self.program.cover
 | 
			
		||||
        if self.parent is None:
 | 
			
		||||
            raise ValueError('missing parent program')
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
@ -155,6 +157,8 @@ class Diffusion(BaseRerun):
 | 
			
		||||
    #    help_text = _('use this input port'),
 | 
			
		||||
    # )
 | 
			
		||||
 | 
			
		||||
    item_template_name = 'aircox/diffusion_item.html'
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _('Diffusion')
 | 
			
		||||
        verbose_name_plural = _('Diffusions')
 | 
			
		||||
 | 
			
		||||
@ -46,6 +46,11 @@ class PageQuerySet(InheritanceQuerySet):
 | 
			
		||||
    def trash(self):
 | 
			
		||||
        return self.filter(status=Page.STATUS_TRASH)
 | 
			
		||||
 | 
			
		||||
    def parent(self, parent=None, id=None):
 | 
			
		||||
        """ Return pages having this parent. """
 | 
			
		||||
        return self.filter(parent=parent) if id is None else \
 | 
			
		||||
               self.filter(parent__id=id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Page(models.Model):
 | 
			
		||||
    """ Base class for publishable content """
 | 
			
		||||
@ -58,6 +63,8 @@ class Page(models.Model):
 | 
			
		||||
        (STATUS_TRASH, _('trash')),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True,
 | 
			
		||||
                               related_name='child_set')
 | 
			
		||||
    title = models.CharField(max_length=128)
 | 
			
		||||
    slug = models.SlugField(_('slug'), blank=True, unique=True)
 | 
			
		||||
    status = models.PositiveSmallIntegerField(
 | 
			
		||||
@ -74,7 +81,7 @@ class Page(models.Model):
 | 
			
		||||
    content = RichTextField(
 | 
			
		||||
        _('content'), blank=True, null=True,
 | 
			
		||||
    )
 | 
			
		||||
    date = models.DateTimeField(default=tz.now)
 | 
			
		||||
    pub_date = models.DateTimeField(blank=True, null=True)
 | 
			
		||||
    featured = models.BooleanField(
 | 
			
		||||
        _('featured'), default=False,
 | 
			
		||||
    )
 | 
			
		||||
@ -85,17 +92,19 @@ class Page(models.Model):
 | 
			
		||||
    objects = PageQuerySet.as_manager()
 | 
			
		||||
 | 
			
		||||
    detail_url_name = None
 | 
			
		||||
 | 
			
		||||
    item_template_name = 'aircox/page_item.html'
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return '{}: {}'.format(self._meta.verbose_name,
 | 
			
		||||
                               self.title or self.pk)
 | 
			
		||||
        return '{}'.format(self.title or self.pk)
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        # TODO: ensure unique slug
 | 
			
		||||
        if not self.slug:
 | 
			
		||||
            self.slug = slugify(self.title)
 | 
			
		||||
        print(self.title, '--', self.slug)
 | 
			
		||||
        if self.is_published and self.pub_date is None:
 | 
			
		||||
            self.pub_date = tz.datetime.now()
 | 
			
		||||
        elif not self.is_published:
 | 
			
		||||
            self.pub_date = None
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_absolute_url(self):
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,8 @@ from .station import Station
 | 
			
		||||
logger = logging.getLogger('aircox')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['Program', 'ProgramQuerySet', 'Stream', 'Schedule']
 | 
			
		||||
__all__ = ['Program', 'ProgramQuerySet', 'Stream', 'Schedule',
 | 
			
		||||
           'ProgramChildQuerySet', 'BaseRerun', 'BaseRerunQuerySet']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProgramQuerySet(PageQuerySet):
 | 
			
		||||
@ -49,15 +50,8 @@ class Program(Page):
 | 
			
		||||
    name if it does not exists.
 | 
			
		||||
    """
 | 
			
		||||
    # explicit foreign key in order to avoid related name clashes
 | 
			
		||||
    page = models.OneToOneField(
 | 
			
		||||
        Page, models.CASCADE,
 | 
			
		||||
        parent_link=True, related_name='program_page'
 | 
			
		||||
    )
 | 
			
		||||
    station = models.ForeignKey(
 | 
			
		||||
        Station,
 | 
			
		||||
        verbose_name=_('station'),
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
    )
 | 
			
		||||
    station = models.ForeignKey(Station, models.CASCADE,
 | 
			
		||||
                                verbose_name=_('station'))
 | 
			
		||||
    active = models.BooleanField(
 | 
			
		||||
        _('active'),
 | 
			
		||||
        default=True,
 | 
			
		||||
@ -146,10 +140,17 @@ class Program(Page):
 | 
			
		||||
                 .update(path=Concat('path', Substr(F('path'), len(path_))))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InProgramQuerySet(models.QuerySet):
 | 
			
		||||
    """
 | 
			
		||||
    Queryset for model having a ForeignKey field "program" to `Program`.
 | 
			
		||||
    """
 | 
			
		||||
class ProgramChildQuerySet(PageQuerySet):
 | 
			
		||||
    def station(self, station=None, id=None):
 | 
			
		||||
        return self.filter(parent__program__station=station) if id is None else \
 | 
			
		||||
               self.filter(parent__program__station__id=id)
 | 
			
		||||
 | 
			
		||||
    def program(self, program=None, id=None):
 | 
			
		||||
        return self.parent(program, id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseRerunQuerySet(models.QuerySet):
 | 
			
		||||
    """ Queryset for BaseRerun (sub)classes. """
 | 
			
		||||
    def station(self, station=None, id=None):
 | 
			
		||||
        return self.filter(program__station=station) if id is None else \
 | 
			
		||||
               self.filter(program__station__id=id)
 | 
			
		||||
@ -158,9 +159,6 @@ class InProgramQuerySet(models.QuerySet):
 | 
			
		||||
        return self.filter(program=program) if id is None else \
 | 
			
		||||
               self.filter(program__id=id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseRerunQuerySet(InProgramQuerySet):
 | 
			
		||||
    """ Queryset for BaseRerun (sub)classes. """
 | 
			
		||||
    def rerun(self):
 | 
			
		||||
        return self.filter(initial__isnull=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -93,7 +93,7 @@ def schedule_pre_delete(sender, instance, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
@receiver(signals.post_delete, sender=Diffusion)
 | 
			
		||||
def diffusion_post_delete(sender, instance, *args, **kwargs):
 | 
			
		||||
    Episode.objects.filter(diffusion__isnull=True, content_isnull=True,
 | 
			
		||||
    Episode.objects.filter(diffusion__isnull=True, content__isnull=True,
 | 
			
		||||
                           sound__isnull=True) \
 | 
			
		||||
                   .delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,9 @@
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  margin: 1em 0em; }
 | 
			
		||||
 | 
			
		||||
ul.menu-list li {
 | 
			
		||||
  list-style-type: none; }
 | 
			
		||||
 | 
			
		||||
@keyframes spinAround {
 | 
			
		||||
  from {
 | 
			
		||||
    transform: rotate(0deg); }
 | 
			
		||||
@ -7180,6 +7183,11 @@ label.panel-block {
 | 
			
		||||
.has-background-transparent {
 | 
			
		||||
  background-color: transparent; }
 | 
			
		||||
 | 
			
		||||
.is-opacity-light {
 | 
			
		||||
  opacity: 0.7; }
 | 
			
		||||
  .is-opacity-light:hover {
 | 
			
		||||
    opacity: 1; }
 | 
			
		||||
 | 
			
		||||
.navbar + .container {
 | 
			
		||||
  margin-top: 1em; }
 | 
			
		||||
 | 
			
		||||
@ -7192,6 +7200,29 @@ a.navbar-item.is-active {
 | 
			
		||||
.navbar .navbar-dropdown {
 | 
			
		||||
  z-index: 2000; }
 | 
			
		||||
 | 
			
		||||
.navbar .navbar-split {
 | 
			
		||||
  margin: 0.2em 0em;
 | 
			
		||||
  margin-right: 1em;
 | 
			
		||||
  padding-right: 1em;
 | 
			
		||||
  border-right: 1px #b5b5b5 solid;
 | 
			
		||||
  display: inline-block; }
 | 
			
		||||
 | 
			
		||||
.navbar form {
 | 
			
		||||
  margin: 0em;
 | 
			
		||||
  padding: 0em; }
 | 
			
		||||
 | 
			
		||||
.filters {
 | 
			
		||||
  margin: 1em 0em;
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  margin-bottom: 1em; }
 | 
			
		||||
  .filters .title {
 | 
			
		||||
    padding-right: 2em;
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
    border-right: 1px #b5b5b5 solid;
 | 
			
		||||
    font-size: 1.25rem;
 | 
			
		||||
    color: #7a7a7a;
 | 
			
		||||
    font-weight: 300; }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
.navbar-brand img {
 | 
			
		||||
    min-height: 6em;
 | 
			
		||||
 | 
			
		||||
@ -305,7 +305,7 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse
 | 
			
		||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n\n\nconst splitReg = new RegExp(`,\\s*`, 'g');\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n    data() {\n        return {\n            counts: {},\n        }\n    },\n\n    methods: {\n        update() {\n            const items = this.$el.querySelectorAll('input[name=\"data\"]:checked')\n            const counts = {};\n\n            console.log(items)\n            for(var item of items)\n                if(item.value)\n                    for(var tag of item.value.split(splitReg))\n                        counts[tag.trim()] = (counts[tag.trim()] || 0) + 1;\n            this.counts = counts;\n            console.log('counts', this.counts)\n        }\n    },\n\n    mounted() {\n        this.$refs.form.addEventListener('change', () => this.update())\n        this.update()\n    }\n});\n\n\n//# sourceURL=webpack:///./assets/admin/statistics.vue?./node_modules/vue-loader/lib??vue-loader-options");
 | 
			
		||||
eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n\n\nconst splitReg = new RegExp(`,\\s*`, 'g');\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n    data() {\n        return {\n            counts: {},\n        }\n    },\n\n    methods: {\n        update() {\n            const items = this.$el.querySelectorAll('input[name=\"data\"]:checked')\n            const counts = {};\n\n            console.log(items)\n            for(var item of items)\n                if(item.value)\n                    for(var tag of item.value.split(splitReg))\n                        counts[tag.trim()] = (counts[tag.trim()] || 0) + 1;\n            this.counts = counts;\n        }\n    },\n\n    mounted() {\n        this.$refs.form.addEventListener('change', () => this.update())\n        this.update()\n    }\n});\n\n\n//# sourceURL=webpack:///./assets/admin/statistics.vue?./node_modules/vue-loader/lib??vue-loader-options");
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7162,6 +7162,11 @@ label.panel-block {
 | 
			
		||||
.has-background-transparent {
 | 
			
		||||
  background-color: transparent; }
 | 
			
		||||
 | 
			
		||||
.is-opacity-light {
 | 
			
		||||
  opacity: 0.7; }
 | 
			
		||||
  .is-opacity-light:hover {
 | 
			
		||||
    opacity: 1; }
 | 
			
		||||
 | 
			
		||||
.navbar + .container {
 | 
			
		||||
  margin-top: 1em; }
 | 
			
		||||
 | 
			
		||||
@ -7174,6 +7179,29 @@ a.navbar-item.is-active {
 | 
			
		||||
.navbar .navbar-dropdown {
 | 
			
		||||
  z-index: 2000; }
 | 
			
		||||
 | 
			
		||||
.navbar .navbar-split {
 | 
			
		||||
  margin: 0.2em 0em;
 | 
			
		||||
  margin-right: 1em;
 | 
			
		||||
  padding-right: 1em;
 | 
			
		||||
  border-right: 1px #b5b5b5 solid;
 | 
			
		||||
  display: inline-block; }
 | 
			
		||||
 | 
			
		||||
.navbar form {
 | 
			
		||||
  margin: 0em;
 | 
			
		||||
  padding: 0em; }
 | 
			
		||||
 | 
			
		||||
.filters {
 | 
			
		||||
  margin: 1em 0em;
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  margin-bottom: 1em; }
 | 
			
		||||
  .filters .title {
 | 
			
		||||
    padding-right: 2em;
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
    border-right: 1px #b5b5b5 solid;
 | 
			
		||||
    font-size: 1.25rem;
 | 
			
		||||
    color: #7a7a7a;
 | 
			
		||||
    font-weight: 300; }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
.navbar-brand img {
 | 
			
		||||
    min-height: 6em;
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
{% block content %}{{ block.super }}
 | 
			
		||||
{# TODO: date subtitle #}
 | 
			
		||||
<a-statistics>
 | 
			
		||||
<div class="columns">
 | 
			
		||||
 | 
			
		||||
<a-statistics class="column">
 | 
			
		||||
<template v-slot:default="{counts}">
 | 
			
		||||
    <table class="table is-hoverable is-fullwidth">
 | 
			
		||||
        <thead>
 | 
			
		||||
@ -63,6 +65,15 @@
 | 
			
		||||
</template>
 | 
			
		||||
</a-statistics>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<nav class="column menu is-one-fifth-desktop" role="menu">
 | 
			
		||||
    {% with "admin:tools-stats" as url_name %}
 | 
			
		||||
    {% include "aircox/widgets/dates_menu.html" %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,7 @@
 | 
			
		||||
                    <a class="navbar-link" href="{% url "admin:aircox_article_changelist" %}">{% trans "Articles" %}</a>
 | 
			
		||||
                    <div class="navbar-dropdown is-boxed is-right">
 | 
			
		||||
                        {% for program in programs %}
 | 
			
		||||
                        <a class="navbar-item" href="{% url "admin:aircox_article_changelist" %}?program={{ program.pk }}">
 | 
			
		||||
                        <a class="navbar-item" href="{% url "admin:aircox_article_changelist" %}?parent={{ program.pk }}">
 | 
			
		||||
                            {{ program.title }}</a>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
@ -56,7 +56,7 @@
 | 
			
		||||
                    <a class="navbar-link" href="{% url "admin:aircox_episode_changelist" %}">{% trans "Episodes" %}</a>
 | 
			
		||||
                    <div class="navbar-dropdown is-boxed is-right">
 | 
			
		||||
                        {% for program in programs %}
 | 
			
		||||
                        <a class="navbar-item" href="{% url "admin:aircox_episode_changelist" %}?program={{ program.pk }}">
 | 
			
		||||
                        <a class="navbar-item" href="{% url "admin:aircox_episode_changelist" %}?parent={{ program.pk }}">
 | 
			
		||||
                            {{ program.title }}</a>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
{% extends "aircox/page_detail.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block side_nav %}
 | 
			
		||||
{% block sidebar %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
{% if side_items %}
 | 
			
		||||
{% if sidebar_items %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h4 class="title is-4">{% trans "Latest news" %}</h4>
 | 
			
		||||
 | 
			
		||||
    {% for object in side_items %}
 | 
			
		||||
    {% for object in sidebar_items %}
 | 
			
		||||
    {% include "aircox/page_item.html" %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@
 | 
			
		||||
Context:
 | 
			
		||||
- cover: image cover
 | 
			
		||||
- site: current website
 | 
			
		||||
- has_filters: display filter bar (using block "filters")
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
@ -71,19 +72,50 @@ Context:
 | 
			
		||||
                            {% endblock %}
 | 
			
		||||
                        </header>
 | 
			
		||||
 | 
			
		||||
                        {% if has_filters %}
 | 
			
		||||
                        <nav class="navbar filters"
 | 
			
		||||
                            aria-label="{% trans "list filters" %}">
 | 
			
		||||
                        {% block filters %}{% endblock %}
 | 
			
		||||
                        </nav>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
 | 
			
		||||
                        {% block main %}{% endblock main %}
 | 
			
		||||
                    </main>
 | 
			
		||||
 | 
			
		||||
                    {% if show_side_nav %}
 | 
			
		||||
                    {% if has_sidebar %}
 | 
			
		||||
                    <aside class="column is-one-third-desktop">
 | 
			
		||||
                        {# FIXME: block cover into side_nav one #}
 | 
			
		||||
                        {# FIXME: block cover into sidebar one #}
 | 
			
		||||
                        {% block cover %}
 | 
			
		||||
                        {% if cover is not None %}
 | 
			
		||||
                        <img class="cover" src="{{ cover.url }}" class="cover"/>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        {% endblock %}
 | 
			
		||||
 | 
			
		||||
                        {% block side_nav %}
 | 
			
		||||
                        {% block sidebar %}
 | 
			
		||||
                        {% if sidebar_items %} 
 | 
			
		||||
                        <section>
 | 
			
		||||
                            <h4 class="title is-4">
 | 
			
		||||
                                {% block sidebar_title %}{% trans "Recently" %}{% endblock %}
 | 
			
		||||
                            </h4>
 | 
			
		||||
 | 
			
		||||
                        {% for object in sidebar_items %}
 | 
			
		||||
                        {% include "aircox/episode_item.html" %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
 | 
			
		||||
                        <br>
 | 
			
		||||
                        <nav class="pagination is-centered">
 | 
			
		||||
                            <ul class="pagination-list">
 | 
			
		||||
                                <li>
 | 
			
		||||
                                    <a {% if parent %}href="{% url "page-list" parent_slug=parent.slug %}"{% else %}href="{% url "page-list" %}"{% endif %}
 | 
			
		||||
                                        class="pagination-link"
 | 
			
		||||
                                        aria-label="{% trans "Show all program's diffusions" %}">
 | 
			
		||||
                                        {% trans "Show more" %}
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </li>
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </nav>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        </section>
 | 
			
		||||
                        {% endblock %}
 | 
			
		||||
                    </aside>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								aircox/templates/aircox/diffusion_item.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								aircox/templates/aircox/diffusion_item.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
{% comment %}
 | 
			
		||||
Context:
 | 
			
		||||
- object: diffusion
 | 
			
		||||
- "episode_item"'s context (except object and diffusion)
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% with object as diffusion %}
 | 
			
		||||
{% with object.episode as object %}
 | 
			
		||||
{% include "aircox/episode_item.html" %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
{% extends "aircox/page.html" %}
 | 
			
		||||
{% load i18n aircox %}
 | 
			
		||||
{% load i18n aircox humanize %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
{% with station.name as station %}
 | 
			
		||||
@ -9,17 +9,22 @@
 | 
			
		||||
 | 
			
		||||
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main %}{{ block.super }}
 | 
			
		||||
<div class="columns">
 | 
			
		||||
{% block filters %}
 | 
			
		||||
{% with "diffusion-list" as url_name %}
 | 
			
		||||
{% include "aircox/widgets/dates_menu.html" %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main %}{{ block.super }}
 | 
			
		||||
{% with True as hide_schedule %}
 | 
			
		||||
<section class="column">
 | 
			
		||||
<section role="list">
 | 
			
		||||
    <div id="timetable-{{ date|date:"Y-m-d" }}">
 | 
			
		||||
        {% for diffusion in object_list %}
 | 
			
		||||
        <div class="columns">
 | 
			
		||||
        {# FIXME: opacity should work -- maybe hidden tz #}
 | 
			
		||||
        <div class="columns {% if diffusion.start.date != date and diffusion.start.end <= date %}is-opacity-light{% endif %}">
 | 
			
		||||
            <div class="column is-one-fifth has-text-right">
 | 
			
		||||
                <time datetime="{{ diffusion.start|date:"c" }}">
 | 
			
		||||
                {{ diffusion.start|date:"H:i" }} - {{ diffusion.end|date:"H:i" }}
 | 
			
		||||
                {{ diffusion.start|date:"d H:i" }} - {{ diffusion.end|date:"d H:i" }}
 | 
			
		||||
                </time>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="column">
 | 
			
		||||
@ -33,12 +38,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
{% endwith %}
 | 
			
		||||
 | 
			
		||||
{% comment %}
 | 
			
		||||
<nav class="column menu is-one-third-desktop" role="menu">
 | 
			
		||||
    {% with "diffusion-list" as url_name %}
 | 
			
		||||
    {% include "aircox/widgets/dates_menu.html" %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
</nav>
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,9 +11,7 @@ for design review.
 | 
			
		||||
 | 
			
		||||
{% if object|is_diffusion %}
 | 
			
		||||
    {% with object as diffusion %}
 | 
			
		||||
    {% with diffusion.episode as object %}
 | 
			
		||||
    {% include "aircox/episode_item.html" %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
    {% include "aircox/diffusion_item.html" %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
{% else %}
 | 
			
		||||
    {% with object.track as object %}
 | 
			
		||||
 | 
			
		||||
@ -10,14 +10,17 @@
 | 
			
		||||
 | 
			
		||||
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block filters %}
 | 
			
		||||
{% with "log-list" as url_name %}
 | 
			
		||||
{% include "aircox/widgets/dates_menu.html" %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main %}
 | 
			
		||||
<div class="columns">
 | 
			
		||||
 | 
			
		||||
<section class="section column">
 | 
			
		||||
<section>
 | 
			
		||||
    {# <h4 class="subtitle size-4">{{ date }}</h4> #}
 | 
			
		||||
    {% with True as hide_schedule %}
 | 
			
		||||
    <table class="table is-striped is-hoverable is-fullwidth has-background-transparent">
 | 
			
		||||
    <table class="table is-striped is-hoverable is-fullwidth">
 | 
			
		||||
        {% for object in object_list %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
@ -37,13 +40,5 @@
 | 
			
		||||
    </table>
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
<nav class="column menu is-one-third-desktop" role="menu">
 | 
			
		||||
    {% with "logs" as url_name %}
 | 
			
		||||
    {% include "aircox/widgets/dates_menu.html" %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,63 +2,65 @@
 | 
			
		||||
{% load i18n aircox %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
{{ view.model|verbose_name:True|title }}
 | 
			
		||||
{% if not parent %}{{ view.model|verbose_name:True|title }}
 | 
			
		||||
{% else %}
 | 
			
		||||
{% with parent.title as title %}
 | 
			
		||||
{% blocktrans %}Publications of {{ title }}{% endblocktrans %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block side_nav %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
{% if filter_categories|length != 1 %}
 | 
			
		||||
<section class="toolbar">
 | 
			
		||||
    <h4 class="subtitle is-5">{% trans "Filters" %}</h4>
 | 
			
		||||
    <form method="GET" action="">
 | 
			
		||||
        {% block list_filters %}
 | 
			
		||||
        <div class="field is-horizontal">
 | 
			
		||||
            <div class="field-label">
 | 
			
		||||
                <label class="label">{% trans "Categories" %}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="field-body">
 | 
			
		||||
                <div class="field is-grouped is-narrow">
 | 
			
		||||
                    <div class="control">
 | 
			
		||||
                        {% for category in filter_categories %}
 | 
			
		||||
                        <label class="checkbox">
 | 
			
		||||
                            <input type="checkbox" class="checkbox" name="categories"
 | 
			
		||||
                                   value="{{ category.slug }}"
 | 
			
		||||
                                   {% if category.slug in categories %}checked{% endif %} />
 | 
			
		||||
                            {{ category.title }}
 | 
			
		||||
                        </label>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
{% block filters %}
 | 
			
		||||
<div class="navbar-branding">
 | 
			
		||||
    <h4 class="navbar-item title">{% trans "Filters" %}</h4>
 | 
			
		||||
</div>
 | 
			
		||||
<form method="GET" action="" class="navbar-menu">
 | 
			
		||||
    <div class="navbar-start">
 | 
			
		||||
        <div class="navbar-item">
 | 
			
		||||
            {% block list_filters %}
 | 
			
		||||
            <div class="field is-horizontal">
 | 
			
		||||
                <div class="field-label">
 | 
			
		||||
                    <label class="label">{% trans "Categories" %}</label>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="field-body">
 | 
			
		||||
                    <div class="field is-grouped is-narrow">
 | 
			
		||||
                        <div class="control">
 | 
			
		||||
                            {% for category in filter_categories %}
 | 
			
		||||
                            <label class="checkbox">
 | 
			
		||||
                                <input type="checkbox" class="checkbox" name="categories"
 | 
			
		||||
                                       value="{{ category.slug }}"
 | 
			
		||||
                                       {% if category.slug in categories %}checked{% endif %} />
 | 
			
		||||
                                {{ category.title }}
 | 
			
		||||
                            </label>
 | 
			
		||||
                            {% endfor %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endblock %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
        <div class="field is-horizontal">
 | 
			
		||||
            <div class="field-label">
 | 
			
		||||
                <label class="label"></label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="field-body">
 | 
			
		||||
                <div class="field is-grouped is-grouped-right">
 | 
			
		||||
                    <div class="control">
 | 
			
		||||
                        <button class="button is-primary"/>{% trans "Apply" %}</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="control">
 | 
			
		||||
                        <a href="?" class="button is-secondary">{% trans "Reset" %}</a>
 | 
			
		||||
                    </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="navbar-end">
 | 
			
		||||
        <div class="navbar-item">
 | 
			
		||||
            <div class="field is-grouped is-grouped-right">
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    <button class="button is-primary"/>{% trans "Apply" %}</button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    <a href="?" class="button is-secondary">{% trans "Reset" %}</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block main %}
 | 
			
		||||
<section>
 | 
			
		||||
<section role="list">
 | 
			
		||||
{% for object in object_list %}
 | 
			
		||||
{% block list_object %}
 | 
			
		||||
{% include item_template_name %}
 | 
			
		||||
{% include object.item_template_name|default:item_template_name %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,16 @@
 | 
			
		||||
{% extends "aircox/page_detail.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block side_nav %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
{% if side_items %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h4 class="title is-4">{% trans "Last shows" %}</h4>
 | 
			
		||||
 | 
			
		||||
    {% for object in side_items %}
 | 
			
		||||
    {% include "aircox/episode_item.html" %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
    <nav class="pagination is-centered">
 | 
			
		||||
        <ul class="pagination-list">
 | 
			
		||||
            <li>
 | 
			
		||||
                <a href="{% url "episode-list" parent_slug=program.slug %}"
 | 
			
		||||
                    class="pagination-link"
 | 
			
		||||
                    aria-label="{% trans "Show all program's diffusions" %}">
 | 
			
		||||
                    {% trans "More shows" %}
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </nav>
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% block sidebar_title %}
 | 
			
		||||
{% with program.title as program %}
 | 
			
		||||
{% blocktrans %} Recently on {{ program }}{% endblocktrans %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block sidebar %}
 | 
			
		||||
{% with program as parent %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,9 +4,9 @@ Context:
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
 | 
			
		||||
<span class="has-text-info is-size-5">♬</span>
 | 
			
		||||
<span>{{ track.title }}</span>
 | 
			
		||||
<span>{{ object.title }}</span>
 | 
			
		||||
<span class="has-text-grey-dark has-text-weight-light">
 | 
			
		||||
— {{ track.artist }}
 | 
			
		||||
{% if track.info %}(<i>{{ track.info }}</i>){% endif %}
 | 
			
		||||
— {{ object.artist }}
 | 
			
		||||
{% if object.info %}(<i>{{ object.info }}</i>){% endif %}
 | 
			
		||||
</span>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,40 +9,35 @@ Context:
 | 
			
		||||
 | 
			
		||||
An empty date results to a title or a separator
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n humanize %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<nav class="menu is-one-third-desktop " role="menu"
 | 
			
		||||
<div class="navbar-menu" role="menu"
 | 
			
		||||
        aria-label="{% trans "pick a date" %}">
 | 
			
		||||
    <p class="menu-label">{% trans "Pick a date" %}</p>
 | 
			
		||||
    <form action="{% url url_name %}" method="GET"
 | 
			
		||||
            aria-label="{% trans "Jump to date" %}">
 | 
			
		||||
        <div class="field has-addons">
 | 
			
		||||
            <div class="control has-icons-left">
 | 
			
		||||
                <span class="icon is-small is-left"><span class="far fa-calendar"></span></span>
 | 
			
		||||
                <input type="{{ date_input|default:"date" }}" class="input date"
 | 
			
		||||
                       name="date" value="{{ date|date:"Y-m-d" }}">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="control">
 | 
			
		||||
                <button class="button is-primary">{% trans "Go" %}</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
    <ul class="menu-list">
 | 
			
		||||
        {% for day, title in dates %}
 | 
			
		||||
        {% if not day %}
 | 
			
		||||
            {% if title %}
 | 
			
		||||
            </ul>
 | 
			
		||||
            <p class="menu-label">{{ title }}</p>
 | 
			
		||||
            <ul class="menu-list">
 | 
			
		||||
            {% else %}<hr class="dropdown-divider">{% endif %}
 | 
			
		||||
        {% else %}
 | 
			
		||||
        <li><a href="{% url url_name date=day %}" {% if day == date %}class="is-active"{% endif %}>
 | 
			
		||||
            {% if title %}{{ title }}{% else %}{{ day|naturalday:"l d" }}{% endif %}
 | 
			
		||||
            </a></li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    <div class="navbar-start">
 | 
			
		||||
        {% for day in dates %}
 | 
			
		||||
        <a href="{% url url_name date=day %}" class="navbar-item {% if day == date %}is-active{% endif %}">
 | 
			
		||||
            {{ day|date:"D. d" }}
 | 
			
		||||
            </a>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </ul>
 | 
			
		||||
</nav>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="navbar-end">
 | 
			
		||||
        <div class="navbar-item">
 | 
			
		||||
            <form action="{% url url_name %}" method="GET" class="navbar-body"
 | 
			
		||||
                    aria-label="{% trans "Jump to date" %}">
 | 
			
		||||
                <div class="field has-addons">
 | 
			
		||||
                    <div class="control has-icons-left">
 | 
			
		||||
                        <span class="icon is-small is-left"><span class="far fa-calendar"></span></span>
 | 
			
		||||
                        <input type="{{ date_input|default:"date" }}" class="input date"
 | 
			
		||||
                               name="date" value="{{ date|date:"Y-m-d" }}">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="control">
 | 
			
		||||
                        <button class="button is-primary">{% trans "Go" %}</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,8 @@ api = [
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
urls = [
 | 
			
		||||
    path(_(''),
 | 
			
		||||
         views.DiffusionListView.as_view(), name='home'),
 | 
			
		||||
    path('api/', include(api)),
 | 
			
		||||
 | 
			
		||||
    # path('', views.PageDetailView.as_view(model=models.Article),
 | 
			
		||||
@ -35,15 +37,6 @@ urls = [
 | 
			
		||||
         views.ArticleDetailView.as_view(),
 | 
			
		||||
         name='article-detail'),
 | 
			
		||||
 | 
			
		||||
    path(_('programs/'), views.PageListView.as_view(model=models.Program),
 | 
			
		||||
         name='program-list'),
 | 
			
		||||
    path(_('programs/<slug:slug>/'),
 | 
			
		||||
         views.ProgramDetailView.as_view(), name='program-detail'),
 | 
			
		||||
    path(_('programs/<slug:parent_slug>/episodes/'),
 | 
			
		||||
         views.EpisodeListView.as_view(), name='episode-list'),
 | 
			
		||||
    path(_('programs/<slug:parent_slug>/articles/'),
 | 
			
		||||
         views.ArticleListView.as_view(), name='article-list'),
 | 
			
		||||
 | 
			
		||||
    path(_('episodes/'),
 | 
			
		||||
         views.EpisodeListView.as_view(), name='episode-list'),
 | 
			
		||||
    path(_('episodes/<slug:slug>/'),
 | 
			
		||||
@ -53,7 +46,23 @@ urls = [
 | 
			
		||||
    path(_('week/<date:date>/'),
 | 
			
		||||
         views.DiffusionListView.as_view(), name='diffusion-list'),
 | 
			
		||||
 | 
			
		||||
    path(_('logs/'), views.LogListView.as_view(), name='logs'),
 | 
			
		||||
    path(_('logs/<date:date>/'), views.LogListView.as_view(), name='logs'),
 | 
			
		||||
    path(_('logs/'), views.LogListView.as_view(), name='log-list'),
 | 
			
		||||
    path(_('logs/<date:date>/'), views.LogListView.as_view(), name='log-list'),
 | 
			
		||||
    # path('<page_path:path>', views.route_page, name='page'),
 | 
			
		||||
 | 
			
		||||
    path(_('publications/'),
 | 
			
		||||
         views.ProgramPageListView.as_view(), name='page-list'),
 | 
			
		||||
 | 
			
		||||
    path(_('programs/'), views.PageListView.as_view(model=models.Program),
 | 
			
		||||
         name='program-list'),
 | 
			
		||||
    path(_('programs/<slug:slug>/'),
 | 
			
		||||
         views.ProgramDetailView.as_view(), name='program-detail'),
 | 
			
		||||
    path(_('programs/<slug:parent_slug>/episodes/'),
 | 
			
		||||
         views.EpisodeListView.as_view(), name='episode-list'),
 | 
			
		||||
    path(_('programs/<slug:parent_slug>/articles/'),
 | 
			
		||||
         views.ArticleListView.as_view(), name='article-list'),
 | 
			
		||||
    path(_('programs/<slug:parent_slug>/publications/'),
 | 
			
		||||
         views.ProgramPageListView.as_view(), name='page-list'),
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -5,5 +5,5 @@ from .base import BaseView
 | 
			
		||||
from .episode import EpisodeDetailView, EpisodeListView, DiffusionListView
 | 
			
		||||
from .log import LogListView
 | 
			
		||||
from .page import PageDetailView, PageListView
 | 
			
		||||
from .program import ProgramDetailView
 | 
			
		||||
from .program import ProgramDetailView, ProgramPageListView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -49,6 +49,9 @@ class AdminSite(admin.AdminSite):
 | 
			
		||||
            path('tools/statistics/',
 | 
			
		||||
                 self.admin_view(StatisticsView.as_view()),
 | 
			
		||||
                 name='tools-stats'),
 | 
			
		||||
            path('tools/statistics/<date:date>/',
 | 
			
		||||
                 self.admin_view(StatisticsView.as_view()),
 | 
			
		||||
                 name='tools-stats'),
 | 
			
		||||
        ]
 | 
			
		||||
        return urls
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,10 @@ __all__ = ['ArticleDetailView', 'ArticleListView']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ArticleDetailView(PageDetailView):
 | 
			
		||||
    show_side_nav = True
 | 
			
		||||
    has_sidebar = True
 | 
			
		||||
    model = Article
 | 
			
		||||
 | 
			
		||||
    def get_side_queryset(self):
 | 
			
		||||
    def get_sidebar_queryset(self):
 | 
			
		||||
        qs = Article.objects.select_related('cover') \
 | 
			
		||||
                    .filter(is_static=False) \
 | 
			
		||||
                    .order_by('-date')
 | 
			
		||||
@ -27,9 +27,7 @@ class ArticleListView(ParentMixin, PageListView):
 | 
			
		||||
    template_name = 'aircox/article_list.html'
 | 
			
		||||
    show_headline = True
 | 
			
		||||
    is_static = False
 | 
			
		||||
 | 
			
		||||
    parent_model = Program
 | 
			
		||||
    fk_parent = 'program'
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().filter(is_static=self.is_static)
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ from django.http import Http404
 | 
			
		||||
from django.views.generic import DetailView, ListView
 | 
			
		||||
from django.views.generic.base import TemplateResponseMixin, ContextMixin
 | 
			
		||||
 | 
			
		||||
from ..models import Page
 | 
			
		||||
from ..utils import Redirect
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,8 +16,10 @@ class BaseView(TemplateResponseMixin, ContextMixin):
 | 
			
		||||
    cover = None
 | 
			
		||||
    """ Page cover """
 | 
			
		||||
 | 
			
		||||
    show_side_nav = False
 | 
			
		||||
    has_sidebar = True
 | 
			
		||||
    """ Show side navigation """
 | 
			
		||||
    has_filters = False
 | 
			
		||||
    """ Show filters nav """
 | 
			
		||||
    list_count = 5
 | 
			
		||||
    """ Item count for small lists displayed on page. """
 | 
			
		||||
 | 
			
		||||
@ -24,27 +27,29 @@ class BaseView(TemplateResponseMixin, ContextMixin):
 | 
			
		||||
    def station(self):
 | 
			
		||||
        return self.request.station
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().station(self.station)
 | 
			
		||||
    # def get_queryset(self):
 | 
			
		||||
    #    return super().get_queryset().station(self.station)
 | 
			
		||||
 | 
			
		||||
    def get_side_queryset(self):
 | 
			
		||||
    def get_sidebar_queryset(self):
 | 
			
		||||
        """ Return a queryset of items to render on the side nav. """
 | 
			
		||||
        return None
 | 
			
		||||
        return Page.objects.select_subclasses().published() \
 | 
			
		||||
                           .order_by('-pub_date')
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, side_items=None, **kwargs):
 | 
			
		||||
    def get_context_data(self, sidebar_items=None, **kwargs):
 | 
			
		||||
        kwargs.setdefault('station', self.station)
 | 
			
		||||
        kwargs.setdefault('cover', self.cover)
 | 
			
		||||
        kwargs.setdefault('has_filters', self.has_filters)
 | 
			
		||||
 | 
			
		||||
        show_side_nav = kwargs.setdefault('show_side_nav', self.show_side_nav)
 | 
			
		||||
        if show_side_nav and side_items is None:
 | 
			
		||||
            side_items = self.get_side_queryset()
 | 
			
		||||
            side_items = None if side_items is None else \
 | 
			
		||||
                side_items[:self.list_count]
 | 
			
		||||
        has_sidebar = kwargs.setdefault('has_sidebar', self.has_sidebar)
 | 
			
		||||
        if has_sidebar and sidebar_items is None:
 | 
			
		||||
            sidebar_items = self.get_sidebar_queryset()
 | 
			
		||||
            sidebar_items = None if sidebar_items is None else \
 | 
			
		||||
                sidebar_items[:self.list_count]
 | 
			
		||||
 | 
			
		||||
        if not 'audio_streams' in kwargs:
 | 
			
		||||
            streams = self.station.audio_streams
 | 
			
		||||
            streams = streams and streams.split('\n')
 | 
			
		||||
            kwargs['audio_streams'] = streams
 | 
			
		||||
 | 
			
		||||
        return super().get_context_data(side_items=side_items, **kwargs)
 | 
			
		||||
        return super().get_context_data(sidebar_items=sidebar_items, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,18 +35,14 @@ class EpisodeListView(ParentMixin, PageListView):
 | 
			
		||||
    model = Episode
 | 
			
		||||
    item_template_name = 'aircox/episode_item.html'
 | 
			
		||||
    show_headline = True
 | 
			
		||||
 | 
			
		||||
    parent_model = Program
 | 
			
		||||
    fk_parent = 'program'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DiffusionListView(GetDateMixin, BaseView, ListView):
 | 
			
		||||
    """ View for timetables """
 | 
			
		||||
    model = Diffusion
 | 
			
		||||
 | 
			
		||||
    date = None
 | 
			
		||||
    start = None
 | 
			
		||||
    end = None
 | 
			
		||||
    has_filters = True
 | 
			
		||||
    redirect_date_url = 'diffusion-list'
 | 
			
		||||
 | 
			
		||||
    def get_date(self):
 | 
			
		||||
        date = super().get_date()
 | 
			
		||||
@ -56,19 +52,7 @@ class DiffusionListView(GetDateMixin, BaseView, ListView):
 | 
			
		||||
        return super().get_queryset().today(self.date).order_by('start')
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        today = datetime.date.today()
 | 
			
		||||
        start = self.date - datetime.timedelta(days=self.date.weekday())
 | 
			
		||||
        dates = [
 | 
			
		||||
            (today, None),
 | 
			
		||||
            (today - datetime.timedelta(days=1), None),
 | 
			
		||||
            (today + datetime.timedelta(days=1), None),
 | 
			
		||||
            (today - datetime.timedelta(days=7), _('next week')),
 | 
			
		||||
            (today + datetime.timedelta(days=7), _('last week')),
 | 
			
		||||
            (None, None)
 | 
			
		||||
        ] + [
 | 
			
		||||
            (date, date.strftime('%A %d'))
 | 
			
		||||
            for date in (start + datetime.timedelta(days=i)
 | 
			
		||||
                         for i in range(0, 7)) if date != today
 | 
			
		||||
        ]
 | 
			
		||||
        dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
 | 
			
		||||
        return super().get_context_data(date=self.date, dates=dates, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,9 @@ class LogListView(BaseView, LogListMixin, ListView):
 | 
			
		||||
    Return list of logs for the provided date (from `kwargs` or
 | 
			
		||||
    `request.GET`, defaults to today).
 | 
			
		||||
    """
 | 
			
		||||
    redirect_date_url = 'log-list'
 | 
			
		||||
    has_filters = True
 | 
			
		||||
 | 
			
		||||
    def get_date(self):
 | 
			
		||||
        date, today = super().get_date(), datetime.date.today()
 | 
			
		||||
        return today if date is None else min(date, today)
 | 
			
		||||
@ -56,8 +59,7 @@ class LogListView(BaseView, LogListMixin, ListView):
 | 
			
		||||
        today = datetime.date.today()
 | 
			
		||||
        kwargs.update({
 | 
			
		||||
            'date': self.date,
 | 
			
		||||
            'dates': ((today - datetime.timedelta(days=i), None)
 | 
			
		||||
                      for i in range(0, 7)),
 | 
			
		||||
            'dates': (today - datetime.timedelta(days=i) for i in range(0, 7)),
 | 
			
		||||
            'object_list': self.get_object_list(self.object_list),
 | 
			
		||||
        })
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
import dateutil
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from ..utils import str_to_date
 | 
			
		||||
 | 
			
		||||
@ -12,13 +14,18 @@ class GetDateMixin:
 | 
			
		||||
    `kwargs['date']`
 | 
			
		||||
    """
 | 
			
		||||
    date = None
 | 
			
		||||
    redirect_date_url = None
 | 
			
		||||
 | 
			
		||||
    def get_date(self):
 | 
			
		||||
        if 'date' in self.request.GET:
 | 
			
		||||
            return str_to_date(self.request.GET['date'], '-')
 | 
			
		||||
        return self.kwargs['date'] if 'date' in self.kwargs else None
 | 
			
		||||
        date = self.request.GET.get('date')
 | 
			
		||||
        return str_to_date(date, '-') if date is not None else \
 | 
			
		||||
            self.kwargs['date'] if 'date' in self.kwargs else None
 | 
			
		||||
 | 
			
		||||
    def get(self, *args, **kwargs):
 | 
			
		||||
        if self.redirect_date_url and self.request.GET.get('date'):
 | 
			
		||||
            return redirect(self.redirect_date_url,
 | 
			
		||||
                            date=self.request.GET['date'].replace('-', '/'))
 | 
			
		||||
 | 
			
		||||
        self.date = self.get_date()
 | 
			
		||||
        return super().get(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
@ -35,8 +42,6 @@ class ParentMixin:
 | 
			
		||||
    """ Url lookup argument """
 | 
			
		||||
    parent_field = 'slug'
 | 
			
		||||
    """ Parent field for url lookup """
 | 
			
		||||
    fk_parent = 'page'
 | 
			
		||||
    """ Page foreign key to the parent """
 | 
			
		||||
    parent = None
 | 
			
		||||
    """ Parent page object """
 | 
			
		||||
 | 
			
		||||
@ -54,8 +59,7 @@ class ParentMixin:
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        if self.parent is not None:
 | 
			
		||||
            lookup = {self.fk_parent: self.parent}
 | 
			
		||||
            return super().get_queryset().filter(**lookup)
 | 
			
		||||
            return super().get_queryset().filter(parent=self.parent)
 | 
			
		||||
        return super().get_queryset()
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
 | 
			
		||||
from django.http import Http404, HttpResponse
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.views.generic import DetailView, ListView
 | 
			
		||||
 | 
			
		||||
@ -19,9 +18,11 @@ __all__ = ['PageDetailView', 'PageListView']
 | 
			
		||||
class PageListView(BaseView, ListView):
 | 
			
		||||
    template_name = 'aircox/page_list.html'
 | 
			
		||||
    item_template_name = 'aircox/page_item.html'
 | 
			
		||||
    has_sidebar = True
 | 
			
		||||
    has_filters = True
 | 
			
		||||
 | 
			
		||||
    paginate_by = 20
 | 
			
		||||
    show_headline = True
 | 
			
		||||
    show_side_nav = True
 | 
			
		||||
    categories = None
 | 
			
		||||
 | 
			
		||||
    def get(self, *args, **kwargs):
 | 
			
		||||
@ -36,7 +37,7 @@ class PageListView(BaseView, ListView):
 | 
			
		||||
        # (by id)
 | 
			
		||||
        if self.categories:
 | 
			
		||||
            qs = qs.filter(category__slug__in=self.categories)
 | 
			
		||||
        return qs.order_by('-date')
 | 
			
		||||
        return qs.order_by('-pub_date')
 | 
			
		||||
 | 
			
		||||
    def get_categories_queryset(self):
 | 
			
		||||
        # TODO: use generic reverse field lookup
 | 
			
		||||
@ -56,6 +57,7 @@ class PageListView(BaseView, ListView):
 | 
			
		||||
class PageDetailView(BaseView, DetailView):
 | 
			
		||||
    """ Base view class for pages. """
 | 
			
		||||
    context_object_name = 'page'
 | 
			
		||||
    has_filters = False
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().select_related('cover', 'category')
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,13 @@
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
 | 
			
		||||
from aircox.models import Episode, Program
 | 
			
		||||
from ..models import Episode, Program, Page
 | 
			
		||||
from .mixins import ParentMixin
 | 
			
		||||
from .page import PageDetailView, PageListView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['ProgramPageDetailView', 'ProgramDetailView']
 | 
			
		||||
__all__ = ['ProgramPageDetailView', 'ProgramDetailView', 'ProgramPageListView']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProgramPageDetailView(PageDetailView):
 | 
			
		||||
@ -13,24 +15,25 @@ class ProgramPageDetailView(PageDetailView):
 | 
			
		||||
    Base view class for a page that is displayed as a program's child page.
 | 
			
		||||
    """
 | 
			
		||||
    program = None
 | 
			
		||||
    show_side_nav = True
 | 
			
		||||
    has_sidebar = True
 | 
			
		||||
    list_count = 5
 | 
			
		||||
 | 
			
		||||
    def get_side_queryset(self):
 | 
			
		||||
        return self.program.episode_set.published().order_by('-date')
 | 
			
		||||
    def get_sidebar_queryset(self):
 | 
			
		||||
        return super().get_sidebar_queryset().filter(parent=self.object)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProgramPageListView(ParentMixin, PageListView):
 | 
			
		||||
    model = Page
 | 
			
		||||
    parent_model = Program
 | 
			
		||||
    queryset = Page.objects.select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProgramDetailView(ProgramPageDetailView):
 | 
			
		||||
    model = Program
 | 
			
		||||
 | 
			
		||||
    def get_articles_queryset(self):
 | 
			
		||||
        return self.program.article_set.published().order_by('-date')
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        self.program = kwargs.setdefault('program', self.object)
 | 
			
		||||
        if 'articles' not in kwargs:
 | 
			
		||||
            kwargs['articles'] = \
 | 
			
		||||
                self.get_articles_queryset()[:self.list_count]
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,3 +24,9 @@
 | 
			
		||||
    margin: 1em 0em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ul.menu-list li {
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,6 @@ export default {
 | 
			
		||||
                    for(var tag of item.value.split(splitReg))
 | 
			
		||||
                        counts[tag.trim()] = (counts[tag.trim()] || 0) + 1;
 | 
			
		||||
            this.counts = counts;
 | 
			
		||||
            console.log('counts', this.counts)
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ $body-background-color: $light;
 | 
			
		||||
 | 
			
		||||
@import "~bulma/bulma";
 | 
			
		||||
 | 
			
		||||
//-- helpers/modifiers
 | 
			
		||||
.is-fullwidth { width: 100%; }
 | 
			
		||||
.is-fixed-bottom {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
@ -18,6 +19,14 @@ $body-background-color: $light;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-opacity-light {
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
    &:hover {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-- navbar
 | 
			
		||||
.navbar + .container {
 | 
			
		||||
    margin-top: 1em;
 | 
			
		||||
}
 | 
			
		||||
@ -30,10 +39,44 @@ a.navbar-item.is-active {
 | 
			
		||||
    border-bottom: 1px grey solid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar .navbar-dropdown {
 | 
			
		||||
    z-index: 2000;
 | 
			
		||||
.navbar {
 | 
			
		||||
    .navbar-dropdown {
 | 
			
		||||
        z-index: 2000;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .navbar-split {
 | 
			
		||||
        margin: 0.2em 0em;
 | 
			
		||||
        margin-right: 1em;
 | 
			
		||||
        padding-right: 1em;
 | 
			
		||||
        border-right: 1px $grey-light solid;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    form {
 | 
			
		||||
        margin: 0em;
 | 
			
		||||
        padding: 0em;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- filters
 | 
			
		||||
.filters {
 | 
			
		||||
    margin: 1em 0em;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    margin-bottom: 1em;
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
        padding-right: 2em;
 | 
			
		||||
        margin-right: 1em;
 | 
			
		||||
        border-right: 1px $grey-light solid;
 | 
			
		||||
 | 
			
		||||
        font-size: $size-5;
 | 
			
		||||
        color: $text-light;
 | 
			
		||||
        font-weight: $weight-light;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
.navbar-brand img {
 | 
			
		||||
    min-height: 6em;
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
Django>=2.2.0
 | 
			
		||||
djangorestframework>=3.9.4
 | 
			
		||||
django-model-utils>=3.2.0
 | 
			
		||||
 | 
			
		||||
dateutils>=0.6.6
 | 
			
		||||
watchdog>=0.8.3
 | 
			
		||||
psutil>=5.0.1
 | 
			
		||||
tzlocal>=1.4
 | 
			
		||||
@ -12,7 +12,6 @@ django-filer>=1.5.0
 | 
			
		||||
django-ckeditor>=5.7.1
 | 
			
		||||
django-admin-sortable2>=0.7.2
 | 
			
		||||
django-content-editor>=1.4.2
 | 
			
		||||
 | 
			
		||||
django-honeypot>=0.5.0
 | 
			
		||||
 | 
			
		||||
gunicorn>=19.6.0
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user