From 6238da264d1dc3d041eda6a00c028b3e60d0e40d Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Fri, 21 Jun 2013 17:35:04 +0200 Subject: [PATCH] Move event to database, support closed events Move event information from settings.py to database. Create a middleware to check that an event is active. Return a "no event" page when no event is detected (allows to have the website always running). Leverage the middleware to push event title/subtitle info into templates. Remove the context processor we used to that end before. Add support for 'CFP closed' status that removes the session suggestion button. Replace the loadtopics command by a loadevent command that lets you create the event and topics in the database in one shot. Change-Id: I1233962606a59fe1d353eaad024c8d06c8fcbeef Reviewed-on: https://review.openstack.org/34001 Approved: Thierry Carrez Reviewed-by: Thierry Carrez Tested-by: Jenkins --- README.rst | 6 +- cfp/admin.py | 3 +- cfp/context_processors.py | 20 ------- .../commands/{loadtopics.py => loadevent.py} | 8 ++- cfp/middleware.py | 35 +++++++++++ cfp/models.py | 16 +++++ cfp/templates/cfplist.html | 4 ++ cfp/templates/noevent.html | 25 ++++++++ cfp/views.py | 52 +++++++++-------- topics.json.sample => event.json.sample | 58 ++++++++++--------- local_settings.py.sample | 6 -- scheduling/views.py | 48 +++++++-------- settings.py | 9 +-- 13 files changed, 177 insertions(+), 113 deletions(-) delete mode 100644 cfp/context_processors.py rename cfp/management/commands/{loadtopics.py => loadevent.py} (85%) create mode 100644 cfp/middleware.py create mode 100644 cfp/templates/noevent.html rename topics.json.sample => event.json.sample (72%) diff --git a/README.rst b/README.rst index be9b059..0dffdd8 100644 --- a/README.rst +++ b/README.rst @@ -31,10 +31,10 @@ settings there. Create empty database: ./manage.py syncdb -Copy topics.json.sample to topics.json and edit the file to match -the topics you want to have. Then run: +Copy event.json.sample to event.json and edit the file to match +the event and topics you want to have. Then run: -./manage.py loadtopics topics.json +./manage.py loadevent event.json Then run a dev server using: ./manage.py runserver diff --git a/cfp/admin.py b/cfp/admin.py index 1c4a55c..0f6e588 100644 --- a/cfp/admin.py +++ b/cfp/admin.py @@ -13,9 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from odsreg.cfp.models import Topic, Proposal +from odsreg.cfp.models import Topic, Proposal, Event from django.contrib import admin admin.site.register(Topic) admin.site.register(Proposal) +admin.site.register(Event) diff --git a/cfp/context_processors.py b/cfp/context_processors.py deleted file mode 100644 index cefe44d..0000000 --- a/cfp/context_processors.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2012 Thierry Carrez -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -from django.conf import settings - - -def event(request): - return {'event': {'title': settings.EVENT_TITLE, - 'subtitle': settings.EVENT_SUBTITLE}} diff --git a/cfp/management/commands/loadtopics.py b/cfp/management/commands/loadevent.py similarity index 85% rename from cfp/management/commands/loadtopics.py rename to cfp/management/commands/loadevent.py index f3b4f5c..7abc8da 100644 --- a/cfp/management/commands/loadtopics.py +++ b/cfp/management/commands/loadevent.py @@ -16,7 +16,7 @@ import json from django.core.management.base import BaseCommand, CommandError -from cfp.models import Topic +from cfp.models import Event, Topic class Command(BaseCommand): @@ -34,7 +34,11 @@ class Command(BaseCommand): except ValueError as exc: raise CommandError("Malformed JSON: %s" % exc.message) - for topicname, desc in data.iteritems(): + e = Event(title=data['event']['title'], + subtitle=data['event']['subtitle']) + e.save() + + for topicname, desc in data['topics'].iteritems(): t = Topic(name=topicname, lead_username=desc['lead_username'], description=desc['description']) t.save() diff --git a/cfp/middleware.py b/cfp/middleware.py new file mode 100644 index 0000000..8086671 --- /dev/null +++ b/cfp/middleware.py @@ -0,0 +1,35 @@ +# Copyright 2013 Thierry Carrez +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.shortcuts import render + +from odsreg.cfp.models import Event + + +class EventMiddleware(): + + def process_request(self, request): + # Check that we have an event available, return canned page if we don't + events = Event.objects.filter(status__in=['A', 'C']) + if events.count() != 1: + return render(request, "noevent.html") + else: + self.event = events[0] + return None + + def process_template_response(self, request, response): + # Add event to the response context + response.context_data['event'] = self.event + return response diff --git a/cfp/models.py b/cfp/models.py index aba12b5..8967639 100644 --- a/cfp/models.py +++ b/cfp/models.py @@ -19,6 +19,22 @@ from django.contrib.auth.models import User from cfp.utils import validate_bp +class Event(models.Model): + STATUSES = ( + ('I', 'Inactive'), + ('A', 'Active'), + ('C', 'CFP closed'), + ) + title = models.CharField(max_length=50) + subtitle = models.CharField(max_length=80) + sched_url = models.CharField(max_length=40, blank=True) + sched_api_key = models.CharField(max_length=50, blank=True) + status = models.CharField(max_length=1, choices=STATUSES, default='A') + + def __unicode__(self): + return self.title + + class Topic(models.Model): name = models.CharField(max_length=40) lead_username = models.CharField(max_length=40) diff --git a/cfp/templates/cfplist.html b/cfp/templates/cfplist.html index 89162ed..07c8ceb 100644 --- a/cfp/templates/cfplist.html +++ b/cfp/templates/cfplist.html @@ -7,7 +7,11 @@ {% endblock %} {% block content %} +{% if event.status == 'A' %} Suggest session +{% else %} +Session suggestion is now closed. +{% endif %} {% for topic in reviewable_topics %} Review topic: {{ topic.name }} {% endfor %} diff --git a/cfp/templates/noevent.html b/cfp/templates/noevent.html new file mode 100644 index 0000000..116ae85 --- /dev/null +++ b/cfp/templates/noevent.html @@ -0,0 +1,25 @@ + + + + + OpenStack Design Summit + + + +
+ +
+ +
+

This site opens a few months before every OpenStack Summit to handle + session suggestions and scheduling for the "Design Summit" track.

+

Please come back as we get nearer to the event.

+
+ + diff --git a/cfp/views.py b/cfp/views.py index 89b744a..10facb3 100644 --- a/cfp/views.py +++ b/cfp/views.py @@ -15,11 +15,11 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.shortcuts import render from django.conf import settings from django.contrib.auth import logout from django.core.mail import EmailMessage from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.template.response import TemplateResponse from django.utils.encoding import smart_str from odsreg.cfp.models import Proposal, Topic, Comment @@ -34,9 +34,9 @@ def list(request): reviewable_topics = Topic.objects.filter( lead_username=request.user.username) request.session['lastlist'] = "" - return render(request, "cfplist.html", - {'proposals': proposals, - 'reviewable_topics': reviewable_topics}) + return TemplateResponse(request, "cfplist.html", + {'proposals': proposals, + 'reviewable_topics': reviewable_topics}) @login_required @@ -46,15 +46,15 @@ def topiclist(request, topicid): return HttpResponseForbidden("Forbidden") proposals = Proposal.objects.filter(topic=topicid) request.session['lastlist'] = "cfp/topic/%s" % topicid - return render(request, "topiclist.html", - {'proposals': proposals, - 'topic': topic}) + return TemplateResponse(request, "topiclist.html", + {'proposals': proposals, + 'topic': topic}) @login_required def topicstatus(request): topics = Topic.objects.all() - return render(request, "topicstatus.html", {'topics': topics}) + return TemplateResponse(request, "topicstatus.html", {'topics': topics}) @login_required @@ -71,7 +71,9 @@ def create(request): form = ProposalForm() topics = Topic.objects.all() - return render(request, 'cfpcreate.html', {'topics': topics, 'form': form}) + return TemplateResponse(request, 'cfpcreate.html', + {'topics': topics, + 'form': form}) @login_required @@ -87,12 +89,12 @@ def details(request, proposalid): else: form = CommentForm() comments = Comment.objects.filter(proposal=proposal) - return render(request, "cfpdetails.html", - {'proposal': proposal, - 'form': form, - 'comments': comments, - 'editable': is_editable(proposal, request.user), - 'blueprints': linkify(proposal.blueprints)}) + return TemplateResponse(request, "cfpdetails.html", + {'proposal': proposal, + 'form': form, + 'comments': comments, + 'editable': is_editable(proposal, request.user), + 'blueprints': linkify(proposal.blueprints)}) @login_required @@ -107,8 +109,8 @@ def edit(request, proposalid): return HttpResponseRedirect('/%s' % request.session['lastlist']) else: form = ProposalEditForm(instance=proposal) - return render(request, 'cfpedit.html', {'form': form, - 'proposal': proposal}) + return TemplateResponse(request, 'cfpedit.html', {'form': form, + 'proposal': proposal}) @login_required @@ -119,7 +121,7 @@ def delete(request, proposalid): if request.method == 'POST': proposal.delete() return HttpResponseRedirect('/%s' % request.session['lastlist']) - return render(request, 'cfpdelete.html', {'proposal': proposal}) + return TemplateResponse(request, 'cfpdelete.html', {'proposal': proposal}) @login_required @@ -138,8 +140,8 @@ def switch(request, proposalid): return HttpResponseRedirect('/%s' % request.session['lastlist']) else: form = ProposalSwitchForm(instance=proposal) - return render(request, 'cfpswitch.html', {'form': form, - 'proposal': proposal}) + return TemplateResponse(request, 'cfpswitch.html', {'form': form, + 'proposal': proposal}) @login_required @@ -191,11 +193,11 @@ You can edit your proposal at: %s/cfp/edit/%s""" \ else: form = ProposalReviewForm(instance=proposal) comments = Comment.objects.filter(proposal=proposal) - return render(request, 'cfpreview.html', - {'form': form, - 'proposal': proposal, - 'comments': comments, - 'blueprints': linkify(proposal.blueprints)}) + return TemplateResponse(request, 'cfpreview.html', + {'form': form, + 'proposal': proposal, + 'comments': comments, + 'blueprints': linkify(proposal.blueprints)}) def dologout(request): diff --git a/topics.json.sample b/event.json.sample similarity index 72% rename from topics.json.sample rename to event.json.sample index e85c365..f19b38f 100644 --- a/topics.json.sample +++ b/event.json.sample @@ -1,54 +1,60 @@ { - "Nova": { + "event": { + "title": "Grizzly Design Summit", + "subtitle": "OpenStack Summit, San Diego, Oct 15-18, 2012" + }, + "topics": { + "Nova": { "description": "Sessions about OpenStack Compute (Nova)", "lead_username": "vishvananda" - }, - "Swift": { + }, + "Swift": { "description": "Sessions about OpenStack Object Storage (Swift)", "lead_username": "notmyname" - }, - "Glance": { + }, + "Glance": { "description": "Sessions about OpenStack Image service (Glance)", "lead_username": "bcwaldon" - }, - "Keystone": { + }, + "Keystone": { "description": "Sessions about OpenStack Identity (Keystone)", "lead_username": "heckj" - }, - "Horizon": { + }, + "Horizon": { "description": "Sessions about OpenStack Dashboard (Horizon)", "lead_username": "gabriel-hurley" - }, - "Cinder": { + }, + "Cinder": { "description": "Sessions about OpenStack Block Storage (Cinder)", "lead_username": "john-griffith" - }, - "Networking": { - "description": "Sessions about OpenStack Network service (Quantum), and the future of Nova networking", + }, + "Neutron": { + "description": "Sessions about OpenStack Network service (Neutron), and the future of Nova networking", "lead_username": "danwent" - }, - "Oslo": { + }, + "Oslo": { "description": "Common code and libraries between OpenStack projects", "lead_username": "markmc" - }, - "Ceilometer": { + }, + "Ceilometer": { "description": "Sessions about Ceilometer (Metering, Monitoring)", "lead_username": "nijaba" - }, - "Heat": { + }, + "Heat": { "description": "Sessions about Heat (Resource Orchestration)", "lead_username": "sdake" - }, - "Documentation": { + }, + "Documentation": { "description": "Future efforts on OpenStack documentation", "lead_username": "annegentle" - }, - "QA": { + }, + "QA": { "description": "Sessions about QA efforts: unit tests, integration tests, upgrade tests, Tempest...", "lead_username": "david-kranz" - }, - "Process": { + }, + "Process": { "description": "Development processes and tools, release schedule, core infrastructure for the project", "lead_username": "ttx" + } } } diff --git a/local_settings.py.sample b/local_settings.py.sample index fdfbe7c..b0fc797 100644 --- a/local_settings.py.sample +++ b/local_settings.py.sample @@ -32,12 +32,6 @@ DEBUG = False TEMPLATE_DEBUG = DEBUG #OPENID_USE_AS_ADMIN_LOGIN = True -# Change to match your event -EVENT_TITLE = "Grizzly Design Summit" -EVENT_SUBTITLE = "OpenStack Summit, San Diego, Oct 15-18, 2012" -SCHED_URL = "essexdesignsummit" -SCHED_API_KEY = "getThisFromSched" - # Emails SEND_MAIL = False EMAIL_PREFIX = "[DesignSummit] " diff --git a/scheduling/views.py b/scheduling/views.py index 5315cdd..bb9b716 100644 --- a/scheduling/views.py +++ b/scheduling/views.py @@ -16,11 +16,10 @@ import urllib import urllib2 -from django.shortcuts import render -from django.conf import settings from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.template.response import TemplateResponse from django.utils.encoding import smart_str -from odsreg.cfp.models import Proposal, Topic +from odsreg.cfp.models import Proposal, Topic, Event from odsreg.cfp.utils import topiclead from odsreg.scheduling.forms import SlotForm from odsreg.scheduling.models import Slot @@ -56,21 +55,22 @@ def scheduling(request, topicid): accepted = Proposal.objects.filter(status='A', scheduled=False, topic=topic) schedule = Slot.objects.filter(topic=topic) - return render(request, "scheduling.html", - {'accepted': accepted, - 'schedule': schedule, - 'topic': topic}) + return TemplateResponse(request, "scheduling.html", + {'accepted': accepted, + 'schedule': schedule, + 'topic': topic}) def publish(request, topicid): + event = Event.objects.get(status__in=['A', 'C']) topic = Topic.objects.get(id=topicid) if not topiclead(request.user, topic): return HttpResponseForbidden("Forbidden") list_calls = "" - baseurl = "http://%s.sched.org/api/session/" % settings.SCHED_URL + baseurl = "http://%s.sched.org/api/session/" % event.sched_url for slot in Slot.objects.filter(topic=topicid): if len(slot.proposals.all()) > 0: - values = {'api_key': settings.SCHED_API_KEY, + values = {'api_key': event.sched_api_key, 'session_key': "slot-%d" % combined_id(slot), 'name': smart_str(combined_title(slot)), 'session_start': slot.start_time, @@ -81,7 +81,7 @@ def publish(request, topicid): 'description': htmlize(smart_str( full_description(slot)))} data = urllib.urlencode(values) - if settings.SCHED_API_KEY == "getThisFromSched": + if not event.sched_api_key: list_calls += "%s

" % data else: f = urllib2.urlopen(baseurl + "mod", data) @@ -89,9 +89,9 @@ def publish(request, topicid): f.close() f = urllib2.urlopen(baseurl + "add", data) f.close() - return render(request, "sched.html", - {'list_calls': list_calls, - 'topic': topic}) + return TemplateResponse(request, "sched.html", + {'list_calls': list_calls, + 'topic': topic}) def edit(request, slotid): @@ -105,11 +105,11 @@ def edit(request, slotid): return HttpResponseRedirect('/scheduling/%s' % slot.topic.id) else: form = SlotForm(instance=slot) - return render(request, 'slotedit.html', - {'form': form, - 'title': combined_title(slot), - 'full_desc': combined_description(slot), - 'slot': slot}) + return TemplateResponse(request, 'slotedit.html', + {'form': form, + 'title': combined_title(slot), + 'full_desc': combined_description(slot), + 'slot': slot}) def swap(request, slotid): @@ -135,10 +135,10 @@ def swap(request, slotid): for slot in available_slots: triplet = (slot.start_time, slot.id, combined_title(slot)) newslots.append(triplet) - return render(request, 'slotswap.html', - {'title': combined_title(oldslot), - 'oldslot': oldslot, - 'newslots': newslots}) + return TemplateResponse(request, 'slotswap.html', + {'title': combined_title(oldslot), + 'oldslot': oldslot, + 'newslots': newslots}) def graph(request, topicid): @@ -161,4 +161,6 @@ def graph(request, topicid): nbproposed += 1 stats['max'] = max(stats['avail'], nbproposed + nbscheduled) - return render(request, "graph.html", {'stats': stats, 'topic': topic}) + return TemplateResponse(request, "graph.html", + {'stats': stats, + 'topic': topic}) diff --git a/settings.py b/settings.py index 26d0e4e..fca91b4 100644 --- a/settings.py +++ b/settings.py @@ -34,12 +34,6 @@ DATABASES = { } } -EVENT_TITLE = "Grizzly Design Summit" -EVENT_SUBTITLE = "OpenStack Summit, San Diego, Oct 15-18, 2012" - -SCHED_URL = "essexdesignsummit" -SCHED_API_KEY = "getThisFromSched" - SITE_ID = 1 STATIC_URL = '/media/' @@ -59,7 +53,6 @@ TEMPLATE_CONTEXT_PROCESSORS = ( "django.core.context_processors.i18n", "django.core.context_processors.media", "django.core.context_processors.request", - "odsreg.cfp.context_processors.event", ) MIDDLEWARE_CLASSES = ( @@ -67,6 +60,8 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.locale.LocaleMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'odsreg.cfp.middleware.EventMiddleware', ) ROOT_URLCONF = 'odsreg.urls'