Skip to content

Commit f6261a9

Browse files
committed
Introduce generic states and reminders
- Legacy-Id: 3615
1 parent 08ff44d commit f6261a9

3 files changed

Lines changed: 153 additions & 27 deletions

File tree

redesign/doc/admin.py

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,88 @@
1+
from django.utils.safestring import mark_safe
12
from django.contrib import admin
3+
from django import forms
4+
25
from models import *
3-
from person.models import *
6+
from redesign.person.models import *
7+
from redesign.doc.utils import get_state_types
48

5-
class DocAliasAdmin(admin.ModelAdmin):
6-
list_display = [ 'name', 'document_link', ]
7-
search_fields = [ 'name', 'document__name', ]
8-
raw_id_fields = ['document']
9-
admin.site.register(DocAlias, DocAliasAdmin)
9+
class StateTypeAdmin(admin.ModelAdmin):
10+
list_display = ["slug", "label"]
11+
admin.site.register(StateType, StateTypeAdmin)
12+
13+
class StateAdmin(admin.ModelAdmin):
14+
list_display = ["slug", "type", 'name', 'order', 'desc']
15+
filter_horizontal = ["next_states"]
16+
admin.site.register(State, StateAdmin)
1017

1118
class DocAliasInline(admin.TabularInline):
1219
model = DocAlias
1320
extra = 1
1421

22+
# document form for managing states in a less confusing way
23+
24+
class StatesWidget(forms.SelectMultiple):
25+
"""Display all applicable states as separate select boxes,
26+
requires 'instance' have been set on the widget."""
27+
def render(self, name, value, attrs=None, choices=()):
28+
29+
types = StateType.objects.filter(slug__in=get_state_types(self.instance))
30+
31+
categorized_choices = []
32+
for t in types:
33+
states = State.objects.filter(type=t).select_related()
34+
if states:
35+
categorized_choices.append((t.label, states))
36+
37+
html = []
38+
first = True
39+
for label, states in categorized_choices:
40+
htmlid = "id_%s_%s" % (name, label)
41+
42+
html.append('<div style="clear:both;padding-top:%s">' % ("1em" if first else "0.5em"))
43+
html.append(u'<label for="%s">%s:</label>' % (htmlid, label))
44+
html.append(u'<select name="%s" id="%s">' % (name, htmlid))
45+
html.append(u'<option value="">-----------</option>')
46+
for s in states:
47+
html.append('<option %s value="%s">%s</option>' % ("selected" if s.pk in value else "", s.pk, s.name))
48+
html.append(u'</select>')
49+
html.append("</div>")
50+
51+
first = False
52+
53+
return mark_safe(u"".join(html))
54+
55+
class StatesField(forms.ModelMultipleChoiceField):
56+
def __init__(self, *args, **kwargs):
57+
# use widget with multiple select boxes
58+
kwargs['widget'] = StatesWidget
59+
super(StatesField, self).__init__(*args, **kwargs)
60+
61+
def clean(self, value):
62+
if value and isinstance(value, (list, tuple)):
63+
# remove "", in case a state is reset
64+
value = [x for x in value if x]
65+
return super(StatesField, self).clean(value)
66+
67+
class DocumentForm(forms.ModelForm):
68+
states = StatesField(queryset=State.objects.all(), required=False)
69+
70+
def __init__(self, *args, **kwargs):
71+
super(DocumentForm, self).__init__(*args, **kwargs)
72+
73+
# we don't normally have access to the instance in the widget
74+
# so set it here
75+
self.fields["states"].widget.instance = self.instance
76+
77+
class Meta:
78+
model = Document
79+
1580
class DocumentAdmin(admin.ModelAdmin):
1681
list_display = ['name', 'rev', 'state', 'group', 'pages', 'intended_std_level', 'author_list', 'time']
1782
search_fields = ['name']
1883
raw_id_fields = ['authors', 'related', 'group', 'shepherd', 'ad']
1984
inlines = [DocAliasInline]
85+
form = DocumentForm
2086

2187
admin.site.register(Document, DocumentAdmin)
2288

@@ -25,8 +91,15 @@ class DocHistoryAdmin(admin.ModelAdmin):
2591
search_fields = ['doc__name']
2692
ordering = ['time', 'doc', 'rev']
2793
raw_id_fields = ['doc', 'authors', 'related', 'group', 'shepherd', 'ad']
94+
2895
admin.site.register(DocHistory, DocHistoryAdmin)
2996

97+
class DocAliasAdmin(admin.ModelAdmin):
98+
list_display = [ 'name', 'document_link', ]
99+
search_fields = [ 'name', 'document__name', ]
100+
raw_id_fields = ['document']
101+
admin.site.register(DocAlias, DocAliasAdmin)
102+
30103

31104
# events
32105

redesign/doc/models.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,43 @@
1111

1212
import datetime, os
1313

14+
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, ...
17+
18+
def __unicode__(self):
19+
return self.label
20+
21+
class State(models.Model):
22+
type = models.ForeignKey(StateType)
23+
slug = models.SlugField()
24+
name = models.CharField(max_length=255)
25+
used = models.BooleanField(default=True)
26+
desc = models.TextField(blank=True)
27+
order = models.IntegerField(default=0)
28+
29+
next_states = models.ManyToManyField('State', related_name="previous_states")
30+
31+
def __unicode__(self):
32+
return self.name
33+
34+
class Meta:
35+
ordering = ["type", "order"]
36+
1437
class DocumentInfo(models.Model):
1538
"""Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement"""
1639
time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True
1740
# Document related
1841
type = models.ForeignKey(DocTypeName, blank=True, null=True) # Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, Review, Issue, Wiki, External ...
1942
title = models.CharField(max_length=255)
2043
# State
44+
states = models.ManyToManyField(State, blank=True)
45+
2146
state = models.ForeignKey(DocStateName, blank=True, null=True) # Active/Expired/RFC/Replaced/Withdrawn
22-
tags = models.ManyToManyField(DocInfoTagName, blank=True, null=True) # Revised ID Needed, ExternalParty, AD Followup, ...
47+
tags = models.ManyToManyField(DocTagName, blank=True, null=True) # Revised ID Needed, ExternalParty, AD Followup, ...
2348
stream = models.ForeignKey(DocStreamName, blank=True, null=True) # IETF, IAB, IRTF, Independent Submission
2449
group = models.ForeignKey(Group, blank=True, null=True) # WG, RG, IAB, IESG, Edu, Tools
25-
wg_state = models.ForeignKey(WgDocStateName, verbose_name="WG state", blank=True, null=True) # Not/Candidate/Active/Parked/LastCall/WriteUp/Submitted/Dead
26-
iesg_state = models.ForeignKey(IesgDocStateName, verbose_name="IESG state", blank=True, null=True) #
50+
iesg_state = models.ForeignKey(IesgDocStateName, verbose_name="IESG state", blank=True, null=True) #
2751
iana_state = models.ForeignKey(IanaDocStateName, verbose_name="IANA state", blank=True, null=True)
2852
rfc_state = models.ForeignKey(RfcDocStateName, verbose_name="RFC state", blank=True, null=True)
2953
# Other
@@ -48,7 +72,24 @@ def get_file_path(self):
4872
return os.path.join(settings.AGENDA_PATH, meeting, self.type_id) + "/"
4973
else:
5074
raise NotImplemented
51-
75+
76+
def set_state(self, state):
77+
already_set = self.states.filter(type=state.type)
78+
others = [s for s in already_set if s != state]
79+
if others:
80+
self.states.remove(*others)
81+
if state not in already_set:
82+
self.states.add(state)
83+
84+
def unset_state(self, state):
85+
self.states.remove(state)
86+
87+
def get_state(self, state_type):
88+
try:
89+
return self.states.get(type=state_type)
90+
except State.DoesNotExist:
91+
return None
92+
5293
class Meta:
5394
abstract = True
5495
def author_list(self):
@@ -116,7 +157,6 @@ def canonical_name(self):
116157
if a:
117158
name = a[0].name
118159
return name
119-
120160

121161
class RelatedDocHistory(models.Model):
122162
source = models.ForeignKey('DocHistory')
@@ -144,6 +184,9 @@ class DocHistory(DocumentInfo):
144184
authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True)
145185
def __unicode__(self):
146186
return unicode(self.doc.name)
187+
class Meta:
188+
verbose_name = "document history"
189+
verbose_name_plural = "document histories"
147190

148191
def save_document_in_history(doc):
149192
def get_model_fields_as_dict(obj):
@@ -198,20 +241,28 @@ class Meta:
198241
verbose_name = "document alias"
199242
verbose_name_plural = "document aliases"
200243

244+
class DocReminder(models.Model):
245+
event = models.ForeignKey('DocEvent')
246+
type = models.ForeignKey(DocReminderTypeName)
247+
due = models.DateTimeField()
248+
active = models.BooleanField(default=True)
249+
201250

202251
EVENT_TYPES = [
203252
# core events
204253
("new_revision", "Added new revision"),
205254
("changed_document", "Changed document metadata"),
206255

207256
# misc document events
257+
("changed_stream", "Changed document stream"),
208258
("added_comment", "Added comment"),
209259
("expired_document", "Expired document"),
210260
("requested_resurrect", "Requested resurrect"),
211261
("completed_resurrect", "Completed resurrect"),
212262
("published_rfc", "Published RFC"),
213263

214264
# WG events
265+
("changed_group", "Changed group"),
215266
("changed_protocol_writeup", "Changed protocol writeup"),
216267

217268
# IESG events

redesign/doc/utils.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,30 @@ def get_state_types(doc):
77
return res
88

99
res.append(doc.type_id)
10+
1011
#if doc.type_id in ("agenda", "minutes", "slides", "liai-att"):
1112
# res.append(doc.type_id)
1213
if doc.type_id == "draft":
13-
if doc.stream_id == "ietf":
14-
wg_specific = doc.type_id + ":" + "wg" + ":" + doc.group.acronym
15-
if State.objects.filter(type=wg_specific):
16-
res.append(wg_specific)
17-
else:
18-
res.append(doc.type_id + ":" + "wg")
19-
elif doc.stream_id == "irtf":
20-
res.append(doc.type_id + ":" + "rg")
21-
elif doc.stream_id == "iab":
22-
res.append(doc.type_id + ":" + "iab")
23-
elif doc.stream_id == "ise":
24-
res.append(doc.type_id + ":" + "ise")
25-
26-
res.append(doc.type_id + ":" + "iesg")
27-
res.append(doc.type_id + ":" + "iana")
28-
res.append(doc.type_id + ":" + "rfc-editor")
14+
if doc.stream_id != "legacy":
15+
res.append("draft-stream-%s" % doc.stream_id)
16+
17+
res.append("draft-iesg")
18+
res.append("draft-iana")
19+
res.append("draft-rfceditor")
2920

3021
return res
3122

23+
def get_tags_for_stream_id(stream_id):
24+
if stream_id == "ietf":
25+
return ["w-expert", "w-extern", "w-merge", "need-aut", "w-refdoc", "w-refing", "rev-wglc", "rev-ad", "rev-iesg", "sheph-u", "other"]
26+
elif stream_id == "iab":
27+
return ["need-ed", "w-part", "w-review", "need-rev", "sh-f-up"]
28+
elif stream_id == "irtf":
29+
return ["need-ed", "need-sh", "w-dep", "need-rev", "iesg-com"]
30+
elif stream_id == "ise":
31+
return ["w-dep", "w-review", "need-rev", "iesg-com"]
32+
else:
33+
return []
3234

3335
def active_ballot_positions(doc):
3436
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""

0 commit comments

Comments
 (0)