+ {{ page.start }} {{ page.end }} {{ page.body }} +
+diff --git a/mpicms/base/models.py b/mpicms/base/models.py index 3138166..be5fc74 100644 --- a/mpicms/base/models.py +++ b/mpicms/base/models.py @@ -1,3 +1,4 @@ +import json from django.apps import apps from django.db import models from django.utils.translation import gettext as _ @@ -13,6 +14,7 @@ from wagtail.snippets.edit_handlers import SnippetChooserPanel from mpicms.news.mixins import NewsMixin +from mpicms.events.models import Event Page.show_in_menus_default = True @@ -51,6 +53,24 @@ class HomePage(NewsMixin, Page): content_panels = Page.content_panels + def get_context(self, request, *args, **kwargs): + context = super().get_context(request, *args, **kwargs) + + # Events + events = [] + for event in Event.objects.live(): + events.append({ + 'title': event.title, + 'start': event.start.isoformat(), + 'end': event.end.isoformat(), + 'url': event.get_url(request=request), + 'color': '#006c66' + }) + + context["events"] = json.dumps(events) + + return context + class Meta: # noqa verbose_name = _("homepage") verbose_name_plural = _("homepages") diff --git a/mpicms/events/__init__.py b/mpicms/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mpicms/events/apps.py b/mpicms/events/apps.py new file mode 100644 index 0000000..5af7c10 --- /dev/null +++ b/mpicms/events/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EventAppConfig(AppConfig): + name = "mpicms.events" + verbose_name = "Events" diff --git a/mpicms/events/managers.py b/mpicms/events/managers.py new file mode 100644 index 0000000..4a5a50b --- /dev/null +++ b/mpicms/events/managers.py @@ -0,0 +1,28 @@ +from wagtail.core.models import PageManager + + +class DatedEventManager(PageManager): + @staticmethod + def _get_min_time(dt): + """ + Makes clock to 00:00:00 + :param dt: datetime + :return: datetime + """ + return dt.replace(hour=0, minute=0, second=0) + + def in_date_range(self, start, end): + """ + Get event dates that appear between the start and end dates + :return: Filtered django model queryset + """ + start = self._get_min_time(start) + end = self._get_min_time(end) + return self.filter(start_date__gte=start, start_date__lte=end) + + def live(self): + """ + Get event dates associated with live event series + :return: Filtered django model queryset + """ + return self.filter(live=True) diff --git a/mpicms/events/migrations/0001_initial.py b/mpicms/events/migrations/0001_initial.py new file mode 100644 index 0000000..ed1b877 --- /dev/null +++ b/mpicms/events/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.1 on 2019-06-01 11:08 + +from django.db import migrations, models +import django.db.models.deletion +import wagtail.core.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), + ] + + operations = [ + migrations.CreateModel( + name='Event', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('start_date', models.DateTimeField()), + ('end_date', models.DateTimeField(blank=True, null=True)), + ('description', models.TextField(blank=True, max_length=400)), + ('description_en', models.TextField(blank=True, max_length=400, null=True)), + ('description_de', models.TextField(blank=True, max_length=400, null=True)), + ('body', wagtail.core.fields.RichTextField(blank=True, verbose_name='content')), + ('body_en', wagtail.core.fields.RichTextField(blank=True, null=True, verbose_name='content')), + ('body_de', wagtail.core.fields.RichTextField(blank=True, null=True, verbose_name='content')), + ], + options={ + 'ordering': ['start_date'], + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='EventIndex', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/mpicms/events/migrations/0002_auto_20190601_1604.py b/mpicms/events/migrations/0002_auto_20190601_1604.py new file mode 100644 index 0000000..ecbc07c --- /dev/null +++ b/mpicms/events/migrations/0002_auto_20190601_1604.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.1 on 2019-06-01 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='end_time', + field=models.TimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='event', + name='start_time', + field=models.TimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='event', + name='end_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='event', + name='start_date', + field=models.DateField(), + ), + ] diff --git a/mpicms/events/migrations/__init__.py b/mpicms/events/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mpicms/events/models.py b/mpicms/events/models.py new file mode 100644 index 0000000..0fb891c --- /dev/null +++ b/mpicms/events/models.py @@ -0,0 +1,134 @@ +import json +from datetime import datetime + +from django.db import models +from django.core.exceptions import ValidationError +# from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +# from django.db import models +# from django.utils import timezone +from django.utils.translation import gettext as _ + +from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel +from wagtail.core.models import Page +# from wagtail.images.blocks import ImageChooserBlock +# from wagtail.images.edit_handlers import ImageChooserPanel +from wagtail.core.fields import RichTextField + +# from .date_filters import get_range +# from .managers import DatedEventManager +# from .utils import date_to_datetime + + +class Event(Page): + start_date = models.DateField() + end_date = models.DateField(blank=True, null=True) + start_time = models.TimeField(blank=True, null=True) + end_time = models.TimeField(blank=True, null=True) + # objects = DatedEventManager() + description = models.TextField(max_length=400, null=False, blank=True) + body = RichTextField(_("content"), blank=True) + + parent_page_types = ['events.EventIndex'] + subpage_types = [] + + content_panels = Page.content_panels + [ + FieldPanel('description'), + MultiFieldPanel( + [ + FieldPanel('start_date'), + FieldPanel('start_time'), + FieldPanel('end_date'), + FieldPanel('end_time'), + ], + heading="Event Start / End Dates" + ), + FieldPanel('body', classname="full"), + ] + + @property + def start(self): + if self.start_time: + return datetime.combine(self.start_date, self.start_time) + return self.start_date + + @property + def end(self): + if self.end_time: + return datetime.combine(self.end_date, self.end_time) + return self.end_date + + def clean(self): + """Clean the model fields, if end_date is before start_date raise a ValidationError.""" + super().clean() + + if self.end_date and self.end_date < self.start_date: + raise ValidationError({'end_date': 'The end date cannot be before the start date.'}) + + class Meta(object): # noqa + ordering = ['start_date'] + + +class EventIndex(Page): + parent_page_types = ['base.HomePage'] + subpage_types = ['events.Event'] + + # PAGINATE_BY = 3 + + # search_fields = Page.search_fields + [ + # index.SearchField('body'), + # index.FilterField('date'), + # ] + + content_panels = Page.content_panels + # promote_panels = [ + # MultiFieldPanel(Page.promote_panels, "Common page configuration"), + # ] + + def get_context(self, request, *args, **kwargs): + context = super().get_context(request, *args, **kwargs) + + events = [] + for child in self.get_children().type(Event).live().specific(): + events.append({ + 'title': child.title, + 'start': child.start.isoformat(), + 'end': child.end.isoformat(), + 'url': child.get_url(request=request), + 'color': '#006c66' + }) + + context["events"] = json.dumps(events) + + return context + + + # def get_context(self, request, *args, **kwargs): + # """ + # Adds child pages to the context and paginates them. + # """ + # context = super().get_context(request, *args, **kwargs) + # children = self.get_children().type(Event) + + # # Period + # period = request.GET.get('scope', None) + # start_date = request.GET.get('start_date', '') + # if period: + # self.get_start_end(period, start_date) + # children.filter(start_date__gte=start_date).filter(end_date__lte=end_date) + + # # Pagination + # paginator = Paginator(queryset, PAGINATE_BY) + # page_num = request.GET.get('page', 1) or 1 + + # try: + # queryset = paginator.page(page_num) + # except PageNotAnInteger: + # queryset = paginator.page(1) + # except EmptyPage: + # queryset = paginator.page(paginator.num_pages) + + # context.update( + # children=queryset, + # paginator=paginator + # ) + # return context diff --git a/mpicms/events/templatetags/__init__.py b/mpicms/events/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mpicms/events/templatetags/wagtail_events_tags.py b/mpicms/events/templatetags/wagtail_events_tags.py new file mode 100644 index 0000000..b97668f --- /dev/null +++ b/mpicms/events/templatetags/wagtail_events_tags.py @@ -0,0 +1,64 @@ +from urllib.parse import urlencode + +from django import template + + +register = template.Library() + + +@register.simple_tag(takes_context=True) +def querystring(context, *args, **kwargs): + """ + Display all GET values (except page) encoded as url params + + :param context: template context + :return: string|encoded params as urlstring + """ + try: + params = context['request'].GET.dict() + except (KeyError, AttributeError): + params = {} + else: + for value in args: + params.pop(value, None) + for key, value in kwargs.items(): + params[key] = value + return urlencode(params) + + +def _patch(context, key, data): + """ + Patch the GET value + + :param context: template context dict + :param key: item name + :param data: item value + :return: patched url params + """ + getvars = dict(context['request'].GET) + getvars[key] = [data] + return '?{0}'.format(urlencode(getvars, doseq=True)) + + +@register.simple_tag(takes_context=True) +def patch_scope(context, scope): + """ + Prepare scope for agenda + + :param context: + :param scope: + :return: + """ + return _patch(context, 'scope', scope) + + +@register.simple_tag(takes_context=True) +def patch_start_date(context, date): + """ + Prepare `start_date` url for agenda + + :param context: template context dict + :param date: start_date + :return: + """ + return _patch(context, 'start_date', date.strftime('%Y.%m.%d')) diff --git a/mpicms/events/translation.py b/mpicms/events/translation.py new file mode 100644 index 0000000..acd5367 --- /dev/null +++ b/mpicms/events/translation.py @@ -0,0 +1,17 @@ +from modeltranslation.translator import TranslationOptions +from modeltranslation.decorators import register + +from .models import Event, EventIndex + + +@register(Event) +class EventPageTR(TranslationOptions): + fields = ( + 'description', + 'body' + ) + + +@register(EventIndex) +class EventIndexPageTR(TranslationOptions): + pass diff --git a/mpicms/events/utils.py b/mpicms/events/utils.py new file mode 100644 index 0000000..02b25dd --- /dev/null +++ b/mpicms/events/utils.py @@ -0,0 +1,204 @@ +# from copy import copy +import calendar +import datetime as dt +from datetime import datetime +from isoweek import Week + + +# from django.utils import timezone + + +DATE_FORMAT_RE = r'^([0-9]){4}\.([0-9]){2}\.([0-9]){2}$' + + +# class datetime(dt.datetime): + +# @classmethod +# def from_date(cls, init_date, time_choice='min'): +# """ +# Convert date to datetime. + +# :param date: date to convert +# :param time_choice: max or min +# :return: datetime +# """ +# choice = getattr(cls, 'min' if time_choice == 'min' else 'max').time() +# return timezone.make_aware( +# cls.combine(init_date, choice), +# timezone.get_current_timezone(), +# ) + + +class date(dt.date): + # def __init__(self, date): + # self.date = date + + # # If start date = string + + # if re.match(cls.DATE_FORMAT_RE, start_date): + # date_params = [int(i) for i in start_date.split('.')] + # start_date = date_to_datetime(datetime.date(*date_params)) + # else: + # start_date = timezone.now().replace( + # hour=0, + # minute=0, + # second=0, + # microsecond=0, + # ) + + # self.time_periods = { + # 'year': get_year_range, + # 'week': get_week_range, + # 'month': get_month_range, + # 'day': get_day_range, + # } + + + # @property + # def get_range(start_date, period): + # """ + # Get the start and end datetimes for the given period + + # :param start_date: period start_date + # :type start_date: datetime.datetime() + # :type period: String + # :return: tuple start_datetime, end_datetime + # """ + # time_periods = { + # 'year': get_year_range, + # 'week': get_week_range, + # 'month': get_month_range, + # 'day': get_day_range, + # } + # return time_periods[period.lower()](start_date) + + + @property + def year_range(self): + """ + Get the start and end datetimes for the year + + :param start_date: period start_date + :type start_date: datetime.datetime() + :return: tuple start_datetime, end_datetime + """ + start_date = datetime.combine(self.replace(month=1, day=1), datetime.min.time()) + end_date = datetime.combine(self.replace(month=12, day=31), datetime.min.time()) + return start_date, end_date + + @property + def month_range(self): + """ + Get the start and end datetimes for the month + + :param start_date: period start_date + :type start_date: datetime.datetime() + :return: tuple start_datetime, end_datetime + """ + month_days = calendar.monthrange(self.year, self.month)[1] + start_date = datetime.combine(self.replace(day=1), datetime.min.time()) + end_date = datetime.combine(self.replace(month_days), datetime.max.time()) + + return start_date, end_date + + @property + def week_range(self): + """ + Get the start and end datetimes for the week + + :param start_date: period start_date + :type start_date: datetime.datetime() + :return: tuple start_datetime, end_datetime + """ + period = Week(self.year, self.isocalendar()[1]) + start_date = datetime.combine(period.monday(), datetime.min.time()) + end_date = datetime.combine(period.sunday(), datetime.max.time()) + return start_date, end_date + + @property + def day_range(self): + """ + Get the start and end datetimes for the day + + :param start_date: period start_date + :type start_date: datetime.datetime() + :return: tuple start_datetime, end_datetime + """ + start_date = datetime.combine(self, datetime.min.time()) + end_date = datetime.combine(self, datetime.max.time()) + return start_date, end_date + + # def add_months(self, months): + # """ + # Add months to the date. + + # :param date: + # :param months: + # :return: + # """ + # month = self.month - 1 + months + # year = int(self.year + month / 12) + # month = month % 12 + 1 + # day = min(self.day, calendar.monthrange(year, month)[1]) + + # return self.__class__(year, month, day) + + # def remove_months(self, months): + # """ + # Add months to the date. + + # :param date: + # :param months: + # :return: + # """ + # month = self.month - 1 - months + # year = int(self.year + month / 12) + # month = month % 12 + 1 + # day = min(self.day, calendar.monthrange(year, month)[1]) + + # return self.__class__(year, month, day) + + + + + + + + + + +# def date_to_datetime(date, time_choice='min'): +# """ +# Convert date to datetime. + +# :param date: date to convert +# :param time_choice: max or min +# :return: datetime +# """ +# choice = getattr(datetime.datetime, 'min' if time_choice == 'min' else 'max').time() +# return timezone.make_aware( +# datetime.datetime.combine(date, choice), +# timezone.get_current_timezone(), +# ) + + + + +# def get_start_end(period, start_date): +# if re.match(cls.DATE_FORMAT_RE, start_date): +# date_params = [int(i) for i in start_date.split('.')] +# start_date = date_to_datetime(datetime.date(*date_params)) +# else: +# start_date = timezone.now().replace( +# hour=0, +# minute=0, +# second=0, +# microsecond=0, +# ) + +# # Clean the start and end dates to conform to the requested period +# start_date, end_date = cls.TIME_PERIODS[period.lower()](start_date) +# return start_date, end_date + + + diff --git a/mpicms/news/migrations/0005_newspage_show_all.py b/mpicms/news/migrations/0005_newspage_show_all.py new file mode 100644 index 0000000..ae7c4c3 --- /dev/null +++ b/mpicms/news/migrations/0005_newspage_show_all.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-06-01 17:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0004_newsentry_date'), + ] + + operations = [ + migrations.AddField( + model_name='newspage', + name='show_all', + field=models.BooleanField(default=False), + ), + ] diff --git a/mpicms/news/models.py b/mpicms/news/models.py index 1bee895..af456af 100644 --- a/mpicms/news/models.py +++ b/mpicms/news/models.py @@ -9,12 +9,22 @@ class NewsPage(CategoryMixin, Page): - content_panels = Page.content_panels + content_panels = Page.content_panels + [ + FieldPanel('show_all') + ] + show_all = models.BooleanField(default=False) parent_page_types = ['base.CategoryPage', 'base.HomePage'] subpage_types = ['NewsEntry'] show_in_menus_default = False + @property + def news_items(self): + if self.show_all: + return NewsEntry.objects.descendant_of(self.get_parent()).live().order_by('-date') + return NewsEntry.objects.child_of(self).live().order_by('-date') + + class Meta: # noqa verbose_name = _("news Blog") verbose_name_plural = _("news Blogs") diff --git a/mpicms/static/css/custom.css b/mpicms/static/css/custom.css index 50e7a30..1b5fa78 100644 --- a/mpicms/static/css/custom.css +++ b/mpicms/static/css/custom.css @@ -241,4 +241,9 @@ select, input { .select:not(.is-multiple):not(.is-loading)::after { border-color: #006c66; +} + +/* Full Calendar */ +.fc-button { + border-radius: 0 !important; } \ No newline at end of file diff --git a/mpicms/templates/base.html b/mpicms/templates/base.html index 872bbe8..a2e02a7 100644 --- a/mpicms/templates/base.html +++ b/mpicms/templates/base.html @@ -4,6 +4,7 @@
+ {% block head %}+ {{ page.start }} {{ page.end }} {{ page.body }} +
+Start Date: {{ event.start_date }}
+ {% if event.end_date %} +End Date: {{ event.end_date }}
+ {% endif %} +{{ event.body|safe }}
+{% trans "No events found" %}
+ {% endif %} +{% endblock %} diff --git a/mpicms/templates/events/old.html b/mpicms/templates/events/old.html new file mode 100644 index 0000000..37b3271 --- /dev/null +++ b/mpicms/templates/events/old.html @@ -0,0 +1,34 @@ + + + +{% trans "Results found" %}:{{ children.items|length }}
+ ++ {% trans "Previous" %} | + {% trans "Next" %} +
+ + {% if children.items %} +Start Date: {{ child.start_date }}
+ {% if child.end_date %} +End Date: {{ child.end_date }}
+ {% endif %} +{{ child.body|safe }}
+{% trans "No events found" %}
+ {% endif %}s \ No newline at end of file diff --git a/mpicms/templates/menus/main.html b/mpicms/templates/menus/main.html index ddd94d7..0dcd870 100644 --- a/mpicms/templates/menus/main.html +++ b/mpicms/templates/menus/main.html @@ -48,6 +48,9 @@