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 <thierry@openstack.org>
Reviewed-by: Thierry Carrez <thierry@openstack.org>
Tested-by: Jenkins
This commit is contained in:
Thierry Carrez 2013-06-21 17:35:04 +02:00 committed by Jenkins
parent ca6f9e856e
commit 6238da264d
13 changed files with 177 additions and 113 deletions

View File

@ -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

View File

@ -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)

View File

@ -1,20 +0,0 @@
# Copyright 2012 Thierry Carrez <thierry@openstack.org>
# 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}}

View File

@ -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()

35
cfp/middleware.py Normal file
View File

@ -0,0 +1,35 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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

View File

@ -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)

View File

@ -7,7 +7,11 @@
{% endblock %}
{% block content %}
<script type="text/javascript" src="/media/sorting.js"></script>
{% if event.status == 'A' %}
<a class=roundedButton href=/cfp/create>Suggest session</A>
{% else %}
Session suggestion is now closed.
{% endif %}
{% for topic in reviewable_topics %}
<a class=roundedButton href="/cfp/topic/{{ topic.id }}">Review topic: {{ topic.name }}</A>
{% endfor %}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>OpenStack Design Summit</title>
<link href="/media/odsreg.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<div id="header">
<div class="span-5">
<h1 id="logo"><a href="/">OpenStack</a></h1>
</div>
<h1>Design Summit</h1>
<h3>The sessions suggestion site is currently closed.</h3>
</div>
</div>
<div class="container">
<p>This site opens a few months before every OpenStack Summit to handle
session suggestions and scheduling for the "Design Summit" track.</p>
<p>Please come back as we get nearer to the event.</p>
</div>
</body>
</html>

View File

@ -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):

View File

@ -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"
}
}
}

View File

@ -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] "

View File

@ -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<P>" % 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})

View File

@ -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'