Skip to content

Commit bcd28cc

Browse files
committed
Merged in fixes to the migration from olau@iola.dk. Merged in charter branch from olau@iola.dk. Added various fixes and correction of merge errors.
- Legacy-Id: 4353
3 parents 8972ef7 + 1161d0c + 7f5a86f commit bcd28cc

135 files changed

Lines changed: 4878 additions & 4059 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ietf/doc/admin.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.template.defaultfilters import slugify
12
from django.utils.safestring import mark_safe
23
from django.contrib import admin
34
from django import forms
@@ -43,7 +44,7 @@ def render(self, name, value, attrs=None, choices=()):
4344
html = []
4445
first = True
4546
for label, states in categorized_choices:
46-
htmlid = "id_%s_%s" % (name, label)
47+
htmlid = "id_%s_%s" % (name, slugify(label))
4748

4849
html.append('<div style="clear:both;padding-top:%s">' % ("1em" if first else "0.5em"))
4950
html.append(u'<label for="%s">%s:</label>' % (htmlid, label))
@@ -108,11 +109,14 @@ def state(self, instance):
108109
admin.site.register(DocHistory, DocHistoryAdmin)
109110

110111
class DocAliasAdmin(admin.ModelAdmin):
111-
list_display = [ 'name', 'document_link', ]
112-
search_fields = [ 'name', 'document__name', ]
112+
list_display = ['name', 'document_link']
113+
search_fields = ['name', 'document__name']
113114
raw_id_fields = ['document']
114115
admin.site.register(DocAlias, DocAliasAdmin)
115116

117+
class BallotTypeAdmin(admin.ModelAdmin):
118+
list_display = ["slug", "doc_type", "name", "question"]
119+
admin.site.register(BallotType, BallotTypeAdmin)
116120

117121
# events
118122

@@ -126,12 +130,13 @@ def rev(obj):
126130
admin.site.register(DocEvent, DocEventAdmin)
127131

128132
admin.site.register(NewRevisionDocEvent, DocEventAdmin)
133+
admin.site.register(BallotDocEvent, DocEventAdmin)
129134
admin.site.register(WriteupDocEvent, DocEventAdmin)
130135
admin.site.register(LastCallDocEvent, DocEventAdmin)
131136
admin.site.register(TelechatDocEvent, DocEventAdmin)
132137

133138
class BallotPositionDocEventAdmin(DocEventAdmin):
134-
raw_id_fields = ["doc", "by", "ad"]
139+
raw_id_fields = ["doc", "by", "ad", "ballot"]
135140

136141
admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin)
137142

ietf/doc/migrations/0001_initial.py

Lines changed: 648 additions & 0 deletions
Large diffs are not rendered by default.

ietf/doc/migrations/0002_auto__del_groupballotpositiondocevent__add_ballottype__add_ballotdocev.py

Lines changed: 402 additions & 0 deletions
Large diffs are not rendered by default.

ietf/doc/migrations/__init__.py

Whitespace-only changes.

ietf/doc/models.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
import datetime, os
1313

1414
class StateType(models.Model):
15-
slug = models.CharField(primary_key=True, max_length=30) # draft, draft_iesg, charter, ...
16-
label = models.CharField(max_length=255) # State, IESG state, WG state, ...
15+
slug = models.CharField(primary_key=True, max_length=30) # draft, draft-iesg, charter, ...
16+
label = models.CharField(max_length=255, help_text="Label that should be used (e.g. in admin) for state drop-down for this type of state") # State, IESG state, WG state, ...
1717

1818
def __unicode__(self):
19-
return self.label
19+
return self.slug
2020

2121
class State(models.Model):
2222
type = models.ForeignKey(StateType)
@@ -46,10 +46,10 @@ class DocumentInfo(models.Model):
4646
stream = models.ForeignKey(StreamName, blank=True, null=True) # IETF, IAB, IRTF, Independent Submission
4747
group = models.ForeignKey(Group, blank=True, null=True) # WG, RG, IAB, IESG, Edu, Tools
4848

49-
abstract = models.TextField()
49+
abstract = models.TextField(blank=True)
5050
rev = models.CharField(verbose_name="revision", max_length=16, blank=True)
5151
pages = models.IntegerField(blank=True, null=True)
52-
order = models.IntegerField(default=1)
52+
order = models.IntegerField(default=1, blank=True)
5353
intended_std_level = models.ForeignKey(IntendedStdLevelName, verbose_name="Intended standardization level", blank=True, null=True)
5454
std_level = models.ForeignKey(StdLevelName, verbose_name="Standardization level", blank=True, null=True)
5555
ad = models.ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True)
@@ -149,15 +149,10 @@ def __unicode__(self):
149149

150150
def get_absolute_url(self):
151151
name = self.name
152-
if self.type_id == "charter":
153-
return urlreverse('wg_view', kwargs={ 'name': self.group.acronym }, urlconf="ietf.urls")
154-
elif self.type_id == "draft":
155-
if self.get_state_slug() == "rfc":
156-
aliases = self.docalias_set.filter(name__startswith="rfc")
157-
if aliases:
158-
name = aliases[0].name
159-
return urlreverse('doc_view', kwargs={ 'name': name }, urlconf="ietf.urls")
160-
152+
if self.type_id == "draft" and self.get_state_slug() == "rfc":
153+
aliases = self.docalias_set.filter(name__startswith="rfc")
154+
if aliases:
155+
name = aliases[0].name
161156
elif self.type_id in ('slides','agenda','minutes'):
162157
session = self.session_set.all()[0]
163158
meeting = session.meeting
@@ -172,6 +167,9 @@ def get_absolute_url(self):
172167
self.type_id,
173168
filename)
174169
return url
170+
return urlreverse('doc_view', kwargs={ 'name': name }, urlconf="ietf.urls")
171+
172+
175173

176174
def file_tag(self):
177175
return u"<%s>" % self.filename_with_rev()
@@ -194,6 +192,9 @@ def canonical_name(self):
194192
a = self.docalias_set.filter(name__startswith="rfc")
195193
if a:
196194
name = a[0].name
195+
elif self.type_id == "charter":
196+
return "charter-ietf-%s" % self.chartered_group.acronym
197+
197198
return name
198199

199200
class RelatedDocHistory(models.Model):
@@ -216,11 +217,19 @@ class Meta:
216217

217218
class DocHistory(DocumentInfo):
218219
doc = models.ForeignKey(Document, related_name="history_set")
219-
name = models.CharField(max_length=255) # WG charter names can change if the group acronym changes
220+
name = models.CharField(max_length=255) # WG charter canonical names can change if the group acronym changes
220221
related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True)
221222
authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True)
222223
def __unicode__(self):
223224
return unicode(self.doc.name)
225+
226+
def canonical_name(self):
227+
return self.name
228+
229+
def latest_event(self, *args, **kwargs):
230+
kwargs["time__lte"] = self.time
231+
return self.doc.latest_event(*args, **kwargs)
232+
224233
class Meta:
225234
verbose_name = "document history"
226235
verbose_name_plural = "document histories"
@@ -234,7 +243,7 @@ def get_model_fields_as_dict(obj):
234243
# copy fields
235244
fields = get_model_fields_as_dict(doc)
236245
fields["doc"] = doc
237-
fields["name"] = doc.name
246+
fields["name"] = doc.canonical_name()
238247

239248
dochist = DocHistory(**fields)
240249
dochist.save()
@@ -311,6 +320,8 @@ class DocReminder(models.Model):
311320
# IESG events
312321
("started_iesg_process", "Started IESG process on document"),
313322

323+
("created_ballot", "Created ballot"),
324+
("closed_ballot", "Closed ballot"),
314325
("sent_ballot_announcement", "Sent ballot announcement"),
315326
("changed_ballot_position", "Changed ballot position"),
316327

@@ -347,7 +358,26 @@ class NewRevisionDocEvent(DocEvent):
347358
rev = models.CharField(max_length=16)
348359

349360
# IESG events
361+
class BallotType(models.Model):
362+
doc_type = models.ForeignKey(DocTypeName, blank=True, null=True)
363+
slug = models.SlugField()
364+
name = models.CharField(max_length=255)
365+
question = models.TextField(blank=True)
366+
used = models.BooleanField(default=True)
367+
order = models.IntegerField(default=0)
368+
positions = models.ManyToManyField(BallotPositionName, blank=True)
369+
370+
def __unicode__(self):
371+
return self.name
372+
373+
class Meta:
374+
ordering = ['order']
375+
376+
class BallotDocEvent(DocEvent):
377+
ballot_type = models.ForeignKey(BallotType)
378+
350379
class BallotPositionDocEvent(DocEvent):
380+
ballot = models.ForeignKey(BallotDocEvent, null=True, default=None) # default=None is a temporary migration period fix, should be removed when charter branch is live
351381
ad = models.ForeignKey(Person)
352382
pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord")
353383
discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True)
@@ -365,14 +395,6 @@ class TelechatDocEvent(DocEvent):
365395
telechat_date = models.DateField(blank=True, null=True)
366396
returning_item = models.BooleanField(default=False)
367397

368-
# Charter ballot events
369-
class GroupBallotPositionDocEvent(DocEvent):
370-
ad = models.ForeignKey(Person)
371-
pos = models.ForeignKey(GroupBallotPositionName, verbose_name="position", default="norecord")
372-
block_comment = models.TextField(help_text="Blocking comment if position is comment", blank=True)
373-
block_comment_time = models.DateTimeField(help_text="Blocking comment was written", blank=True, null=True)
374-
comment = models.TextField(help_text="Non-blocking comment", blank=True)
375-
comment_time = models.DateTimeField(help_text="Time non-blocking comment was written", blank=True, null=True)
376-
398+
# charter events
377399
class InitialReviewDocEvent(DocEvent):
378400
expires = models.DateTimeField(blank=True, null=True)

ietf/doc/proxy.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -175,32 +175,6 @@ def review_by_rfc_editor(self):
175175
def expired_tombstone(self):
176176
return False
177177

178-
def calc_process_start_end(self):
179-
import datetime
180-
start, end = datetime.datetime.min, datetime.datetime.max
181-
e = self.latest_event(type="started_iesg_process")
182-
if e:
183-
start = e.time
184-
if self.get_state_slug() == "rfc" and self.name.startswith("draft") and not hasattr(self, "viewing_as_rfc"):
185-
previous_process = self.latest_event(type="started_iesg_process", time__lt=e.time)
186-
if previous_process:
187-
start = previous_process.time
188-
end = e.time
189-
self._process_start = start
190-
self._process_end = end
191-
192-
@property
193-
def process_start(self):
194-
if not hasattr(self, "_process_start"):
195-
self.calc_process_start_end()
196-
return self._process_start
197-
198-
@property
199-
def process_end(self):
200-
if not hasattr(self, "_process_end"):
201-
self.calc_process_start_end()
202-
return self._process_end
203-
204178
#shepherd = BrokenForeignKey('PersonOrOrgInfo', null=True, blank=True, null_values=(0, )) # same name
205179

206180
#idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
@@ -823,7 +797,7 @@ class Meta:
823797
class Position(BallotPositionDocEvent):
824798
def from_object(self, base):
825799
for f in base._meta.fields:
826-
if not f.name in ('discuss', "ad"): # don't overwrite properties
800+
if not f.name in ('discuss', 'ad', 'ballot', ): # don't overwrite properties
827801
setattr(self, f.name, getattr(base, f.name))
828802

829803
self.orig = base

ietf/doc/utils.py

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,14 @@ def get_tags_for_stream_id(stream_id):
3030
else:
3131
return []
3232

33-
def active_ballot_positions(doc):
33+
def active_ballot_positions(doc, ballot=None):
3434
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
3535
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
3636
res = {}
3737

38-
start = datetime.datetime.min
39-
e = doc.latest_event(type="started_iesg_process")
40-
if e:
41-
start = e.time
42-
43-
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, time__gte=start).select_related('ad').order_by("-time", "-id")
38+
if not ballot:
39+
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
40+
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, ballot=ballot).select_related('ad', 'pos').order_by("-time", "-id")
4441

4542
for pos in positions:
4643
if pos.ad not in res:
@@ -51,7 +48,64 @@ def active_ballot_positions(doc):
5148
res[ad] = None
5249

5350
return res
51+
52+
def needed_ballot_positions(doc, active_positions):
53+
'''Returns text answering the question "what does this document
54+
need to pass?". The return value is only useful if the document
55+
is currently in IESG evaluation.'''
56+
yes = [p for p in active_positions if p and p.pos_id == "yes"]
57+
noobj = [p for p in active_positions if p and p.pos_id == "noobj"]
58+
blocking = [p for p in active_positions if p and p.pos.blocking]
59+
recuse = [p for p in active_positions if p and p.pos_id == "recuse"]
60+
61+
answer = []
62+
if yes < 1:
63+
answer.append("Needs a YES.")
64+
if blocking:
65+
if blocking:
66+
answer.append("Has a %s." % blocking[0].pos.name.upper())
67+
else:
68+
answer.append("Has %d %s." % (len(blocking), blocking[0].name.upper()))
69+
needed = 1
70+
if doc.type_id == "draft" and doc.intended_std_level_id in ("bcp", "ps", "ds", "std"):
71+
# For standards-track, need positions from 2/3 of the
72+
# non-recused current IESG.
73+
needed = (len(active_positions) - len(recuse)) * 2 // 3
74+
75+
have = len(yes) + len(noobj) + len(blocking)
76+
if have < needed:
77+
more = needed - have
78+
if more == 1:
79+
answer.append("Needs %d more position." % more)
80+
else:
81+
answer.append("Needs %d more positions." % more)
82+
else:
83+
if blocking:
84+
answer.append("Has enough positions to pass once %s positions are resolved." % blocking[0].pos.name.upper())
85+
else:
86+
answer.append("Has enough positions to pass.")
87+
88+
return " ".join(answer)
5489

90+
def ballot_open(doc, ballot_type_slug):
91+
e = doc.latest_event(BallotDocEvent, ballot_type__slug=ballot_type_slug)
92+
return e and not e.type == "closed_ballot"
93+
94+
def create_ballot_if_not_open(doc, by, ballot_type_slug):
95+
if not ballot_open(doc, ballot_type_slug):
96+
e = BallotDocEvent(type="created_ballot", by=by, doc=doc)
97+
e.ballot_type = BallotType.objects.get(doc_type=doc.type, slug=ballot_type_slug)
98+
e.desc = u'Created "%s" ballot' % e.ballot_type.name
99+
e.save()
100+
101+
def close_open_ballots(doc, by):
102+
for t in BallotType.objects.filter(doc_type=doc.type_id):
103+
if ballot_open(doc, t.slug):
104+
e = BallotDocEvent(type="closed_ballot", doc=doc, by=by)
105+
e.ballot_type = t
106+
e.desc = 'Closed "%s" ballot' % t.name
107+
e.save()
108+
55109
def get_rfc_number(doc):
56110
qs = doc.docalias_set.filter(name__startswith='rfc')
57111
return qs[0].name[3:] if qs else None
@@ -79,6 +133,16 @@ def augment_with_start_time(docs):
79133

80134
return docs
81135

136+
def get_chartering_type(doc):
137+
chartering = ""
138+
if doc.get_state_slug() not in ("notrev", "approved"):
139+
if doc.group.state_id == "proposed":
140+
chartering = "initial"
141+
elif doc.group.state_id == "active":
142+
chartering = "rechartering"
143+
144+
return chartering
145+
82146
def augment_with_telechat_date(docs):
83147
"""Add a telechat_date attribute to each document with the
84148
scheduled telechat or None if it's not scheduled."""
@@ -99,6 +163,3 @@ def augment_with_telechat_date(docs):
99163
seen.add(e.doc_id)
100164

101165
return docs
102-
103-
104-

ietf/group/proxy.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from ietf.utils.proxy import TranslatingManager, proxy_role_email
22

33
from models import *
4-
from ietf.doc.models import Document # for charter text
5-
from ietf.wgcharter.utils import get_charter_for_revision, approved_revision
64

75
class Acronym(Group):
86
class LazyIndividualSubmitter(object):

ietf/group/utils.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,23 @@ def get_charter_text(group):
3535
# get file path from settings. Syntesize file name from path, acronym, and suffix
3636
try:
3737
# Try getting charter from new charter tool
38-
from ietf.wgcharter.utils import get_charter_for_revision, approved_revision
39-
40-
charter = group.charter
41-
ch = get_charter_for_revision(charter, charter.rev)
42-
name = ch.name
43-
rev = approved_revision(ch.rev)
44-
filename = os.path.join(charter.get_file_path(), "%s-%s.txt" % (name, rev))
45-
desc_file = open(filename)
46-
desc = desc_file.read()
47-
return desc
48-
except:
38+
c = group.charter
39+
40+
# find the latest, preferably approved, revision
41+
for h in group.charter.history_set.exclude(rev="").order_by("time"):
42+
h_appr = "-" not in h.rev
43+
c_appr = "-" not in c.rev
44+
if (h.rev > c.rev and not (c_appr and not h_appr)) or (h_appr and not c_appr):
45+
c = h
46+
47+
filename = os.path.join(c.get_file_path(), "%s-%s.txt" % (c.canonical_name(), c.rev))
48+
with open(filename) as f:
49+
return f.read()
50+
except IOError:
4951
try:
5052
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, group.acronym) + ".desc.txt"
5153
desc_file = open(filename)
5254
desc = desc_file.read()
53-
except:
55+
except BaseException:
5456
desc = 'Error Loading Work Group Description'
5557
return desc

0 commit comments

Comments
 (0)