Merged in branch/iola/milestones, which provides datatracker support for management of WG milestones.

- Legacy-Id: 5739
This commit is contained in:
Henrik Levkowetz 2013-05-15 22:07:23 +00:00
commit bd0c07290a
71 changed files with 4263 additions and 670 deletions

11
INSTALL
View file

@ -121,6 +121,17 @@ usual, with re-pointing the symlink and restarting apache.
Additional Version-Specific Instructions
========================================
Version 4.46
------------
Before you run step 3 (migration) of the general instructions, please run some specific
initial migrations with the a --fake argument:
cd $releasenumber
PYTHONPATH=$PWD ietf/manage.py migrate group 0001 --fake
cd ..
Version 4.42
------------

View file

@ -1,3 +1,14 @@
ietfdb (4.46) ietf; urgency=medium
This is a major feature release, which introduces datatracker support
for milestone management. WG Chairs will, if they are logged in, see
an 'Add or edit milestones' button on the WG's Charter page in the
datatracker, just below the charter text, and Area Directors and
Secretariat staff will see a new link 'Milestones' in the left-hand
menu-bar, leading to a page which lists Milestones Needing Review.
-- Henrik Levkowetz <henrik@levkowetz.com> 14 May 2013 21:50:09 +0200
ietfdb (4.45) ietf; urgency=medium
This release contains bugfixes and some minor features.

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8-no-bom -*-
# Copyright The IETF Trust 2007, All Rights Reserved
__version__ = "4.46-dev"

View file

@ -0,0 +1,45 @@
#!/usr/bin/env python
#
# This script will send various milestone reminders. It's supposed to
# be run daily, and will then send reminders weekly/monthly as
# appropriate.
import datetime, os
import syslog
from ietf import settings
from django.core import management
management.setup_environ(settings)
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.wginfo.mails import *
today = datetime.date.today()
MONDAY = 1
FIRST_DAY_OF_MONTH = 1
if today.isoweekday() == MONDAY:
# send milestone review reminders - ideally we'd keep track of
# exactly when we sent one last time for a group, but it's a bit
# complicated because people can change the milestones in the mean
# time, so dodge all of this by simply sending once a week only
for g in groups_with_milestones_needing_review():
mail_sent = email_milestone_review_reminder(g, grace_period=7)
if mail_sent:
syslog.syslog("Sent milestone review reminder for %s %s" % (g.acronym, g.type.name))
early_warning_days = 30
# send any milestones due reminders
for g in groups_needing_milestones_due_reminder(early_warning_days):
email_milestones_due(g, early_warning_days)
syslog.syslog("Sent milestones due reminder for %s %s" % (g.acronym, g.type.name))
if today.day == FIRST_DAY_OF_MONTH:
# send milestone overdue reminders - once a month
for g in groups_needing_milestones_overdue_reminder(grace_period=30):
email_milestones_overdue(g)
syslog.syslog("Sent milestones overdue reminder for %s %s" % (g.acronym, g.type.name))

View file

@ -465,6 +465,7 @@ EVENT_TYPES = [
# WG events
("changed_group", "Changed group"),
("changed_protocol_writeup", "Changed protocol writeup"),
("changed_charter_milestone", "Changed charter milestone"),
# charter events
("initial_review", "Set initial review time"),

View file

@ -116,9 +116,9 @@ class GroupHistoryAdmin(admin.ModelAdmin):
admin.site.register(GroupHistory, GroupHistoryAdmin)
class GroupMilestoneAdmin(admin.ModelAdmin):
list_display = ["group", "desc", "expected_due_date", "time"]
search_fields = ["group__name", "group__acronym", "desc"]
raw_id_fields = ["group"]
list_display = ["group", "desc", "due", "resolved", "time"]
search_fields = ["group__name", "group__acronym", "desc", "resolved"]
raw_id_fields = ["group", "docs"]
admin.site.register(GroupMilestone, GroupMilestoneAdmin)

View file

@ -0,0 +1,481 @@
# 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 'Group'
db.create_table('group_group', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('time', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
('name', self.gf('django.db.models.fields.CharField')(max_length=80)),
('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.GroupStateName'], null=True)),
('type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.GroupTypeName'], null=True)),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'], null=True, blank=True)),
('ad', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Person'], null=True, blank=True)),
('list_email', self.gf('django.db.models.fields.CharField')(max_length=64, blank=True)),
('list_subscribe', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('list_archive', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('comments', self.gf('django.db.models.fields.TextField')(blank=True)),
('acronym', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=40, db_index=True)),
('charter', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='chartered_group', unique=True, null=True, to=orm['doc.Document'])),
))
db.send_create_signal('group', ['Group'])
# Adding M2M table for field unused_states on 'Group'
db.create_table('group_group_unused_states', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['group.group'], null=False)),
('state', models.ForeignKey(orm['doc.state'], null=False))
))
db.create_unique('group_group_unused_states', ['group_id', 'state_id'])
# Adding M2M table for field unused_tags on 'Group'
db.create_table('group_group_unused_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['group.group'], null=False)),
('doctagname', models.ForeignKey(orm['name.doctagname'], null=False))
))
db.create_unique('group_group_unused_tags', ['group_id', 'doctagname_id'])
# Adding model 'GroupHistory'
db.create_table('group_grouphistory', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('time', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
('name', self.gf('django.db.models.fields.CharField')(max_length=80)),
('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.GroupStateName'], null=True)),
('type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.GroupTypeName'], null=True)),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'], null=True, blank=True)),
('ad', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Person'], null=True, blank=True)),
('list_email', self.gf('django.db.models.fields.CharField')(max_length=64, blank=True)),
('list_subscribe', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('list_archive', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('comments', self.gf('django.db.models.fields.TextField')(blank=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(related_name='history_set', to=orm['group.Group'])),
('acronym', self.gf('django.db.models.fields.CharField')(max_length=40)),
))
db.send_create_signal('group', ['GroupHistory'])
# Adding M2M table for field unused_states on 'GroupHistory'
db.create_table('group_grouphistory_unused_states', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('grouphistory', models.ForeignKey(orm['group.grouphistory'], null=False)),
('state', models.ForeignKey(orm['doc.state'], null=False))
))
db.create_unique('group_grouphistory_unused_states', ['grouphistory_id', 'state_id'])
# Adding M2M table for field unused_tags on 'GroupHistory'
db.create_table('group_grouphistory_unused_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('grouphistory', models.ForeignKey(orm['group.grouphistory'], null=False)),
('doctagname', models.ForeignKey(orm['name.doctagname'], null=False))
))
db.create_unique('group_grouphistory_unused_tags', ['grouphistory_id', 'doctagname_id'])
# Adding model 'GroupURL'
db.create_table('group_groupurl', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('url', self.gf('django.db.models.fields.URLField')(max_length=200)),
))
db.send_create_signal('group', ['GroupURL'])
# Adding model 'GroupMilestone'
db.create_table('group_groupmilestone', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])),
('desc', self.gf('django.db.models.fields.TextField')()),
('expected_due_date', self.gf('django.db.models.fields.DateField')()),
('done', self.gf('django.db.models.fields.BooleanField')(default=False)),
('done_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)),
('time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
))
db.send_create_signal('group', ['GroupMilestone'])
# Adding model 'GroupStateTransitions'
db.create_table('group_groupstatetransitions', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])),
('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['doc.State'])),
))
db.send_create_signal('group', ['GroupStateTransitions'])
# Adding M2M table for field next_states on 'GroupStateTransitions'
db.create_table('group_groupstatetransitions_next_states', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('groupstatetransitions', models.ForeignKey(orm['group.groupstatetransitions'], null=False)),
('state', models.ForeignKey(orm['doc.state'], null=False))
))
db.create_unique('group_groupstatetransitions_next_states', ['groupstatetransitions_id', 'state_id'])
# Adding model 'GroupEvent'
db.create_table('group_groupevent', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])),
('time', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
('type', self.gf('django.db.models.fields.CharField')(max_length=50)),
('by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Person'])),
('desc', self.gf('django.db.models.fields.TextField')()),
))
db.send_create_signal('group', ['GroupEvent'])
# Adding model 'ChangeStateGroupEvent'
db.create_table('group_changestategroupevent', (
('groupevent_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['group.GroupEvent'], unique=True, primary_key=True)),
('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.GroupStateName'])),
))
db.send_create_signal('group', ['ChangeStateGroupEvent'])
# Adding model 'Role'
db.create_table('group_role', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.RoleName'])),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])),
('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Person'])),
('email', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Email'])),
))
db.send_create_signal('group', ['Role'])
# Adding model 'RoleHistory'
db.create_table('group_rolehistory', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.RoleName'])),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.GroupHistory'])),
('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Person'])),
('email', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Email'])),
))
db.send_create_signal('group', ['RoleHistory'])
def backwards(self, orm):
# Deleting model 'Group'
db.delete_table('group_group')
# Removing M2M table for field unused_states on 'Group'
db.delete_table('group_group_unused_states')
# Removing M2M table for field unused_tags on 'Group'
db.delete_table('group_group_unused_tags')
# Deleting model 'GroupHistory'
db.delete_table('group_grouphistory')
# Removing M2M table for field unused_states on 'GroupHistory'
db.delete_table('group_grouphistory_unused_states')
# Removing M2M table for field unused_tags on 'GroupHistory'
db.delete_table('group_grouphistory_unused_tags')
# Deleting model 'GroupURL'
db.delete_table('group_groupurl')
# Deleting model 'GroupMilestone'
db.delete_table('group_groupmilestone')
# Deleting model 'GroupStateTransitions'
db.delete_table('group_groupstatetransitions')
# Removing M2M table for field next_states on 'GroupStateTransitions'
db.delete_table('group_groupstatetransitions_next_states')
# Deleting model 'GroupEvent'
db.delete_table('group_groupevent')
# Deleting model 'ChangeStateGroupEvent'
db.delete_table('group_changestategroupevent')
# Deleting model 'Role'
db.delete_table('group_role')
# Deleting model 'RoleHistory'
db.delete_table('group_rolehistory')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'group.changestategroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']"})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'group.grouphistory': {
'Meta': {'object_name': 'GroupHistory'},
'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupmilestone': {
'Meta': {'ordering': "['expected_due_date']", 'object_name': 'GroupMilestone'},
'desc': ('django.db.models.fields.TextField', [], {}),
'done': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'expected_due_date': ('django.db.models.fields.DateField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'group.groupstatetransitions': {
'Meta': {'object_name': 'GroupStateTransitions'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']"})
},
'group.groupurl': {
'Meta': {'object_name': 'GroupURL'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'group.role': {
'Meta': {'object_name': 'Role'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'group.rolehistory': {
'Meta': {'object_name': 'RoleHistory'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'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.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.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.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.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'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['group']

View file

@ -0,0 +1,361 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
depends_on = (
("name", "0009_add_groupmilestonestatenames"),
)
def forwards(self, orm):
# Adding model 'MilestoneGroupEvent'
db.create_table('group_milestonegroupevent', (
('groupevent_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['group.GroupEvent'], unique=True, primary_key=True)),
('milestone', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.GroupMilestone'])),
))
db.send_create_signal('group', ['MilestoneGroupEvent'])
# rename expected_due_date
db.rename_column('group_groupmilestone', 'expected_due_date', "due")
# Adding field 'GroupMilestone.resolved'
db.add_column('group_groupmilestone', 'resolved', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True), keep_default=False)
# Adding field 'GroupMilestone.state'
db.add_column('group_groupmilestone', 'state', self.gf('django.db.models.fields.related.ForeignKey')(default='active', to=orm['name.GroupMilestoneStateName']), keep_default=False)
# Adding M2M table for field docs on 'GroupMilestone'
db.create_table('group_groupmilestone_docs', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('groupmilestone', models.ForeignKey(orm['group.groupmilestone'], null=False)),
('document', models.ForeignKey(orm['doc.document'], null=False))
))
db.create_unique('group_groupmilestone_docs', ['groupmilestone_id', 'document_id'])
# Changing field 'GroupMilestone.desc'
db.alter_column('group_groupmilestone', 'desc', self.gf('django.db.models.fields.CharField')(max_length=500))
def backwards(self, orm):
# Deleting model 'MilestoneGroupEvent'
db.delete_table('group_milestonegroupevent')
# rename due
db.rename_column('group_groupmilestone', 'due', "expected_due_date")
# Deleting field 'GroupMilestone.resolved'
db.delete_column('group_groupmilestone', 'resolved')
# Deleting field 'GroupMilestone.state'
db.delete_column('group_groupmilestone', 'state_id')
# Removing M2M table for field docs on 'GroupMilestone'
db.delete_table('group_groupmilestone_docs')
# Changing field 'GroupMilestone.desc'
db.alter_column('group_groupmilestone', 'desc', self.gf('django.db.models.fields.TextField')())
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'group.changestategroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']"})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'group.grouphistory': {
'Meta': {'object_name': 'GroupHistory'},
'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupmilestone': {
'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestone'},
'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'done': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'due': ('django.db.models.fields.DateField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupMilestoneStateName']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'group.groupstatetransitions': {
'Meta': {'object_name': 'GroupStateTransitions'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']"})
},
'group.groupurl': {
'Meta': {'object_name': 'GroupURL'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'group.milestonegroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'MilestoneGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupMilestone']"})
},
'group.role': {
'Meta': {'object_name': 'Role'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'group.rolehistory': {
'Meta': {'object_name': 'RoleHistory'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'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.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.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'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.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.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'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['group']

View file

@ -0,0 +1,331 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
"Write your forwards methods here."
system_person = orm["person.Person"].objects.get(name="(System)")
for m in orm.GroupMilestone.objects.all():
if m.done:
m.resolved = "Done"
m.save()
orm.MilestoneGroupEvent.objects.get_or_create(
group_id=m.group_id,
type="changed_milestone",
time=datetime.datetime.combine(m.done_date, datetime.time(0, 0, 0)),
by=system_person,
desc='Changed milestone "%s", resolved as "%s"' % (m.desc, m.resolved),
milestone=m,
)
def backwards(self, orm):
"Write your backwards methods here."
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'group.changestategroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']"})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'group.grouphistory': {
'Meta': {'object_name': 'GroupHistory'},
'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupmilestone': {
'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestone'},
'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'done': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'due': ('django.db.models.fields.DateField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupMilestoneStateName']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'group.groupstatetransitions': {
'Meta': {'object_name': 'GroupStateTransitions'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']"})
},
'group.groupurl': {
'Meta': {'object_name': 'GroupURL'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'group.milestonegroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'MilestoneGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupMilestone']"})
},
'group.role': {
'Meta': {'object_name': 'Role'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'group.rolehistory': {
'Meta': {'object_name': 'RoleHistory'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'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.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.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'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.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.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'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['group']

View file

@ -0,0 +1,322 @@
# 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):
# Deleting field 'GroupMilestone.done_date'
db.delete_column('group_groupmilestone', 'done_date')
# Deleting field 'GroupMilestone.done'
db.delete_column('group_groupmilestone', 'done')
def backwards(self, orm):
# Adding field 'GroupMilestone.done_date'
db.add_column('group_groupmilestone', 'done_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True), keep_default=False)
# Adding field 'GroupMilestone.done'
db.add_column('group_groupmilestone', 'done', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'group.changestategroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']"})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'group.grouphistory': {
'Meta': {'object_name': 'GroupHistory'},
'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupmilestone': {
'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestone'},
'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'due': ('django.db.models.fields.DateField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupMilestoneStateName']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'group.groupstatetransitions': {
'Meta': {'object_name': 'GroupStateTransitions'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']"})
},
'group.groupurl': {
'Meta': {'object_name': 'GroupURL'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'group.milestonegroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'MilestoneGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupMilestone']"})
},
'group.role': {
'Meta': {'object_name': 'Role'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'group.rolehistory': {
'Meta': {'object_name': 'RoleHistory'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'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.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.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'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.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.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'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['group']

View file

@ -0,0 +1,349 @@
# 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 'GroupMilestoneHistory'
db.create_table('group_groupmilestonehistory', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])),
('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.GroupMilestoneStateName'])),
('desc', self.gf('django.db.models.fields.CharField')(max_length=500)),
('due', self.gf('django.db.models.fields.DateField')()),
('resolved', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True)),
('time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('milestone', self.gf('django.db.models.fields.related.ForeignKey')(related_name='history_set', to=orm['group.GroupMilestone'])),
))
db.send_create_signal('group', ['GroupMilestoneHistory'])
# Adding M2M table for field docs on 'GroupMilestoneHistory'
db.create_table('group_groupmilestonehistory_docs', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('groupmilestonehistory', models.ForeignKey(orm['group.groupmilestonehistory'], null=False)),
('document', models.ForeignKey(orm['doc.document'], null=False))
))
db.create_unique('group_groupmilestonehistory_docs', ['groupmilestonehistory_id', 'document_id'])
def backwards(self, orm):
# Deleting model 'GroupMilestoneHistory'
db.delete_table('group_groupmilestonehistory')
# Removing M2M table for field docs on 'GroupMilestoneHistory'
db.delete_table('group_groupmilestonehistory_docs')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'group.changestategroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']"})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'group.grouphistory': {
'Meta': {'object_name': 'GroupHistory'},
'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'group.groupmilestone': {
'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestone'},
'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'due': ('django.db.models.fields.DateField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupMilestoneStateName']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'group.groupmilestonehistory': {
'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestoneHistory'},
'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'due': ('django.db.models.fields.DateField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'milestone': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['group.GroupMilestone']"}),
'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupMilestoneStateName']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'group.groupstatetransitions': {
'Meta': {'object_name': 'GroupStateTransitions'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']"})
},
'group.groupurl': {
'Meta': {'object_name': 'GroupURL'},
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'group.milestonegroupevent': {
'Meta': {'ordering': "['-time', 'id']", 'object_name': 'MilestoneGroupEvent', '_ormbases': ['group.GroupEvent']},
'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupMilestone']"})
},
'group.role': {
'Meta': {'object_name': 'Role'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'group.rolehistory': {
'Meta': {'object_name': 'RoleHistory'},
'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.GroupHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RoleName']"}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"})
},
'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.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.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'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.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.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'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['group']

View file

View file

@ -66,17 +66,31 @@ class GroupURL(models.Model):
def __unicode__(self):
return u"%s (%s)" % (self.url, self.name)
class GroupMilestone(models.Model):
class GroupMilestoneInfo(models.Model):
group = models.ForeignKey(Group)
desc = models.TextField(verbose_name="Description")
expected_due_date = models.DateField()
done = models.BooleanField()
done_date = models.DateField(null=True, blank=True)
time = models.DateTimeField(auto_now=True)
# a group has two sets of milestones, current milestones
# (active/under review/deleted) and charter milestones (active
# during a charter/recharter event), events for charter milestones
# are stored on the charter document
state = models.ForeignKey(GroupMilestoneStateName)
desc = models.CharField(verbose_name="Description", max_length=500)
due = models.DateField()
resolved = models.CharField(max_length=50, blank=True, help_text="Explanation of why milestone is resolved (usually \"Done\"), or empty if still due")
docs = models.ManyToManyField('doc.Document', blank=True)
def __unicode__(self):
return self.desc[:20] + "..."
class Meta:
ordering = ['expected_due_date']
abstract = True
ordering = ['due', 'id']
class GroupMilestone(GroupMilestoneInfo):
time = models.DateTimeField(auto_now=True)
class GroupMilestoneHistory(GroupMilestoneInfo):
time = models.DateTimeField()
milestone = models.ForeignKey(GroupMilestone, related_name="history_set")
class GroupStateTransitions(models.Model):
"""Captures that a group has overriden the default available
@ -93,6 +107,7 @@ GROUP_EVENT_CHOICES = [
("added_comment", "Added comment"),
("info_changed", "Changed metadata"),
("requested_close", "Requested closing group"),
("changed_milestone", "Changed milestone"),
]
class GroupEvent(models.Model):
@ -112,6 +127,9 @@ class GroupEvent(models.Model):
class ChangeStateGroupEvent(GroupEvent):
state = models.ForeignKey(GroupStateName)
class MilestoneGroupEvent(GroupEvent):
milestone = models.ForeignKey(GroupMilestone)
class Role(models.Model):
name = models.ForeignKey(RoleName)
group = models.ForeignKey(Group)

View file

@ -3,6 +3,7 @@ import os
from django.conf import settings
from ietf.group.models import *
from ietf.utils.history import get_history_object_for, copy_many_to_many_for_history
def save_group_in_history(group):
@ -11,30 +12,17 @@ def save_group_in_history(group):
the Group entry contain the current state. XXX TODO: Call this
directly from Group.save()
"""
def get_model_fields_as_dict(obj):
return dict((field.name, getattr(obj, field.name))
for field in obj._meta.fields
if field is not obj._meta.pk)
# copy fields
fields = get_model_fields_as_dict(group)
del fields["charter"] # Charter is saved canonically on Group
fields["group"] = group
grouphist = GroupHistory(**fields)
grouphist.save()
h = get_history_object_for(group)
h.save()
# save RoleHistory
for role in group.role_set.all():
rh = RoleHistory(name=role.name, group=grouphist, email=role.email, person=role.person)
rh = RoleHistory(name=role.name, group=h, email=role.email, person=role.person)
rh.save()
# copy many to many
for field in group._meta.many_to_many:
if field.rel.through and field.rel.through._meta.auto_created:
setattr(grouphist, field.name, getattr(group, field.name).all())
copy_many_to_many_for_history(h, group)
return grouphist
return h
def get_charter_text(group):
# get file path from settings. Syntesize file name from path, acronym, and suffix
@ -60,3 +48,12 @@ def get_charter_text(group):
except BaseException:
desc = 'Error Loading Work Group Description'
return desc
def save_milestone_in_history(milestone):
h = get_history_object_for(milestone)
h.milestone = milestone
h.save()
copy_many_to_many_for_history(h, milestone)
return h

View file

@ -150,10 +150,17 @@ def document_main(request, name, rev=None):
else:
ballot_summary = "No active ballot found."
chartering = get_chartering_type(doc)
# inject milestones from group
milestones = None
if chartering and not snapshot:
milestones = doc.group.groupmilestone_set.filter(state="charter")
return render_to_response("idrfc/document_charter.html",
dict(doc=doc,
top=top,
chartering=get_chartering_type(doc),
chartering=chartering,
content=content,
txt_url=settings.CHARTER_TXT_URL + filename,
revisions=revisions,
@ -161,6 +168,7 @@ def document_main(request, name, rev=None):
telechat=telechat,
ballot_summary=ballot_summary,
group=group,
milestones=milestones,
),
context_instance=RequestContext(request))
@ -237,35 +245,36 @@ def document_history(request, name):
doc = get_object_or_404(Document, docalias__name=name)
top = render_document_top(request, doc, "history", name)
diff_documents = [ doc ]
diff_documents.extend(Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces"))
# pick up revisions from events
diff_revisions = []
seen = set()
diffable = name.startswith("draft") or name.startswith("charter") or name.startswith("conflict-review") or name.startswith("status-change")
if diffable:
diff_documents = [ doc ]
diff_documents.extend(Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces"))
seen = set()
for e in NewRevisionDocEvent.objects.filter(type="new_revision", doc__in=diff_documents).select_related('doc').order_by("-time", "-id"):
if not (e.doc.name, e.rev) in seen:
seen.add((e.doc.name, e.rev))
if (e.doc.name, e.rev) in seen:
continue
url = ""
if name.startswith("charter"):
h = find_history_active_at(e.doc, e.time)
url = settings.CHARTER_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev))
elif name.startswith("conflict-review"):
h = find_history_active_at(e.doc, e.time)
url = settings.CONFLICT_REVIEW_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev))
elif name.startswith("status-change"):
h = find_history_active_at(e.doc, e.time)
url = settings.STATUS_CHANGE_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev))
elif name.startswith("draft"):
# rfcdiff tool has special support for IDs
url = e.doc.name + "-" + e.rev
seen.add((e.doc.name, e.rev))
diff_revisions.append((e.doc.name, e.rev, e.time, url))
url = ""
if name.startswith("charter"):
h = find_history_active_at(e.doc, e.time)
url = settings.CHARTER_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev))
elif name.startswith("conflict-review"):
h = find_history_active_at(e.doc, e.time)
url = settings.CONFLICT_REVIEW_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev))
elif name.startswith("status-change"):
h = find_history_active_at(e.doc, e.time)
url = settings.STATUS_CHANGE_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev))
elif name.startswith("draft"):
# rfcdiff tool has special support for IDs
url = e.doc.name + "-" + e.rev
diff_revisions.append((e.doc.name, e.rev, e.time, url))
# grab event history
events = doc.docevent_set.all().order_by("-time", "-id").select_related("by")
@ -533,6 +542,7 @@ def document_main_idrfc(request, name, tab):
'doc':doc, 'info':info, 'tab':tab,
'include_text':include_text(request),
'stream_info': get_full_info_for_draft(id),
'milestones': id.groupmilestone_set.filter(state="active"),
'versions':versions, 'history':history},
context_instance=RequestContext(request));

View file

@ -60,7 +60,8 @@ urlpatterns += patterns('',
(r'^agenda/documents/$', views.agenda_documents),
(r'^agenda/telechat-(?P<year>\d+)-(?P<month>\d+)-(?P<day>\d+)-docs.tgz', views.telechat_docs_tarfile),
(r'^discusses/$', views.discusses),
(r'^telechatdates/$', views.telechat_dates),
(r'^milestones', views.milestones_needing_review),
(r'^telechatdates/$', 'django.views.generic.simple.redirect_to', { 'url': '/admin/iesg/telechatdate/' }),
url(r'^wgactions/$', views.working_group_actions, name="iesg_working_group_actions"),
url(r'^wgactions/add/$', views.edit_working_group_action, { 'wga_id': None }, name="iesg_add_working_group_action"),
url(r'^wgactions/(?P<wga_id>\d+)/$', views.edit_working_group_action, name="iesg_edit_working_group_action"),

View file

@ -51,11 +51,11 @@ from ietf.iesg.models import TelechatDates, TelechatAgendaItem, WGAction
from ietf.idrfc.idrfc_wrapper import IdWrapper, RfcWrapper
from ietf.idrfc.models import RfcIndex
from ietf.idrfc.utils import update_telechat
from ietf.ietfauth.decorators import group_required
from ietf.ietfauth.decorators import group_required, role_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ipr.models import IprDocAlias
from ietf.doc.models import Document, TelechatDocEvent, LastCallDocEvent, ConsensusDocEvent
from ietf.group.models import Group
from ietf.group.models import Group, GroupMilestone
def date_threshold():
"""Return the first day of the month that is 185 days ago."""
@ -663,43 +663,25 @@ def discusses(request):
return direct_to_template(request, 'iesg/discusses.html', {'docs':res})
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
class TelechatDatesForm(forms.ModelForm):
class Meta:
model = TelechatDates
fields = ['date1', 'date2', 'date3', 'date4']
@role_required('Area Director', 'Secretariat')
def milestones_needing_review(request):
# collect milestones, grouped on AD and group
ads = {}
for m in GroupMilestone.objects.filter(state="review").exclude(group__state="concluded", group__ad=None).distinct().select_related("group", "group__ad"):
groups = ads.setdefault(m.group.ad, {})
milestones = groups.setdefault(m.group, [])
milestones.append(m)
@group_required('Secretariat')
def telechat_dates(request):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return HttpResponseRedirect("/admin/iesg/telechatdate/")
ad_list = []
for ad, groups in ads.iteritems():
ad_list.append(ad)
ad.groups_needing_review = sorted(groups, key=lambda g: g.acronym)
for g, milestones in groups.iteritems():
g.milestones_needing_review = sorted(milestones, key=lambda m: m.due)
dates = TelechatDates.objects.all()[0]
if request.method == 'POST':
if request.POST.get('rollup_dates'):
TelechatDates.objects.all().update(
date1=dates.date2, date2=dates.date3, date3=dates.date4,
date4=dates.date4 + datetime.timedelta(days=14))
form = TelechatDatesForm(instance=dates)
else:
form = TelechatDatesForm(request.POST, instance=dates)
if form.is_valid():
form.save(commit=False)
TelechatDates.objects.all().update(date1 = dates.date1,
date2 = dates.date2,
date3 = dates.date3,
date4 = dates.date4)
else:
form = TelechatDatesForm(instance=dates)
from django.contrib.humanize.templatetags import humanize
for f in form.fields:
form.fields[f].label = "Date " + humanize.ordinal(form.fields[f].label[4])
form.fields[f].thursday = getattr(dates, f).isoweekday() == 4
return render_to_response("iesg/telechat_dates.html",
dict(form=form),
return render_to_response('iesg/milestones_needing_review.html',
dict(ads=sorted(ad_list, key=lambda ad: ad.plain_name()),
),
context_instance=RequestContext(request))
def parse_wg_action_file(path):

View file

@ -40,7 +40,9 @@ def stream_state(context, doc):
data.update({'workflow': workflow,
'draft': draft,
'state': state})
'state': state,
'milestones': draft.groupmilestone_set.filter(state="active")
})
return data

View file

@ -83,6 +83,7 @@ def _edit_draft_stream(request, draft, form_class=DraftTagsStateForm):
stream = get_stream_from_draft(draft)
history = get_workflow_history_for_draft(draft, 'objectworkflowhistoryentry')
tags = get_annotation_tags_for_draft(draft)
milestones = draft.groupmilestone_set.all()
return render_to_response('ietfworkflows/state_edit.html',
{'draft': draft,
'state': state,
@ -91,6 +92,7 @@ def _edit_draft_stream(request, draft, form_class=DraftTagsStateForm):
'history': history,
'tags': tags,
'form': form,
'milestones': milestones,
},
context_instance=RequestContext(request))

View file

@ -135,7 +135,7 @@ def agenda_infoREDESIGN(num=None):
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
for g in Group.objects.filter(type="area").order_by("acronym"):
history = find_history_active_at(g, meeting_time)
if history:
if history and history != g:
if history.state_id == "active":
ads.extend(IESGHistory().from_role(x, meeting_time) for x in history.rolehistory_set.filter(name="ad").select_related())
else:

View file

@ -411,35 +411,29 @@
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="no" model="name.groupballotpositionname">
<field type="CharField" name="name">No</field>
<object pk="active" model="name.groupmilestonestatename">
<field type="CharField" name="name">Active</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field type="IntegerField" name="order">1</field>
</object>
<object pk="yes" model="name.groupballotpositionname">
<field type="CharField" name="name">Yes</field>
<object pk="deleted" model="name.groupmilestonestatename">
<field type="CharField" name="name">Deleted</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field type="IntegerField" name="order">2</field>
</object>
<object pk="abstain" model="name.groupballotpositionname">
<field type="CharField" name="name">Abstain</field>
<object pk="review" model="name.groupmilestonestatename">
<field type="CharField" name="name">For review</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field type="IntegerField" name="order">3</field>
</object>
<object pk="block" model="name.groupballotpositionname">
<field type="CharField" name="name">Block</field>
<object pk="charter" model="name.groupmilestonestatename">
<field type="CharField" name="name">Chartering/rechartering</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="norecord" model="name.groupballotpositionname">
<field type="CharField" name="name">No record</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field type="IntegerField" name="order">4</field>
</object>
<object pk="bof" model="name.groupstatename">
<field type="CharField" name="name">BOF</field>

View file

@ -0,0 +1,183 @@
# 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):
# Deleting model 'GroupBallotPositionName'
try:
db.delete_table('name_groupballotpositionname')
except Exception:
pass
# Adding model 'GroupMilestoneStateName'
db.create_table('name_groupmilestonestatename', (
('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', ['GroupMilestoneStateName'])
def backwards(self, orm):
# Adding model 'GroupBallotPositionName'
db.create_table('name_groupballotpositionname', (
('used', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('slug', self.gf('django.db.models.fields.CharField')(max_length=8, primary_key=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
('desc', self.gf('django.db.models.fields.TextField')(blank=True)),
))
db.send_create_signal('name', ['GroupBallotPositionName'])
# Deleting model 'GroupMilestoneStateName'
db.delete_table('name_groupmilestonestatename')
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'})
},
'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.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'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,170 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
# add names
orm.GroupMilestoneStateName.objects.get_or_create(slug="active",
name="Active",
order=1)
orm.GroupMilestoneStateName.objects.get_or_create(slug="deleted",
name="Deleted",
order=2)
orm.GroupMilestoneStateName.objects.get_or_create(slug="review",
name="For review",
order=3)
orm.GroupMilestoneStateName.objects.get_or_create(slug="charter",
name="Chartering/rechartering",
order=4)
def backwards(self, orm):
# remove names
orm.GroupMilestoneStateName.objects.filter(slug__in=("active", "deleted", "review", "charter")).delete()
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'})
},
'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.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'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

@ -20,6 +20,8 @@ class GroupStateName(NameModel):
"""BOF, Proposed, Active, Dormant, Concluded, Abandoned"""
class GroupTypeName(NameModel):
"""IETF, Area, WG, RG, Team, etc."""
class GroupMilestoneStateName(NameModel):
"""Active, Deleted, For Review, Chartering"""
class RoleName(NameModel):
"""AD, Chair"""
class StreamName(NameModel):
@ -47,8 +49,6 @@ class DocReminderTypeName(NameModel):
class BallotPositionName(NameModel):
""" Yes, No Objection, Abstain, Discuss, Block, Recuse """
blocking = models.BooleanField(default=False)
class GroupBallotPositionName(NameModel):
""" Yes, No, Block, Abstain """
class MeetingTypeName(NameModel):
"""IETF, Interim"""
class SessionStatusName(NameModel):

View file

@ -14,22 +14,30 @@ def json_emails(emails):
return simplejson.dumps([{"id": e.address + "", "name": escape(u"%s <%s>" % (e.person.name, e.address))} for e in emails])
class EmailsField(forms.CharField):
"""Multi-select field using jquery.tokeninput.js. Since the API of
tokeninput" is asymmetric, we have to pass it a JSON
representation on the way out and parse the ids coming back as a
comma-separated list on the way in."""
def __init__(self, *args, **kwargs):
kwargs["max_length"] = 1000
if not "help_text" in kwargs:
kwargs["help_text"] = "Type in name to search for person"
super(EmailsField, self).__init__(*args, **kwargs)
self.widget.attrs["class"] = "emails-field"
self.widget.attrs["class"] = "tokenized-field"
self.widget.attrs["data-ajax-url"] = lazy(urlreverse, str)("ajax_search_emails") # make this lazy to prevent initialization problem
def parse_tokenized_value(self, value):
return Email.objects.filter(address__in=[x.strip() for x in value.split(",") if x.strip()]).select_related("person")
def prepare_value(self, value):
if not value:
return ""
if isinstance(value, str):
return value
if isinstance(value, str) or isinstance(value, unicode):
value = self.parse_tokenized_value(value)
return json_emails(value)
def clean(self, value):
value = super(EmailsField, self).clean(value)
return Email.objects.filter(address__in=[x.strip() for x in value.split(",") if x.strip()]).select_related("person")
return self.parse_tokenized_value(value)

View file

@ -107,8 +107,8 @@ class Email(models.Model):
return self.person.plain_name() if self.person else self.address
def formatted_email(self):
if self.person and self.person.name:
return u'"%s" <%s>' % (self.person.plain_name(), self.address)
if self.person and self.person.ascii:
return u'"%s" <%s>' % (self.person.ascii, self.address)
else:
return self.address

View file

@ -44,12 +44,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<li><a href="{% url doc_search_by_ad2 name=user.get_profile.person.full_name_as_key %}">My Documents (new)</a></li>
<li><a href="{% url ietf.iesg.views.agenda_documents %}">Next Telechat</a></li>
<li><a href="{% url ietf.iesg.views.discusses %}">Discusses</a></li>
<li><a href="{% url ietf.iesg.views.milestones_needing_review %}">Milestones</a></li>
{# FIXME: this link should be removed when the old WG Actions are completely dead #}
<li><a href="{% url ietf.iesg.views.working_group_actions %}">Working Groups</a></li>
{% endif %}
{% if user|in_group:"Secretariat" %}
<li class="sect first">Secretariat</li>
<li><a href="{% url ietf.iesg.views.telechat_dates %}">Telechat Dates</a></li>
<li><a href="/admin/iesg/telechatdate/">Telechat Dates</a></li>
<li><a href="/admin/iesg/telechatagendaitem/">Management Items</a></li>
<li><a href="{% url ietf.iesg.views.milestones_needing_review %}">Milestones</a></li>
{# FIXME: this link should be removed when the old WG Actions are completely dead #}
<li><a href="{% url ietf.iesg.views.working_group_actions %}">Working Groups</a></li>
<li><a href="{% url ietf.sync.views.discrepancies %}">Sync discrepancies</a>
{% endif %}

View file

@ -99,9 +99,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% ifequal stream_info.stream.name "IETF" %}
<tr>
<td>IETF State:</td>
<td>{{ stream_info.state.name }} ({{ stream_info.streamed.get_group }})
{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}
</td>
<td class="stream-state">{{ stream_info.state.name }} ({{ stream_info.streamed.get_group }})
{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</i>{% endif %}
{% if milestones %}{% for m in milestones %}<span title="In {{ m.group.acronym }} milestone: {{ m.desc }}" class="milestone">{{ m.due|date:"M Y" }}</span>{% endfor %}{% endif %}
</td>
</tr>
{% else %}
{% if stream_info.stream %}
@ -109,7 +110,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<td>{{ stream_info.stream.name }} status:</td>
<td>
{{ stream_info.state.name }} {% if stream_info.streamed.get_group %}({{ stream_info.streamed.get_group }}) {% endif %}
{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}
{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</i>{% endif %}
</td>
</tr>
{% endif %}

View file

@ -47,7 +47,6 @@
</a>
{% if chartering == "initial" %} - (Initial Chartering){% endif %}
{% if chartering == "rechartering" %} - (Rechartering){% endif %}
</div>
{% if not snapshot and chartering %}
@ -134,11 +133,25 @@
{% endif %}
</h3>
{% if doc.rev %}
{% if doc.rev != "" %}
<div class="markup_draft">
{{ content|safe|keep_spacing|sanitize_html|wordwrap:80|safe }}
</div>
{% endif %}
{% if not snapshot and chartering %}
<h3>Proposed Milestones
{% if user|has_role:"Area Director,Secretariat" %}
<a class="edit" href="{% url wg_edit_charter_milestones acronym=doc.group.acronym %}">Edit charter milestones</a>
{% endif %}
</h3>
{% if milestones %}
{% include "wginfo/milestones.html" %}
{% else %}
<p>No milestones for charter found.</p>
{% endif %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Milestones Needing Review{% endblock %}
{% block morecss %}
h3.ad { margin-bottom: 0.5em; }
div.milestones-for-group { margin: 0.5em 0; }
{% endblock %}
{% block content %}
<h1>Milestones Needing Review</h1>
{% for ad in ads %}
<h3 class="ad">{{ ad.plain_name }}</h4>
{% for g in ad.groups_needing_review %}
<div class="milestones-for-group">{{ g.name }} ({{ g.acronym }}) <a href="{% url wg_edit_milestones acronym=g.acronym %}">has new milestones</a>:</div>
{% with g.milestones_needing_review as milestones %}
{% include "wginfo/milestones.html" %}
{% endwith %}
{% endfor %}
{% endfor %}
{% endblock %}

View file

@ -1,26 +0,0 @@
{% extends "base.html" %}
{% block title %}Manage Telechat Dates{% endblock %}
{% block content %}
<h1>Manage Telechat Dates</h1>
<form action="" method="POST">
<table>
{% for f in form %}
<tr>
<td>{{ f.label_tag }}</td>
<td>{{ f }}</td>
<td style="font-weight:bold;color:#a00">{% if not f.field.thursday %}NOT THURSDAY{% endif %}</td>
</tr>
{% endfor %}
</table>
<div class="actions">
<input type="reset" value="Reset"/>
<input type="submit" value="Save"/>
<input style="margin-left: 8px" type="submit" name="rollup_dates" value="Rollup"/>
</div>
</form>
{% endblock %}

View file

@ -29,6 +29,19 @@ table.edit-form-tags ul li { padding: 0px; }
<a href="{% url doc_view draft.filename %}">Return to document view</a>
</p>
</div>
{% if state and state.slug == "wg-doc" and not milestones %}
<p>This document is not part of any milestone. You may wish to <a href="{% url wg_edit_milestones acronym=draft.group.acronym %}">add it to one</a>.</p>
{% endif %}
{% if state and state.slug == "sub-pub" and milestones %}
<p>This document is part of {% if milestones|length > 1 %}{{ milestones|length }}
milestones{% else %}a milestone{% endif %}. Now that the draft is
submitted to IESG for publication, you may wish to
<a href="{% url wg_edit_milestones acronym=draft.group.acronym %}">update the
milestone{{ milestones|pluralize }}</a>.</p>
{% endif %}
<table class="ietf-table" style="width: 100%;">
<tr>
<th>Current stream</th>

View file

@ -3,6 +3,7 @@
<div class="stream_state_more" style="float: left; margin: 1px 0.5em 0 0;"><a href="{% url stream_history draft.filename %}" class="show_stream_info" title="Stream information for {{ draft.filename }}" style="text-decoration: none; color:transparent; margin: 0; padding: 0; border: 0;"><img src="/images/plus.png" style="margin: 0; padding: 0; border:0;"></img></a></div>
{% if stream %}
{% if state %}{{ state.name }}{% else %}{{ stream }}{% endif %}
{% if milestones %}{% for m in milestones %}<span title="Included in milestone: {{ m.desc }}" class="milestone">{{ m.due|date:"M Y" }}</span>{% endfor %}{% endif %}
{% else %}
No stream assigned
{% endif %}

View file

@ -1,8 +1,8 @@
{% load ietf_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
To: IETF-Announce <ietf-announce@ietf.org>{% if wg.list_email %}
Cc: {{ wg.acronym }} WG <{{ wg.list_email }}> {% endif %}
Subject: WG Action: {{ action_type }} {{ wg.name }} ({{ wg.acronym }})
To: IETF-Announce <ietf-announce@ietf.org>{% if group.list_email %}
Cc: {{ group.acronym }} {{ group.type.name }} <{{ group.list_email }}> {% endif %}
Subject: WG Action: {{ action_type }} {{ group.name }} ({{ group.acronym }})
{% filter wordwrap:73 %}{% ifequal action_type "Formed" %}A new IETF working group has been formed in the {{ wg.parent.name }}.{% endifequal %}{% ifequal action_type "Rechartered" %}The {{ wg.name }} ({{ wg.acronym }}) working group in the {{ wg.parent.name }} of the IETF has been rechartered.{% endifequal %} For additional information please contact the Area Directors or the WG Chair{{ chairs|pluralize}}.
{% filter wordwrap:73 %}{% ifequal action_type "Formed" %}A new IETF working group has been formed in the {{ group.parent.name }}.{% endifequal %}{% ifequal action_type "Rechartered" %}The {{ group.name }} ({{ group.acronym }}) working group in the {{ group.parent.name }} of the IETF has been rechartered.{% endifequal %} For additional information please contact the Area Directors or the {{ group.type.name }} Chair{{ chairs|pluralize}}.
{% include "wgcharter/wg_info.txt" %}{% endfilter %}{% endautoescape %}
{% include "wgcharter/group_info.txt" %}{% endfilter %}{% endautoescape %}

View file

@ -0,0 +1,8 @@
{% load ietf_filters %}{% autoescape off %}{% filter wrap_long_lines %}{{ charter_text }}{% endfilter %}
Milestones
{% for milestone in milestones %}{% if milestone.resolved %}{{ milestone.resolved|ljust:8 }}{% else %}{{ milestone.due|date:"M Y" }}{% endif %} - {{ milestone.desc_filled }}{% for d in milestone.docs.all %}
o {{ d.name }}{% endfor %}
{% endfor %}{% endautoescape %}

View file

@ -1,6 +1,6 @@
{% load ietf_filters %}{% autoescape off %}
{{ text|fill:70 }}
WG: {{ wg_url }}
{{ group.type.name}}: {{ group_url }}
Charter: {{ charter_url }}
{% endautoescape %}

View file

@ -0,0 +1,28 @@
{{ group.name }} ({{ group.acronym }})
------------------------------------------------
Current Status: {{ group.state.name }} {{ group.type.name }}
{% if chairs %}Chairs:
{% for r in chairs %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
{% endif %}{% if secr %}Secretaries:
{% for r in secr %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
{% endif %}{% if techadv %}Technical advisors:
{% for r in techadv %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
{% endif %}{% if group.ad %}Assigned Area Director:
{{ group.ad.plain_name }} <{{ ad_email }}>
{% endif %}{% if group.list_email %}Mailing list
Address: {{ group.list_email }}
To Subscribe: {{ group.list_subscribe }}
Archive: {{ group.list_archive }}
{% endif %}
Charter:
{{ charter_text }}
Milestones:
{% for milestone in milestones %} {% if milestone.resolved %}{{ milestone.resolved }} {% else %}{{ milestone.due|date:"M Y" }}{% endif %} - {{ milestone.desc|safe }}
{% endfor %}

View file

@ -1,8 +1,8 @@
{% load ietf_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
To: IETF-Announce <ietf-announce@ietf.org>{% if wg.list_email %}
Cc: {{ wg.acronym }} WG <{{ wg.list_email }}> {% endif %}
Subject: WG Review: {{ wg.name }} ({{ wg.acronym }})
To: IETF-Announce <ietf-announce@ietf.org>{% if group.list_email %}
Cc: {{ group.acronym }} {{ group.type.name }} <{{ group.list_email }}> {% endif %}
Subject: WG Review: {{ group.name }} ({{ group.acronym }})
{% filter wordwrap:73 %}{% ifequal review_type "new" %}A new IETF working group has been proposed in the {{ wg.parent.name }}.{% endifequal %}{% ifequal review_type "recharter" %}The {{ wg.name }} ({{wg.acronym}}) working group in the {{ wg.parent.name }} of the IETF is undergoing rechartering.{% endifequal %} The IESG has not made any determination yet. The following draft charter was submitted, and is provided for informational purposes only. Please send your comments to the IESG mailing list (iesg at ietf.org) by {{ review_date }}.
{% filter wordwrap:73 %}{% ifequal review_type "new" %}A new IETF working group has been proposed in the {{ group.parent.name }}.{% endifequal %}{% ifequal review_type "recharter" %}The {{ group.name }} ({{group.acronym}}) working group in the {{ group.parent.name }} of the IETF is undergoing rechartering.{% endifequal %} The IESG has not made any determination yet. The following draft charter was submitted, and is provided for informational purposes only. Please send your comments to the IESG mailing list (iesg at ietf.org) by {{ review_date }}.
{% include "wgcharter/wg_info.txt" %}{% endfilter %}{% endautoescape %}
{% include "wgcharter/group_info.txt" %}{% endfilter %}{% endautoescape %}

View file

@ -1,13 +0,0 @@
{% comment %}
Copyright The IETF Trust 2011, All Rights Reserved
{% endcomment %}
{% load ietf_filters %}
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
<td class="acronym">
<a href="{% url wg_view name=wg.acronym %}">{{ wg.acronym|safe }}</a>
</td>
<td class="title">{{ wg.name }}</td>
{% include "wgcharter/date_column.html" %}
{% include "wgcharter/status_columns.html" %}
</tr>

View file

@ -8,13 +8,13 @@ form #id_content {
{% endblock %}
{% block title %}
Charter submission for {{ wg.acronym }}
Charter submission for {{ group.acronym }} {{ group.type.name }}
{% endblock %}
{% block content %}
<h1>Charter submission for {{ wg.acronym }}</h1>
<h1>Charter submission for {{ group.acronym }} {{ group.type.name }}</h1>
<p>The text will be submitted as <strong>charter-ietf-{{ wg.acronym }}-{{ next_rev }}</strong></p>
<p>The text will be submitted as <strong>charter-ietf-{{ group.acronym }}-{{ next_rev }}</strong></p>
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
<table>
{% for field in form.visible_fields %}
@ -30,7 +30,7 @@ Charter submission for {{ wg.acronym }}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=wg.charter.name %}">Back</a>
<a href="{% url doc_view name=group.charter.name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>

View file

@ -1,28 +0,0 @@
{{ wg.name }} ({{ wg.acronym }})
------------------------------------------------
Current Status: {{ wg.state.name }} Working Group
{% if chairs %}Chairs:
{% for r in chairs %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
{% endif %}{% if secr %}Secretaries:
{% for r in secr %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
{% endif %}{% if techadv %}Technical advisors:
{% for r in techadv %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
{% endif %}{% if wg.ad %}Assigned Area Director:
{{ wg.ad.plain_name }} <{{ ad_email }}>
{% endif %}{% if wg.list_email %}Mailing list
Address: {{ wg.list_email }}
To Subscribe: {{ wg.list_subscribe }}
Archive: {{ wg.list_archive }}
{% endif %}
Charter of Working Group:
{{ charter_text }}
Milestones:
{% for milestone in milestones %} {% if milestone.done %}Done {% else %}{{ milestone.expected_due_date|date:"M Y" }}{% endif %} - {{ milestone.desc|safe }}
{% endfor %}

View file

@ -84,7 +84,7 @@ Create new WG or BoF
{% block content_end %}
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/lib/json2.js"></script>
<script type="text/javascript" src="/js/emails-field.js"></script>
<script type="text/javascript" src="/js/tokenized-field.js"></script>
<script>
jQuery(function () {
if (jQuery('input[name="confirmed"]').length > 0) {

View file

@ -0,0 +1,104 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block morecss %}
tr.milestone td { padding: 0.2em 0; cursor: pointer; vertical-align: top; }
tr.milestone:hover { background-color: #e8f0fa; }
td.due { width: 5em; }
.milestone.changed { font-weight: bold; }
.milestone .note { font-style: italic; display: inline-block; margin-left: 0.5em; color: #2647a0; }
.milestone .doc { display: block; padding-left: 1em; }
.edit-milestone { display: none; }
.milestone.delete, .edit-milestone.delete, .edit-milestone.delete input { color: #aaa !important; }
.edit-milestone table { margin: 1em 0; }
.edit-milestone table td { padding: 0.1em; }
.edit-milestone .desc input { width: 50em; }
.edit-milestone .due input { width: 6em; }
.edit-milestone input[type=checkbox] { vertical-align: middle; margin: 0 0.2em 0 0.8em;}
.edit-milestone .resolved label { vertical-align: middle; }
.edit-milestone .delete label { vertical-align: middle; }
.edit-milestone .accept ul { display: inline-block; margin: 0; padding: 0; }
.edit-milestone .accept ul li { list-style: none; display: inline-block; margin: 0; padding: 0; padding-left: 0.4em; }
.edit-milestone .accept ul li label { vertical-align: middle; }
.edit-milestone .accept ul li input { margin: 0; padding: 0; vertical-align: middle; }
.edit-milestone .docs td { vertical-align: top; }
ul.errorlist { border-width: 0px; padding: 0px; margin: 0px; display: inline-block; }
ul.errorlist li { color: #a00; margin: 0px; padding: 0px; list-style: none; }
p.help { font-style: italic; }
p.error { color: #a00; font-size: larger; }
tr.milestone.add { font-style: italic; }
{% endblock %}
{% block pagehead %}
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
{% endblock %}
{% block content %}
{% load ietf_filters %}
<h1>{{ title }}</h1>
<noscript>This page depends on Javascript being enabled to work properly.</noscript>
<p class="help">{% if forms %}Click a milestone to edit it.{% endif %}
{% if needs_review %}
Note that as {{ group.type.name }} Chair you cannot edit descriptions of existing
milestones and milestones you add are subject to review by the Area
Director.
{% endif %}
</p>
{% if can_reset %}
<p>
You can <a href="{% url wg_reset_charter_milestones acronym=group.acronym %}">reset
this list</a> to the currently in-use milestones for the {{ group.acronym }} {{ group.type.name }}.
</p>
{% endif %}
{% if form_errors %}
<p class="error">There were errors, see below.</p>
{% endif %}
<form action="" method="post" id="milestones-form">
<table cellspacing="0" cellpadding="0">
{% for form in forms %}
<tr class="milestone{% if form.delete.data %} delete{% endif %}">
<td class="due">{% if form.milestone.resolved %}{{ form.milestone.resolved }}{% else %}{{ form.milestone.due|date:"M Y" }}{% endif %}</td>
<td>
<div>{{ form.milestone.desc }}
{% if form.needs_review %}<span class="note">awaiting accept</span>{% endif %}
{% if form.changed %}<span class="note">changed</span>{% endif %}
</div>
{% for d in form.docs_names %}
<div class="doc">{{ d }}</div>
{% endfor %}
</td>
</tr>
<tr class="edit-milestone{% if form.changed %} changed{% endif %}"><td colspan="2">{% include "wginfo/milestone_form.html" %}</td></tr>
{% endfor %}
<tr class="milestone add"><td></td><td>Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for AD review{% endif %}</td></tr>
<tr class="edit-milestone template"><td colspan="2">{% with empty_form as form %}{% include "wginfo/milestone_form.html" %}{% endwith %}</td></tr>
</table>
<div class="actions">
<a class="button" href="{% if milestone_set == "charter" %}{% url doc_view name=group.charter.canonical_name %}{% else %}{% url wg_charter acronym=group.acronym %}{% endif %}">Cancel</a>
<input class="button" type="submit" data-labelsave="Save" data-labelreview="Review changes" value="Save" style="display:none"/>
<input type="hidden" name="action" value="save">
</div>
</form>
{% endblock %}
{% block content_end %}
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/lib/json2.js"></script>
<script type="text/javascript" src="/js/tokenized-field.js"></script>
<script>
var finishedMilestoneText = "{{ finished_milestone_text|escapejs }}";
</script>
<script type="text/javascript" src="/js/edit-milestones.js"></script>
{% endblock %}

View file

@ -0,0 +1,42 @@
{# assumes group, form, needs_review are in the context #}
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
{{ form.id }}
<table cellspacing="0" cellpadding="0">
<tr>
<td>{{ form.desc.label_tag }}:</td>
<td>
<span class="desc">
{% if needs_review and form.milestone and form.milestone.state_id != "review" %}
{{ form.milestone.desc }} {{ form.desc.as_hidden }}
{% else %}
{{ form.desc }}
{% endif %}
</span>
<span class="delete">{{ form.delete }} {{ form.delete.label_tag }}</span>
</td>
</tr>
{% if form.desc.errors %}<tr><td></td><td colspan="2">{{ form.desc.errors }}</td></tr>{% endif %}
<tr>
<td>Due date:</td>
<td><span class="due">{{ form.due_month }} {{ form.due_year }}</span> {{ form.due_month.errors }} {{ form.due_year.errors }}
<span class="resolved">{{ form.resolved_checkbox }} {{ form.resolved_checkbox.label_tag }} {{ form.resolved }}</span>
{{ form.resolved.errors }}
</td>
</tr>
<tr class="docs">
<td>Drafts:</td>
<td><input name="{{ form.docs.html_name }}" class="tokenized-field" data-ajaxurl="{% url wg_ajax_search_docs group.acronym %}" data-pre="{{ form.docs_prepopulate }}"/>
{{ form.docs.errors }}
</td>
</tr>
{% if form.needs_review %}
<tr class="needs-review">
<td>Review:</td>
<td class="accept">
This milestone is not active yet, awaiting
AD acceptance{% if needs_review %}.{% else %}: {{ form.accept }}{% endif %}
</td>
</tr>
{% endif %}
</table>

View file

@ -0,0 +1,17 @@
{# assumes milestones is in context #}
<table class="milestones">
{% for milestone in milestones %}
<tr>
<td class="due">
{% if milestone.resolved %}{{ milestone.resolved }}{% else %}{{ milestone.due|date:"M Y" }}{% endif %}
</td>
<td>
<div>{{ milestone.desc|escape }}</div>
{% for d in milestone.docs.all %}
<a class="doc" href="{% url doc_view name=d.name %}">{{ d.name }}</a>
{% endfor %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -0,0 +1,7 @@
{% autoescape off %}{% filter wordwrap:73 %}This is a reminder that milestones in "{{ group.name }}" are soon due.
{% for m in milestones %}"{{ m.desc }}" is due {% if m.due == today %}today!{% else %}in {{ early_warning_days }} days.{% endif %}
{% endfor %}
URL: {{ url }}
{% endfilter %}{% endautoescape %}

View file

@ -0,0 +1,10 @@
{% autoescape off %}{% filter wordwrap:73 %}{{ milestones|length }} new milestone{{ milestones|pluralize }} in "{{ group.name }}" {% if milestones|length > 1 %}need{% else %}needs{%endif %} an AD review:
{% for m in milestones %}"{{ m.desc }}"{% if m.days_ready != None %}
Waiting for {{ m.days_ready }} day{{ m.days_ready|pluralize }}.{% endif %}
{% endfor %}
Go here to either accept or reject the new milestones:
{{ url }}
{% endfilter %}{% endautoescape %}

View file

@ -0,0 +1,7 @@
{% autoescape off %}{% filter wordwrap:73 %}This is a reminder that milestones in "{{ group.name }}" are overdue.
{% for m in milestones %}"{{ m.desc }}" is overdue{% if m.months_overdue > 0 %} with {{ m.months_overdue }} month{{ m.months_overdue|pluralize }}{% endif %}!
{% endfor %}
URL: {{ url }}
{% endfilter %}{% endautoescape %}

View file

@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block title %}Reset Charter Milestones for {{ group.acronym }} {{ group.type.name }}{% endblock %}
{% block morecss %}
#reset-form .date { display: inline-block; min-width: 5em; }
{% endblock %}
{% block content %}
{% load ietf_filters %}
<h1>Reset Charter Milestones for {{ group.acronym }} {{ group.type.name }}</h1>
<p>Select which of the current {{ group.type.name }} milestones you would like to copy to the charter.
{% if charter_milestones %}This will discard {{ charter_milestones|length }} existing charter milestone{{ charter_milestones|pluralize }}{% endif %}
</p>
<form action="" method="post" id="reset-form">
{% for milestone in current_milestones %}
<div>
<label>
<input type="checkbox" name="milestone" value="{{ milestone.id }}" {% if not milestone.resolved %}checked="checked"{% endif %} />
<span class="date">{% if milestone.resolved %}{{ milestone.resolved }}{% else %}{{ milestone.due|date:"M Y" }}{% endif %}</span>
{{ milestone.desc }}
</label>
</div>
{% endfor %}
<div class="actions">
<a href="{% url wg_edit_charter_milestones acronym=group.acronym %}">Back</a>
<input type="submit" value="Reset charter milestones"/>
</div>
</form>
{% endblock %}

View file

@ -34,7 +34,7 @@ Description of Working Group:
{{ wg.charter_text|indent|safe }}
Goals and Milestones:
{% for milestone in wg.milestones %} {% if milestone.done %}Done {% else %}{{ milestone.expected_due_date|date:"M Y" }}{% endif %} - {{ milestone.desc|safe }}
{% for milestone in wg.milestones %} {% if milestone.resolved %}{{ milestone.resolved }} {% else %}{{ milestone.due|date:"M Y" }}{% endif %} - {{ milestone.desc|safe }}
{% endfor %}
Internet-Drafts:
{% for alias in wg.drafts %} - {{alias.document.title|safe}} [{{alias.name}}-{{alias.document.rev}}] ({{ alias.document.pages }} pages)

View file

@ -35,119 +35,146 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% load ietf_filters %}
{% block wg_titledetail %}Charter{% endblock %}
{% block wg_content %}
{% block morecss %}
{{ block.super }}
h2 a.button { margin-left: 0.5em; font-size: 13px; }
{% endblock %}
{% block wg_content %}
<div class="ietf-box ietf-wg-details">
{% if concluded %}
<span class="ietf-concluded-warning">Note: The data for concluded WGs
is occasionally incorrect.</span>
{% endif %}
<table>
<tr>
<td colspan="2">
<b>Personnel</b>
</td>
</tr>
<tr><th colspan="2">Group</th></tr>
<tr valign="top">
<td style="width:14ex;">Chair{{ wg.chairs.count|pluralize:",s" }}:</td>
<td>
{% for chair in wg.chairs %}
<a href="mailto:{{ chair.person.email.1 }}">{{ chair.person|escape }} &lt;{{ chair.person.email.1 }}&gt;</a><br/>
{% endfor %}
</td></tr>
<tr><td>Area Director:</td>
<td>
{% ifequal wg.area_director.person.email.1 "noreply@ietf.org" %}?{%else%}
<a href="mailto:{{ wg.area_director.person.email.1 }}">{{ wg.area_director.person }} &lt;{{wg.area_director.person.email.1 }}&gt;</a>{% endifequal %}
</td>
</tr>
{% if wg.wgtechadvisor_set.count %}
<tr>
<td>Tech Advisor{{ wg.wgtechadvisor_set.count|pluralize:",s" }}:</td>
<td>
{% for techadvisor in wg.wgtechadvisor_set.all %}
<a href="mailto:{{ techadvisor.person.email.1 }}">{{ techadvisor.person }} &lt;{{ techadvisor.person.email.1 }}&gt;</a><br/>
{% endfor %}
</td></tr>
{% endif %}
{% if wg.wgeditor_set.count %}
<td>Editor{{ wg.wgeditor_set.count|pluralize:",s" }}:</td>
<td>
{% for editor in wg.wgeditor_set.all %}
<a href="mailto:{{ editor.person.email.1 }}">{{ editor.person }} &lt;{{ editor.person.email.1 }}&gt;</a><br/>
{% endfor %}
</td></tr>
{% endif %}
{% if wg.secretaries %}
<tr><td>Secretar{{ wg.secretaries.count|pluralize:"y,ies" }}:</td>
<td>
{% for secretary in wg.secretaries %}
<a href="mailto:{{ secretary.person.email.1 }}">{{ secretary.person }} &lt;{{ secretary.person.email.1 }}&gt;</a><br/>
{% endfor %}
</td></tr>
{% endif %}
<tr valign="top">
<td style="width:14ex;">Name:</td>
<td>{{ wg.name }}</td>
</tr>
<tr>
<td colspan="2">
<br/><b>Mailing List</b>
</td>
</tr>
<tr><td>Acronym:</td><td>{{ wg.acronym }}</td></tr>
<tr><td>Address:</td><td>{{ wg.email_address|urlize }}</td></tr>
<tr><td>To Subscribe:</td><td>{{ wg.email_subscribe|urlize }}</td></tr>
<tr><td>Archive:</td><td>{{ wg.clean_email_archive|urlize }}</td></tr>
{% if wg.parent %}
<tr><td>Area:</td><td>{{ wg.parent.name }} ({{ wg.parent.acronym }})</td></tr>
{% endif %}
{% if not concluded %}
<tr>
<td colspan="2">
<br/><b>Jabber Chat</b>
</td>
</tr>
<tr>
<td>State:</td>
<td>{{ wg.state.name }}
{% if requested_close %}
(but in the process of being closed)
{% endif %}
</td>
</tr>
<tr><td>Room Address:</td><td><a href="xmpp:{{wg}}@jabber.ietf.org">xmpp:{{wg}}@jabber.ietf.org</a></td></tr>
<tr><td>Logs:</td><td><a href="http://jabber.ietf.org/logs/{{wg}}/">http://jabber.ietf.org/logs/{{wg}}/</a></td></tr>
{% endif %}
<tr>
<td>Charter:</td>
<td>
{% if wg.charter %}
<a href="{% url doc_view name=wg.charter.name %}">{{ wg.charter.name }}-{{ wg.charter.rev }}</a> ({{ wg.charter.get_state.name }})
{% else %}
none
{% if user|has_role:"Area Director,Secretariat" %}
- <a href="{% url wginfo.edit.submit_initial_charter acronym=wg.acronym %}">Submit Charter</a>
{% endif %}
{% endif %}
</td>
</tr>
<tr><th colspan="2">Personnel</th></tr>
<tr valign="top">
<td>Chair{{ wg.chairs|pluralize }}:</td>
<td>
{% for chair in wg.chairs %}
<a href="mailto:{{ chair.address }}">{{ chair.person.plain_name }} &lt;{{ chair.address }}&gt;</a><br/>
{% endfor %}
</td>
</tr>
<tr><td>Area Director:</td>
<td>
{% if not wg.ad %}?{% else %}
<a href="mailto:{{ wg.areadirector.address }}">{{ wg.ad.plain_name }} &lt;{{ wg.areadirector.address }}&gt;</a>{% endif %}
</td>
</tr>
{% if wg.techadvisors %}
<tr>
<td>Tech Advisor{{ wg.techadvisors|pluralize }}:</td>
<td>
{% for techadvisor in wg.techadvisors %}
<a href="mailto:{{ techadvisor.address }}">{{ techadvisor.person.plain_name }} &lt;{{ techadvisor.address }}&gt;</a><br/>
{% endfor %}
</td>
</tr>
{% endif %}
{% if wg.editors %}
<td>Editor{{ wg.editors|pluralize }}:</td>
<td>
{% for editor in wg.editors %}
<a href="mailto:{{ editor.address }}">{{ editor.person.plain_name }} &lt;{{ editor.address }}&gt;</a><br/>
{% endfor %}
</td></tr>
{% endif %}
{% if wg.secretaries %}
<tr>
<td>Secretar{{ wg.secretaries|pluralize:"y,ies" }}:</td>
<td>
{% for secretary in wg.secretaries %}
<a href="mailto:{{ secretary.address }}">{{ secretary.person.plain_name }} &lt;{{ secretary.address }}&gt;</a><br/>
{% endfor %}
</td>
</tr>
{% endif %}
<tr><th colspan="2">Mailing List</th></tr>
<tr><td>Address:</td><td>{{ wg.email_address|urlize }}</td></tr>
<tr><td>To Subscribe:</td><td>{{ wg.email_subscribe|urlize }}</td></tr>
<tr><td>Archive:</td><td>{{ wg.clean_email_archive|urlize }}</td></tr>
{% if not concluded %}
<tr><th colspan="2">Jabber Chat</th></tr>
<tr><td>Room Address:</td><td><a href="xmpp:{{ wg.acronym }}@jabber.ietf.org">xmpp:{{ wg.acronym }}@jabber.ietf.org</a></td></tr>
<tr><td>Logs:</td><td><a href="http://jabber.ietf.org/logs/{{ wg.acronym }}/">http://jabber.ietf.org/logs/{{ wg.acronym }}/</a></td></tr>
{% endif %}
</table>
{% if user|has_role:"Area Director,Secretariat" %}
<div style="margin: 2px; margin-top: 2em;">
{% for name, url in actions %}
<a href="{{ url }}">{{ name }}</a>
{% if not forloop.last %}|{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% if wg.additional_urls %}
<p>In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at:
{% for url in wg.additional_urls %}
<a href="{{ url.url }}">{{ url.description}}</a>{% if not forloop.last %}, {% endif %}
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
{% endif %}
<h2>Description of Working Group</h2>
<h2>Charter for Working Group</h2>
<p>{{ wg.charter_text|escape|format_charter|safe }}</p>
<h2>Goals and Milestones</h2>
<table>
{% for milestone in wg.milestones %}
<tr>
<td width="80px">
{% ifequal milestone.done 'Done' %} Done
{% else %}
{%ifequal milestone.expected_due_date.month 1 %}Jan{% endifequal %}
{%ifequal milestone.expected_due_date.month 2 %}Feb{% endifequal %}
{%ifequal milestone.expected_due_date.month 3 %}Mar{% endifequal %}
{%ifequal milestone.expected_due_date.month 4 %}Apr{% endifequal %}
{%ifequal milestone.expected_due_date.month 5 %}May{% endifequal %}
{%ifequal milestone.expected_due_date.month 6 %}Jun{% endifequal %}
{%ifequal milestone.expected_due_date.month 7 %}Jul{% endifequal %}
{%ifequal milestone.expected_due_date.month 8 %}Aug{% endifequal %}
{%ifequal milestone.expected_due_date.month 9 %}Sep{% endifequal %}
{%ifequal milestone.expected_due_date.month 10 %}Oct{% endifequal %}
{%ifequal milestone.expected_due_date.month 11 %}Nov{% endifequal %}
{%ifequal milestone.expected_due_date.month 12 %}Dec{% endifequal %}
{{ milestone.expected_due_date.year }}
{% endifequal %}
</td>
<td>{{ milestone.description|escape }}
</td></tr>
{% endfor %}
</table>
{% endblock wg_content %}
<h2>Milestones
{% if user|has_role:"Area Director,Secretariat" or is_chair %}
<a class="button" href="{% url wg_edit_milestones acronym=wg.acronym %}">Add or edit milestones</a>
{% endif %}
</h2>
{% with wg.milestones as milestones %}{% include "wginfo/milestones.html" %}{% endwith %}
{% if milestones_in_review %}
<p>+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
currently in Area Director review.</p>
{% endif %}
{% endblock wg_content %}

View file

@ -1,174 +0,0 @@
{% extends "wginfo/wg_base.html" %}
{% comment %}
Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the Nokia Corporation and/or its
subsidiary(-ies) nor the names of its contributors may be used
to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endcomment %}
{% load ietf_filters %}
{% block wg_titledetail %}Charter{% endblock %}
{% block wg_content %}
<div class="ietf-box ietf-wg-details">
{% if concluded %}
<span class="ietf-concluded-warning">Note: The data for concluded WGs
is occasionally incorrect.</span>
{% endif %}
<table>
<tr><th colspan="2">Group</th></tr>
<tr valign="top">
<td style="width:14ex;">Name:</td>
<td>{{ wg.name }}</td>
</tr>
<tr><td>Acronym:</td><td>{{ wg.acronym }}</td></tr>
{% if wg.parent %}
<tr><td>Area:</td><td>{{ wg.parent.name }} ({{ wg.parent.acronym }})</td></tr>
{% endif %}
<tr>
<td>State:</td>
<td>{{ wg.state.name }}
{% if requested_close %}
(but in the process of being closed)
{% endif %}
</td>
</tr>
<tr>
<td>Charter:</td>
<td>
{% if wg.charter %}
<a href="{% url doc_view name=wg.charter.name %}">{{ wg.charter.name }}-{{ wg.charter.rev }}</a> ({{ wg.charter.get_state.name }})
{% else %}
none
{% if user|has_role:"Area Director,Secretariat" %}
- <a href="{% url wginfo.edit.submit_initial_charter acronym=wg.acronym %}">Submit Charter</a>
{% endif %}
{% endif %}
</td>
</tr>
<tr><th colspan="2">Personnel</th></tr>
<tr valign="top">
<td>Chair{{ wg.chairs|pluralize }}:</td>
<td>
{% for chair in wg.chairs %}
<a href="mailto:{{ chair.address }}">{{ chair.person.plain_name }} &lt;{{ chair.address }}&gt;</a><br/>
{% endfor %}
</td>
</tr>
<tr><td>Area Director:</td>
<td>
{% if not wg.ad %}?{% else %}
<a href="mailto:{{ wg.areadirector.address }}">{{ wg.ad.plain_name }} &lt;{{ wg.areadirector.address }}&gt;</a>{% endif %}
</td>
</tr>
{% if wg.techadvisors %}
<tr>
<td>Tech Advisor{{ wg.techadvisors|pluralize }}:</td>
<td>
{% for techadvisor in wg.techadvisors %}
<a href="mailto:{{ techadvisor.address }}">{{ techadvisor.person.plain_name }} &lt;{{ techadvisor.address }}&gt;</a><br/>
{% endfor %}
</td>
</tr>
{% endif %}
{% if wg.editors %}
<td>Editor{{ wg.editors|pluralize }}:</td>
<td>
{% for editor in wg.editors %}
<a href="mailto:{{ editor.address }}">{{ editor.person.plain_name }} &lt;{{ editor.address }}&gt;</a><br/>
{% endfor %}
</td></tr>
{% endif %}
{% if wg.secretaries %}
<tr>
<td>Secretar{{ wg.secretaries|pluralize:"y,ies" }}:</td>
<td>
{% for secretary in wg.secretaries %}
<a href="mailto:{{ secretary.address }}">{{ secretary.person.plain_name }} &lt;{{ secretary.address }}&gt;</a><br/>
{% endfor %}
</td>
</tr>
{% endif %}
<tr><th colspan="2">Mailing List</th></tr>
<tr><td>Address:</td><td>{{ wg.email_address|urlize }}</td></tr>
<tr><td>To Subscribe:</td><td>{{ wg.email_subscribe|urlize }}</td></tr>
<tr><td>Archive:</td><td>{{ wg.clean_email_archive|urlize }}</td></tr>
{% if not concluded %}
<tr><th colspan="2">Jabber Chat</th></tr>
<tr><td>Room Address:</td><td><a href="xmpp:{{ wg.acronym }}@jabber.ietf.org">xmpp:{{ wg.acronym }}@jabber.ietf.org</a></td></tr>
<tr><td>Logs:</td><td><a href="http://jabber.ietf.org/logs/{{ wg.acronym }}/">http://jabber.ietf.org/logs/{{ wg.acronym }}/</a></td></tr>
{% endif %}
</table>
{% if user|has_role:"Area Director,Secretariat" %}
<div style="margin: 2px; margin-top: 2em;">
{% for name, url in actions %}
<a href="{{ url }}">{{ name }}</a>
{% if not forloop.last %}|{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% if wg.additional_urls %}
<p>In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at:
{% for url in wg.additional_urls %}
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
{% endif %}
<h2>Description of Working Group</h2>
<p>{{ wg.charter_text|escape|format_charter|safe }}</p>
<h2>Goals and Milestones</h2>
<table>
{% for milestone in wg.milestones %}
<tr>
<td width="80px">
{% if milestone.done %}Done{% else %}{{ milestone.expected_due_date|date:"M Y" }}{% endif %}
</td>
<td>{{ milestone.desc|escape }}
</td></tr>
{% endfor %}
</table>
{% endblock wg_content %}

View file

@ -1,17 +1,18 @@
def find_history_active_at(obj, time):
"""Assumes obj has a corresponding history object (e.g. obj could
be Person with a corresponding PersonHistory model), then returns
the history object active at time, or None if the object itself
was active at the time.
"""Assumes obj has a corresponding history model (e.g. obj could
be Person with a corresponding PersonHistory model), then either
returns the object itself if it was active at time, or the history
object active at time, or None if time predates the object and its
history (assuming history is complete).
For this to work, the history model must use
related_name="history_set" for the foreign key connecting to the
live model, both models must have a "time" DateTimeField and a
history object must be saved with a copy of the old values and
time when the time field changes.
old time when the time field changes.
"""
if obj.time <= time:
return None
return obj
histories = obj.history_set.order_by('-time')
@ -20,3 +21,40 @@ def find_history_active_at(obj, time):
return h
return None
def get_history_object_for(obj):
"""Construct history object for obj, i.e. instantiate history
object, copy relevant attributes and set a link to obj, but don't
save. Any customizations can be done by the caller afterwards.
Many-to-many fields are not copied (impossible without save).
The history model must use related_name="history_set" for the
foreign key connecting to the live model for this function to be
able to discover it."""
history_model = obj.history_set.model
h = history_model()
# copy attributes shared between history and obj
history_field_names = set(f.name for f in history_model._meta.fields)
for field in obj._meta.fields:
if field is not obj._meta.pk and field.name in history_field_names:
setattr(h, field.name, getattr(obj, field.name))
# try setting foreign key to obj
key_name = obj._meta.object_name.lower()
if key_name in history_field_names:
setattr(h, key_name, obj)
# we can't copy many-to-many fields as h isn't saved yet, leave
# that to caller
return h
def copy_many_to_many_for_history(history_obj, obj):
"""Copy basic many-to-many fields from obj to history_obj."""
# copy many to many
for field in obj._meta.many_to_many:
if field.rel.through and field.rel.through._meta.auto_created:
setattr(history_obj, field.name, getattr(obj, field.name).all())

View file

@ -58,6 +58,7 @@ def make_test_data():
state_id="active",
type_id="wg",
parent=area,
list_email="mars-wg@ietf.org",
)
charter = Document.objects.create(
name="charter-ietf-" + group.acronym,
@ -80,6 +81,7 @@ def make_test_data():
state_id="proposed",
type_id="wg",
parent=area,
list_email="ames-wg@ietf.org",
)
charter = Document.objects.create(
name="charter-ietf-" + group.acronym,
@ -163,6 +165,9 @@ def make_test_data():
person=p,
email=email)
mars_wg.ad = ad
mars_wg.save()
# create a bunch of ads for swarm tests
for i in range(1, 10):
u = User.objects.create(username="ad%s" % i)

View file

@ -20,7 +20,7 @@ from workflows.models import Transition
from ietf.doc.models import WriteupDocEvent
from ietf.person.models import Person, Email
from ietf.group.models import Role, RoleName
from ietf.group.models import Group, Role, RoleName
from ietf.group.utils import save_group_in_history
from ietf.name.models import DocTagName
@ -183,7 +183,7 @@ class RemoveDelegateForm(RelatedWGForm):
def save(self):
delegates = self.cleaned_data.get('delete')
save_group_in_history(self.wg)
save_group_in_history(Group.objects.get(pk=self.wg.pk))
WGDelegate.objects.filter(pk__in=delegates).delete()
self.set_message('success', 'Delegates removed')
@ -284,7 +284,7 @@ class AddDelegateForm(RelatedWGForm):
e = Email.objects.get(address=self.cleaned_data.get('email'))
if not Role.objects.filter(name="delegate", group=self.wg, person=person, email=e):
created = True
save_group_in_history(self.wg)
save_group_in_history(Group.objects.get(pk=self.wg.pk))
delegate, _ = Role.objects.get_or_create(
name=RoleName.objects.get(slug="delegate"), group=self.wg, person=e.person, email=e)
else:

View file

@ -9,7 +9,6 @@ from django.core.urlresolvers import reverse as urlreverse
from django.utils.html import strip_tags
from django.utils.text import truncate_words
from ietf.utils.history import find_history_active_at
from ietf.group.models import Group, GroupEvent
from ietf.doc.models import DocEvent

View file

@ -13,7 +13,7 @@ from ietf.doc.models import WriteupDocEvent, DocAlias, BallotPositionDocEvent
from ietf.person.models import Person
from ietf.wgcharter.utils import *
def email_secretariat(request, wg, type, text):
def email_secretariat(request, group, type, text):
to = ["iesg-secretary@ietf.org"]
types = {}
@ -24,16 +24,17 @@ def email_secretariat(request, wg, type, text):
types['state-extrev'] = "State changed to External review"
types['state-iesgrev'] = "State changed to IESG review"
types['state-approved'] = "Charter approved"
types['conclude'] = "Request closing of WG"
types['conclude'] = "Request closing of group"
subject = u"Regarding WG %s: %s" % (wg.acronym, types[type])
subject = u"Regarding %s %s: %s" % (group.type.name, group.acronym, types[type])
text = strip_tags(text)
send_mail(request, to, None, subject,
"wgcharter/email_secretariat.txt",
dict(text=text,
wg_url=settings.IDTRACKER_BASE_URL + urlreverse('wg_charter', kwargs=dict(acronym=wg.acronym)),
charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=wg.charter.name)),
group=group,
group_url=settings.IDTRACKER_BASE_URL + urlreverse('wg_charter', kwargs=dict(acronym=group.acronym)),
charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=group.charter.name)),
)
)
@ -62,8 +63,8 @@ def generate_ballot_writeup(request, doc):
return e
def default_action_text(wg, charter, user):
if next_approved_revision(wg.charter.rev) == "01":
def default_action_text(group, charter, user):
if next_approved_revision(group.charter.rev) == "01":
action = "Formed"
else:
action = "Rechartered"
@ -71,38 +72,38 @@ def default_action_text(wg, charter, user):
e = WriteupDocEvent(doc=charter, by=user)
e.by = user
e.type = "changed_action_announcement"
e.desc = "WG action text was changed"
e.desc = "%s action text was changed" % group.type.name
e.text = render_to_string("wgcharter/action_text.txt",
dict(wg=wg,
dict(group=group,
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
charter_text=read_charter_text(charter),
chairs=wg.role_set.filter(name="chair"),
secr=wg.role_set.filter(name="secr"),
techadv=wg.role_set.filter(name="techadv"),
milestones=wg.groupmilestone_set.all(),
ad_email=wg.ad.role_email("ad") if wg.ad else None,
chairs=group.role_set.filter(name="chair"),
secr=group.role_set.filter(name="secr"),
techadv=group.role_set.filter(name="techadv"),
milestones=group.groupmilestone_set.filter(state="charter"),
ad_email=group.ad.role_email("ad") if group.ad else None,
action_type=action,
))
e.save()
return e
def default_review_text(wg, charter, user):
def default_review_text(group, charter, user):
e = WriteupDocEvent(doc=charter, by=user)
e.by = user
e.type = "changed_review_announcement"
e.desc = "WG review text was changed"
e.desc = "%s review text was changed" % group.type.name
e.text = render_to_string("wgcharter/review_text.txt",
dict(wg=wg,
dict(group=group,
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
charter_text=read_charter_text(charter),
chairs=wg.role_set.filter(name="chair"),
secr=wg.role_set.filter(name="secr"),
techadv=wg.role_set.filter(name="techadv"),
milestones=wg.groupmilestone_set.all(),
ad_email=wg.ad.role_email("ad") if wg.ad else None,
chairs=group.role_set.filter(name="chair"),
secr=group.role_set.filter(name="secr"),
techadv=group.role_set.filter(name="techadv"),
milestones=group.groupmilestone_set.filter(state="charter"),
ad_email=group.ad.role_email("ad") if group.ad else None,
review_date=(datetime.date.today() + datetime.timedelta(weeks=1)).isoformat(),
review_type="new" if wg.state_id == "proposed" else "recharter",
review_type="new" if group.state_id == "proposed" else "recharter",
)
)
e.save()

View file

@ -197,7 +197,7 @@ class EditCharterTestCase(django.test.TestCase):
self.assertEquals(f.read(),
"Windows line\nMac line\nUnix line\n" + utf_8_snippet)
class CharterApproveBallotTestCase(django.test.TestCase):
class ApproveCharterTestCase(django.test.TestCase):
fixtures = ['names']
def setUp(self):
@ -232,6 +232,28 @@ class CharterApproveBallotTestCase(django.test.TestCase):
charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev"))
due_date = datetime.date.today() + datetime.timedelta(days=180)
m1 = GroupMilestone.objects.create(group=group,
state_id="active",
desc="Has been copied",
due=due_date,
resolved="")
m2 = GroupMilestone.objects.create(group=group,
state_id="active",
desc="To be deleted",
due=due_date,
resolved="")
m3 = GroupMilestone.objects.create(group=group,
state_id="charter",
desc="Has been copied",
due=due_date,
resolved="")
m4 = GroupMilestone.objects.create(group=group,
state_id="charter",
desc="New charter milestone",
due=due_date,
resolved="")
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
@ -255,3 +277,8 @@ class CharterApproveBallotTestCase(django.test.TestCase):
self.assertEquals(len(outbox), mailbox_before + 2)
self.assertTrue("WG Action" in outbox[-1]['Subject'])
self.assertTrue("Charter approved" in outbox[-2]['Subject'])
self.assertEquals(group.groupmilestone_set.filter(state="charter").count(), 0)
self.assertEquals(group.groupmilestone_set.filter(state="active").count(), 2)
self.assertEquals(group.groupmilestone_set.filter(state="active", desc=m1.desc).count(), 1)
self.assertEquals(group.groupmilestone_set.filter(state="active", desc=m4.desc).count(), 1)

View file

@ -1,55 +0,0 @@
200 /
200 /wgcharter/
# In IESG process
200 /wgcharter/in_process/
# by AD
# current AD -- needs to be updated at some point
200 /wgcharter/ad/aread.irector/
# former AD
200 /wgcharter/ad/exaread.irector/
404 /wgcharter/ad/no.body/
# create
302 /wgcharter/create/
# WG
200 /wgcharter/mars/
200 /wgcharter/mars/00-00/
200 /wgcharter/mars/_ballot.data
# Edit WG
302 /wgcharter/mars/edit/state/
302 /wgcharter/mars/edit/info/
302 /wgcharter/mars/edit/conclude/
302 /wgcharter/mars/edit/addcomment/
302 /wgcharter/mars/edit/action/
302 /wgcharter/mars/edit/review/
# ballots
302 /wgcharter/mars/edit/position/
302 /wgcharter/mars/edit/sendballotcomment/
302 /wgcharter/mars/edit/approveballot/
# submission of charters
302 /wgcharter/mars/submit/
# search
200 /wgcharter/search/
302 /wgcharter/search/?name=martian
200 /wgcharter/search/?name=something
200 /wgcharter/search/?name=something&by=acronym&acronym=some
200 /wgcharter/search/?name=something&by=state&state=active&charter_state=
200 /wgcharter/search/?name=something&by=state&state=&charter_state=approved
200 /wgcharter/search/?name=something&by=ad&ad=1
200 /wgcharter/search/?name=something&by=area&area=2
200 /wgcharter/search/?name=something&by=anyfield&anyfield=something
200 /wgcharter/search/?name=something&by=eacronym&eacronym=someold
# searchPerson (ajax)
200 /wgcharter/searchPerson/
#

View file

@ -12,5 +12,6 @@ urlpatterns = patterns('',
url(r'^ballotwriteupnotes/$', "ietf.wgcharter.views.ballot_writeupnotes"),
url(r'^approve/$', "ietf.wgcharter.views.approve", name='charter_approve'),
url(r'^submit/$', "ietf.wgcharter.views.submit", name='charter_submit'),
url(r'^submit/(?P<option>initcharter|recharter)/$', "ietf.wgcharter.views.submit", name='charter_submit'),
url(r'^submit/(?P<option>initcharter|recharter)/$', "ietf.wgcharter.views.submit", name='charter_submit'), # shouldn't be here
url(r'^withmilestones-(?P<rev>[0-9-]+).txt$', "ietf.wgcharter.views.charter_with_milestones_txt", name='charter_with_milestones_txt'),
)

View file

@ -4,7 +4,6 @@ from django.conf import settings
from ietf.group.models import GroupEvent, ChangeStateGroupEvent
from ietf.doc.models import Document, DocAlias, DocHistory, RelatedDocument, DocumentAuthor, DocEvent
from ietf.utils.history import find_history_active_at
def log_state_changed(request, doc, by, prev_state):
e = DocEvent(doc=doc, by=by)

View file

@ -1,6 +1,6 @@
import re, os, string, datetime, shutil
import re, os, string, datetime, shutil, textwrap
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse as urlreverse
@ -14,8 +14,9 @@ from django.utils.safestring import mark_safe
from django.conf import settings
from django.contrib import messages
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.utils.mail import send_mail_preformatted
from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.utils.history import find_history_active_at
from ietf.ietfauth.decorators import has_role, role_required
from ietf.iesg.models import TelechatDate
from ietf.doc.models import *
@ -23,7 +24,7 @@ from ietf.doc.utils import *
from ietf.name.models import *
from ietf.person.models import *
from ietf.group.models import *
from ietf.group.utils import save_group_in_history
from ietf.group.utils import save_group_in_history, save_milestone_in_history
from ietf.wgcharter.mails import *
from ietf.wgcharter.utils import *
@ -44,10 +45,10 @@ class ChangeStateForm(forms.Form):
@role_required("Area Director", "Secretariat")
def change_state(request, name, option=None):
"""Change state of WG and charter, notifying parties as necessary
and logging the change as a comment."""
"""Change state of charter, notifying parties as necessary and
logging the change as a comment."""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
group = charter.group
chartering_type = get_chartering_type(charter)
@ -75,17 +76,17 @@ def change_state(request, name, option=None):
if "-" not in charter_rev:
charter_rev = charter_rev + "-00"
elif option == "abandon":
oldstate = wg.state_id
if oldstate in ("proposed","bof","unknown"):
oldstate = group.state
if oldstate.slug in ("proposed", "bof", "unknown"):
charter_state = State.objects.get(used=True, type="charter", slug="notrev")
#TODO : set an abandoned state and leave some comments here
wg.state=GroupStateName.objects.get(slug='abandon')
wg.save()
e = ChangeStateGroupEvent(group=wg, type="changed_state")
e.time = wg.time
group.state = GroupStateName.objects.get(slug='abandon')
group.save()
e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = group.time
e.by = login
e.state_id = clean["state"].slug
e.desc = "Group state changed to %s from %s" % clean["state"].name,oldstate
e.state_id = group.state.slug
e.desc = "Group state changed to %s from %s" % (group.state, oldstate)
e.save()
else:
@ -125,7 +126,7 @@ def change_state(request, name, option=None):
charter.save()
if message:
email_secretariat(request, wg, "state-%s" % charter_state.slug, message)
email_secretariat(request, group, "state-%s" % charter_state.slug, message)
email_state_changed(request, charter, "State changed to %s." % charter_state)
@ -134,8 +135,8 @@ def change_state(request, name, option=None):
create_ballot_if_not_open(charter, login, "r-wo-ext")
else:
create_ballot_if_not_open(charter, login, "r-extrev")
default_review_text(wg, charter, login)
default_action_text(wg, charter, login)
default_review_text(group, charter, login)
default_action_text(group, charter, login)
elif charter_state.slug == "iesgrev":
create_ballot_if_not_open(charter, login, "approve")
@ -152,10 +153,10 @@ def change_state(request, name, option=None):
init = dict()
elif option == "initcharter":
hide = ['charter_state']
init = dict(initial_time=1, message='%s has initiated chartering of the proposed WG:\n "%s" (%s).' % (login.plain_name(), wg.name, wg.acronym))
init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
elif option == "abandon":
hide = ['initial_time', 'charter_state']
init = dict(message='%s has abandoned the chartering effort on the WG:\n "%s" (%s).' % (login.plain_name(), wg.name, wg.acronym))
init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
else:
hide = ['initial_time']
s = charter.get_state()
@ -168,27 +169,27 @@ def change_state(request, name, option=None):
prev_charter_state = charter_hists[0].get_state()
title = {
"initcharter": "Initiate chartering of WG %s" % wg.acronym,
"recharter": "Recharter WG %s" % wg.acronym,
"abandon": "Abandon effort on WG %s" % wg.acronym,
"initcharter": "Initiate chartering of %s %s" % (group.acronym, group.type.name),
"recharter": "Recharter %s %s" % (group.acronym, group.type.name),
"abandon": "Abandon effort on %s %s" % (group.acronym, group.type.name),
}.get(option)
if not title:
title = "Change chartering state of WG %s" % wg.acronym
title = "Change chartering state of %s %s" % (group.acronym, group.type.name)
def state_pk(slug):
return State.objects.get(used=True, type="charter", slug=slug).pk
info_msg = {
state_pk("infrev"): 'The WG "%s" (%s) has been set to Informal IESG review by %s.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("intrev"): 'The WG "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("extrev"): 'The WG "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (wg.name, wg.acronym, login.plain_name()),
state_pk("infrev"): 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name()),
state_pk("intrev"): 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name()),
state_pk("extrev"): 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name()),
}
states_for_ballot_wo_extern = State.objects.filter(used=True, type="charter", slug="intrev").values_list("pk", flat=True)
return render_to_response('wgcharter/change_state.html',
dict(form=form,
doc=wg.charter,
doc=group.charter,
login=login,
option=option,
prev_charter_state=prev_charter_state,
@ -340,8 +341,8 @@ class UploadForm(forms.Form):
def clean_txt(self):
return get_cleaned_text_file_content(self.cleaned_data["txt"])
def save(self, wg, rev):
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (wg.charter.canonical_name(), rev))
def save(self, group, rev):
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (group.charter.canonical_name(), rev))
with open(filename, 'wb') as destination:
if self.cleaned_data['txt']:
destination.write(self.cleaned_data['txt'])
@ -356,7 +357,7 @@ def submit(request, name=None, acronym=None, option=None):
elif acronym:
name = "charter-ietf-" + acronym
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
group = charter.group
login = request.user.get_profile()
@ -378,7 +379,7 @@ def submit(request, name=None, acronym=None, option=None):
if form.is_valid():
save_document_in_history(charter)
# Also save group history so we can search for it
save_group_in_history(wg)
save_group_in_history(group)
charter.rev = next_rev
@ -388,7 +389,7 @@ def submit(request, name=None, acronym=None, option=None):
e.save()
# Save file on disk
form.save(wg, charter.rev)
form.save(group, charter.rev)
charter.time = datetime.datetime.now()
charter.save()
@ -419,7 +420,7 @@ def submit(request, name=None, acronym=None, option=None):
return render_to_response('wgcharter/submit.html',
{'form': form,
'next_rev': next_rev,
'wg': wg },
'group': group },
context_instance=RequestContext(request))
class AnnouncementTextForm(forms.Form):
@ -432,19 +433,20 @@ class AnnouncementTextForm(forms.Form):
def announcement_text(request, name, ann):
"""Editing of announcement text"""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
group = charter.group
login = request.user.get_profile()
if ann == "action":
existing = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
elif ann == "review":
existing = charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
if ann in ("action", "review"):
existing = charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann)
if not existing:
if ann == "action":
existing = default_action_text(wg, charter, login)
existing = default_action_text(group, charter, login)
elif ann == "review":
existing = default_review_text(wg, charter, login)
existing = default_review_text(group, charter, login)
if not existing:
raise Http404
form = AnnouncementTextForm(initial=dict(announcement_text=existing.text))
@ -456,7 +458,7 @@ def announcement_text(request, name, ann):
e = WriteupDocEvent(doc=charter, by=login)
e.by = login
e.type = "changed_%s_announcement" % ann
e.desc = "WG %s text was changed" % ann
e.desc = "%s %s text was changed" % (group.type.name, ann)
e.text = t
e.save()
@ -470,20 +472,14 @@ def announcement_text(request, name, ann):
if "regenerate_text" in request.POST:
if ann == "action":
e = default_action_text(wg, charter, login)
e = default_action_text(group, charter, login)
elif ann == "review":
e = default_review_text(wg, charter, login)
e = default_review_text(group, charter, login)
# make sure form has the updated text
form = AnnouncementTextForm(initial=dict(announcement_text=e.text))
if "send_text" in request.POST and form.is_valid():
msg = form.cleaned_data['announcement_text']
import email
parsed_msg = email.message_from_string(msg.encode("utf-8"))
send_mail_text(request, parsed_msg["To"],
parsed_msg["From"], parsed_msg["Subject"],
parsed_msg.get_payload())
send_mail_preformatted(request, form.cleaned_data['announcement_text'])
messages.success(request, "The email To: '%s' with Subjet: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
return redirect('doc_writeup', name=charter.name)
@ -505,7 +501,6 @@ class BallotWriteupForm(forms.Form):
def ballot_writeupnotes(request, name):
"""Editing of ballot write-up and notes"""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
ballot = charter.latest_event(BallotDocEvent, type="created_ballot")
if not ballot:
@ -573,13 +568,13 @@ def ballot_writeupnotes(request, name):
def approve(request, name):
"""Approve charter, changing state, fixing revision, copying file to final location."""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
group = charter.group
login = request.user.get_profile()
e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
if not e:
announcement = default_action_text(wg, charter, login).text
announcement = default_action_text(group, charter, login).text
else:
announcement = e.text
@ -601,23 +596,22 @@ def approve(request, name):
change_description = e.desc
new_state = GroupStateName.objects.get(slug="active")
if wg.state != new_state:
# update history with current state
save_group_in_history(wg)
# change wg state and save the wg
prev_state = wg.state
wg.state = new_state
wg.time = e.time
wg.save()
if group.state != new_state:
save_group_in_history(group)
prev_state = group.state
group.state = new_state
group.time = e.time
group.save()
# create an event for the wg state change, too
e = ChangeStateGroupEvent(group=wg, type="changed_state")
e.time = wg.time
e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = group.time
e.by = login
e.state_id = "active"
e.desc = "Charter approved, group active"
e.save()
# update the change description for the email
change_description += " and WG state has been changed to %s" % new_state.name
change_description += " and %s state has been changed to %s" % (group.type.name, new_state.name)
e = log_state_changed(request, charter, login, prev_charter_state)
@ -627,7 +621,9 @@ def approve(request, name):
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
shutil.copy(old, new)
except IOError:
raise Http404("Charter text %s" % filename)
return HttpResponse("There was an error copying %s to %s" %
('%s-%s.txt' % (charter.canonical_name(), charter.rev),
'%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev))))
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
e.rev = next_approved_revision(charter.rev)
@ -638,7 +634,57 @@ def approve(request, name):
charter.time = e.time
charter.save()
email_secretariat(request, wg, "state-%s" % new_charter_state.slug, change_description)
email_secretariat(request, group, "state-%s" % new_charter_state.slug, change_description)
# move milestones over
milestones_to_delete = list(group.groupmilestone_set.filter(state__in=("active", "review")))
for m in group.groupmilestone_set.filter(state="charter"):
# see if we got this milestone already (i.e. it was copied
# verbatim to the charter)
found = False
for i, o in enumerate(milestones_to_delete):
if o.desc == m.desc and o.due == m.due and set(o.docs.all()) == set(m.docs.all()):
found = True
break
if found:
# keep existing, whack charter milestone
if not o.state_id == "active":
save_milestone_in_history(o)
o.state_id = "active"
o.save()
MilestoneGroupEvent.objects.create(
group=group, type="changed_milestone", by=login,
desc="Changed milestone \"%s\", set state to active from review" % o.desc,
milestone=o)
del milestones_to_delete[i]
# don't generate a DocEvent for this, it's implicit in the approval event
save_milestone_in_history(m)
m.state_id = "deleted"
m.save()
else:
# move charter milestone
save_milestone_in_history(m)
m.state_id = "active"
m.save()
MilestoneGroupEvent.objects.create(
group=group, type="changed_milestone", by=login,
desc="Added milestone \"%s\", due %s, from approved charter" % (m.desc, m.due),
milestone=m)
for m in milestones_to_delete:
save_milestone_in_history(m)
m.state_id = "deleted"
m.save()
MilestoneGroupEvent.objects.create(
group=group, type="changed_milestone", by=login,
desc="Deleted milestone \"%s\", not present in approved charter" % m.desc,
milestone=m)
# send announcement
send_mail_preformatted(request, announcement)
@ -647,7 +693,61 @@ def approve(request, name):
return render_to_response('wgcharter/approve.html',
dict(charter=charter,
announcement=announcement,
wg=wg),
announcement=announcement),
context_instance=RequestContext(request))
def charter_with_milestones_txt(request, name, rev):
charter = get_object_or_404(Document, type="charter", docalias__name=name)
revision_event = charter.latest_event(NewRevisionDocEvent, type="new_revision", rev=rev)
if not revision_event:
return HttpResponseNotFound("Revision %s not found in database" % rev)
# read charter text
c = find_history_active_at(charter, revision_event.time) or charter
filename = '%s-%s.txt' % (c.canonical_name(), rev)
charter_text = ""
try:
with open(os.path.join(settings.CHARTER_PATH, filename), 'r') as f:
charter_text = f.read()
except IOError:
charter_text = "Error reading charter text %s" % filename
# find milestones
chartering = "-" in rev
if chartering:
need_state = "charter"
else:
need_state = "active"
# slight complication - we can assign milestones to a revision up
# until the point where the next superseding revision is
# published, so that time shall be our limit
e = charter.docevent_set.filter(time__gt=revision_event.time, type="new_revision").order_by("time")
if not chartering:
e = e.exclude(newrevisiondocevent__rev__contains="-")
if e:
# subtract a margen of error
just_before_next_rev = e[0].time - datetime.timedelta(seconds=5)
else:
just_before_next_rev = datetime.datetime.now()
wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent=" " * 11, width=80, break_long_words=False)
milestones = []
for m in charter.chartered_group.groupmilestone_set.all():
mh = find_history_active_at(m, just_before_next_rev)
if mh and mh.state_id == need_state:
mh.desc_filled = wrapper.fill(mh.desc)
milestones.append(mh)
return render_to_response('wgcharter/charter_with_milestones.txt',
dict(charter_text=charter_text,
milestones=milestones),
context_instance=RequestContext(request),
mimetype="text/plain")

129
ietf/wginfo/mails.py Normal file
View file

@ -0,0 +1,129 @@
# generation of mails
import textwrap, datetime, re
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.utils.text import wrap
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import send_mail, send_mail_text
from ietf.group.models import *
def email_milestones_changed(request, group, changes):
def wrap_up_email(to, text):
text = wrap(strip_tags(text), 70)
text += "\n\n"
text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + urlreverse("wg_charter", kwargs=dict(acronym=group.acronym)))
send_mail_text(request, to, None,
u"Milestones changed for %s %s" % (group.acronym, group.type.name),
text)
# first send to AD and chairs
to = []
if group.ad:
to.append(group.ad.role_email("ad").formatted_email())
for r in group.role_set.filter(name="chair"):
to.append(r.formatted_email())
if to:
wrap_up_email(to, u"\n\n".join(c + "." for c in changes))
# then send to WG
if group.list_email:
review_re = re.compile("Added .* for review, due")
to = [ group.list_email ]
wrap_up_email(to, u"\n\n".join(c + "." for c in changes if not review_re.match(c)))
def email_milestone_review_reminder(group, grace_period=7):
"""Email reminders about milestones needing review to AD."""
if not group.ad:
return False
to = [group.ad.role_email("ad").formatted_email()]
cc = [r.formatted_email() for r in group.role_set.filter(name="chair")]
now = datetime.datetime.now()
too_early = True
milestones = group.groupmilestone_set.filter(state="review")
for m in milestones:
e = m.milestonegroupevent_set.filter(type="changed_milestone").order_by("-time")[:1]
m.days_ready = (now - e[0].time).days if e else None
if m.days_ready == None or m.days_ready >= grace_period:
too_early = False
if too_early:
return False
subject = u"Reminder: Milestone%s needing review in %s %s" % ("s" if len(milestones) > 1 else "", group.acronym, group.type.name)
send_mail(None, to, None,
subject,
"wginfo/reminder_milestones_need_review.txt",
dict(group=group,
milestones=milestones,
url=settings.IDTRACKER_BASE_URL + urlreverse("wg_edit_milestones", kwargs=dict(acronym=group.acronym))
))
return True
def groups_with_milestones_needing_review():
return Group.objects.filter(groupmilestone__state="review").distinct()
def email_milestones_due(group, early_warning_days):
to = [r.formatted_email() for r in group.role_set.filter(name="chair")]
today = datetime.date.today()
early_warning = today + datetime.timedelta(days=early_warning_days)
milestones = group.groupmilestone_set.filter(due__in=[today, early_warning],
resolved="", state="active")
subject = u"Reminder: Milestone%s are soon due in %s %s" % ("s" if len(milestones) > 1 else "", group.acronym, group.type.name)
send_mail(None, to, None,
subject,
"wginfo/reminder_milestones_due.txt",
dict(group=group,
milestones=milestones,
today=today,
early_warning_days=early_warning_days,
url=settings.IDTRACKER_BASE_URL + urlreverse("wg_charter", kwargs=dict(acronym=group.acronym))
))
def groups_needing_milestones_due_reminder(early_warning_days):
"""Return groups having milestones that are either
early_warning_days from being due or are due today."""
today = datetime.date.today()
return Group.objects.filter(state="active", groupmilestone__due__in=[today, today + datetime.timedelta(days=early_warning_days)], groupmilestone__resolved="", groupmilestone__state="active").distinct()
def email_milestones_overdue(group):
to = [r.formatted_email() for r in group.role_set.filter(name="chair")]
today = datetime.date.today()
milestones = group.groupmilestone_set.filter(due__lt=today, resolved="", state="active")
for m in milestones:
m.months_overdue = (today - m.due).days // 30
subject = u"Reminder: Milestone%s overdue in %s %s" % ("s" if len(milestones) > 1 else "", group.acronym, group.type.name)
send_mail(None, to, None,
subject,
"wginfo/reminder_milestones_overdue.txt",
dict(group=group,
milestones=milestones,
url=settings.IDTRACKER_BASE_URL + urlreverse("wg_charter", kwargs=dict(acronym=group.acronym))
))
def groups_needing_milestones_overdue_reminder(grace_period=30):
cut_off = datetime.date.today() - datetime.timedelta(days=grace_period)
return Group.objects.filter(state="active", groupmilestone__due__lt=cut_off, groupmilestone__resolved="", groupmilestone__state="active").distinct()

401
ietf/wginfo/milestones.py Normal file
View file

@ -0,0 +1,401 @@
# WG milestone editing views
import re, os, string, datetime, shutil, calendar
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse
from django.template import RequestContext
from django import forms
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
from django.utils import simplejson
from django.utils.html import mark_safe, escape
from django.utils.functional import lazy
from django.core.urlresolvers import reverse as urlreverse
from ietf.ietfauth.decorators import role_required, has_role
from ietf.doc.models import Document, DocEvent
from ietf.doc.utils import get_chartering_type
from ietf.group.models import *
from ietf.group.utils import save_group_in_history, save_milestone_in_history
from ietf.wginfo.mails import email_milestones_changed
def json_doc_names(docs):
return simplejson.dumps([{"id": doc.pk, "name": doc.name } for doc in docs])
def parse_doc_names(s):
return Document.objects.filter(pk__in=[x.strip() for x in s.split(",") if x.strip()], type="draft")
class MilestoneForm(forms.Form):
id = forms.IntegerField(required=True, widget=forms.HiddenInput)
desc = forms.CharField(max_length=500, label="Milestone", required=True)
due_month = forms.TypedChoiceField(choices=(), required=True, coerce=int)
due_year = forms.TypedChoiceField(choices=(), required=True, coerce=int)
resolved_checkbox = forms.BooleanField(required=False, label="Resolved")
resolved = forms.CharField(max_length=50, required=False)
delete = forms.BooleanField(required=False, initial=False)
docs = forms.CharField(max_length=10000, required=False)
accept = forms.ChoiceField(choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")),
required=False, initial="noaction", widget=forms.RadioSelect)
def __init__(self, *args, **kwargs):
m = self.milestone = kwargs.pop("instance", None)
self.needs_review = kwargs.pop("needs_review", False)
can_review = not self.needs_review
if m:
self.needs_review = m.state_id == "review"
if not "initial" in kwargs:
kwargs["initial"] = {}
kwargs["initial"].update(dict(id=m.pk,
desc=m.desc,
due_month=m.due.month,
due_year=m.due.year,
resolved_checkbox="on" if m.resolved else False,
resolved=m.resolved,
docs=",".join(m.docs.values_list("pk", flat=True)),
delete=False,
accept="noaction" if can_review and self.needs_review else None,
))
kwargs["prefix"] = "m%s" % m.pk
super(MilestoneForm, self).__init__(*args, **kwargs)
# set choices for due date
this_year = datetime.date.today().year
self.fields["due_month"].choices = [(m, datetime.date(this_year, m, 1).strftime("%B")) for m in range(1, 13)]
years = [ y for y in range(this_year, this_year + 10)]
initial = self.initial.get("due_year")
if initial and initial not in years:
years.insert(0, initial)
self.fields["due_year"].choices = zip(years, map(str, years))
# figure out what to prepopulate many-to-many field with
pre = ""
if not self.is_bound:
pre = self.initial.get("docs", "")
else:
pre = self["docs"].data or ""
# this is ugly, but putting it on self["docs"] is buggy with a
# bound/unbound form in Django 1.2
self.docs_names = parse_doc_names(pre)
self.docs_prepopulate = json_doc_names(self.docs_names)
# calculate whether we've changed
self.changed = self.is_bound and (not self.milestone or any(str(self[f].data) != str(self.initial[f]) for f in self.fields.iterkeys()))
def clean_docs(self):
s = self.cleaned_data["docs"]
return Document.objects.filter(pk__in=[x.strip() for x in s.split(",") if x.strip()], type="draft")
def clean_resolved(self):
r = self.cleaned_data["resolved"].strip()
if self.cleaned_data["resolved_checkbox"]:
if not r:
raise forms.ValidationError('Please provide explanation (like "Done") for why the milestone is no longer due.')
else:
r = ""
return r
@role_required('WG Chair', 'Area Director', 'Secretariat')
def edit_milestones(request, acronym, milestone_set="current"):
# milestones_set + needs_review: we have several paths into this view
# AD/Secr. -> all actions on current + add new
# group chair -> limited actions on current + add new for review
# (re)charter -> all actions on existing in state charter + add new in state charter
#
# For charters we store the history on the charter document to not confuse people.
login = request.user.get_profile()
group = get_object_or_404(Group, acronym=acronym)
needs_review = False
if not has_role(request.user, ("Area Director", "Secretariat")):
if group.role_set.filter(name="chair", person=login):
if milestone_set == "current":
needs_review = True
else:
return HttpResponseForbidden("You are not chair of this group.")
if milestone_set == "current":
title = "Edit milestones for %s %s" % (group.acronym, group.type.name)
milestones = group.groupmilestone_set.filter(state__in=("active", "review"))
elif milestone_set == "charter":
title = "Edit charter milestones for %s %s" % (group.acronym, group.type.name)
milestones = group.groupmilestone_set.filter(state="charter")
forms = []
milestones_dict = dict((str(m.id), m) for m in milestones)
def due_month_year_to_date(c):
y = c["due_year"]
m = c["due_month"]
return datetime.date(y, m, calendar.monthrange(y, m)[1])
def set_attributes_from_form(f, m):
c = f.cleaned_data
m.group = group
if milestone_set == "current":
if needs_review:
m.state = GroupMilestoneStateName.objects.get(slug="review")
else:
m.state = GroupMilestoneStateName.objects.get(slug="active")
elif milestone_set == "charter":
m.state = GroupMilestoneStateName.objects.get(slug="charter")
m.desc = c["desc"]
m.due = due_month_year_to_date(c)
m.resolved = c["resolved"]
def save_milestone_form(f):
c = f.cleaned_data
if f.milestone:
m = f.milestone
named_milestone = 'milestone "%s"' % m.desc
if milestone_set == "charter":
named_milestone = "charter " + named_milestone
if c["delete"]:
save_milestone_in_history(m)
m.state_id = "deleted"
m.save()
return 'Deleted %s' % named_milestone
# compute changes
history = None
changes = ['Changed %s' % named_milestone]
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
if not history:
history = save_milestone_in_history(m)
if c["accept"] == "accept":
m.state_id = "active"
changes.append("set state to active from review, accepting new milestone")
elif c["accept"] == "reject":
m.state_id = "deleted"
changes.append("set state to deleted from review, rejecting new milestone")
if c["desc"] != m.desc and not needs_review:
if not history:
history = save_milestone_in_history(m)
m.desc = c["desc"]
changes.append('set description to "%s"' % m.desc)
c_due = due_month_year_to_date(c)
if c_due != m.due:
if not history:
history = save_milestone_in_history(m)
changes.append('set due date to %s from %s' % (c_due.strftime("%B %Y"), m.due.strftime("%B %Y")))
m.due = c_due
resolved = c["resolved"]
if resolved != m.resolved:
if resolved and not m.resolved:
changes.append('resolved as "%s"' % resolved)
elif not resolved and m.resolved:
changes.append("reverted to not being resolved")
elif resolved and m.resolved:
changes.append('set resolution to "%s"' % resolved)
if not history:
history = save_milestone_in_history(m)
m.resolved = resolved
new_docs = set(c["docs"])
old_docs = set(m.docs.all())
if new_docs != old_docs:
added = new_docs - old_docs
if added:
changes.append('added %s to milestone' % ", ".join(d.name for d in added))
removed = old_docs - new_docs
if removed:
changes.append('removed %s from milestone' % ", ".join(d.name for d in removed))
if not history:
history = save_milestone_in_history(m)
m.docs = new_docs
if len(changes) > 1:
m.save()
return ", ".join(changes)
else: # new milestone
m = f.milestone = GroupMilestone()
set_attributes_from_form(f, m)
m.save()
m.docs = c["docs"]
named_milestone = 'milestone "%s"' % m.desc
if milestone_set == "charter":
named_milestone = "charter " + named_milestone
if m.state_id in ("active", "charter"):
return 'Added %s, due %s' % (named_milestone, m.due.strftime("%B %Y"))
elif m.state_id == "review":
return 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%B %Y"))
finished_milestone_text = "Done"
form_errors = False
if request.method == 'POST':
# parse out individual milestone forms
for prefix in request.POST.getlist("prefix"):
if not prefix: # empty form
continue
# new milestones have non-existing ids so instance end up as None
instance = milestones_dict.get(request.POST.get(prefix + "-id", ""), None)
f = MilestoneForm(request.POST, prefix=prefix, instance=instance,
needs_review=needs_review)
forms.append(f)
form_errors = form_errors or not f.is_valid()
action = request.POST.get("action", "review")
if action == "review":
for f in forms:
if not f.is_valid():
continue
# let's fill in the form milestone so we can output it in the template
if not f.milestone:
f.milestone = GroupMilestone()
set_attributes_from_form(f, f.milestone)
elif action == "save" and not form_errors:
changes = []
for f in forms:
change = save_milestone_form(f)
if not change:
continue
if milestone_set == "charter":
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
by=login, desc=change)
else:
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
by=login, desc=change, milestone=f.milestone)
changes.append(change)
if milestone_set == "current":
email_milestones_changed(request, group, changes)
if milestone_set == "charter":
return redirect('doc_view', name=group.charter.canonical_name())
else:
return redirect('wg_charter', acronym=group.acronym)
else:
for m in milestones:
forms.append(MilestoneForm(instance=m, needs_review=needs_review))
can_reset = milestone_set == "charter" and get_chartering_type(group.charter) == "rechartering"
empty_form = MilestoneForm(needs_review=needs_review)
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
return render_to_response('wginfo/edit_milestones.html',
dict(group=group,
title=title,
forms=forms,
form_errors=form_errors,
empty_form=empty_form,
milestone_set=milestone_set,
finished_milestone_text=finished_milestone_text,
needs_review=needs_review,
can_reset=can_reset),
context_instance=RequestContext(request))
@role_required('WG Chair', 'Area Director', 'Secretariat')
def reset_charter_milestones(request, acronym):
"""Reset charter milestones to the currently in-use milestones."""
login = request.user.get_profile()
group = get_object_or_404(Group, acronym=acronym)
if (not has_role(request.user, ("Area Director", "Secretariat")) and
not group.role_set.filter(name="chair", person=login)):
return HttpResponseForbidden("You are not chair of this group.")
current_milestones = group.groupmilestone_set.filter(state="active")
charter_milestones = group.groupmilestone_set.filter(state="charter")
if request.method == 'POST':
try:
milestone_ids = [int(v) for v in request.POST.getlist("milestone")]
except ValueError as e:
return HttpResponseBadRequest("error in list of ids - %s" % e)
# delete existing
for m in charter_milestones:
save_milestone_in_history(m)
m.state_id = "deleted"
m.save()
DocEvent.objects.create(type="changed_charter_milestone",
doc=group.charter,
desc='Deleted milestone "%s"' % m.desc,
by=login,
)
# add current
for m in current_milestones.filter(id__in=milestone_ids):
new = GroupMilestone.objects.create(group=m.group,
state_id="charter",
desc=m.desc,
due=m.due,
resolved=m.resolved,
)
new.docs = m.docs.all()
DocEvent.objects.create(type="changed_charter_milestone",
doc=group.charter,
desc='Added milestone "%s", due %s, from current group milestones' % (new.desc, new.due.strftime("%B %Y")),
by=login,
)
return redirect('wg_edit_charter_milestones', acronym=group.acronym)
return render_to_response('wginfo/reset_charter_milestones.html',
dict(group=group,
charter_milestones=charter_milestones,
current_milestones=current_milestones,
),
context_instance=RequestContext(request))
def ajax_search_docs(request, acronym):
docs = Document.objects.filter(name__icontains=request.GET.get('q',''), type="draft").order_by('name').distinct()[:20]
return HttpResponse(json_doc_names(docs), mimetype='application/json')

View file

@ -30,7 +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 os, unittest, shutil
import os, unittest, shutil, calendar
import django.test
from django.conf import settings
@ -48,6 +48,7 @@ from ietf.group.models import *
from ietf.group.utils import *
from ietf.name.models import *
from ietf.person.models import *
from ietf.wginfo.mails import *
class WgInfoUrlTestCase(SimpleUrlTestCase):
@ -262,3 +263,409 @@ class WgEditTestCase(django.test.TestCase):
# the WG remains active until the Secretariat takes action
group = Group.objects.get(acronym=group.acronym)
self.assertEquals(group.state_id, "active")
class MilestoneTestCase(django.test.TestCase):
fixtures = ["names"]
def create_test_milestones(self):
draft = make_test_data()
group = Group.objects.get(acronym="mars")
m1 = GroupMilestone.objects.create(group=group,
desc="Test 1",
due=datetime.date.today(),
resolved="",
state_id="active")
m1.docs = [draft]
m2 = GroupMilestone.objects.create(group=group,
desc="Test 2",
due=datetime.date.today(),
resolved="",
state_id="charter")
m2.docs = [draft]
return (m1, m2, group)
def last_day_of_month(self, d):
return datetime.date(d.year, d.month, calendar.monthrange(d.year, d.month)[1])
def test_milestone_sets(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
self.assertTrue(m1.desc in r.content)
self.assertTrue(m2.desc not in r.content)
url = urlreverse('wg_edit_charter_milestones', kwargs=dict(acronym=group.acronym))
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
self.assertTrue(m1.desc not in r.content)
self.assertTrue(m2.desc in r.content)
def test_add_milestone(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
milestones_before = GroupMilestone.objects.count()
events_before = group.groupevent_set.count()
docs = Document.objects.filter(type="draft").values_list("name", flat=True)
due = self.last_day_of_month(datetime.date.today() + datetime.timedelta(days=365))
# faulty post
r = self.client.post(url, { 'prefix': "m-1",
'm-1-id': "-1",
'm-1-desc': "", # no description
'm-1-due_month': str(due.month),
'm-1-due_year': str(due.year),
'm-1-resolved': "",
'm-1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEquals(GroupMilestone.objects.count(), milestones_before)
# add
r = self.client.post(url, { 'prefix': "m-1",
'm-1-id': "-1",
'm-1-desc': "Test 3",
'm-1-due_month': str(due.month),
'm-1-due_year': str(due.year),
'm-1-resolved': "",
'm-1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before + 1)
self.assertEquals(group.groupevent_set.count(), events_before + 1)
m = GroupMilestone.objects.get(desc="Test 3")
self.assertEquals(m.state_id, "active")
self.assertEquals(m.due, due)
self.assertEquals(m.resolved, "")
self.assertEquals(set(m.docs.values_list("name", flat=True)), set(docs))
self.assertTrue("Added milestone" in m.milestonegroupevent_set.all()[0].desc)
def test_add_milestone_as_chair(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "marschairman", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
milestones_before = GroupMilestone.objects.count()
events_before = group.groupevent_set.count()
due = self.last_day_of_month(datetime.date.today() + datetime.timedelta(days=365))
# add
r = self.client.post(url, { 'prefix': "m-1",
'm-1-id': -1,
'm-1-desc': "Test 3",
'm-1-due_month': str(due.month),
'm-1-due_year': str(due.year),
'm-1-resolved': "",
'm-1-docs': "",
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before + 1)
m = GroupMilestone.objects.get(desc="Test 3")
self.assertEquals(m.state_id, "review")
self.assertEquals(group.groupevent_set.count(), events_before + 1)
self.assertTrue("for review" in m.milestonegroupevent_set.all()[0].desc)
def test_accept_milestone(self):
m1, m2, group = self.create_test_milestones()
m1.state_id = "review"
m1.save()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "ad", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
events_before = group.groupevent_set.count()
due = self.last_day_of_month(datetime.date.today() + datetime.timedelta(days=365))
# add
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': m1.desc,
'm1-due_month': str(m1.due.month),
'm1-due_year': str(m1.due.year),
'm1-resolved': m1.resolved,
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
'm1-accept': "accept",
'action': "save",
})
print r.content
self.assertEquals(r.status_code, 302)
m = GroupMilestone.objects.get(pk=m1.pk)
self.assertEquals(m.state_id, "active")
self.assertEquals(group.groupevent_set.count(), events_before + 1)
self.assertTrue("to active from review" in m.milestonegroupevent_set.all()[0].desc)
def test_delete_milestone(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
milestones_before = GroupMilestone.objects.count()
events_before = group.groupevent_set.count()
# delete
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': m1.desc,
'm1-due_month': str(m1.due.month),
'm1-due_year': str(m1.due.year),
'm1-resolved': "",
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
'm1-delete': "checked",
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before)
self.assertEquals(group.groupevent_set.count(), events_before + 1)
m = GroupMilestone.objects.get(pk=m1.pk)
self.assertEquals(m.state_id, "deleted")
self.assertTrue("Deleted milestone" in m.milestonegroupevent_set.all()[0].desc)
def test_edit_milestone(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
milestones_before = GroupMilestone.objects.count()
events_before = group.groupevent_set.count()
docs = Document.objects.filter(type="draft").values_list("name", flat=True)
due = self.last_day_of_month(datetime.date.today() + datetime.timedelta(days=365))
# faulty post
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': "", # no description
'm1-due_month': str(due.month),
'm1-due_year': str(due.year),
'm1-resolved': "",
'm1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
m = GroupMilestone.objects.get(pk=m1.pk)
self.assertEquals(GroupMilestone.objects.count(), milestones_before)
self.assertEquals(m.due, m1.due)
# edit
mailbox_before = len(outbox)
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': "Test 2 - changed",
'm1-due_month': str(due.month),
'm1-due_year': str(due.year),
'm1-resolved': "Done",
'm1-resolved_checkbox': "checked",
'm1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before)
self.assertEquals(group.groupevent_set.count(), events_before + 1)
m = GroupMilestone.objects.get(pk=m1.pk)
self.assertEquals(m.state_id, "active")
self.assertEquals(m.due, due)
self.assertEquals(m.resolved, "Done")
self.assertEquals(set(m.docs.values_list("name", flat=True)), set(docs))
self.assertTrue("Changed milestone" in m.milestonegroupevent_set.all()[0].desc)
self.assertEquals(len(outbox), mailbox_before + 2)
self.assertTrue("Milestones changed" in outbox[-2]["Subject"])
self.assertTrue(group.ad.role_email("ad").address in str(outbox[-2]))
self.assertTrue("Milestones changed" in outbox[-1]["Subject"])
self.assertTrue(group.list_email in str(outbox[-1]))
def test_reset_charter_milestones(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_reset_charter_milestones', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(q('input[name=milestone]').val(), str(m1.pk))
events_before = group.charter.docevent_set.count()
# reset
r = self.client.post(url, dict(milestone=[str(m1.pk)]))
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.get(pk=m1.pk).state_id, "active")
self.assertEquals(GroupMilestone.objects.get(pk=m2.pk).state_id, "deleted")
self.assertEquals(GroupMilestone.objects.filter(due=m1.due, desc=m1.desc, state="charter").count(), 1)
self.assertEquals(group.charter.docevent_set.count(), events_before + 2) # 1 delete, 1 add
def test_send_review_needed_reminders(self):
draft = make_test_data()
group = Group.objects.get(acronym="mars")
person = Person.objects.get(user__username="marschairman")
m1 = GroupMilestone.objects.create(group=group,
desc="Test 1",
due=datetime.date.today(),
resolved="",
state_id="review")
MilestoneGroupEvent.objects.create(
group=group, type="changed_milestone",
by=person, desc='Added milestone "%s"' % m1.desc, milestone=m1,
time=datetime.datetime.now() - datetime.timedelta(seconds=60))
# send
mailbox_before = len(outbox)
for g in groups_with_milestones_needing_review():
email_milestone_review_reminder(g)
self.assertEquals(len(outbox), mailbox_before) # too early to send reminder
# add earlier added milestone
m2 = GroupMilestone.objects.create(group=group,
desc="Test 2",
due=datetime.date.today(),
resolved="",
state_id="review")
MilestoneGroupEvent.objects.create(
group=group, type="changed_milestone",
by=person, desc='Added milestone "%s"' % m2.desc, milestone=m2,
time=datetime.datetime.now() - datetime.timedelta(days=10))
# send
mailbox_before = len(outbox)
for g in groups_with_milestones_needing_review():
email_milestone_review_reminder(g)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue(group.acronym in outbox[-1]["Subject"])
self.assertTrue(m1.desc in unicode(outbox[-1]))
self.assertTrue(m2.desc in unicode(outbox[-1]))
def test_send_milestones_due_reminders(self):
draft = make_test_data()
group = Group.objects.get(acronym="mars")
person = Person.objects.get(user__username="marschairman")
early_warning_days = 30
# due dates here aren't aligned on the last day of the month,
# but everything should still work
m1 = GroupMilestone.objects.create(group=group,
desc="Test 1",
due=datetime.date.today(),
resolved="Done",
state_id="active")
m2 = GroupMilestone.objects.create(group=group,
desc="Test 2",
due=datetime.date.today() + datetime.timedelta(days=early_warning_days - 10),
resolved="",
state_id="active")
# send
mailbox_before = len(outbox)
for g in groups_needing_milestones_due_reminder(early_warning_days):
email_milestones_due(g, early_warning_days)
self.assertEquals(len(outbox), mailbox_before) # none found
m1.resolved = ""
m1.save()
m2.due = datetime.date.today() + datetime.timedelta(days=early_warning_days)
m2.save()
# send
mailbox_before = len(outbox)
for g in groups_needing_milestones_due_reminder(early_warning_days):
email_milestones_due(g, early_warning_days)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue(group.acronym in outbox[-1]["Subject"])
self.assertTrue(m1.desc in unicode(outbox[-1]))
self.assertTrue(m2.desc in unicode(outbox[-1]))
def test_send_milestones_overdue_reminders(self):
draft = make_test_data()
group = Group.objects.get(acronym="mars")
person = Person.objects.get(user__username="marschairman")
# due dates here aren't aligned on the last day of the month,
# but everything should still work
m1 = GroupMilestone.objects.create(group=group,
desc="Test 1",
due=datetime.date.today() - datetime.timedelta(days=200),
resolved="Done",
state_id="active")
m2 = GroupMilestone.objects.create(group=group,
desc="Test 2",
due=datetime.date.today() - datetime.timedelta(days=10),
resolved="",
state_id="active")
# send
mailbox_before = len(outbox)
for g in groups_needing_milestones_overdue_reminder(grace_period=30):
email_milestones_overdue(g)
self.assertEquals(len(outbox), mailbox_before) # none found
m1.resolved = ""
m1.save()
m2.due = self.last_day_of_month(datetime.date.today() - datetime.timedelta(days=300))
m2.save()
# send
mailbox_before = len(outbox)
for g in groups_needing_milestones_overdue_reminder(grace_period=30):
email_milestones_overdue(g)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue(group.acronym in outbox[-1]["Subject"])
self.assertTrue(m1.desc in unicode(outbox[-1]))
self.assertTrue(m2.desc in unicode(outbox[-1]))

View file

@ -1,7 +1,7 @@
# Copyright The IETF Trust 2008, All Rights Reserved
from django.conf.urls.defaults import patterns, include
from ietf.wginfo import views, edit
from ietf.wginfo import views, edit, milestones
from django.views.generic.simple import redirect_to
@ -21,11 +21,14 @@ urlpatterns = patterns('',
(r'^(?P<acronym>[a-zA-Z0-9-]+)/documents/txt/$', views.wg_documents_txt),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/$', views.wg_documents_html, None, "wg_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.wg_charter, None, 'wg_charter'),
(r'^(?P<acronym>[A-Za-z0-9-]+)/charter/', include('ietf.wgcharter.urls')),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/', views.history),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/', edit.edit, {'action': "edit"}, "wg_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/init-charter/', edit.submit_initial_charter, None, "wg_init_charter"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/', edit.conclude, None, "wg_conclude"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/$', views.history),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/$', edit.edit, {'action': "edit"}, "wg_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/$', edit.conclude, None, "wg_conclude"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "wg_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "wg_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "wg_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "wg_ajax_search_docs"),
(r'^(?P<acronym>[^/]+)/management/', include('ietf.wgchairs.urls')),
)

View file

@ -55,7 +55,7 @@ def fill_in_charter_info(wg, include_drafts=False):
wg.techadvisors = Email.objects.filter(role__group=wg, role__name="techadv")
wg.editors = Email.objects.filter(role__group=wg, role__name="editor")
wg.secretaries = Email.objects.filter(role__group=wg, role__name="secr")
wg.milestones = wg.groupmilestone_set.all().order_by('expected_due_date')
wg.milestones = wg.groupmilestone_set.filter(state="active").order_by('due')
if include_drafts:
aliases = DocAlias.objects.filter(document__type="draft", document__group=wg).select_related('document').order_by("name")
@ -194,29 +194,28 @@ def wg_charter(request, acronym):
concluded = wg.status_id in [ 2, 3, ]
proposed = (wg.status_id == 4)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
fill_in_charter_info(wg)
actions = []
fill_in_charter_info(wg)
actions = []
if wg.state_id != "conclude":
actions.append(("Edit WG", urlreverse("wg_edit", kwargs=dict(acronym=wg.acronym))))
e = wg.latest_event(type__in=("changed_state", "requested_close",))
requested_close = wg.state_id != "conclude" and e and e.type == "requested_close"
e = wg.latest_event(type__in=("changed_state", "requested_close",))
requested_close = wg.state_id != "conclude" and e and e.type == "requested_close"
if wg.state_id != "conclude":
actions.append(("Edit WG", urlreverse("wg_edit", kwargs=dict(acronym=wg.acronym))))
if wg.state_id in ("active", "dormant"):
actions.append(("Request closing WG", urlreverse("wg_conclude", kwargs=dict(acronym=wg.acronym))))
if wg.state_id in ("active", "dormant"):
actions.append(("Request closing WG", urlreverse("wg_conclude", kwargs=dict(acronym=wg.acronym))))
context = get_wg_menu_context(wg, "charter")
context.update(dict(
actions=actions,
requested_close=requested_close,
))
context = get_wg_menu_context(wg, "charter")
context.update(dict(
actions=actions,
is_chair=request.user.is_authenticated() and wg.role_set.filter(name="chair", person__user=request.user),
milestones_in_review=wg.groupmilestone_set.filter(state="review"),
requested_close=requested_close,
))
return render_to_response('wginfo/wg_charterREDESIGN.html',
context,
RequestContext(request))
return render_to_response('wginfo/wg_charter.html', {'wg': wg, 'concluded':concluded, 'proposed': proposed, 'selected':'charter'}, RequestContext(request))
return render_to_response('wginfo/wg_charter.html',
context,
RequestContext(request))
def get_wg_menu_context(wg, selected):
# it would probably be better to refactor wginfo into rendering

View file

@ -203,4 +203,13 @@ li.warning { margin: 0.5em; background-color: #fc8; color: black;}
li.error { margin: 0.5em; background-color: #f44; }
.errorlist { background: red; color: white; padding: 0.2ex 0.2ex 0.2ex 0.5ex; border: 0px; margin: 0px; font-family: Arial, sans-serif; }
table.milestones td.due { vertical-align: top; width: 80px; }
table.milestones .doc { display: block; padding-left: 1em; }
.stream-state .milestone { display: inline-block; font-size: smaller; background-color: #d5dde6; padding: 0 0.2em; margin-left: 0.3em; }
.button { display: inline-block; font-weight: normal; background: #eee; border: 1px solid #bbb; border-radius: 3px; color: #333; padding: 2px 8px; text-align: center; text-decoration: none; outline: none; transition-duration: 0.2s; cursor: pointer }
.button:hover { background: #ddd; color: #222; }
.button:active { background: #ccc; color: #000; }

View file

@ -6,8 +6,6 @@ ul.token-input-list {
width: 400px;
border: 1px solid #999;
cursor: text;
font-size: 12px;
font-family: Verdana;
z-index: 999;
margin: 0;
padding: 0;
@ -34,9 +32,8 @@ li.token-input-token {
height: 1%;
margin: 3px;
padding: 3px 5px;
background-color: #d0efa0;
background-color: #e8f0fa;
color: #000;
font-weight: bold;
cursor: default;
display: block;
}
@ -54,8 +51,8 @@ li.token-input-token span {
}
li.token-input-selected-token {
background-color: #08844e;
color: #fff;
background-color: #d5dde6;
color: #000;
}
li.token-input-selected-token span {
@ -71,15 +68,12 @@ div.token-input-dropdown {
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
cursor: default;
font-size: 12px;
font-family: Verdana;
z-index: 1;
}
div.token-input-dropdown p {
margin: 0;
padding: 5px;
font-weight: bold;
color: #777;
}
@ -108,6 +102,6 @@ div.token-input-dropdown ul li em {
}
div.token-input-dropdown ul li.token-input-selected-dropdown-item {
background-color: #d0efa0;
background-color: #d5dde6;
}

View file

@ -0,0 +1,133 @@
jQuery(function () {
var idCounter = -1;
// make sure we got the lowest number for idCounter
jQuery('#milestones-form .edit-milestone input[name$="-id"]').each(function () {
var v = +this.value;
if (!isNaN(v) && v < idCounter)
idCounter = v - 1;
});
function setChanged() {
$(this).closest(".edit-milestone").addClass("changed");
}
jQuery('#milestones-form .edit-milestone select,#milestones-form .edit-milestone input,#milestones-form .edit-milestone textarea').live("change", setChanged);
jQuery('#milestones-form .edit-milestone .token-input-list input').live("click", setChanged);
function setSubmitButtonState() {
var action, label;
if (jQuery("#milestones-form input[name$=delete]:visible").length > 0)
action = "review";
else
action = "save";
jQuery("#milestones-form input[name=action]").val(action);
var submit = jQuery("#milestones-form input[type=submit]");
submit.val(submit.data("label" + action));
if (jQuery("#milestones-form .edit-milestone.changed").length > 0 || action == "review")
submit.show();
else
submit.hide();
}
jQuery("#milestones-form tr.milestone").click(function () {
var row = jQuery(this), editRow = row.next("tr.edit-milestone");
if (row.hasClass("add")) {
// move Add milestone row and duplicate hidden template
row.closest("table").append(row).append(editRow.clone());
// fixup template
var newId = idCounter;
--idCounter;
var prefix = "m" + newId;
editRow.find('input[name="prefix"]').val(prefix);
editRow.find("input,select,textarea").each(function () {
if (this.name == "prefix")
return;
if (this.name == "id")
this.value = "" + idCounter;
this.name = prefix + "-" + this.name;
});
editRow.removeClass("template");
setupTokenizedField(editRow.find(".tokenized-field")); // from tokenized-field.js
editRow.show();
}
else {
row.hide();
editRow.show();
}
editRow.find('input[name$="desc"]').focus();
setSubmitButtonState();
// collapse unchanged rows
jQuery("#milestones-form tr.milestone").not(this).each(function () {
var e = jQuery(this).next('tr.edit-milestone');
if (e.is(":visible") && !e.hasClass("changed")) {
jQuery(this).show();
e.hide();
}
});
});
function setResolvedState() {
var resolved = jQuery(this).is(":checked");
var label = jQuery(this).siblings("label");
var reason = jQuery(this).siblings("input[type=text]");
if (resolved) {
if (label.text().indexOf(":") == -1)
label.text(label.text() + ":");
reason.show();
if (!reason.val())
reason.val(finishedMilestoneText);
}
else {
if (label.text().indexOf(":") != -1)
label.text(label.text().replace(":", ""));
reason.hide();
reason.val("");
}
}
jQuery("#milestones-form .edit-milestone .resolved input[type=checkbox]")
.each(setResolvedState)
.live("change", setResolvedState);
function setDeleteState() {
var edit = jQuery(this).closest(".edit-milestone"), row = edit.prev("tr.milestone");
if (jQuery(this).is(":checked")) {
if (+edit.find('input[name$="id"]').val() < 0) {
edit.remove();
setSubmitButtonState();
}
else {
row.addClass("delete");
edit.addClass("delete");
}
}
else {
row.removeClass("delete");
edit.removeClass("delete");
}
}
jQuery("#milestones-form .edit-milestone .delete input[type=checkbox]")
.each(setDeleteState)
.live("change", setDeleteState);
jQuery('#milestones-form .edit-milestone .errorlist').each(function () {
jQuery(this).closest(".edit-milestone").prev().click();
});
setSubmitButtonState();
});

View file

@ -0,0 +1,20 @@
function setupTokenizedField(field) {
if (field.parents(".template").length > 0)
return; // don't tokenize hidden template snippets
var pre = [];
if (field.val())
pre = JSON.parse((field.val() || "").replace(/&quot;/g, '"'));
else if (field.data("pre"))
pre = JSON.parse((field.attr("data-pre") || "").replace(/&quot;/g, '"'));
field.tokenInput(field.data("ajaxurl"), {
hintText: "",
preventDuplicates: true,
prePopulate: pre
});
}
jQuery(function () {
jQuery(".tokenized-field").each(function () { setupTokenizedField(jQuery(this)); });
});