Commit bbae04e0 authored by Thom Wiggers's avatar Thom Wiggers 📐
Browse files

Merge branch 'feature/header-slider' into 'master'

Add header slider with admin manageable slides

Closes #694

See merge request thalia/concrexit!1010
parents fae9589e 50fe96f1
......@@ -5,7 +5,7 @@ from django.template.defaultfilters import striptags
from thaliawebsite.templatetags.bleach_tags import bleach
from utils.translation import TranslatedModelAdmin
from .models import Announcement, FrontpageArticle
from .models import Announcement, FrontpageArticle, Slide
@admin.register(Announcement)
......@@ -44,3 +44,17 @@ class FrontpageArticleAdmin(TranslatedModelAdmin):
"""Is the object visible"""
return obj.is_visible
visible.boolean = True
@admin.register(Slide)
class SlideAdmin(TranslatedModelAdmin):
"""Manage the admin pages for the slides"""
#: show these fields in the admin overview list
#: see :py:method:visible for the visible field
list_display = ('title', 'since', 'until', 'visible')
def visible(self, obj): # pylint: disable=no-self-use
"""Is the object visible"""
return obj.is_visible
visible.boolean = True
......@@ -6,4 +6,4 @@ from django.utils.translation import gettext_lazy as _
class AnnouncementsConfig(AppConfig):
"""AppConfig for the announcement package"""
name = 'announcements'
verbose_name = _('Site header announcements')
verbose_name = _('Site announcements')
......@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-11 20:07+0200\n"
"PO-Revision-Date: 2018-02-12 13:49+0100\n"
"POT-Creation-Date: 2018-10-18 10:54+0200\n"
"PO-Revision-Date: 2018-10-18 10:57+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: nl\n"
......@@ -16,60 +16,104 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.6\n"
"X-Generator: Poedit 2.1.1\n"
#: announcements/apps.py
msgid "Site header announcements"
#: apps.py
msgid "Site announcements"
msgstr "Site kop aankondigingen"
#: announcements/models.py
#: models.py
msgid "Content"
msgstr "Inhoud"
#: announcements/models.py
#: models.py
msgid "The content of the announcement; what text to display."
msgstr "De inhoud van de aankondiging; welke tekst weer te geven."
#: announcements/models.py
#: models.py
msgid "Display since"
msgstr "Geef weer vanaf"
#: announcements/models.py
#: models.py
msgid "Hide this announcement before this time."
msgstr "Verberg deze aankondiging voor dit tijdstip."
#: announcements/models.py
#: models.py
msgid "Display until"
msgstr "Laat zien tot"
#: announcements/models.py
#: models.py
msgid "Hide this announcement after this time."
msgstr "Verberg de aankondiging na dit tijdstip."
#: announcements/models.py
#: models.py
msgid "Font Awesome icon"
msgstr "Font Awesome-icoon"
#: announcements/models.py
#: models.py
msgid "Font Awesome abbreviation for icon to use."
msgstr "Font Awesome afkorting voor het te gebruiken icoon."
#: announcements/models.py
#: models.py
msgid "Title"
msgstr "Titel"
#: announcements/models.py
#: models.py
msgid "The title of the article; what goes in the header"
msgstr "De tekst van het artikel; wat er in de kop moet"
#: announcements/models.py
#: models.py
msgid "The content of the article; what text to display."
msgstr "De inhoud van het artikel; welke tekst weer te geven."
#: announcements/models.py
#: models.py
msgid "Hide this article before this time."
msgstr "Verberg dit artikel voor dit tijdstip."
#: announcements/models.py
#: models.py
msgid "Hide this article after this time."
msgstr "Verberg dit artikel na dit tijdstip."
#: models.py
msgid "The title of the slide; just for the admin."
msgstr "De titel van de slide; alleen voor in de admin."
#: models.py
msgid "The content of the slide; what image to display."
msgstr "De inhoud van de slide; welke afbeelding weer te geven."
#: models.py
msgid "Hide this slide before this time."
msgstr "Verberg deze slide voor dit tijdstip."
#: models.py
msgid "Hide this slide after this time."
msgstr "Verberg deze slide na dit tijdstip."
#: models.py
msgid "Order"
msgstr "Volgorde"
#: models.py
msgid "Approximately where this slide should appear in the order"
msgstr "Ongeveer waar in de volgorde deze slide weergegeven wordt"
#: models.py
msgid "Display only for authenticated members"
msgstr "Alleen tonen aan ingelogde leden"
#: models.py
msgid "Link"
msgstr "Link"
#: models.py
msgid "Place the user is taken to when clicking the slide"
msgstr "Plek waar naartoe wordt genavigeerd als er op de slide wordt geklikt"
#: models.py
msgid "Link outside thalia.nu"
msgstr "Link buiten thalia.nu"
#: models.py
msgid "Clicking the slide will open a new tab"
msgstr "Op de slide klikken opent een nieuwe tab"
# Generated by Django 2.0.8 on 2018-10-18 08:55
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('announcements', '0004_frontpagearticle'),
]
operations = [
migrations.CreateModel(
name='Slide',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(help_text='The title of the slide; just for the admin.', max_length=100, verbose_name='Title')),
('since', models.DateTimeField(default=django.utils.timezone.now, help_text='Hide this slide before this time.', verbose_name='Display since')),
('until', models.DateTimeField(blank=True, help_text='Hide this slide after this time.', null=True, verbose_name='Display until')),
('order', models.PositiveIntegerField(default=0, help_text='Approximately where this slide should appear in the order', verbose_name='Order')),
('members_only', models.BooleanField(default=False, verbose_name='Display only for authenticated members')),
('url', models.URLField(blank=True, help_text='Place the user is taken to when clicking the slide', null=True, verbose_name='Link')),
('url_blank', models.BooleanField(default=False, help_text='Clicking the slide will open a new tab', verbose_name='Link outside thalia.nu')),
('content_en', models.ImageField(help_text='The content of the slide; what image to display.', upload_to='public/announcements/slides/', verbose_name='Content (EN)')),
('content_nl', models.ImageField(help_text='The content of the slide; what image to display.', upload_to='public/announcements/slides/', verbose_name='Content (NL)')),
],
options={
'ordering': ('-since',),
},
),
]
"""The models defined by the announcement package"""
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models import ImageField, CharField
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from tinymce.models import HTMLField
from utils.translation import ModelTranslateMeta, MultilingualField
......@@ -97,3 +98,69 @@ class FrontpageArticle(models.Model, metaclass=ModelTranslateMeta):
"""Is this announcement currently visible"""
return ((self.until is None or self.until > timezone.now()) and
(self.since is None or self.since <= timezone.now()))
class Slide(models.Model, metaclass=ModelTranslateMeta):
"""Describes an announcement"""
title = CharField(
verbose_name=_('Title'),
help_text=_('The title of the slide; just for the admin.'),
blank=False,
max_length=100,
)
content = MultilingualField(
ImageField,
verbose_name=_('Content'),
help_text=_('The content of the slide; what image to display.'),
blank=False,
upload_to='public/announcements/slides/'
)
since = models.DateTimeField(
verbose_name=_('Display since'),
help_text=_("Hide this slide before this time."),
default=timezone.now,
)
until = models.DateTimeField(
verbose_name=_('Display until'),
help_text=_("Hide this slide after this time."),
blank=True,
null=True,
)
order = models.PositiveIntegerField(
verbose_name=_('Order'),
help_text=_("Approximately where this slide "
"should appear in the order"),
default=0
)
members_only = models.BooleanField(
verbose_name=_('Display only for authenticated members'),
default=False
)
url = models.URLField(
verbose_name=_('Link'),
help_text=_('Place the user is taken to when clicking the slide'),
blank=True,
null=True,
)
url_blank = models.BooleanField(
verbose_name=_('Link outside thalia.nu'),
help_text=_('Clicking the slide will open a new tab'),
default=False
)
class Meta:
ordering = ('-since', )
@property
def is_visible(self):
"""Is this slide currently visible"""
return ((self.until is None or self.until > timezone.now()) and
(self.since is None or self.since <= timezone.now()))
#announcements-alerts {
.announcement {
background: $preset;
color: $white;
text-align: center;
padding: 0.5rem 1rem;
position: relative;
.close {
color: $white;
}
.announcement {
background: $preset;
color: $white;
text-align: center;
padding: 0.5rem 1rem;
position: relative;
.close {
color: $white;
}
p {
display: inline;
margin-left: 0.5rem;
color: $white;
font-size: 0.9rem;
font-weight: $bold;
}
p {
display: inline;
margin-left: 0.5rem;
color: $white;
font-size: 0.9rem;
font-weight: $bold;
a {
color: $white;
text-decoration: underline;
}
a:hover {
color: $light-grey;
}
button {
line-height: 0.8;
i {
font-size: 1rem;
}
}
}
}
a {
color: $white;
text-decoration: underline;
#announcements-slider {
.carousel-indicators {
opacity: 0;
transition: all 300ms ease-in-out 0s;
-moz-transition: all 300ms ease-in-out 0s;
-webkit-transition: all 300ms ease-in-out 0s;
-o-transition: all 300ms ease-in-out 0s;
}
a:hover {
color: $light-grey;
.carousel-control-next, .carousel-control-prev {
width: 7%;
font-size: 1.5rem;
opacity: 0;
transition: all 300ms ease-in-out 0s;
-moz-transition: all 300ms ease-in-out 0s;
-webkit-transition: all 300ms ease-in-out 0s;
-o-transition: all 300ms ease-in-out 0s;
}
button {
line-height: 0.8;
i {
font-size: 1rem;
}
&:hover {
.carousel-control-next, .carousel-control-prev {
opacity: 0.5;
&:hover {
opacity: .9;
}
}
.carousel-indicators {
opacity: 1;
}
}
}
}
{% load thumbnail pick_header_image %}
{% if slides|length > 0 %}
<div id="announcements-slider" class="carousel slide w-100 h-100"
data-ride="carousel">
<div class="carousel-inner h-100">
{% for slide in slides %}
<div
class="carousel-item{% if forloop.counter0 == 0 %} active{% endif %} h-100">
{% if slide.url %}
<a href="{{ slide.url }}"
class="w-100 h-100"
{% if slide.url_blank %}target="_blank"{% endif %}
>
{% endif %}
<img class="image"
src="{% thumbnail slide.content slide_size %}"
alt="{{ slide.title }}">
{% if slide.url %}</a>{% endif %}
</div>
{% endfor %}
</div>
{% if slides|length > 1 %}
<ol class="carousel-indicators">
{% for slide in slides %}
<li
data-target="#announcements-slider"
data-slide-to="{{ forloop.counter0 }}"
{% if forloop.counter0 == 0 %}class="active"{% endif %}
></li>
{% endfor %}
</ol>
<a class="carousel-control-prev"
href="#announcements-slider"
role="button" data-slide="prev">
<i class="fas fa-chevron-left"></i>
</a>
<a class="carousel-control-next"
href="#announcements-slider"
role="button" data-slide="next">
<i class="fas fa-chevron-right"></i>
</a>
{% endif %}
</div>
{% else %}
<img src="{% block header_image %}{% pick_header_image %}{% endblock header_image %}" class="image"/>
{% endif %}
from django import template
from django.conf import settings
from announcements.models import Slide
register = template.Library()
@register.inclusion_tag('announcements/slider.html', takes_context=True)
def render_slider(context):
return {'slides': [s for s in Slide.objects.all().order_by('order')
if s.is_visible and (
not s.members_only or
context['request'].member
)],
'slide_size': settings.THUMBNAIL_SIZES['slide'],
}
......@@ -115,13 +115,13 @@ TEMPLATES = [
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'thaliawebsite.context_processors.source_commit',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.template.context_processors.media',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'announcements.context_processors.announcements',
'thaliawebsite.context_processors.source_commit',
'thaliawebsite.context_processors.thumbnail_sizes',
],
},
......@@ -262,6 +262,7 @@ THUMBNAIL_SIZES = {
'small': '150x150',
'medium': '300x300',
'large': '1024x768',
'slide': '2000x430'
}
# Default FROM email
......
......@@ -107,14 +107,11 @@
#page-header {
height: 360px;
overflow: hidden;
display: flex;
justify-content: center;
.image {
width: auto;
width: 100%;
height: 100%;
max-width: none;
object-fit: cover;
}
}
......
{% extends 'base.html' %}
{% load i18n frontpage_events frontpage_articles partner_banners %}
{% load i18n frontpage_events frontpage_articles partner_banners slider %}
{% block header_image_container %}
{% render_slider %}
{% endblock header_image_container %}
{% block pre-body %}
{% render_partner_banners %}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment