Repository: DjangoGirls/djangogirls

Issue:
catch-all url for event
Hi

In core/urls.py, there is this line:
url(r'^(?P<city>[\w\d/]+)$', 'core.views.event', name='event'),

This basically says "match djangogirls.org/ plus any combination of letters and go to the event view".  Then in core/views.py, there is a try/except clause on line 50 that says if the event page doesn't exist, redirect to /events.  When developing the jobs site, we've found that the url for /jobs/ redirects to /events when the trailing slash is omitted - the event url plus the redirect is a catchall url, so we can never get a 404 (any nonsense url e.g. djangogirls.org/sdfshdjkfkasejaoefjasdlk redirects to djangogirls.org/events).  

I can see that a workaround redirect has already been done for /admin in djangogirls/urls.py, but if the site is being expanded with more apps/sections, this will mean that it will need to be done for every url.  Is this how you'd prefer it to be done, or would it be better to add a prefix for event (/event/berlin etc - which admittedly will need to deal with the event pages already set up)

Thanks
Becky



Discussion:
Hi @rebkwok!

Yeah, I am aware of this for a while. I'd rather fix this (so all urls have / at the end, for example), than to change the url of every event we have. Especially if we're printing posters & stuff, djangogirls.org/london is better and easier to remember than djangogirls.org/event/london.

djangogirls.org/london/ is not as bad as djangogirls.org/event/london. We should redirect everything that doesn't have the last / to the same address with /. 

If the event does not exist, we could show 404 (but maybe with a big link to the events page?)

Do you want to work on this?

Thanks,
Ola

Sure, I would be happy to work on this :)

On Sat, Jan 10, 2015 at 12:42 PM, Ola Sitarska notifications@github.com
wrote:

> Hi @rebkwok https://github.com/rebkwok!
> 
> Yeah, I am aware of this for a while. I'd rather fix this (so all urls
> have / at the end, for example), than to change the url of every event we
> have. Especially if we're printing posters & stuff, djangogirls.org/london
> is better and easier to remember than djangogirls.org/event/london.
> 
> djangogirls.org/london/ is not as bad as djangogirls.org/event/london. We
> should redirect everything that doesn't have the last / to the same address
> with /.
> 
> If the event does not exist, we could show 404 (but maybe with a big link
> to the events page?)
> 
> Do you want to work on this?
> 
> Thanks,
> Ola
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/DjangoGirls/djangogirls/issues/13#issuecomment-69454527
> .


Resolving patch:
diff --git a/core/urls.py b/core/urls.py
--- a/core/urls.py
+++ b/core/urls.py
@@ -2,12 +2,13 @@
 from . import views
 
 urlpatterns = patterns('',
+#    url(r'^test-404/$', 'core.views.error404'),
     url(r'^events/$', views.events, name='events'),
     url(r'^events/calendar.ics$', views.events_ical, name='icalendar'),
     url(r'^resources/$', views.resources, name='resources'),
     url(r'^organize/$', views.organize, name='organize'),
     url(r'^story/$', views.stories, name='stories'),
 
-    url(r'^(?P<city>[\w\d/]+)$', 'core.views.event', name='event'),
+    url(r'^(?P<city>[\w\d/]+)/$', 'core.views.event', name='event'),
     url(r'^$', views.index, name='index'),
 )
diff --git a/core/views.py b/core/views.py
--- a/core/views.py
+++ b/core/views.py
@@ -2,12 +2,14 @@
 
 import icalendar
 
-from django.http import HttpResponse
+from django.http import HttpResponse, Http404
 from django.shortcuts import render, redirect
 from django.utils import timezone
+from django_date_extensions.fields import ApproximateDate
 
 from .models import *
 
+
 def index(request):
 
     stories = Story.objects.all().order_by('-created')[:4]
@@ -18,6 +20,7 @@ def index(request):
         'stories': stories,
     })
 
+
 def events(request):
 
     return render(request, 'events.html', {
@@ -25,12 +28,15 @@ def events(request):
         'past_events': Event.objects.past(),
     })
 
+
 def resources(request):
     return render(request, 'resources.html', {})
 
+
 def organize(request):
     return render(request, 'organize.html', {})
 
+
 def stories(request):
 
     return render(request, 'stories.html', {
@@ -39,16 +45,19 @@ def stories(request):
 
 
 def event(request, city):
-    if city[-1:] == "/":
-        city = city[:-1]
 
+    now = timezone.now()
+    now_approx = ApproximateDate(year=now.year, month=now.month, day=now.day)
     try:
-        if request.user.is_authenticated() or request.GET.has_key('preview'):
-            page = EventPage.objects.get(url=city)
-        else:
-            page = EventPage.objects.get(url=city, is_live=True)
+        page = EventPage.objects.get(url=city)
+        if not (request.user.is_authenticated()
+                or request.GET.has_key('preview')):
+            if not page.is_live:
+                past = page.event.date <= now_approx
+                return render(request, "event_not_live.html",
+                              {'city': city, 'past': past})
     except EventPage.DoesNotExist:
-        return redirect('core:events')
+        raise Http404
 
     menu = EventPageMenu.objects.filter(page=page)
     content = EventPageContent.objects.filter(page=page, is_public=True)
@@ -70,4 +79,5 @@ def events_ical(request):
             continue  # Skip events with an approximate date
         calendar.add_component(ical_event)
 
-    return HttpResponse(calendar.to_ical(), content_type='text/calendar; charset=UTF-8')
+    return HttpResponse(calendar.to_ical(),
+                        content_type='text/calendar; charset=UTF-8')


Test patch:
diff --git a/core/tests/test_views.py b/core/tests/test_views.py
--- a/core/tests/test_views.py
+++ b/core/tests/test_views.py
@@ -1,8 +1,13 @@
-from django.test import TestCase
+from datetime import timedelta
+from django.test import TestCase, RequestFactory
 from django.test.client import Client
 from django.core.urlresolvers import reverse
+from django.utils import timezone
+from django_date_extensions.fields import ApproximateDate
 
 from core.models import User, Event, EventPage, EventPageContent, EventPageMenu, Sponsor
+from core.views import event
+
 
 class CoreViewsTestCase(TestCase):
 
@@ -10,6 +15,7 @@ class CoreViewsTestCase(TestCase):
 
     def setUp(self):
         self.client = Client()
+        self.factory = RequestFactory()
 
         self.ola = User.objects.get(pk=1)
         self.peter = User.objects.get(pk=2)
@@ -18,6 +24,7 @@ def setUp(self):
         self.event_1 = Event.objects.get(pk=1) # In the future
         self.event_2 = Event.objects.get(pk=2) # In the past
         self.event_3 = Event.objects.get(pk=3) # Hidden from homepage
+        self.event_4 = Event.objects.get(pk=4) # Not live, no date set
 
     def test_index(self):
         # Access homepage
@@ -44,14 +51,16 @@ def test_event_published(self):
         event_page_2 = EventPage.objects.get(event=self.event_2)
 
         # Check if it's possible to access the page
-        resp_1 = self.client.get('/'+event_page_1.url)
+        url1 = '/' + event_page_1.url + '/'
+        resp_1 = self.client.get(url1)
         self.assertEqual(resp_1.status_code, 200)
 
         # Check if it's possible to access the page
-        resp_2 = self.client.get('/'+event_page_2.url)
+        url2 = '/' + event_page_2.url + '/'
+        resp_2 = self.client.get(url2)
         self.assertEqual(resp_2.status_code, 200)
 
-        # Check if website is returing correct data
+        # Check if website is returning correct data
         self.assertTrue('page' and 'menu' and 'content' in resp_1.context)
         self.assertTrue('page' and 'menu' and 'content' in resp_2.context)
 
@@ -61,6 +70,58 @@ def test_event_published(self):
     def test_event_unpublished(self):
         event_page_3 = EventPage.objects.get(event=self.event_3)
 
-        # Check if accessing the page is redirecting to index
-        resp = self.client.get('/'+event_page_3.url)
-        self.assertEqual(resp.status_code, 302)
+        # Check if accessing unpublished page renders the event_not_live page
+        url = '/' + event_page_3.url + '/'
+        resp = self.client.get(url)
+        self.assertEqual(resp.status_code, 200)
+
+        # Check if website is returning correct data
+        self.assertTrue('city' and 'past' in resp.context)
+
+    def test_event_unpublished_with_future_and_past_dates(self):
+        future_date = timezone.now() + timedelta(days=1)
+        past_date = timezone.now() - timedelta(days=1)
+        event_page_4 = EventPage.objects.get(event=self.event_4)
+
+        # make the event date in the future
+        self.event_4.date = ApproximateDate(
+            year=future_date.year, month=future_date.month, day=future_date.day)
+        self.event_4.save()
+
+        # Check if accessing unpublished page renders the event_not_live page
+        url = '/' + event_page_4.url + '/'
+        resp = self.client.get(url)
+        self.assertEqual(resp.status_code, 200)
+
+        # Check if website is returning correct content
+        self.assertIn('will be coming soon', resp.content, 'Incorrect content')
+
+        # make the event date in the past
+        self.event_4.date = ApproximateDate(
+            year=past_date.year, month=past_date.month, day=past_date.day)
+        self.event_4.save()
+
+        # Check if accessing unpublished page renders the event_not_live page
+        url = '/' + event_page_4.url + '/'
+        resp = self.client.get(url)
+        self.assertEqual(resp.status_code, 200)
+
+        # Check if website is returning correct content
+        self.assertIn('has already happened', resp.content, 'Incorrect content')
+
+    def test_event_unpublished_with_authenticated_user(self):
+        """ Test that an unpublished page can be accessed when the user is
+        authenticated """
+
+        event_page_3 = EventPage.objects.get(event=self.event_3)
+        url = '/' + event_page_3.url + '/'
+        request = self.factory.get(url)
+
+        # Set the user on the request to an authenticated user
+        request.user = self.ola
+
+        # Check if the unpublished page can be accessed
+        resp = event(request, event_page_3.url)
+        self.assertEqual(resp.status_code, 200)
+        # Check if website is returning correct data
+        self.assertIn(event_page_3.title, resp.content.decode('utf-8'))
\ No newline at end of file