From 039165e4c301dc25d70cc2a093c5bcbb93870178 Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Thu, 5 Oct 2017 19:52:19 +0100 Subject: [PATCH 1/7] Fix testing errors under Windows --- blogs/tests/test_parser.py | 2 +- events/tests/test_importer.py | 2 +- peps/converters.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blogs/tests/test_parser.py b/blogs/tests/test_parser.py index dd144705f..1cacf9d11 100644 --- a/blogs/tests/test_parser.py +++ b/blogs/tests/test_parser.py @@ -11,7 +11,7 @@ class BlogParserTest(unittest.TestCase): def setUpClass(cls): super().setUpClass() cls.test_file_path = get_test_rss_path() - cls.entries = get_all_entries("file://{}".format(cls.test_file_path)) + cls.entries = get_all_entries("file:///{}".format(cls.test_file_path)) def test_entries(self): self.assertEqual(len(self.entries), 25) diff --git a/events/tests/test_importer.py b/events/tests/test_importer.py index 8df0db327..50efb4968 100644 --- a/events/tests/test_importer.py +++ b/events/tests/test_importer.py @@ -20,7 +20,7 @@ def setUpClass(cls): def test_injest(self): importer = ICSImporter(self.calendar) - with open(EVENTS_CALENDAR) as fh: + with open(EVENTS_CALENDAR, encoding='utf-8') as fh: ical = fh.read() importer.import_events_from_text(ical) diff --git a/peps/converters.py b/peps/converters.py index 430ccaa6f..59d3d6b85 100644 --- a/peps/converters.py +++ b/peps/converters.py @@ -33,7 +33,7 @@ def convert_pep0(): for a Python.org Page returns the core body HTML necessary only """ pep0_path = os.path.join(settings.PEP_REPO_PATH, 'pep-0000.html') - pep0_content = open(pep0_path).read() + pep0_content = open(pep0_path, encoding="utf-8 ").read() soup = BeautifulSoup(pep0_content) From d5d9d9f6f7da9d88406e6943da1154ae10999394 Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Thu, 5 Oct 2017 19:54:15 +0100 Subject: [PATCH 2/7] Change the way in which we date or datetimes from the iCal file while importing events --- events/importer.py | 6 ++-- events/tests/test_importer.py | 52 ++++++++++++++++++++++++++++++++++- events/utils.py | 4 +++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/events/importer.py b/events/importer.py index 4987af8c2..2930b4043 100644 --- a/events/importer.py +++ b/events/importer.py @@ -4,7 +4,7 @@ import requests from .models import EventLocation, Event, OccurringRule -from .utils import convert_dt_to_aware +from .utils import extract_date_or_datetime DATE_RESOLUTION = timedelta(1) TIME_RESOLUTION = timedelta(0, 0, 1) @@ -29,8 +29,8 @@ def import_occurrence(self, event, event_data): # Django will already convert to datetime by setting the time to 0:00, # but won't add any timezone information. We will convert them to # aware datetime objects manually. - dt_start = convert_dt_to_aware(event_data['DTSTART'].dt) - dt_end = convert_dt_to_aware(event_data['DTEND'].dt) + dt_start = extract_date_or_datetime(event_data['DTSTART'].dt) + dt_end = extract_date_or_datetime(event_data['DTEND'].dt) # Let's mark those occurrences as 'all-day'. all_day = ( diff --git a/events/tests/test_importer.py b/events/tests/test_importer.py index 50efb4968..5243e9c7d 100644 --- a/events/tests/test_importer.py +++ b/events/tests/test_importer.py @@ -1,6 +1,7 @@ import os -import unittest + from django.test import TestCase +from django.utils.timezone import datetime, make_aware from django.conf import settings from events.importer import ICSImporter @@ -61,6 +62,9 @@ def test_modified_event(self): e.description.rendered, 'PythonCamp Cologne 2016' ) + self.assertTrue(e.next_or_previous_time.all_day) + self.assertEqual(e.next_or_previous_time.dt_start, make_aware(datetime(year=2016, month=4, day=2))) + self.assertEqual(e.next_or_previous_time.dt_end, make_aware(datetime(year=2016, month=4, day=4))) ical = """\ BEGIN:VCALENDAR @@ -94,3 +98,49 @@ def test_modified_event(self): self.assertEqual(e.pk, e2.pk) self.assertEqual(e2.calendar.url, EVENTS_CALENDAR_URL) self.assertEqual(e2.description.rendered, 'Python Istanbul') + self.assertTrue(e2.next_or_previous_time.all_day) + self.assertEqual(e2.next_or_previous_time.dt_start, make_aware(datetime(year=2016, month=4, day=2))) + self.assertEqual(e2.next_or_previous_time.dt_end, make_aware(datetime(year=2016, month=4, day=4))) + + ical = """\ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//www.marudot.com//iCal Event Maker +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/London +TZURL:http://tzurl.org/zoneinfo-outlook/Europe/London +X-LIC-LOCATION:Europe/London +BEGIN:DAYLIGHT +TZOFFSETFROM:+0000 +TZOFFSETTO:+0100 +TZNAME:BST +DTSTART:19700329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0100 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:19701025T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20171005T184245Z +UID:20171005T184245Z-752317732@marudot.com +DTSTART;TZID="Europe/London":20171031T190000 +DTEND;TZID="Europe/London":20171031T210000 +SUMMARY:Python Sheffield +DESCRIPTION:Monthly Python user group in Sheffield\, United Kingdom http://groups.google.com/group/python-sheffield Twitter: @pysheff +LOCATION:GIST Lab in Sheffield (http://thegisthub.net/groups/gistlab/) +END:VEVENT +END:VCALENDAR +""" + importer.import_events_from_text(ical) + + e3 = Event.objects.get(uid='20171005T184245Z-752317732@marudot.com') + self.assertEqual(e3.description.rendered, 'Monthly Python user group in Sheffield, United Kingdom http://groups.google.com/group/python-sheffield Twitter: @pysheff') + self.assertFalse(e3.next_or_previous_time.all_day) + self.assertEqual(e3.next_or_previous_time.dt_start, make_aware(datetime(year=2017, month=10, day=31, hour=19))) + self.assertEqual(e3.next_or_previous_time.dt_end, make_aware(datetime(year=2017, month=10, day=31, hour=21))) diff --git a/events/utils.py b/events/utils.py index 7eca18f95..e99281a6d 100644 --- a/events/utils.py +++ b/events/utils.py @@ -14,6 +14,10 @@ def minutes_resolution(dt): return dt - dt.second * datetime.timedelta(0, 1, 0) - dt.microsecond * datetime.timedelta(0, 0, 1) +def extract_date_or_datetime(dt): + return dt if not isinstance(dt, datetime.datetime) else convert_dt_to_aware(dt) + + def date_to_datetime(date, tzinfo=None): if tzinfo is None: tzinfo = pytz.UTC From 068292d9e4474ee698cd753ccc2503010ab6c7f9 Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Mon, 9 Oct 2017 22:52:09 +0100 Subject: [PATCH 3/7] Create the exclude_ending_day filter, and use when necessary in time_tag --- events/templatetags/events.py | 7 ++++ events/tests/test_templatetags.py | 27 +++++++++++++ templates/events/includes/time_tag.html | 53 +++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 events/tests/test_templatetags.py diff --git a/events/templatetags/events.py b/events/templatetags/events.py index 1c580c6ef..23af52f43 100644 --- a/events/templatetags/events.py +++ b/events/templatetags/events.py @@ -1,5 +1,6 @@ from django import template from django.utils import timezone +from datetime import date, timedelta from ..models import Event @@ -14,3 +15,9 @@ def get_events_upcoming(limit=5, only_featured=False): if only_featured: qs.filter(featured=True) return qs[:limit] + + +@register.filter_function +def exclude_ending_day(next_event_date): + return next_event_date - timedelta(days=1) + diff --git a/events/tests/test_templatetags.py b/events/tests/test_templatetags.py new file mode 100644 index 000000000..bb41239ab --- /dev/null +++ b/events/tests/test_templatetags.py @@ -0,0 +1,27 @@ +from django.test import TestCase + +import datetime + +from ..templatetags.events import exclude_ending_day + + +class TemplateTagsTests(TestCase): + def test_exclude_ending_day(self): + ending_date = datetime.datetime(year=2017, month=10, day=9, hour=0, minute=0, second=0) + + ending_date_after_filter = exclude_ending_day(ending_date) + + self.assertEqual( + ending_date_after_filter, + datetime.datetime(year=2017, month=10, day=8) + ) + + def test_exclude_ending_date_1st_january(self): + ending_date = datetime.datetime(year=2017, month=1, day=1, hour=0, minute=0, second=0) + + ending_date_after_filter = exclude_ending_day(ending_date) + + self.assertEqual( + ending_date_after_filter, + datetime.datetime(year=2016, month=12, day=31) + ) \ No newline at end of file diff --git a/templates/events/includes/time_tag.html b/templates/events/includes/time_tag.html index 34bcaa60f..da0658285 100644 --- a/templates/events/includes/time_tag.html +++ b/templates/events/includes/time_tag.html @@ -1,5 +1,52 @@ +{% load events %} + +{% with dt_start=next_time.dt_start %} + {% if next_time.single_day %} - + +{% with dt_end=next_time.dt_end %} + +{% endwith %} + +{% elif next_time.all_day %} + +{% with dt_end=next_time.dt_end|exclude_ending_day %} + +{% endwith %} + {% else %} - -{% endif %} \ No newline at end of file + +{% with dt_end=next_time.dt_end %} + +{% endwith %} + +{% endif %} +{% endwith %} From a27aa9a4713919898185981fe3ae43bb3cbd598b Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Mon, 9 Oct 2017 23:13:12 +0100 Subject: [PATCH 4/7] Change event_detail to use the new exclude_ending_day filter --- templates/events/event_detail.html | 49 +++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/templates/events/event_detail.html b/templates/events/event_detail.html index 80773bd6b..59ffc04df 100644 --- a/templates/events/event_detail.html +++ b/templates/events/event_detail.html @@ -1,5 +1,5 @@ {% extends "base.html" %} - +{% load events %} {% block page_title %}{{ object.title|striptags }} | {{ SITE_INFO.site_name }}{% endblock %} {% block og_title %}{{ object.title|striptags }}{% endblock %} @@ -25,13 +25,26 @@

{% endif %} {% with object.next_or_previous_time as next_time %} + {% if next_time.single_day %}

- - + + + {% if next_time.valid_dt_end %} - {% endif %}{% endif %}, + + {% endif %}

@@ -39,11 +52,33 @@

- - , - + + + + {% if next_time.all_day %} + + {% else %} + + {% endif %}

{% endif %} + {% endwith %} From 0b20ed9e55cc1f13527223e5a952f2a669524b99 Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Mon, 9 Oct 2017 23:29:07 +0100 Subject: [PATCH 5/7] Change the search results to use the new exclude_ending_day --- templates/search/includes/events.event.html | 56 ++++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/templates/search/includes/events.event.html b/templates/search/includes/events.event.html index f015b5513..99ae28828 100644 --- a/templates/search/includes/events.event.html +++ b/templates/search/includes/events.event.html @@ -1,24 +1,66 @@ +{% load events %} +

Event – {{ result.name }}

{% with result.object.next_or_previous_time as next_time %} +{% with next_time.dt_start as dt_start %} + {% if next_time.single_day %} + +{% with next_time.dt_end as dt_end %}

- - + + {% if next_time.valid_dt_end %} - {% endif %}, - + + {% endif %} +

+{% endwith %} + +{% elif next_time.all_day %} + +{% with next_time.dt_end|exclude_ending_day as dt_end %} + +

+ + + +

+{% endwith %} {% else %} +{% with next_time.dt_end as dt_end %}

- - , - + + +

+{% endwith %} + {% endif %} + +{% endwith %} {% endwith %} {% if result.venue %} From 47dc82f7c444ee631f19074cfe11dfaeca2367c5 Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Tue, 10 Oct 2017 19:13:05 +0100 Subject: [PATCH 6/7] Change the implementation of the single_day property --- events/models.py | 10 ++++++++-- events/tests/test_models.py | 38 ++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/events/models.py b/events/models.py index 1f315654d..a10add51d 100644 --- a/events/models.py +++ b/events/models.py @@ -253,7 +253,10 @@ def duration(self): @property def single_day(self): - return self.dt_start.date() == self.dt_end.date() + return ( + self.dt_start.date() == self.dt_end.date() or + self.all_day is True and self.dt_start + datetime.timedelta(days=1) == self.dt_end + ) def duration_default(): @@ -319,7 +322,10 @@ def dt_end(self): @property def single_day(self): - return self.dt_start.date() == self.dt_end.date() + return ( + self.dt_start.date() == self.dt_end.date() or + self.all_day is True and self.dt_start + datetime.timedelta(days=1) == self.dt_end + ) def save(self, *args, **kwargs): self.duration_internal = timedelta_parse(self.duration) diff --git a/events/tests/test_models.py b/events/tests/test_models.py index 0f3bafe76..52b652c04 100644 --- a/events/tests/test_models.py +++ b/events/tests/test_models.py @@ -47,6 +47,24 @@ def test_occurring_event(self): self.assertEqual(self.event.next_time, None) self.assertTrue(self.event.is_past) + def test_occurring_event_single_day(self): + now = seconds_resolution(timezone.now()) + + occurring_time_dtstart = now + datetime.timedelta(days=3) + occurring_time_dtend = occurring_time_dtstart + datetime.timedelta(days=1) + + ot = OccurringRule.objects.create( + event=self.event, + dt_start=occurring_time_dtstart, + dt_end=occurring_time_dtend, + all_day=True + ) + + self.assertEqual(self.event.next_time.dt_start, occurring_time_dtstart) + self.assertEqual(self.event.previous_time, None) + self.assertEqual(self.event.next_or_previous_time.dt_start, occurring_time_dtstart) + self.assertTrue(self.event.next_time.single_day) + def test_recurring_event(self): now = seconds_resolution(timezone.now()) @@ -62,7 +80,6 @@ def test_recurring_event(self): self.assertEqual(self.event.next_time.dt_start, recurring_time_dtstart) self.assertTrue(rt.valid_dt_end()) - rt.begin = now - datetime.timedelta(days=5) rt.finish = now - datetime.timedelta(days=3) rt.save() @@ -71,6 +88,25 @@ def test_recurring_event(self): self.assertEqual(event.next_time, None) self.assertEqual(Event.objects.for_datetime().count(), 0) + def test_recurring_event_single_day(self): + now = seconds_resolution(timezone.now()) + + occurring_time_dtstart = now + datetime.timedelta(days=3) + occurring_time_dtend = occurring_time_dtstart + datetime.timedelta(days=1) + + rt = RecurringRule.objects.create( + event=self.event, + begin=occurring_time_dtstart, + finish=occurring_time_dtend, + all_day=True, + duration="1 days" + ) + + self.assertEqual(self.event.next_time.dt_start, occurring_time_dtstart) + self.assertEqual(self.event.previous_time, None) + self.assertEqual(self.event.next_or_previous_time.dt_start, occurring_time_dtstart) + self.assertTrue(self.event.next_time.single_day) + def test_rrule(self): now = seconds_resolution(timezone.now()) From 4657d3011d9cc4f1ab302da68dbf571bac712a73 Mon Sep 17 00:00:00 2001 From: Stefano Cianciulli Date: Tue, 10 Oct 2017 20:09:59 +0100 Subject: [PATCH 7/7] Fix variable names in test_models --- events/tests/test_models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/events/tests/test_models.py b/events/tests/test_models.py index 52b652c04..2416b0bca 100644 --- a/events/tests/test_models.py +++ b/events/tests/test_models.py @@ -91,20 +91,20 @@ def test_recurring_event(self): def test_recurring_event_single_day(self): now = seconds_resolution(timezone.now()) - occurring_time_dtstart = now + datetime.timedelta(days=3) - occurring_time_dtend = occurring_time_dtstart + datetime.timedelta(days=1) + recurring_time_dtstart = now + datetime.timedelta(days=3) + recurring_time_dtend = recurring_time_dtstart + datetime.timedelta(days=1) rt = RecurringRule.objects.create( event=self.event, - begin=occurring_time_dtstart, - finish=occurring_time_dtend, + begin=recurring_time_dtstart, + finish=recurring_time_dtend, all_day=True, duration="1 days" ) - self.assertEqual(self.event.next_time.dt_start, occurring_time_dtstart) + self.assertEqual(self.event.next_time.dt_start, recurring_time_dtstart) self.assertEqual(self.event.previous_time, None) - self.assertEqual(self.event.next_or_previous_time.dt_start, occurring_time_dtstart) + self.assertEqual(self.event.next_or_previous_time.dt_start, recurring_time_dtstart) self.assertTrue(self.event.next_time.single_day) def test_rrule(self):