Splitting views.py and models.py

Separating parts of views.py and models.py into forms.py and
utils.py for cleanliness.
This commit is contained in:
Thierry Carrez 2013-06-07 14:48:43 +02:00
parent 87483a2474
commit f0466d87c9
8 changed files with 223 additions and 157 deletions

49
cfp/forms.py Normal file
View File

@ -0,0 +1,49 @@
# Copyright 2011 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.forms import ModelForm, CharField, Textarea
from odsreg.cfp.models import Comment, Proposal
class CommentForm(ModelForm):
class Meta:
model = Comment
exclude = ('proposal', 'posted_date', 'author')
class ProposalForm(ModelForm):
class Meta:
model = Proposal
exclude = ('proposer', 'status', 'scheduled')
class ProposalEditForm(ModelForm):
class Meta:
model = Proposal
exclude = ('topic', 'proposer', 'status', 'scheduled')
class ProposalReviewForm(ModelForm):
comment = CharField(widget=Textarea)
class Meta:
model = Proposal
fields = ('status',)
class ProposalSwitchForm(ModelForm):
class Meta:
model = Proposal
fields = ('topic',)

View File

@ -13,36 +13,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import urllib
from django.db import models from django.db import models
from django.forms import ModelForm, CharField, Textarea
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User from django.contrib.auth.models import User
from cfp.utils import validate_bp
def is_valid_lp_name(value):
return value.replace('-', '').isalnum()
def validate_bp(value):
bps = value.split()
for bp in bps:
members = bp.split("/")
if len(members) != 2:
raise ValidationError(u'Blueprints should be specified under'
' the form project/blueprint-name')
(project, bpname) = list(members)
if not is_valid_lp_name(project):
raise ValidationError(u'Incorrect project name: %s' % project)
if not is_valid_lp_name(bpname):
raise ValidationError(u'Incorrect blueprint name: %s' % bpname)
f = urllib.urlopen("https://api.launchpad.net/devel/%s/+spec/%s"
% (project, bpname))
f.close()
if f.getcode() != 200:
raise ValidationError(u'No such blueprint: %s/%s'
' -- did you create it on Launchpad ?'
% (project, bpname))
class Topic(models.Model): class Topic(models.Model):
@ -95,34 +69,3 @@ class Comment(models.Model):
class Meta: class Meta:
ordering = ['posted_date'] ordering = ['posted_date']
class CommentForm(ModelForm):
class Meta:
model = Comment
exclude = ('proposal', 'posted_date', 'author')
class ProposalForm(ModelForm):
class Meta:
model = Proposal
exclude = ('proposer', 'status', 'scheduled')
class ProposalEditForm(ModelForm):
class Meta:
model = Proposal
exclude = ('topic', 'proposer', 'status', 'scheduled')
class ProposalReviewForm(ModelForm):
comment = CharField(widget=Textarea)
class Meta:
model = Proposal
fields = ('status',)
class ProposalSwitchForm(ModelForm):
class Meta:
model = Proposal
fields = ('topic',)

61
cfp/utils.py Normal file
View File

@ -0,0 +1,61 @@
# Copyright 2011 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.
import urllib
from django.core.exceptions import ValidationError
def is_editable(proposal, user):
return ((not proposal.scheduled) and
((proposal.proposer == user and proposal.status != 'A') or
topiclead(user, proposal.topic)))
def linkify(blueprints):
links = {}
for bp in blueprints.split():
(project, name) = bp.split('/')
links[bp] = "https://blueprints.launchpad.net/%s/+spec/%s" \
% (project, name)
return links
def topiclead(user, topic):
return (user.username == topic.lead_username) or user.is_staff
def _is_valid_lp_name(value):
return value.replace('-', '').isalnum()
def validate_bp(value):
bps = value.split()
for bp in bps:
members = bp.split("/")
if len(members) != 2:
raise ValidationError(u'Blueprints should be specified under'
' the form project/blueprint-name')
(project, bpname) = list(members)
if not _is_valid_lp_name(project):
raise ValidationError(u'Incorrect project name: %s' % project)
if not _is_valid_lp_name(bpname):
raise ValidationError(u'Incorrect blueprint name: %s' % bpname)
f = urllib.urlopen("https://api.launchpad.net/devel/%s/+spec/%s"
% (project, bpname))
f.close()
if f.getcode() != 200:
raise ValidationError(u'No such blueprint: %s/%s'
' -- did you create it on Launchpad ?'
% (project, bpname))

View File

@ -21,26 +21,11 @@ from django.contrib.auth import logout
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.http import HttpResponseRedirect, HttpResponseForbidden from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from odsreg.cfp.models import Proposal, Topic, Comment from odsreg.cfp.models import Proposal, Topic, Comment
from odsreg.cfp.models import ProposalForm, ProposalEditForm, CommentForm from odsreg.cfp.forms import ProposalForm, ProposalEditForm, CommentForm
from odsreg.cfp.models import ProposalReviewForm, ProposalSwitchForm from odsreg.cfp.forms import ProposalReviewForm, ProposalSwitchForm
from odsreg.cfp.utils import linkify, is_editable, topiclead
def linkify(blueprints):
links = {}
for bp in blueprints.split():
(project, name) = bp.split('/')
links[bp] = "https://blueprints.launchpad.net/%s/+spec/%s" \
% (project, name)
return links
def topiclead(user, topic):
return (user.username == topic.lead_username) or user.is_staff
def forbidden():
return HttpResponseForbidden("Forbidden")
@login_required @login_required
@ -58,7 +43,7 @@ def list(request):
def topiclist(request, topicid): def topiclist(request, topicid):
topic = Topic.objects.get(id=topicid) topic = Topic.objects.get(id=topicid)
if not topiclead(request.user, topic): if not topiclead(request.user, topic):
return forbidden() return HttpResponseForbidden("Forbidden")
proposals = Proposal.objects.filter(topic=topicid) proposals = Proposal.objects.filter(topic=topicid)
request.session['lastlist'] = "cfp/topic/%s" % topicid request.session['lastlist'] = "cfp/topic/%s" % topicid
return render(request, "topiclist.html", return render(request, "topiclist.html",
@ -89,12 +74,6 @@ def create(request):
return render(request, 'cfpcreate.html', {'topics': topics, 'form': form}) return render(request, 'cfpcreate.html', {'topics': topics, 'form': form})
def is_editable(proposal, user):
return ((not proposal.scheduled) and
((proposal.proposer == user and proposal.status != 'A') or
topiclead(user, proposal.topic)))
@login_required @login_required
def details(request, proposalid): def details(request, proposalid):
proposal = Proposal.objects.get(id=proposalid) proposal = Proposal.objects.get(id=proposalid)
@ -120,7 +99,7 @@ def details(request, proposalid):
def edit(request, proposalid): def edit(request, proposalid):
proposal = Proposal.objects.get(id=proposalid) proposal = Proposal.objects.get(id=proposalid)
if not is_editable(proposal, request.user): if not is_editable(proposal, request.user):
return forbidden() return HttpResponseForbidden("Forbidden")
if request.method == 'POST': if request.method == 'POST':
form = ProposalEditForm(request.POST, instance=proposal) form = ProposalEditForm(request.POST, instance=proposal)
if form.is_valid(): if form.is_valid():
@ -136,7 +115,7 @@ def edit(request, proposalid):
def delete(request, proposalid): def delete(request, proposalid):
proposal = Proposal.objects.get(id=proposalid) proposal = Proposal.objects.get(id=proposalid)
if ((proposal.proposer != request.user) or proposal.status in ['A', 'S']): if ((proposal.proposer != request.user) or proposal.status in ['A', 'S']):
return forbidden() return HttpResponseForbidden("Forbidden")
if request.method == 'POST': if request.method == 'POST':
proposal.delete() proposal.delete()
return HttpResponseRedirect('/%s' % request.session['lastlist']) return HttpResponseRedirect('/%s' % request.session['lastlist'])
@ -148,7 +127,7 @@ def switch(request, proposalid):
proposal = Proposal.objects.get(id=proposalid) proposal = Proposal.objects.get(id=proposalid)
if ((proposal.proposer != request.user) if ((proposal.proposer != request.user)
and not topiclead(request.user, proposal.topic)) or proposal.scheduled: and not topiclead(request.user, proposal.topic)) or proposal.scheduled:
return forbidden() return HttpResponseForbidden("Forbidden")
if request.method == 'POST': if request.method == 'POST':
form = ProposalSwitchForm(request.POST, instance=proposal) form = ProposalSwitchForm(request.POST, instance=proposal)
if form.is_valid(): if form.is_valid():
@ -166,9 +145,8 @@ def switch(request, proposalid):
@login_required @login_required
def review(request, proposalid): def review(request, proposalid):
proposal = Proposal.objects.get(id=proposalid) proposal = Proposal.objects.get(id=proposalid)
#TODO Allow comment while reviewing to be included in email
if not topiclead(request.user, proposal.topic): if not topiclead(request.user, proposal.topic):
return forbidden() return HttpResponseForbidden("Forbidden")
current_status = proposal.status current_status = proposal.status
status_long = proposal.get_status_display() status_long = proposal.get_status_display()
if request.method == 'POST': if request.method == 'POST':

24
scheduling/forms.py Normal file
View File

@ -0,0 +1,24 @@
# Copyright 2011 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.forms import ModelForm
from odsreg.scheduling.models import Slot
class SlotForm(ModelForm):
class Meta:
model = Slot
fields = ('title', 'description')

View File

@ -14,7 +14,7 @@
# under the License. # under the License.
from django.db import models from django.db import models
from django.forms import ModelForm
from odsreg.cfp.models import Proposal, Topic from odsreg.cfp.models import Proposal, Topic
@ -48,9 +48,3 @@ class Slot(models.Model):
def __unicode__(self): def __unicode__(self):
return "%s %s %s" % (self.topic.name, self.room.code, self.start_time) return "%s %s %s" % (self.topic.name, self.room.code, self.start_time)
class SlotForm(ModelForm):
class Meta:
model = Slot
fields = ('title', 'description')

67
scheduling/utils.py Normal file
View File

@ -0,0 +1,67 @@
# Copyright 2011 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.
def combined_id(slot):
return slot.proposals.order_by('id')[0].id
def combined_title(slot):
if slot.title:
return slot.title
proposals = slot.proposals.all()
if len(proposals) > 0:
return proposals[0].title
return ""
def combined_description(slot):
full_desc = ""
proposals = slot.proposals.all()
if len(proposals) > 1 or slot.title:
full_desc = "This session will include the following subject(s):\n\n"
for p in slot.proposals.all():
if len(proposals) > 1 or slot.title:
full_desc = full_desc + p.title + ":\n\n"
full_desc = full_desc + p.description + "\n\n"
full_desc += "(Session proposed by %s %s)\n\n" % (
p.proposer.first_name, p.proposer.last_name)
return full_desc
def full_description(slot):
desc = ""
if slot.description:
desc = slot.description + "\n\n"
desc += combined_description(slot)
return desc
def htmlize(desc):
return desc.replace('\n', '<br />')
def end_time(start_time):
"""Rough calculation of end time.
Works because we don't start at 08:00 and align on 10's of minutes"""
end_minute = int(start_time[-2:]) + 40
if end_minute >= 60:
end_hour = str(int(start_time[-5:-3]) + 1)
end_minute = end_minute - 60
if end_minute == 0:
return start_time[:-5] + end_hour + ":00"
else:
return start_time[:-5] + end_hour + ":" + str(end_minute)
else:
return start_time[:-2] + str(end_minute)

View File

@ -18,56 +18,21 @@ import urllib2
from django.shortcuts import render from django.shortcuts import render
from django.conf import settings from django.conf import settings
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from odsreg.cfp.models import Proposal, Topic from odsreg.cfp.models import Proposal, Topic
from odsreg.cfp.views import topiclead, forbidden from odsreg.cfp.utils import topiclead
from odsreg.scheduling.models import Slot, SlotForm from odsreg.scheduling.forms import SlotForm
from odsreg.scheduling.models import Slot
from odsreg.scheduling.utils import combined_id, combined_title
def combined_id(slot): from odsreg.scheduling.utils import combined_description, full_description
return slot.proposals.order_by('id')[0].id from odsreg.scheduling.utils import htmlize, end_time
def combined_title(slot):
if slot.title:
return slot.title
proposals = slot.proposals.all()
if len(proposals) > 0:
return proposals[0].title
return ""
def combined_description(slot):
full_desc = ""
proposals = slot.proposals.all()
if len(proposals) > 1 or slot.title:
full_desc = "This session will include the following subject(s):\n\n"
for p in slot.proposals.all():
if len(proposals) > 1 or slot.title:
full_desc = full_desc + p.title + ":\n\n"
full_desc = full_desc + p.description + "\n\n"
full_desc += "(Session proposed by %s %s)\n\n" % (
p.proposer.first_name, p.proposer.last_name)
return full_desc
def htmlize(desc):
return desc.replace('\n', '<br />')
def full_description(slot):
desc = ""
if slot.description:
desc = slot.description + "\n\n"
desc += combined_description(slot)
return desc
def scheduling(request, topicid): def scheduling(request, topicid):
topic = Topic.objects.get(id=topicid) topic = Topic.objects.get(id=topicid)
if not topiclead(request.user, topic): if not topiclead(request.user, topic):
return forbidden() return HttpResponseForbidden("Forbidden")
if request.method == 'POST': if request.method == 'POST':
action = request.POST['action'] action = request.POST['action']
proposal = Proposal.objects.get(id=request.POST['proposal']) proposal = Proposal.objects.get(id=request.POST['proposal'])
@ -97,25 +62,10 @@ def scheduling(request, topicid):
'topic': topic}) 'topic': topic})
def end_time(start_time):
"""Rough calculation of end time.
Works because we don't start at 08:00 and align on 10's of minutes"""
end_minute = int(start_time[-2:]) + 40
if end_minute >= 60:
end_hour = str(int(start_time[-5:-3]) + 1)
end_minute = end_minute - 60
if end_minute == 0:
return start_time[:-5] + end_hour + ":00"
else:
return start_time[:-5] + end_hour + ":" + str(end_minute)
else:
return start_time[:-2] + str(end_minute)
def publish(request, topicid): def publish(request, topicid):
topic = Topic.objects.get(id=topicid) topic = Topic.objects.get(id=topicid)
if not topiclead(request.user, topic): if not topiclead(request.user, topic):
return forbidden() return HttpResponseForbidden("Forbidden")
list_calls = "" list_calls = ""
baseurl = "http://%s.sched.org/api/session/" % settings.SCHED_URL baseurl = "http://%s.sched.org/api/session/" % settings.SCHED_URL
for slot in Slot.objects.filter(topic=topicid): for slot in Slot.objects.filter(topic=topicid):
@ -147,7 +97,7 @@ def publish(request, topicid):
def edit(request, slotid): def edit(request, slotid):
slot = Slot.objects.get(id=slotid) slot = Slot.objects.get(id=slotid)
if not topiclead(request.user, slot.topic): if not topiclead(request.user, slot.topic):
return forbidden() return HttpResponseForbidden("Forbidden")
if request.method == 'POST': if request.method == 'POST':
form = SlotForm(request.POST, instance=slot) form = SlotForm(request.POST, instance=slot)
if form.is_valid(): if form.is_valid():
@ -165,7 +115,7 @@ def edit(request, slotid):
def swap(request, slotid): def swap(request, slotid):
oldslot = Slot.objects.get(id=slotid) oldslot = Slot.objects.get(id=slotid)
if not topiclead(request.user, oldslot.topic): if not topiclead(request.user, oldslot.topic):
return forbidden() return HttpResponseForbidden("Forbidden")
if request.method == 'POST': if request.method == 'POST':
newslotid = int(request.POST['newslotid']) newslotid = int(request.POST['newslotid'])
newslot = Slot.objects.get(id=newslotid, topic=oldslot.topic) newslot = Slot.objects.get(id=newslotid, topic=oldslot.topic)