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'