Introduce more generic ballot models, start porting doc ballot page to the

new database schema.
 - Legacy-Id: 4240
This commit is contained in:
Ole Laursen 2012-03-30 17:16:50 +00:00
parent 0fea5e015a
commit ed15193bcf
10 changed files with 793 additions and 12 deletions

View file

@ -299,6 +299,8 @@ EVENT_TYPES = [
# IESG events
("started_iesg_process", "Started IESG process on document"),
("created_ballot", "Created ballot"),
("closed_ballot", "Closed ballot"),
("sent_ballot_announcement", "Sent ballot announcement"),
("changed_ballot_position", "Changed ballot position"),
@ -335,7 +337,25 @@ class NewRevisionDocEvent(DocEvent):
rev = models.CharField(max_length=16)
# IESG events
class BallotType(models.Model):
doc_type = models.ForeignKey(DocTypeName, blank=True, null=True)
slug = models.SlugField()
name = models.CharField(max_length=255)
question = models.TextField(blank=True)
used = models.BooleanField(default=True)
order = models.IntegerField(default=0)
def __unicode__(self):
return self.name
class Meta:
ordering = ['order']
class BallotDocEvent(DocEvent):
ballot_type = models.ForeignKey(BallotType)
class BallotPositionDocEvent(DocEvent):
# ballot = models.ForeignKey(BallotDocEvent, null=True)
ad = models.ForeignKey(Person)
pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord")
discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True)

View file

@ -30,12 +30,19 @@ def get_tags_for_stream_id(stream_id):
else:
return []
def active_ballot_positions(doc):
def active_ballot_positions(doc, ballot=None):
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
res = {}
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id")
# FIXME: do something with ballot
start = datetime.datetime.min
e = doc.latest_event(type="started_iesg_process")
if e:
start = e.time
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, time__gte=start).select_related('ad').order_by("-time", "-id")
for pos in positions:
if pos.ad not in res:
@ -46,7 +53,46 @@ def active_ballot_positions(doc):
res[ad] = None
return res
def needed_ballot_positions(doc, active_positions):
'''Returns text answering the question "what does this document
need to pass?". The return value is only useful if the document
is currently in IESG evaluation.'''
yes = [p for p in active_positions if p.pos_id == "yes"]
noobj = [p for p in active_positions if p.pos_id == "noobj"]
blocking = [p for p in active_positions if p.pos.blocking]
recuse = [p for p in active_positions if p.pos_id == "recuse"]
answer = []
if yes < 1:
answer.append("Needs a YES.")
if blocking:
if blocking:
answer.append("Has a %s." % blocking[0].name.upper())
else:
answer.append("Has %d %s." % (len(blocking), blocking[0].name.upper()))
needed = 1
if doc.type_id == "draft" and doc.intended_std_level_id in ("bcp", "ps", "ds", "std"):
# For standards-track, need positions from 2/3 of the
# non-recused current IESG.
needed = len(active_positions) - recuse * 2 / 3
have = len(yes) + len(noobj) + len(blocking)
if have < needed:
more = needed - have
if more == 1:
answer.append("Needs %d more position." % more)
else:
answer.append("Needs %d more positions." % more)
else:
if blocking:
answer.append("Has enough positions to pass.")
else:
answer.append("Has enough positions to pass once %s positions are resolved." % blocking[0].name.upper())
return " ".join(answer)
def get_rfc_number(doc):
qs = doc.docalias_set.filter(name__startswith='rfc')
return qs[0].name[3:] if qs else None

View file

@ -30,8 +30,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re, os
from datetime import datetime, time
import re, os, datetime
from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
@ -50,8 +49,8 @@ from ietf.idrfc import markup_txt
from ietf.idrfc.models import RfcIndex, DraftVersions
from ietf.idrfc.idrfc_wrapper import BallotWrapper, IdWrapper, RfcWrapper
from ietf.ietfworkflows.utils import get_full_info_for_draft
from ietf.doc.models import Document, DocEvent, NewRevisionDocEvent, WriteupDocEvent, TelechatDocEvent
from ietf.doc.utils import get_chartering_type
from ietf.doc.models import *
from ietf.doc.utils import get_chartering_type, needed_ballot_positions
from ietf.utils.history import find_history_active_at
from ietf.ietfauth.decorators import has_role
@ -240,7 +239,74 @@ def document_writeup(request, name):
),
context_instance=RequestContext(request))
def document_ballot_content(request, name, ballot, editable=True):
doc = get_object_or_404(Document, docalias__name=name)
if ballot != None:
b = doc.latest_event(BallotDocEvent, type="created_ballot", id=ballot)
else:
b = doc.latest_event(BallotDocEvent, type="created_ballot")
if not b:
raise Http404()
deferred = None
if doc.get_state_slug("%s-iesg" % doc.type) == "defer":
# FIXME: fragile
deferred = doc.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
# collect positions
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active").distinct())
positions = []
seen = {}
# FIXME: restrict on ballot
for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position").select_related('ad', 'pos').order_by("-time", '-id'):
if e.ad not in seen:
e.old_ad = e.ad in active_ads
e.old_positions = []
positions.append(e)
seen[e.ad] = pos
else:
latest = seen[e.ad]
if latest.old_positions:
prev = latest.old_positions[-1]
else:
prev = latest
if e.pos != prev.pos:
latest.old_positions.append(pos)
# add any missing ADs through fake No Record events
for ad in active_ads:
if ad not in seen:
e = BallotPositionDocEvent(type="changed_ballot_position", doc=doc, ad=ad)
e.pos_id = "norecord"
e.old_ad = False
e.old_positions = []
positions.append(e)
# put into position groups
position_groups = []
for n in BallotPositionName.objects.filter(slug__in=[p.pos_id for p in positions]).order_by('order'):
g = (n, [p for p in positions if p.pos_id == n.slug])
if n.blocking:
position_groups.insert(0, g)
else:
position_groups.append(g)
summary = needed_ballot_positions(doc, [p for p in positions if not p.old_ad])
return render_to_string("idrfc/document_ballot_content.html",
dict(doc=doc,
ballot=b,
position_groups=position_groups,
positions=positions,
editable=editable,
deferred=deferred,
summary=summary,
),
context_instance=RequestContext(request))
def document_ballot(request, name, ballot=None):
if name.lower().startswith("draft") or name.lower().startswith("rfc"):
@ -249,9 +315,14 @@ def document_ballot(request, name, ballot=None):
doc = get_object_or_404(Document, docalias__name=name)
top = render_document_top(request, doc, "ballot")
# FIXME: port implementation from wgcharter
c = document_ballot_content(request, name, ballot, editable=True)
raise Http404()
return render_to_response("idrfc/document_ballot.html",
dict(doc=doc,
top=top,
ballot_content=c,
),
context_instance=RequestContext(request))
def document_debug(request, name):
r = re.compile("^rfc([1-9][0-9]*)$")
@ -424,11 +495,11 @@ def _get_history(doc, versions):
# convert plain dates to datetimes (required for sorting)
for x in results:
if not isinstance(x['date'], datetime):
if not isinstance(x['date'], datetime.datetime):
if x['date']:
x['date'] = datetime.combine(x['date'], time(0,0,0))
x['date'] = datetime.datetime.combine(x['date'], datetime.time(0,0,0))
else:
x['date'] = datetime(1970,1,1)
x['date'] = datetime.datetime(1970,1,1)
results.sort(key=lambda x: x['date'])
results.reverse()

View file

@ -0,0 +1,375 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'GroupStateName'
db.create_table('name_groupstatename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['GroupStateName'])
# Adding model 'GroupTypeName'
db.create_table('name_grouptypename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['GroupTypeName'])
# Adding model 'RoleName'
db.create_table('name_rolename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['RoleName'])
# Adding model 'StreamName'
db.create_table('name_streamname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['StreamName'])
# Adding model 'DocRelationshipName'
db.create_table('name_docrelationshipname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['DocRelationshipName'])
# Adding model 'DocTypeName'
db.create_table('name_doctypename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['DocTypeName'])
# Adding model 'DocTagName'
db.create_table('name_doctagname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['DocTagName'])
# Adding model 'StdLevelName'
db.create_table('name_stdlevelname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['StdLevelName'])
# Adding model 'IntendedStdLevelName'
db.create_table('name_intendedstdlevelname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['IntendedStdLevelName'])
# Adding model 'DocReminderTypeName'
db.create_table('name_docremindertypename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['DocReminderTypeName'])
# Adding model 'BallotPositionName'
db.create_table('name_ballotpositionname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['BallotPositionName'])
# Adding model 'GroupBallotPositionName'
db.create_table('name_groupballotpositionname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['GroupBallotPositionName'])
# Adding model 'MeetingTypeName'
db.create_table('name_meetingtypename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['MeetingTypeName'])
# Adding model 'SessionStatusName'
db.create_table('name_sessionstatusname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['SessionStatusName'])
# Adding model 'TimeSlotTypeName'
db.create_table('name_timeslottypename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['TimeSlotTypeName'])
# Adding model 'ConstraintName'
db.create_table('name_constraintname', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['ConstraintName'])
# Adding model 'LiaisonStatementPurposeName'
db.create_table('name_liaisonstatementpurposename', (
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('name', ['LiaisonStatementPurposeName'])
def backwards(self, orm):
# Deleting model 'GroupStateName'
db.delete_table('name_groupstatename')
# Deleting model 'GroupTypeName'
db.delete_table('name_grouptypename')
# Deleting model 'RoleName'
db.delete_table('name_rolename')
# Deleting model 'StreamName'
db.delete_table('name_streamname')
# Deleting model 'DocRelationshipName'
db.delete_table('name_docrelationshipname')
# Deleting model 'DocTypeName'
db.delete_table('name_doctypename')
# Deleting model 'DocTagName'
db.delete_table('name_doctagname')
# Deleting model 'StdLevelName'
db.delete_table('name_stdlevelname')
# Deleting model 'IntendedStdLevelName'
db.delete_table('name_intendedstdlevelname')
# Deleting model 'DocReminderTypeName'
db.delete_table('name_docremindertypename')
# Deleting model 'BallotPositionName'
db.delete_table('name_ballotpositionname')
# Deleting model 'GroupBallotPositionName'
db.delete_table('name_groupballotpositionname')
# Deleting model 'MeetingTypeName'
db.delete_table('name_meetingtypename')
# Deleting model 'SessionStatusName'
db.delete_table('name_sessionstatusname')
# Deleting model 'TimeSlotTypeName'
db.delete_table('name_timeslottypename')
# Deleting model 'ConstraintName'
db.delete_table('name_constraintname')
# Deleting model 'LiaisonStatementPurposeName'
db.delete_table('name_liaisonstatementpurposename')
models = {
'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.constraintname': {
'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.liaisonstatementpurposename': {
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.meetingtypename': {
'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.rolename': {
'Meta': {'ordering': "['order']", 'object_name': 'RoleName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.sessionstatusname': {
'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.timeslottypename': {
'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
}
}
complete_apps = ['name']

View file

@ -0,0 +1,173 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'BallotPositionName.blocking'
db.add_column('name_ballotpositionname', 'blocking', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
# Adding M2M table for field valid_document_types on 'BallotPositionName'
db.create_table('name_ballotpositionname_valid_document_types', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('ballotpositionname', models.ForeignKey(orm['name.ballotpositionname'], null=False)),
('doctypename', models.ForeignKey(orm['name.doctypename'], null=False))
))
db.create_unique('name_ballotpositionname_valid_document_types', ['ballotpositionname_id', 'doctypename_id'])
def backwards(self, orm):
# Deleting field 'BallotPositionName.blocking'
db.delete_column('name_ballotpositionname', 'blocking')
# Removing M2M table for field valid_document_types on 'BallotPositionName'
db.delete_table('name_ballotpositionname_valid_document_types')
models = {
'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'valid_document_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTypeName']", 'symmetrical': 'False', 'blank': 'True'})
},
'name.constraintname': {
'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.liaisonstatementpurposename': {
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.meetingtypename': {
'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.rolename': {
'Meta': {'ordering': "['order']", 'object_name': 'RoleName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.sessionstatusname': {
'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.timeslottypename': {
'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
}
}
complete_apps = ['name']

View file

View file

@ -42,7 +42,9 @@ class IntendedStdLevelName(NameModel):
class DocReminderTypeName(NameModel):
"Stream state"
class BallotPositionName(NameModel):
""" Yes, No Objection, Abstain, Discuss, Recuse """
""" Yes, No Objection, Abstain, Discuss, Block, Recuse """
blocking = models.BooleanField(default=False)
valid_document_types = models.ManyToManyField(DocTypeName, blank=True)
class GroupBallotPositionName(NameModel):
""" Yes, No, Block, Abstain """
class MeetingTypeName(NameModel):

View file

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% load ietf_filters %}
{% block title %}Ballot for {{ doc.name }}-{{ doc.rev }}{% endblock %}
{% block pagehead %}
<link rel="stylesheet" type="text/css" href="/css/doc.css"></link>
{% endblock %}
{% block content %}
{{ top|safe }}
{{ ballot_content|safe }}
{% endblock content %}
{% block content_end %}
{% endblock content_end %}

View file

@ -0,0 +1,56 @@
{% load ietf_filters %}
<div class="ballot-sidebar">
{% if editable and user|has_role:"Area Director,Secretariat" %}
<div class="ballot-actions">
{% if user|has_role:"Area Director" %}
<div class="action"><a href="{% url doc_edit_position name=doc.name %}">Edit position</a></div>
{% endif %}
<div class="action">
{% if deferred %}
<a href="{% url doc_undefer_ballot name=doc.name %}">Undefer ballot</a>
<div>Ballot deferred by {{ deferred.by }} on {{ deferred.time|date:"Y-m-d" }}.</div>
{% else %}
<a href="{% url doc_defer_ballot name=doc.name %}">Defer ballot</a>
{% endif %}
</div>
</div>
{% endif %}
{% for n, positions in position_groups %}
<div class="position-group">
<div class="heading"><span class="square position-{{ n.slug }}"></span> {{ n.name }}</div>
{% for p in positions %}
<div>{% if p.old_ad %}[{% endif %}<a{% if user|has_role:"Secretariat" %} href="{% url doc_edit_position name=doc.name %}?ad={{ p.ad.pk }}" title="Click to edit the position of {{ p.ad.plain_name }}"{% endif %}>{{ p.ad.plain_name }}</a>{% if p.old_ad %}]{% endif %}{% if p.comment_text or p.discuss_text %}&nbsp;<a href="# {{ p.ad.get_name|slugify }}"><img src="/images/comment.png" width="14" height="12" alt="*" border="0"/></a>{% endif %}</div>
{% if p.old_positions %}<div class="was">(was {{ p.old_positions|join:", " }})</div>{% endif %}
{% empty %}
<i>none</i>
{% endfor %}
</div>
{% endfor %}
</div>
<h2 style="margin-top:12px;">Q: {{ ballot.ballot_type.question }}</h2>
<p>Summary: <i>{{ summary }}</i></p>
{% for p in positions|dictsort:"old_ad" %}
{% if p.comment_text or p.discuss_text %}
<h2 id="{{ p.ad.plain_name|slugify }}" class="ad-ballot-comment">{% if p.old_ad %}[{%endif%}{{ p.plain_name }}{% if p.old_ad %}]{%endif%}</h2>
{% if p.discuss_text %}
<p><b>{{ p.pos.name }} ({{ p.discuss_date }})</b> <img src="/images/comment.png" width="14" height="12" alt=""/></p>
<pre>{{ p.discuss_text|fill:"80"|escape }}</pre>
{% endif %}
{% if p.comment_text %}
<p><b>Comment ({{ p.comment_date }})</b> <img src="/images/comment.png" width="14" height="12" alt=""/></p>
<pre>{{ p.comment_text|fill:"80"|escape }}</pre>
{% endif %}
{% endif %}
{% endfor %}

View file

@ -171,3 +171,22 @@ form table .help {
.big { font-size: 109.5%; margin: 0; padding: 0; }
.large { font-size: 120%; margin: 0; padding: 0; }
.huge { font-size: 144%; margin: 0; padding: 0; }
.position-discuss,
.position-block { background-color: #c00000;}
.position-yes { background-color: #80ff80;}
.position-noobj { background-color: #80ff80;}
.position-abstain { background-color: #ffff00;}
.position-recuse { background-color: #c0c0c0;}
.position-norecord { background-color: #ffffff;}
.ballot-sidebar { width: 13em; float: left; margin-top: 0.4em; margin-right: 1em; padding: 0.5em; background: #edf5ff; }
.ballot-sidebar .action { margin-bottom: 1em; }
.ballot-sidebar .position-group { margin-bottom: 1em; }
.ballot-sidebar .position-group .heading { font-weight: bold; }
.ballot-sidebar .position-group .square { border: 1px solid #000; display: inline-block; width: 10px; height: 10px; margin-right:4px; position: static; vertical-align: middle }
.ballot-sidebar .position-group .was { padding-left: 10px; font-size:85%; }
.ballot-sidebar .position-group:last-child { margin-bottom: 0; }
h2.ad-ballot-comment { background: #2647A0; color: #fff; padding: 2px 4px; font-size: 108%; margin-top: 0;}