From 3a5a5f0e47fe93591283cd53894c91a042fd6cc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Tue, 2 Nov 2010 15:33:23 +0000
Subject: [PATCH 01/57] Added head revision of django-permissions and
 django-workflows. See #535  - Legacy-Id: 2598

---
 ietf/settings.py                              |   2 +
 permissions/__init__.py                       | 148 ++++
 permissions/admin.py                          |  13 +
 permissions/backend.py                        |  45 +
 permissions/exceptions.py                     |   3 +
 permissions/fixtures/initial.xml              |  23 +
 permissions/locale/de/LC_MESSAGES/django.mo   | Bin 0 -> 701 bytes
 permissions/locale/de/LC_MESSAGES/django.po   |  52 ++
 permissions/models.py                         | 193 +++++
 permissions/templatetags/__init__.py          |   0
 permissions/templatetags/permissions_tags.py  |  48 ++
 permissions/tests.py                          | 783 ++++++++++++++++++
 permissions/utils.py                          | 665 +++++++++++++++
 workflows/__init__.py                         |  61 ++
 workflows/admin.py                            |  30 +
 workflows/locale/de/LC_MESSAGES/django.mo     | Bin 0 -> 798 bytes
 workflows/locale/de/LC_MESSAGES/django.po     |  60 ++
 workflows/models.py                           | 357 ++++++++
 .../templates/workflows/transitions.html      |  10 +
 workflows/templatetags/__init__.py            |   0
 workflows/templatetags/workflows_tags.py      |  18 +
 workflows/tests.py                            | 600 ++++++++++++++
 workflows/urls.py                             |   7 +
 workflows/utils.py                            | 330 ++++++++
 workflows/views.py                            |   0
 25 files changed, 3448 insertions(+)
 create mode 100644 permissions/__init__.py
 create mode 100644 permissions/admin.py
 create mode 100644 permissions/backend.py
 create mode 100644 permissions/exceptions.py
 create mode 100644 permissions/fixtures/initial.xml
 create mode 100644 permissions/locale/de/LC_MESSAGES/django.mo
 create mode 100644 permissions/locale/de/LC_MESSAGES/django.po
 create mode 100644 permissions/models.py
 create mode 100644 permissions/templatetags/__init__.py
 create mode 100644 permissions/templatetags/permissions_tags.py
 create mode 100644 permissions/tests.py
 create mode 100644 permissions/utils.py
 create mode 100644 workflows/__init__.py
 create mode 100644 workflows/admin.py
 create mode 100644 workflows/locale/de/LC_MESSAGES/django.mo
 create mode 100644 workflows/locale/de/LC_MESSAGES/django.po
 create mode 100644 workflows/models.py
 create mode 100644 workflows/templates/workflows/transitions.html
 create mode 100644 workflows/templatetags/__init__.py
 create mode 100644 workflows/templatetags/workflows_tags.py
 create mode 100644 workflows/tests.py
 create mode 100644 workflows/urls.py
 create mode 100644 workflows/utils.py
 create mode 100644 workflows/views.py

diff --git a/ietf/settings.py b/ietf/settings.py
index c4c9a3440..e6d7f96c7 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -118,6 +118,8 @@ INSTALLED_APPS = (
     'django.contrib.admin',
     'django.contrib.humanize',
     'south',
+    'workflows',
+    'permissions',
     'ietf.announcements',
     'ietf.idindex',
     'ietf.idtracker',
diff --git a/permissions/__init__.py b/permissions/__init__.py
new file mode 100644
index 000000000..b81c7766c
--- /dev/null
+++ b/permissions/__init__.py
@@ -0,0 +1,148 @@
+import permissions.utils
+
+class PermissionBase(object):
+    """Mix-in class for permissions.
+    """
+    def grant_permission(self, role, permission):
+        """Grants passed permission to passed role. Returns True if the
+        permission was able to be added, otherwise False.
+
+        **Parameters:**
+
+        role
+            The role for which the permission should be granted.
+
+        permission
+            The permission which should be granted. Either a permission
+            object or the codename of a permission.
+        """
+        return permissions.utils.grant_permission(self, role, permission)
+
+    def remove_permission(self, role, permission):
+        """Removes passed permission from passed role. Returns True if the
+        permission has been removed.
+
+        **Parameters:**
+
+        role
+            The role for which a permission should be removed.
+
+        permission
+            The permission which should be removed. Either a permission object
+            or the codename of a permission.
+        """
+        return permissions.utils.remove_permission(self, role, permission)
+
+    def has_permission(self, user, permission, roles=[]):
+        """Returns True if the passed user has passed permission for this
+        instance. Otherwise False.
+
+        **Parameters:**
+
+        permission
+            The permission's codename which should be checked. Must be a
+            string with a valid codename.
+
+        user
+            The user for which the permission should be checked.
+
+        roles
+            If passed, these roles will be assigned to the user temporarily
+            before the permissions are checked.
+        """
+        return permissions.utils.has_permission(self, user, permission, roles)
+
+    def check_permission(self, user, permission, roles=[]):
+        """Raise Unauthorized if the the passed user hasn't passed permission 
+        for this instance.
+
+        **Parameters:**
+
+        permission
+            The permission's codename which should be checked. Must be a
+            string with a valid codename.
+
+        user
+            The user for which the permission should be checked.
+
+        roles
+            If passed, these roles will be assigned to the user temporarily
+            before the permissions are checked.
+        """
+        if not self.has_permission(user, permission, roles):
+            raise Unauthorized("User %s doesn't have permission %s for object %s" % (user, permission, obj.slug))
+
+    def add_inheritance_block(self, permission):
+        """Adds an inheritance block for the passed permission.
+
+        **Parameters:**
+
+        permission
+            The permission for which an inheritance block should be added.
+            Either a permission object or the codename of a permission.
+        """
+        return permissions.utils.add_inheritance_block(self, permission)
+
+    def remove_inheritance_block(self, permission):
+        """Removes a inheritance block for the passed permission.
+
+        **Parameters:**
+
+        permission
+            The permission for which an inheritance block should be removed.
+            Either a permission object or the codename of a permission.
+        """
+        return permissions.utils.remove_inheritance_block(self, permission)
+
+    def is_inherited(self, codename):
+        """Returns True if the passed permission is inherited.
+
+        **Parameters:**
+
+        codename
+            The permission which should be checked. Must be the codename of
+            the permission.
+        """
+        return permissions.utils.is_inherited(self, codename)
+
+    def add_role(self, principal, role):
+        """Adds a local role for the principal.
+
+        **Parameters:**
+
+        principal
+            The principal (user or group) which gets the role.
+
+        role
+            The role which is assigned.
+        """
+        return permissions.utils.add_local_role(self, principal, role)
+
+    def get_roles(self, principal):
+        """Returns *direct* local roles for passed principal (user or group).
+        """
+        return permissions.utils.get_local_roles(self, principal)
+
+    def remove_role(self, principal, role):
+        """Adds a local role for the principal to the object.
+
+        **Parameters:**
+
+        principal
+            The principal (user or group) from which the role is removed.
+
+        role
+            The role which is removed.
+        """
+        return permissions.utils.remove_local_role(self, principal, role)
+
+    def remove_roles(self, principal):
+        """Removes all local roles for the passed principal from the object.
+
+        **Parameters:**
+
+        principal
+            The principal (user or group) from which all local roles are
+            removed.
+        """
+        return permissions.utils.remove_local_roles(self, principal)
\ No newline at end of file
diff --git a/permissions/admin.py b/permissions/admin.py
new file mode 100644
index 000000000..f37cfabfc
--- /dev/null
+++ b/permissions/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from permissions.models import ObjectPermission
+admin.site.register(ObjectPermission)
+
+from permissions.models import Permission
+admin.site.register(Permission)
+
+from permissions.models import Role
+admin.site.register(Role)
+
+from permissions.models import PrincipalRoleRelation
+admin.site.register(PrincipalRoleRelation)
diff --git a/permissions/backend.py b/permissions/backend.py
new file mode 100644
index 000000000..a0c2d8ec6
--- /dev/null
+++ b/permissions/backend.py
@@ -0,0 +1,45 @@
+# permissions imports
+import permissions.utils
+
+class ObjectPermissionsBackend(object):
+    """Django backend for object permissions. Needs Django 1.2.
+
+
+    Use it together with the default ModelBackend like so::
+
+        AUTHENTICATION_BACKENDS = (
+            'django.contrib.auth.backends.ModelBackend',
+            'permissions.backend.ObjectPermissionsBackend',
+        )
+
+    Then you can use it like:
+
+        user.has_perm("view", your_object)
+
+    """
+    supports_object_permissions = True
+    supports_anonymous_user = True
+
+    def authenticate(self, username, password):
+        return None
+
+    def has_perm(self, user_obj, perm, obj=None):
+        """Checks whether the passed user has passed permission for passed
+        object (obj).
+
+        This should be the primary method to check wether a user has a certain
+        permission.
+
+        Parameters
+        ==========
+
+        perm
+            The permission's codename which should be checked.
+
+        user_obj
+            The user for which the permission should be checked.
+
+        obj
+            The object for which the permission should be checked.
+        """
+        return permissions.utils.has_permission(obj, user_obj, perm)
\ No newline at end of file
diff --git a/permissions/exceptions.py b/permissions/exceptions.py
new file mode 100644
index 000000000..72c24a317
--- /dev/null
+++ b/permissions/exceptions.py
@@ -0,0 +1,3 @@
+class Unauthorized(Exception):
+    def __init__(self, str):
+        super(Unauthorized, self).__init__(str)
\ No newline at end of file
diff --git a/permissions/fixtures/initial.xml b/permissions/fixtures/initial.xml
new file mode 100644
index 000000000..914abf8f1
--- /dev/null
+++ b/permissions/fixtures/initial.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+    <object pk="1" model="permissions.permission">
+        <field type="CharField" name="name">View</field>
+        <field type="CharField" name="codename">view</field>
+    </object>
+    <object pk="2" model="permissions.permission">
+        <field type="CharField" name="name">Edit</field>
+        <field type="CharField" name="codename">edit</field>
+    </object>
+    <object pk="3" model="permissions.permission">
+        <field type="CharField" name="name">Delete</field>
+        <field type="CharField" name="codename">delete</field>
+    </object>
+    <object pk="4" model="permissions.permission">
+        <field type="CharField" name="name">Cut</field>
+        <field type="CharField" name="codename">cut</field>
+    </object>
+    <object pk="5" model="permissions.permission">
+        <field type="CharField" name="name">Copy</field>
+        <field type="CharField" name="codename">copy</field>
+    </object>
+</django-objects>
diff --git a/permissions/locale/de/LC_MESSAGES/django.mo b/permissions/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000000000000000000000000000000000..37b65347672e47df9ba769575d199a12b8943f51
GIT binary patch
literal 701
zcmY+BF>ll`9K{V3C=o2MGI(M+xy{{4s7j%_v`LRh$=xZ<A;D%YCKr^mBilig55UgA
z#>T?L#K>piGr-Ek^EI@>($mlX`1vV$`FVfm6T$MJhfo9EgML76{epI(-;jI$2Xfxs
zYlPedAAmPO3hsc9KzBaq=f^z{!F^}~xxDuJ#r<>8`9Jjh2;Rc{8FYDHK$riu=Qq&Z
z{|+K*>%9%$f!w-rfvpFY?Q;1p%jJ+0RSL;B0vl<Cw6r*Hg}I#S@^Wi&K&D!CEt$Cs
z3!yi)F}0E;SB)S`BQ#lP^+v2LOG@@!Xm=RU;hs-<)2_Lfi&kmNGE>zhJMJpO3KdXq
zF)!GO7Tm(lB5p-MNB+=f{+Nw@IvNMV(ZC=1K8!3EZ)+FZ`(DIh&ayd+Li%hTWXa&=
zd=`5tH<lHeOVe<xbU;s+X-a2d7Sktj7AEN=jG{a~J3E3^RvoVdD+J#JG!19dWjKW|
zO(#vgr*w7XWl0wQ_c>l)7IS3<)QWd@zioIe59w;nwGsB|vN&Z2S3dVHuZ3o@T&c2_
xRX`73)z-VJKap$RSmR2Tq#sxk{oCR>y6;;2f^GS^Sgoz=9mO9D+1d9f|1VGLqJaPa

literal 0
HcmV?d00001

diff --git a/permissions/locale/de/LC_MESSAGES/django.po b/permissions/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 000000000..0a7e9e9cb
--- /dev/null
+++ b/permissions/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,52 @@
+# German translations for django-permissions
+# Copyright (C) 2010 Kai Diefenbach
+# This file is distributed under the same license as the PACKAGE package.
+# Kai Diefenbach <kai.diefenbach@iqpp.de>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-03-30 23:12+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: models.py:154
+msgid "Name"
+msgstr "Name"
+
+#: models.py:155
+msgid "Codename"
+msgstr "Codename"
+
+#: models.py:156
+msgid "Content Types"
+msgstr "Inhaltstypen"
+
+#: models.py:175 models.py:280
+msgid "Role"
+msgstr "Rolle"
+
+#: models.py:176 models.py:216
+msgid "Permission"
+msgstr "Recht"
+
+#: models.py:178 models.py:218 models.py:282
+msgid "Content type"
+msgstr "Inhaltstyp"
+
+#: models.py:179 models.py:219 models.py:283
+msgid "Content id"
+msgstr "Inhalts-ID"
+
+#: models.py:278
+msgid "User"
+msgstr "Benutzer"
+
+#: models.py:279
+msgid "Group"
+msgstr "Gruppe"
diff --git a/permissions/models.py b/permissions/models.py
new file mode 100644
index 000000000..fdd96773a
--- /dev/null
+++ b/permissions/models.py
@@ -0,0 +1,193 @@
+# python imports
+import sets
+
+# django imports
+from django.db import models
+from django.contrib.auth.models import User
+from django.contrib.auth.models import Group
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext_lazy as _
+
+class Permission(models.Model):
+    """A permission which can be granted to users/groups and objects.
+
+    **Attributes:**
+
+    name
+        The unique name of the permission. This is displayed to users.
+
+    codename
+        The unique codename of the permission. This is used internal to
+        identify a permission.
+
+    content_types
+        The content types for which the permission is active. This can be
+        used to display only reasonable permissions for an object.
+    """
+    name = models.CharField(_(u"Name"), max_length=100, unique=True)
+    codename = models.CharField(_(u"Codename"), max_length=100, unique=True)
+    content_types = models.ManyToManyField(ContentType, verbose_name=_(u"Content Types"), blank=True, null=True, related_name="content_types")
+
+    def __unicode__(self):
+        return "%s (%s)" % (self.name, self.codename)
+
+class ObjectPermission(models.Model):
+    """Grants permission for specific user/group and object.
+
+    **Attributes:**
+
+    role
+        The role for which the permission is granted.
+
+    permission
+        The permission which is granted.
+
+    content
+        The object for which the permission is granted.
+    """
+    role = models.ForeignKey("Role", verbose_name=_(u"Role"), blank=True, null=True)
+    permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"))
+
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"))
+    content_id = models.PositiveIntegerField(verbose_name=_(u"Content id"))
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+
+    def __unicode__(self):
+        if self.role:
+            principal = self.role
+        else:
+            principal = self.user
+
+        return "%s / %s / %s - %s" % (self.permission.name, principal, self.content_type, self.content_id)
+
+    def get_principal(self):
+        """Returns the principal.
+        """
+        return self.user or self.group
+
+    def set_principal(self, principal):
+        """Sets the principal.
+        """
+        if isinstance(principal, User):
+            self.user = principal
+        else:
+            self.group = principal
+
+    principal = property(get_principal, set_principal)
+
+class ObjectPermissionInheritanceBlock(models.Model):
+    """Blocks the inheritance for specific permission and object.
+
+    **Attributes:**
+
+    permission
+        The permission for which inheritance is blocked.
+
+    content
+        The object for which the inheritance is blocked.
+    """
+    permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"))
+
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"))
+    content_id = models.PositiveIntegerField(verbose_name=_(u"Content id"))
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+
+    def __unicode__(self):
+        return "%s / %s - %s" % (self.permission, self.content_type, self.content_id)
+
+class Role(models.Model):
+    """A role gets permissions to do something. Principals (users and groups)
+    can only get permissions via roles.
+
+    **Attributes:**
+
+    name
+        The unique name of the role
+    """
+    name = models.CharField(max_length=100, unique=True)
+
+    class Meta:
+        ordering = ("name", )
+
+    def __unicode__(self):
+        return self.name
+
+    def add_principal(self, principal, content=None):
+        """Addes the given principal (user or group) ot the Role.
+        """
+        if isinstance(principal, User):
+            PrincipalRoleRelation.objects.create(user=principal, role=self)
+        else:
+            PrincipalRoleRelation.objects.create(group=principal, role=self)
+
+    def get_groups(self, content=None):
+        """Returns all groups which has this role assigned. If content is given
+        it returns also the local roles.
+        """
+        if content:
+            ctype = ContentType.objects.get_for_model(content)
+            prrs = PrincipalRoleRelation.objects.filter(role=self,
+                content_id__in = (None, content.id),
+                content_type__in = (None, ctype)).exclude(group=None)
+        else:
+            prrs = PrincipalRoleRelation.objects.filter(role=self,
+            content_id=None, content_type=None).exclude(group=None)
+
+        return [prr.group for prr in prrs]
+
+    def get_users(self, content=None):
+        """Returns all users which has this role assigned. If content is given
+        it returns also the local roles.
+        """
+        if content:
+            ctype = ContentType.objects.get_for_model(content)
+            prrs = PrincipalRoleRelation.objects.filter(role=self,
+                content_id__in = (None, content.id),
+                content_type__in = (None, ctype)).exclude(user=None)
+        else:
+            prrs = PrincipalRoleRelation.objects.filter(role=self,
+                content_id=None, content_type=None).exclude(user=None)
+
+        return [prr.user for prr in prrs]
+
+class PrincipalRoleRelation(models.Model):
+    """A role given to a principal (user or group). If a content object is
+    given this is a local role, i.e. the principal has this role only for this
+    content object. Otherwise it is a global role, i.e. the principal has
+    this role generally.
+
+    user
+        A user instance. Either a user xor a group needs to be given.
+
+    group
+        A group instance. Either a user xor a group needs to be given.
+
+    role
+        The role which is given to the principal for content.
+
+    content
+        The content object which gets the local role (optional).
+    """
+    user = models.ForeignKey(User, verbose_name=_(u"User"), blank=True, null=True)
+    group = models.ForeignKey(Group, verbose_name=_(u"Group"), blank=True, null=True)
+    role = models.ForeignKey(Role, verbose_name=_(u"Role"))
+
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), blank=True, null=True)
+    content_id = models.PositiveIntegerField(verbose_name=_(u"Content id"), blank=True, null=True)
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+
+    def get_principal(self):
+        """Returns the principal.
+        """
+        return self.user or self.group
+
+    def set_principal(self, principal):
+        """Sets the principal.
+        """
+        if isinstance(principal, User):
+            self.user = principal
+        else:
+            self.group = principal
+
+    principal = property(get_principal, set_principal)
\ No newline at end of file
diff --git a/permissions/templatetags/__init__.py b/permissions/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/permissions/templatetags/permissions_tags.py b/permissions/templatetags/permissions_tags.py
new file mode 100644
index 000000000..94116381f
--- /dev/null
+++ b/permissions/templatetags/permissions_tags.py
@@ -0,0 +1,48 @@
+# django imports
+from django import template
+from django.core.exceptions import ImproperlyConfigured
+from django.contrib.auth.models import User, AnonymousUser
+
+import permissions.utils
+register = template.Library()
+
+class PermissionComparisonNode(template.Node):
+    """Implements a node to provide an if current user has passed permission 
+    for current object.
+    """
+    @classmethod
+    def handle_token(cls, parser, token):
+        bits = token.contents.split()
+        if len(bits) != 2:
+            raise template.TemplateSyntaxError(
+                "'%s' tag takes one argument" % bits[0])
+        end_tag = 'endifhasperm'
+        nodelist_true = parser.parse(('else', end_tag))
+        token = parser.next_token()
+        if token.contents == 'else': # there is an 'else' clause in the tag
+            nodelist_false = parser.parse((end_tag,))
+            parser.delete_first_token()
+        else:
+            nodelist_false = ""
+
+        return cls(bits[1], nodelist_true, nodelist_false)
+
+    def __init__(self, permission, nodelist_true, nodelist_false):
+        self.permission = permission
+        self.nodelist_true = nodelist_true
+        self.nodelist_false = nodelist_false
+
+    def render(self, context):
+        obj = context.get("obj")
+        request = context.get("request")
+        if permissions.utils.has_permission(self.permission, request.user, obj):
+            return self.nodelist_true.render(context)
+        else:
+            return self.nodelist_false
+
+@register.tag
+def ifhasperm(parser, token):
+    """This function provides functionality for the 'ifhasperm' template tag.
+    """
+    return PermissionComparisonNode.handle_token(parser, token)
+
diff --git a/permissions/tests.py b/permissions/tests.py
new file mode 100644
index 000000000..91bbf51bd
--- /dev/null
+++ b/permissions/tests.py
@@ -0,0 +1,783 @@
+# django imports
+from django.contrib.flatpages.models import FlatPage
+from django.contrib.auth.models import Group
+from django.contrib.auth.models import User
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+from django.test.client import Client
+
+# permissions imports
+from permissions.models import Permission
+from permissions.models import ObjectPermission
+from permissions.models import ObjectPermissionInheritanceBlock
+from permissions.models import Role
+
+import permissions.utils
+
+class BackendTestCase(TestCase):
+    """
+    """
+    def setUp(self):
+        """
+        """
+        settings.AUTHENTICATION_BACKENDS = (
+            'django.contrib.auth.backends.ModelBackend',
+            'permissions.backend.ObjectPermissionsBackend',
+        )
+        
+        self.role_1 = permissions.utils.register_role("Role 1")        
+        self.user = User.objects.create(username="john")
+        self.page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+        self.view = permissions.utils.register_permission("View", "view")
+        
+        # Add user to role
+        self.role_1.add_principal(self.user)
+
+    def test_has_perm(self):
+        """Tests has perm of the backend.
+        """
+        result = self.user.has_perm(self.view, self.page_1)
+        self.assertEqual(result, False)
+        
+        # assign view permission to role 1
+        permissions.utils.grant_permission(self.page_1, self.role_1, self.view)
+
+        result = self.user.has_perm("view", self.page_1)
+        self.assertEqual(result, True)
+    
+class RoleTestCase(TestCase):
+    """
+    """    
+    def setUp(self):
+        """
+        """
+        self.role_1 = permissions.utils.register_role("Role 1")
+        self.role_2 = permissions.utils.register_role("Role 2")
+
+        self.user = User.objects.create(username="john")
+        self.group = Group.objects.create(name="brights")
+
+        self.user.groups.add(self.group)
+
+        self.page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+        self.page_2 = FlatPage.objects.create(url="/page-1/", title="Page 2")
+        
+    def test_getter(self):
+        """
+        """
+        result = permissions.utils.get_group(self.group.id)
+        self.assertEqual(result, self.group)
+
+        result = permissions.utils.get_group(42)
+        self.assertEqual(result, None)
+
+        result = permissions.utils.get_role(self.role_1.id)
+        self.assertEqual(result, self.role_1)
+
+        result = permissions.utils.get_role(42)
+        self.assertEqual(result, None)
+
+        result = permissions.utils.get_user(self.user.id)
+        self.assertEqual(result, self.user)
+
+        result = permissions.utils.get_user(42)
+        self.assertEqual(result, None)
+
+    def test_global_roles_user(self):
+        """
+        """
+        # Add role 1
+        result = permissions.utils.add_role(self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Add role 1 again
+        result = permissions.utils.add_role(self.user, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_roles(self.user)
+        self.assertEqual(result, [self.role_1])
+
+        # Add role 2
+        result = permissions.utils.add_role(self.user, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.user)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove role 1
+        result = permissions.utils.remove_role(self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Remove role 1 again
+        result = permissions.utils.remove_role(self.user, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_roles(self.user)
+        self.assertEqual(result, [self.role_2])
+
+        # Remove role 2
+        result = permissions.utils.remove_role(self.user, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.user)
+        self.assertEqual(result, [])
+
+    def test_global_roles_group(self):
+        """
+        """
+        # Add role 1
+        result = permissions.utils.add_role(self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # Add role 1 again
+        result = permissions.utils.add_role(self.group, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_roles(self.group)
+        self.assertEqual(result, [self.role_1])
+
+        # Add role 2
+        result = permissions.utils.add_role(self.group, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.group)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove role 1
+        result = permissions.utils.remove_role(self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # Remove role 1 again
+        result = permissions.utils.remove_role(self.group, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_roles(self.group)
+        self.assertEqual(result, [self.role_2])
+
+        # Remove role 2
+        result = permissions.utils.remove_role(self.group, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.group)
+        self.assertEqual(result, [])
+
+    def test_remove_roles_user(self):
+        """
+        """
+        # Add role 1
+        result = permissions.utils.add_role(self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Add role 2
+        result = permissions.utils.add_role(self.user, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.user)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove roles
+        result = permissions.utils.remove_roles(self.user)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.user)
+        self.assertEqual(result, [])
+
+        # Remove roles
+        result = permissions.utils.remove_roles(self.user)
+        self.assertEqual(result, False)
+
+    def test_remove_roles_group(self):
+        """
+        """
+        # Add role 1
+        result = permissions.utils.add_role(self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # Add role 2
+        result = permissions.utils.add_role(self.group, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.group)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove roles
+        result = permissions.utils.remove_roles(self.group)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_roles(self.group)
+        self.assertEqual(result, [])
+
+        # Remove roles
+        result = permissions.utils.remove_roles(self.group)
+        self.assertEqual(result, False)
+
+    def test_local_role_user(self):
+        """
+        """
+        # Add local role to page 1
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Again
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.user)
+        self.assertEqual(result, [self.role_1])
+
+        # Add local role 2
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.user)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove role 1
+        result = permissions.utils.remove_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Remove role 1 again
+        result = permissions.utils.remove_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.user)
+        self.assertEqual(result, [self.role_2])
+
+        # Remove role 2
+        result = permissions.utils.remove_local_role(self.page_1, self.user, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.user)
+        self.assertEqual(result, [])
+
+    def test_local_role_group(self):
+        """
+        """
+        # Add local role to page 1
+        result = permissions.utils.add_local_role(self.page_1, self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # Again
+        result = permissions.utils.add_local_role(self.page_1, self.group, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.group)
+        self.assertEqual(result, [self.role_1])
+
+        # Add local role 2
+        result = permissions.utils.add_local_role(self.page_1, self.group, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.group)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove role 1
+        result = permissions.utils.remove_local_role(self.page_1, self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # Remove role 1 again
+        result = permissions.utils.remove_local_role(self.page_1, self.group, self.role_1)
+        self.assertEqual(result, False)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.group)
+        self.assertEqual(result, [self.role_2])
+
+        # Remove role 2
+        result = permissions.utils.remove_local_role(self.page_1, self.group, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.group)
+        self.assertEqual(result, [])
+
+    def test_remove_local_roles_user(self):
+        """
+        """
+        # Add local role to page 1
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Add local role 2
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_2)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.user)
+        self.assertEqual(result, [self.role_1, self.role_2])
+
+        # Remove all local roles
+        result = permissions.utils.remove_local_roles(self.page_1, self.user)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.get_local_roles(self.page_1, self.user)
+        self.assertEqual(result, [])
+
+        # Remove all local roles again
+        result = permissions.utils.remove_local_roles(self.page_1, self.user)
+        self.assertEqual(result, False)
+
+    def test_get_groups_1(self):
+        """Tests global roles for groups.
+        """
+        result = self.role_1.get_groups()
+        self.assertEqual(len(result), 0)
+
+        result = permissions.utils.add_role(self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        result = self.role_1.get_groups()
+        self.assertEqual(result[0].name, "brights")
+
+        # Add another group
+        self.group_2 = Group.objects.create(name="atheists")
+        result = permissions.utils.add_role(self.group_2, self.role_1)
+
+        result = self.role_1.get_groups()
+        self.assertEqual(result[0].name, "brights")
+        self.assertEqual(result[1].name, "atheists")
+        self.assertEqual(len(result), 2)
+
+        # Add the role to an user
+        result = permissions.utils.add_role(self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # This shouldn't have an effect on the result
+        result = self.role_1.get_groups()
+        self.assertEqual(result[0].name, "brights")
+        self.assertEqual(result[1].name, "atheists")
+        self.assertEqual(len(result), 2)
+
+    def test_get_groups_2(self):
+        """Tests local roles for groups.
+        """
+        result = self.role_1.get_groups(self.page_1)
+        self.assertEqual(len(result), 0)
+
+        result = permissions.utils.add_local_role(self.page_1, self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        result = self.role_1.get_groups(self.page_1)
+        self.assertEqual(result[0].name, "brights")
+
+        # Add another local group
+        self.group_2 = Group.objects.create(name="atheists")
+        result = permissions.utils.add_local_role(self.page_1, self.group_2, self.role_1)
+
+        result = self.role_1.get_groups(self.page_1)
+        self.assertEqual(result[0].name, "brights")
+        self.assertEqual(result[1].name, "atheists")
+
+        # A the global role to group
+        result = permissions.utils.add_role(self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # Nontheless there are just two groups returned (and no duplicate)
+        result = self.role_1.get_groups(self.page_1)
+        self.assertEqual(result[0].name, "brights")
+        self.assertEqual(result[1].name, "atheists")
+        self.assertEqual(len(result), 2)
+
+        # Andere there should one global role
+        result = self.role_1.get_groups()
+        self.assertEqual(result[0].name, "brights")
+
+        # Add the role to an user
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # This shouldn't have an effect on the result
+        result = self.role_1.get_groups(self.page_1)
+        self.assertEqual(result[0].name, "brights")
+        self.assertEqual(result[1].name, "atheists")
+        self.assertEqual(len(result), 2)
+
+    def test_get_users_1(self):
+        """Tests global roles for users.
+        """
+        result = self.role_1.get_users()
+        self.assertEqual(len(result), 0)
+
+        result = permissions.utils.add_role(self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        result = self.role_1.get_users()
+        self.assertEqual(result[0].username, "john")
+
+        # Add another role to an user
+        self.user_2 = User.objects.create(username="jane")
+        result = permissions.utils.add_role(self.user_2, self.role_1)
+
+        result = self.role_1.get_users()
+        self.assertEqual(result[0].username, "john")
+        self.assertEqual(result[1].username, "jane")
+        self.assertEqual(len(result), 2)
+
+        # Add the role to an user
+        result = permissions.utils.add_role(self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # This shouldn't have an effect on the result
+        result = self.role_1.get_users()
+        self.assertEqual(result[0].username, "john")
+        self.assertEqual(result[1].username, "jane")
+        self.assertEqual(len(result), 2)
+
+    def test_get_users_2(self):
+        """Tests local roles for users.
+        """
+        result = self.role_1.get_users(self.page_1)
+        self.assertEqual(len(result), 0)
+
+        result = permissions.utils.add_local_role(self.page_1, self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        result = self.role_1.get_users(self.page_1)
+        self.assertEqual(result[0].username, "john")
+
+        # Add another local role to an user
+        self.user_2 = User.objects.create(username="jane")
+        result = permissions.utils.add_local_role(self.page_1, self.user_2, self.role_1)
+
+        result = self.role_1.get_users(self.page_1)
+        self.assertEqual(result[0].username, "john")
+        self.assertEqual(result[1].username, "jane")
+
+        # A the global role to user
+        result = permissions.utils.add_role(self.user, self.role_1)
+        self.assertEqual(result, True)
+
+        # Nontheless there are just two users returned (and no duplicate)
+        result = self.role_1.get_users(self.page_1)
+        self.assertEqual(result[0].username, "john")
+        self.assertEqual(result[1].username, "jane")
+        self.assertEqual(len(result), 2)
+
+        # Andere there should one user for the global role
+        result = self.role_1.get_users()
+        self.assertEqual(result[0].username, "john")
+
+        # Add the role to an group
+        result = permissions.utils.add_local_role(self.page_1, self.group, self.role_1)
+        self.assertEqual(result, True)
+
+        # This shouldn't have an effect on the result
+        result = self.role_1.get_users(self.page_1)
+        self.assertEqual(result[0].username, "john")
+        self.assertEqual(result[1].username, "jane")
+        self.assertEqual(len(result), 2)
+
+class PermissionTestCase(TestCase):
+    """
+    """
+    def setUp(self):
+        """
+        """
+        self.role_1 = permissions.utils.register_role("Role 1")
+        self.role_2 = permissions.utils.register_role("Role 2")
+
+        self.user = User.objects.create(username="john")
+        permissions.utils.add_role(self.user, self.role_1)
+        self.user.save()
+
+        self.page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+        self.page_2 = FlatPage.objects.create(url="/page-1/", title="Page 2")
+
+        self.permission = permissions.utils.register_permission("View", "view")
+
+    def test_add_permissions(self):
+        """
+        """
+        # Add per object
+        result = permissions.utils.grant_permission(self.page_1, self.role_1, self.permission)
+        self.assertEqual(result, True)
+
+        # Add per codename
+        result = permissions.utils.grant_permission(self.page_1, self.role_1, "view")
+        self.assertEqual(result, True)
+
+        # Add ermission which does not exist
+        result = permissions.utils.grant_permission(self.page_1, self.role_1, "hurz")
+        self.assertEqual(result, False)
+
+    def test_remove_permission(self):
+        """
+        """
+        # Add
+        result = permissions.utils.grant_permission(self.page_1, self.role_1, "view")
+        self.assertEqual(result, True)
+
+        # Remove
+        result = permissions.utils.remove_permission(self.page_1, self.role_1, "view")
+        self.assertEqual(result, True)
+
+        # Remove again
+        result = permissions.utils.remove_permission(self.page_1, self.role_1, "view")
+        self.assertEqual(result, False)
+
+    def test_has_permission_role(self):
+        """
+        """
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, False)
+
+        result = permissions.utils.grant_permission(self.page_1, self.role_1, self.permission)
+        self.assertEqual(result, True)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, True)
+
+        result = permissions.utils.remove_permission(self.page_1, self.role_1, "view")
+        self.assertEqual(result, True)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, False)
+
+    def test_has_permission_owner(self):
+        """
+        """
+        creator = User.objects.create(username="jane")
+
+        result = permissions.utils.has_permission(self.page_1, creator, "view")
+        self.assertEqual(result, False)
+
+        owner = permissions.utils.register_role("Owner")
+        permissions.utils.grant_permission(self.page_1, owner, "view")
+
+        result = permissions.utils.has_permission(self.page_1, creator, "view", [owner])
+        self.assertEqual(result, True)
+
+    def test_local_role(self):
+        """
+        """
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, False)
+
+        permissions.utils.grant_permission(self.page_1, self.role_2, self.permission)
+        permissions.utils.add_local_role(self.page_1, self.user, self.role_2)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, True)
+
+    def test_ineritance(self):
+        """
+        """
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, True)
+
+        # per permission
+        permissions.utils.add_inheritance_block(self.page_1, self.permission)
+
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, False)
+
+        permissions.utils.remove_inheritance_block(self.page_1, self.permission)
+
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, True)
+
+        # per codename
+        permissions.utils.add_inheritance_block(self.page_1, "view")
+
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, False)
+
+        permissions.utils.remove_inheritance_block(self.page_1, "view")
+
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, True)
+
+    def test_unicode(self):
+        """
+        """
+        # Permission
+        self.assertEqual(self.permission.__unicode__(), "View (view)")
+
+        # ObjectPermission
+        permissions.utils.grant_permission(self.page_1, self.role_1, self.permission)
+        opr = ObjectPermission.objects.get(permission=self.permission, role=self.role_1)
+        self.assertEqual(opr.__unicode__(), "View / Role 1 / flat page - 1")
+
+        # ObjectPermissionInheritanceBlock
+        permissions.utils.add_inheritance_block(self.page_1, self.permission)
+        opb = ObjectPermissionInheritanceBlock.objects.get(permission=self.permission)
+
+        self.assertEqual(opb.__unicode__(), "View (view) / flat page - 1")
+
+    def test_reset(self):
+        """
+        """
+        result = permissions.utils.grant_permission(self.page_1, self.role_1, "view")
+        self.assertEqual(result, True)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, True)
+
+        permissions.utils.add_inheritance_block(self.page_1, "view")
+
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, False)
+
+        permissions.utils.reset(self.page_1)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, False)
+
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, True)
+
+        permissions.utils.reset(self.page_1)
+
+class RegistrationTestCase(TestCase):
+    """Tests the registration of different components.
+    """
+    def test_group(self):
+        """Tests registering/unregistering of a group.
+        """
+        # Register a group
+        result = permissions.utils.register_group("Brights")
+        self.failUnless(isinstance(result, Group))
+
+        # It's there
+        group = Group.objects.get(name="Brights")
+        self.assertEqual(group.name, "Brights")
+
+        # Trying to register another group with same name
+        result = permissions.utils.register_group("Brights")
+        self.assertEqual(result, False)
+
+        group = Group.objects.get(name="Brights")
+        self.assertEqual(group.name, "Brights")
+
+        # Unregister the group
+        result = permissions.utils.unregister_group("Brights")
+        self.assertEqual(result, True)
+
+        # It's not there anymore
+        self.assertRaises(Group.DoesNotExist, Group.objects.get, name="Brights")
+
+        # Trying to unregister the group again
+        result = permissions.utils.unregister_group("Brights")
+        self.assertEqual(result, False)
+
+    def test_role(self):
+        """Tests registering/unregistering of a role.
+        """
+        # Register a role
+        result = permissions.utils.register_role("Editor")
+        self.failUnless(isinstance(result, Role))
+
+        # It's there
+        role = Role.objects.get(name="Editor")
+        self.assertEqual(role.name, "Editor")
+
+        # Trying to register another role with same name
+        result = permissions.utils.register_role("Editor")
+        self.assertEqual(result, False)
+
+        role = Role.objects.get(name="Editor")
+        self.assertEqual(role.name, "Editor")
+
+        # Unregister the role
+        result = permissions.utils.unregister_role("Editor")
+        self.assertEqual(result, True)
+
+        # It's not there anymore
+        self.assertRaises(Role.DoesNotExist, Role.objects.get, name="Editor")
+
+        # Trying to unregister the role again
+        result = permissions.utils.unregister_role("Editor")
+        self.assertEqual(result, False)
+
+    def test_permission(self):
+        """Tests registering/unregistering of a permission.
+        """
+        # Register a permission
+        result = permissions.utils.register_permission("Change", "change")
+        self.failUnless(isinstance(result, Permission))
+
+        # Is it there?
+        p = Permission.objects.get(codename="change")
+        self.assertEqual(p.name, "Change")
+
+        # Register a permission with the same codename
+        result = permissions.utils.register_permission("Change2", "change")
+        self.assertEqual(result, False)
+
+        # Is it there?
+        p = Permission.objects.get(codename="change")
+        self.assertEqual(p.name, "Change")
+
+        # Register a permission with the same name
+        result = permissions.utils.register_permission("Change", "change2")
+        self.assertEqual(result, False)
+
+        # Is it there?
+        p = Permission.objects.get(codename="change")
+        self.assertEqual(p.name, "Change")
+
+        # Unregister the permission
+        result = permissions.utils.unregister_permission("change")
+        self.assertEqual(result, True)
+
+        # Is it not there anymore?
+        self.assertRaises(Permission.DoesNotExist, Permission.objects.get, codename="change")
+
+        # Unregister the permission again
+        result = permissions.utils.unregister_permission("change")
+        self.assertEqual(result, False)
+
+# django imports
+from django.core.handlers.wsgi import WSGIRequest
+from django.contrib.auth.models import User
+from django.contrib.sessions.backends.file import SessionStore
+from django.test.client import Client
+
+# Taken from "http://www.djangosnippets.org/snippets/963/"
+class RequestFactory(Client):
+    """
+    Class that lets you create mock Request objects for use in testing.
+
+    Usage:
+
+    rf = RequestFactory()
+    get_request = rf.get('/hello/')
+    post_request = rf.post('/submit/', {'foo': 'bar'})
+
+    This class re-uses the django.test.client.Client interface, docs here:
+    http://www.djangoproject.com/documentation/testing/#the-test-client
+
+    Once you have a request object you can pass it to any view function,
+    just as if that view had been hooked up using a URLconf.
+
+    """
+    def request(self, **request):
+        """
+        Similar to parent class, but returns the request object as soon as it
+        has created it.
+        """
+        environ = {
+            'HTTP_COOKIE': self.cookies,
+            'PATH_INFO': '/',
+            'QUERY_STRING': '',
+            'REQUEST_METHOD': 'GET',
+            'SCRIPT_NAME': '',
+            'SERVER_NAME': 'testserver',
+            'SERVER_PORT': 80,
+            'SERVER_PROTOCOL': 'HTTP/1.1',
+        }
+        environ.update(self.defaults)
+        environ.update(request)
+        return WSGIRequest(environ)
+
+def create_request():
+    """
+    """
+    rf = RequestFactory()
+    request = rf.get('/')
+    request.session = SessionStore()
+
+    user = User()
+    user.is_superuser = True
+    user.save()
+    request.user = user
+
+    return request
\ No newline at end of file
diff --git a/permissions/utils.py b/permissions/utils.py
new file mode 100644
index 000000000..322076fb2
--- /dev/null
+++ b/permissions/utils.py
@@ -0,0 +1,665 @@
+# django imports
+from django.db import IntegrityError
+from django.db.models import Q
+from django.db import connection
+from django.contrib.auth.models import User
+from django.contrib.auth.models import Group
+from django.contrib.contenttypes.models import ContentType
+from django.core.cache import cache
+from django.core.exceptions import ObjectDoesNotExist
+
+# permissions imports
+from permissions.exceptions import Unauthorized
+from permissions.models import ObjectPermission
+from permissions.models import ObjectPermissionInheritanceBlock
+from permissions.models import Permission
+from permissions.models import PrincipalRoleRelation
+from permissions.models import Role
+
+
+# Roles ######################################################################
+
+def add_role(principal, role):
+    """Adds a global role to a principal.
+
+    **Parameters:**
+
+    principal
+        The principal (user or group) which gets the role added.
+
+    role
+        The role which is assigned.
+    """
+    if isinstance(principal, User):
+        try:
+            ppr = PrincipalRoleRelation.objects.get(user=principal, role=role, content_id=None, content_type=None)
+        except PrincipalRoleRelation.DoesNotExist:
+            PrincipalRoleRelation.objects.create(user=principal, role=role)
+            return True
+    else:
+        try:
+            ppr = PrincipalRoleRelation.objects.get(group=principal, role=role, content_id=None, content_type=None)
+        except PrincipalRoleRelation.DoesNotExist:
+            PrincipalRoleRelation.objects.create(group=principal, role=role)
+            return True
+
+    return False
+
+def add_local_role(obj, principal, role):
+    """Adds a local role to a principal.
+
+    **Parameters:**
+
+    obj
+        The object for which the principal gets the role.
+
+    principal
+        The principal (user or group) which gets the role.
+
+    role
+        The role which is assigned.
+    """
+    ctype = ContentType.objects.get_for_model(obj)
+    if isinstance(principal, User):
+        try:
+            ppr = PrincipalRoleRelation.objects.get(user=principal, role=role, content_id=obj.id, content_type=ctype)
+        except PrincipalRoleRelation.DoesNotExist:
+            PrincipalRoleRelation.objects.create(user=principal, role=role, content=obj)
+            return True
+    else:
+        try:
+            ppr = PrincipalRoleRelation.objects.get(group=principal, role=role, content_id=obj.id, content_type=ctype)
+        except PrincipalRoleRelation.DoesNotExist:
+            PrincipalRoleRelation.objects.create(group=principal, role=role, content=obj)
+            return True
+
+    return False
+
+def remove_role(principal, role):
+    """Removes role from passed principal.
+
+    **Parameters:**
+
+    principal
+        The principal (user or group) from which the role is removed.
+
+    role
+        The role which is removed.
+    """
+    try:
+        if isinstance(principal, User):
+            ppr = PrincipalRoleRelation.objects.get(
+                    user=principal, role=role, content_id=None, content_type=None)
+        else:
+            ppr = PrincipalRoleRelation.objects.get(
+                    group=principal, role=role, content_id=None, content_type=None)
+
+    except PrincipalRoleRelation.DoesNotExist:
+        return False
+    else:
+        ppr.delete()
+
+    return True
+
+def remove_local_role(obj, principal, role):
+    """Removes role from passed object and principle.
+
+    **Parameters:**
+
+    obj
+        The object from which the role is removed.
+
+    principal
+        The principal (user or group) from which the role is removed.
+
+    role
+        The role which is removed.
+    """
+    try:
+        ctype = ContentType.objects.get_for_model(obj)
+
+        if isinstance(principal, User):
+            ppr = PrincipalRoleRelation.objects.get(
+                user=principal, role=role, content_id=obj.id, content_type=ctype)
+        else:
+            ppr = PrincipalRoleRelation.objects.get(
+                group=principal, role=role, content_id=obj.id, content_type=ctype)
+
+    except PrincipalRoleRelation.DoesNotExist:
+        return False
+    else:
+        ppr.delete()
+
+    return True
+
+def remove_roles(principal):
+    """Removes all roles passed principal (user or group).
+
+    **Parameters:**
+
+    principal
+        The principal (user or group) from which all roles are removed.
+    """
+    if isinstance(principal, User):
+        ppr = PrincipalRoleRelation.objects.filter(
+            user=principal, content_id=None, content_type=None)
+    else:
+        ppr = PrincipalRoleRelation.objects.filter(
+            group=principal, content_id=None, content_type=None)
+
+    if ppr:
+        ppr.delete()
+        return True
+    else:
+        return False
+
+def remove_local_roles(obj, principal):
+    """Removes all local roles from passed object and principal (user or
+    group).
+
+    **Parameters:**
+
+    obj
+        The object from which the roles are removed.
+
+    principal
+        The principal (user or group) from which the roles are removed.
+    """
+    ctype = ContentType.objects.get_for_model(obj)
+
+    if isinstance(principal, User):
+        ppr = PrincipalRoleRelation.objects.filter(
+            user=principal, content_id=obj.id, content_type=ctype)
+    else:
+        ppr = PrincipalRoleRelation.objects.filter(
+            group=principal, content_id=obj.id, content_type=ctype)
+
+    if ppr:
+        ppr.delete()
+        return True
+    else:
+        return False
+
+def get_roles(user, obj=None):
+    """Returns *all* roles of the passed user.
+
+    This takes direct roles and roles via the user's groups into account.
+
+    If an object is passed local roles will also added. Then all local roles
+    from all ancestors and all user's groups are also taken into account.
+
+    This is the method to use if one want to know whether the passed user
+    has a role in general (for the passed object).
+
+    **Parameters:**
+
+    user
+        The user for which the roles are returned.
+
+    obj
+        The object for which local roles will returned.
+
+    """
+    roles = []
+    groups = user.groups.all()
+    groups_ids_str = ", ".join([str(g.id) for g in groups])
+
+    # Gobal roles for user and the user's groups
+    cursor = connection.cursor()
+    cursor.execute("""SELECT role_id
+                      FROM permissions_principalrolerelation
+                      WHERE (user_id=%s OR group_id IN (%s))
+                      AND content_id is Null""" % (user.id, groups_ids_str))
+
+    for row in cursor.fetchall():
+        roles.append(row[0])
+
+    # Local roles for user and the user's groups and all ancestors of the
+    # passed object.
+    while obj:
+        ctype = ContentType.objects.get_for_model(obj)
+        cursor.execute("""SELECT role_id
+                          FROM permissions_principalrolerelation
+                          WHERE (user_id='%s' OR group_id IN (%s))
+                          AND content_id='%s'
+                          AND content_type_id='%s'""" % (user.id, groups_ids_str, obj.id, ctype.id))
+
+        for row in cursor.fetchall():
+            roles.append(row[0])
+
+        try:
+            obj = obj.get_parent_for_permissions()
+        except AttributeError:
+            obj = None
+
+    return roles
+
+def get_global_roles(principal):
+    """Returns *direct* global roles of passed principal (user or group).
+    """
+    if isinstance(principal, User):
+        return [prr.role for prr in PrincipalRoleRelation.objects.filter(
+            user=principal, content_id=None, content_type=None)]
+    else:
+        if isinstance(principal, Group):
+            principal = (principal,)
+        return [prr.role for prr in PrincipalRoleRelation.objects.filter(
+            group__in=principal, content_id=None, content_type=None)]
+
+def get_local_roles(obj, principal):
+    """Returns *direct* local roles for passed principal and content object.    
+    """
+    ctype = ContentType.objects.get_for_model(obj)
+
+    if isinstance(principal, User):
+        return [prr.role for prr in PrincipalRoleRelation.objects.filter(
+            user=principal, content_id=obj.id, content_type=ctype)]
+    else:
+        return [prr.role for prr in PrincipalRoleRelation.objects.filter(
+            group=principal, content_id=obj.id, content_type=ctype)]
+
+# Permissions ################################################################
+
+def check_permission(obj, user, codename, roles=None):
+    """Checks whether passed user has passed permission for passed obj.
+
+    **Parameters:**
+
+    obj
+        The object for which the permission should be checked.
+
+    codename
+        The permission's codename which should be checked.
+
+    user
+        The user for which the permission should be checked.
+
+    roles
+        If given these roles will be assigned to the user temporarily before
+        the permissions are checked.
+    """
+    if not has_permission(obj, user, codename):
+        raise Unauthorized("User '%s' doesn't have permission '%s' for object '%s' (%s)"
+            % (user, codename, obj.slug, obj.__class__.__name__))
+
+def grant_permission(obj, role, permission):
+    """Grants passed permission to passed role. Returns True if the permission
+    was able to be added, otherwise False.
+
+    **Parameters:**
+
+    obj
+        The content object for which the permission should be granted.
+
+    role
+        The role for which the permission should be granted.
+
+    permission
+        The permission which should be granted. Either a permission
+        object or the codename of a permission.
+    """
+    if not isinstance(permission, Permission):
+        try:
+            permission = Permission.objects.get(codename = permission)
+        except Permission.DoesNotExist:
+            return False
+
+    ct = ContentType.objects.get_for_model(obj)
+    try:
+        ObjectPermission.objects.get(role=role, content_type = ct, content_id=obj.id, permission=permission)
+    except ObjectPermission.DoesNotExist:
+        ObjectPermission.objects.create(role=role, content=obj, permission=permission)
+
+    return True
+
+def remove_permission(obj, role, permission):
+    """Removes passed permission from passed role and object. Returns True if
+    the permission has been removed.
+
+    **Parameters:**
+
+    obj
+        The content object for which a permission should be removed.
+
+    role
+        The role for which a permission should be removed.
+
+    permission
+        The permission which should be removed. Either a permission object
+        or the codename of a permission.
+    """
+    if not isinstance(permission, Permission):
+        try:
+            permission = Permission.objects.get(codename = permission)
+        except Permission.DoesNotExist:
+            return False
+
+    ct = ContentType.objects.get_for_model(obj)
+
+    try:
+        op = ObjectPermission.objects.get(role=role, content_type = ct, content_id=obj.id, permission = permission)
+    except ObjectPermission.DoesNotExist:
+        return False
+
+    op.delete()
+    return True
+
+def has_permission(obj, user, codename, roles=None):
+    """Checks whether the passed user has passed permission for passed object.
+
+    **Parameters:**
+
+    obj
+        The object for which the permission should be checked.
+
+    codename
+        The permission's codename which should be checked.
+
+    request
+        The current request.
+
+    roles
+        If given these roles will be assigned to the user temporarily before
+        the permissions are checked.
+    """
+    cache_key = "%s-%s-%s" % (obj.content_type, obj.id, codename)
+    result = _get_cached_permission(user, cache_key)
+    if result is not None:
+        return result
+
+    if roles is None:
+        roles = []
+
+    if user.is_superuser:
+        return True
+
+    if not user.is_anonymous():
+        roles.extend(get_roles(user, obj))
+
+    ct = ContentType.objects.get_for_model(obj)
+
+    result = False
+    while obj is not None:
+        p = ObjectPermission.objects.filter(
+            content_type=ct, content_id=obj.id, role__in=roles, permission__codename = codename).values("id")
+
+        if len(p) > 0:
+            result = True
+            break
+
+        if is_inherited(obj, codename) == False:
+            result = False
+            break
+
+        try:
+            obj = obj.get_parent_for_permissions()
+            ct = ContentType.objects.get_for_model(obj)
+        except AttributeError:
+            result = False
+            break
+
+    _cache_permission(user, cache_key, result)
+    return result
+
+# Inheritance ################################################################
+
+def add_inheritance_block(obj, permission):
+    """Adds an inheritance for the passed permission on the passed obj.
+
+    **Parameters:**
+
+        permission
+            The permission for which an inheritance block should be added.
+            Either a permission object or the codename of a permission.
+        obj
+            The content object for which an inheritance block should be added.
+    """
+    if not isinstance(permission, Permission):
+        try:
+            permission = Permission.objects.get(codename = permission)
+        except Permission.DoesNotExist:
+            return False
+
+    ct = ContentType.objects.get_for_model(obj)
+    try:
+        ObjectPermissionInheritanceBlock.objects.get(content_type = ct, content_id=obj.id, permission=permission)
+    except ObjectPermissionInheritanceBlock.DoesNotExist:
+        try:
+            result = ObjectPermissionInheritanceBlock.objects.create(content=obj, permission=permission)
+        except IntegrityError:
+            return False
+    return True
+
+def remove_inheritance_block(obj, permission):
+    """Removes a inheritance block for the passed permission from the passed
+    object.
+
+    **Parameters:**
+
+        obj
+            The content object for which an inheritance block should be added.
+
+        permission
+            The permission for which an inheritance block should be removed.
+            Either a permission object or the codename of a permission.
+    """
+    if not isinstance(permission, Permission):
+        try:
+            permission = Permission.objects.get(codename = permission)
+        except Permission.DoesNotExist:
+            return False
+
+    ct = ContentType.objects.get_for_model(obj)
+    try:
+        opi = ObjectPermissionInheritanceBlock.objects.get(content_type = ct, content_id=obj.id, permission=permission)
+    except ObjectPermissionInheritanceBlock.DoesNotExist:
+        return False
+
+    opi.delete()
+    return True
+
+def is_inherited(obj, codename):
+    """Returns True if the passed permission is inherited for passed object.
+
+    **Parameters:**
+
+        obj
+            The content object for which the permission should be checked.
+
+        codename
+            The permission which should be checked. Must be the codename of
+            the permission.
+    """
+    ct = ContentType.objects.get_for_model(obj)
+    try:
+        ObjectPermissionInheritanceBlock.objects.get(
+            content_type=ct, content_id=obj.id, permission__codename = codename)
+    except ObjectDoesNotExist:
+        return True
+    else:
+        return False
+
+def get_group(id):
+    """Returns the group with passed id or None.
+    """
+    try:
+        return Group.objects.get(pk=id)
+    except Group.DoesNotExist:
+        return None
+
+def get_role(id):
+    """Returns the role with passed id or None.
+    """
+    try:
+        return Role.objects.get(pk=id)
+    except Role.DoesNotExist:
+        return None
+
+def get_user(id):
+    """Returns the user with passed id or None.
+    """
+    try:
+        return User.objects.get(pk=id)
+    except User.DoesNotExist:
+        return None
+
+def has_group(user, group):
+    """Returns True if passed user has passed group.
+    """
+    if isinstance(group, str):
+        group = Group.objects.get(name=group)
+
+    return group in user.groups.all()
+
+def reset(obj):
+    """Resets all permissions and inheritance blocks of passed object.
+    """
+    ctype = ContentType.objects.get_for_model(obj)
+    ObjectPermissionInheritanceBlock.objects.filter(content_id=obj.id, content_type=ctype).delete()
+    ObjectPermission.objects.filter(content_id=obj.id, content_type=ctype).delete()
+
+# Registering ################################################################
+
+def register_permission(name, codename, ctypes=[]):
+    """Registers a permission to the framework. Returns the permission if the
+    registration was successfully, otherwise False.
+
+    **Parameters:**
+
+        name
+            The unique name of the permission. This is displayed to the
+            customer.
+        codename
+            The unique codename of the permission. This is used internally to
+            identify the permission.
+        content_types
+            The content type for which the permission is active. This can be
+            used to display only reasonable permissions for an object. This
+            must be a Django ContentType
+    """
+    try:
+        p = Permission.objects.create(name=name, codename=codename)
+
+        ctypes = [ContentType.objects.get_for_model(ctype) for ctype in ctypes]
+        if ctypes:
+            p.content_types = ctypes
+            p.save()
+    except IntegrityError:
+        return False
+    return p
+
+def unregister_permission(codename):
+    """Unregisters a permission from the framework
+
+    **Parameters:**
+
+        codename
+            The unique codename of the permission.
+    """
+    try:
+        permission = Permission.objects.get(codename=codename)
+    except Permission.DoesNotExist:
+        return False
+    permission.delete()
+    return True
+
+def register_role(name):
+    """Registers a role with passed name to the framework. Returns the new
+    role if the registration was successfully, otherwise False.
+
+    **Parameters:**
+
+    name
+        The unique role name.
+    """
+    try:
+        role = Role.objects.create(name=name)
+    except IntegrityError:
+        return False
+    return role
+
+def unregister_role(name):
+    """Unregisters the role with passed name.
+
+    **Parameters:**
+
+    name
+        The unique role name.
+    """
+    try:
+        role = Role.objects.get(name=name)
+    except Role.DoesNotExist:
+        return False
+
+    role.delete()
+    return True
+
+def register_group(name):
+    """Registers a group with passed name to the framework. Returns the new
+    group if the registration was successfully, otherwise False.
+
+    Actually this creates just a default Django Group.
+
+    **Parameters:**
+
+    name
+        The unique group name.
+    """
+    try:
+        group = Group.objects.create(name=name)
+    except IntegrityError:
+        return False
+    return group
+
+def unregister_group(name):
+    """Unregisters the group with passed name. Returns True if the
+    unregistration was succesfull otherwise False.
+
+    Actually this deletes just a default Django Group.
+
+    **Parameters:**
+
+    name
+        The unique role name.
+    """
+    try:
+        group = Group.objects.get(name=name)
+    except Group.DoesNotExist:
+        return False
+
+    group.delete()
+    return True
+
+def _cache_permission(user, cache_key, data):
+    """Stores the passed data on the passed user object.
+
+    **Parameters:**
+
+    user
+        The user on which the data is stored.
+
+    cache_key
+        The key under which the data is stored.
+
+    data
+        The data which is stored.
+    """
+    if not getattr(user, "permissions", None):
+        user.permissions = {}
+    user.permissions[cache_key] = data
+
+def _get_cached_permission(user, cache_key):
+    """Returns the stored data from passed user object for passed cache_key.
+
+    **Parameters:**
+
+    user
+        The user from which the data is retrieved.
+
+    cache_key
+        The key under which the data is stored.
+
+    """
+    permissions = getattr(user, "permissions", None)
+    if permissions:
+        return user.permissions.get(cache_key, None)
\ No newline at end of file
diff --git a/workflows/__init__.py b/workflows/__init__.py
new file mode 100644
index 000000000..271167dbe
--- /dev/null
+++ b/workflows/__init__.py
@@ -0,0 +1,61 @@
+# workflows imports
+import workflows.utils
+
+class WorkflowBase(object):
+    """Mixin class to make objects workflow aware.
+    """
+    def get_workflow(self):
+        """Returns the current workflow of the object.
+        """
+        return workflows.utils.get_workflow(self)
+
+    def remove_workflow(self):
+        """Removes the workflow from the object. After this function has been
+        called the object has no *own* workflow anymore (it might have one via
+        its content type).
+
+        """
+        return workflows.utils.remove_workflow_from_object(self)
+
+    def set_workflow(self, workflow):
+        """Sets the passed workflow to the object. This will set the local
+        workflow for the object.
+
+        If the object has already the given workflow nothing happens.
+        Otherwise the object gets the passed workflow and the state is set to
+        the workflow's initial state.
+
+        **Parameters:**
+
+        workflow
+            The workflow which should be set to the object. Can be a Workflow
+            instance or a string with the workflow name.
+        obj
+            The object which gets the passed workflow.
+        """
+        return workflows.utils.set_workflow_for_object(workflow)
+
+    def get_state(self):
+        """Returns the current workflow state of the object.
+        """
+        return workflows.utils.get_state(self)
+
+    def set_state(self, state):
+        """Sets the workflow state of the object.
+        """
+        return workflows.utils.set_state(self, state)
+
+    def set_initial_state(self):
+        """Sets the initial state of the current workflow to the object.
+        """
+        return self.set_state(self.get_workflow().initial_state)
+
+    def get_allowed_transitions(self, user):
+        """Returns allowed transitions for the current state.
+        """
+        return workflows.utils.get_allowed_transitions(self, user)
+
+    def do_transition(self, transition, user):
+        """Processes the passed transition (if allowed).
+        """
+        return workflows.utils.do_transition(self, transition, user)
\ No newline at end of file
diff --git a/workflows/admin.py b/workflows/admin.py
new file mode 100644
index 000000000..4fe2cd05e
--- /dev/null
+++ b/workflows/admin.py
@@ -0,0 +1,30 @@
+from django.contrib import admin
+from workflows.models import State
+from workflows.models import StateInheritanceBlock
+from workflows.models import StatePermissionRelation
+from workflows.models import StateObjectRelation
+from workflows.models import Transition
+from workflows.models import Workflow
+from workflows.models import WorkflowObjectRelation
+from workflows.models import WorkflowModelRelation
+from workflows.models import WorkflowPermissionRelation
+
+class StateInline(admin.TabularInline):
+    model = State
+
+class WorkflowAdmin(admin.ModelAdmin):
+    inlines = [
+        StateInline,
+    ]
+
+admin.site.register(Workflow, WorkflowAdmin)
+
+admin.site.register(State)
+admin.site.register(StateInheritanceBlock)
+admin.site.register(StateObjectRelation)
+admin.site.register(StatePermissionRelation)
+admin.site.register(Transition)
+admin.site.register(WorkflowObjectRelation)
+admin.site.register(WorkflowModelRelation)
+admin.site.register(WorkflowPermissionRelation)
+
diff --git a/workflows/locale/de/LC_MESSAGES/django.mo b/workflows/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000000000000000000000000000000000..e4512512fa1dfb4c2aff568f0b09cb7566a1b859
GIT binary patch
literal 798
zcmY+BzmL-}6vqveUjZbznZeAJ@6rmP61dZaCU-<6ZIz}VPAqQgriRohw#$LU#>&dZ
z$e+P>5<B~em4$_g35oYYTEvsS`uN$;U;lXDwi+J@#8uEu&|A<A&?yLmo+0Ei@D>nj
znm`BGsq-%I3giP|1Nf|7=XG8H&%^vnAl4lNF9F{HcR=q!=>PfbU*9h^zt%hjqVM;b
zKY-}>v*s`01;~E`(Z6wykZZu}K(MX?*Vln(;r-WvZ^HXr1mS+Lsr>Owupp#cs;RWH
zR5-FiS(?3BicKP?o1w+|Ko~0(uQv4fLXd&b3uz1*q-7z<&~htCrnxeetszfJ{bE*>
z$7G<(=VD@6G-Z#4#w{LgZM&2g%Q<&au`IP^iOJ=ZJy_+2Wu-@*K|f<%Evge)0Nx&L
zyDgWwx0&0f?p?2Sr|Gs`7YbP_UP-jA>z;;w%91_{eEP8OB~kOJ-wU0X8_TNu72KBE
zqX(lnraeCi>HRSAqj=vBf;1cs_n=heE1nCM3BK@X?Dq~w{vp)G@qQt<OP%jINtA^D
zJ;$m4k>Njj)QZ=3XIXHm?$ODdYa{ILC_7-Ce|7j*Goe|iCgoJB+@qbbv<`W+X-%Z&
zys!p3K<Xijg7pw*kEAF_)sIw6<`!E~_hMzp$;YwK`N=1h3*zgskk;^V!B;c#2dq`O
A(f|Me

literal 0
HcmV?d00001

diff --git a/workflows/locale/de/LC_MESSAGES/django.po b/workflows/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 000000000..2b97ddef5
--- /dev/null
+++ b/workflows/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,60 @@
+# German translations for django-workflows
+# Copyright (C) 2010 Kai Diefenbach
+# This file is distributed under the same license as the PACKAGE package.
+# Kai Diefenbach <kai.diefenbach@iqpp.de>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-02 09:16+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: models.py:98 models.py:199 models.py:237
+msgid "Name"
+msgstr "Name"
+
+#: models.py:200 models.py:238 models.py:285 models.py:307
+msgid "Workflow"
+msgstr "Arbeitsablauf"
+
+#: models.py:201
+msgid "Transitions"
+msgstr "Übergänge"
+
+#: models.py:239
+msgid "Destination"
+msgstr "Ziel"
+
+#: models.py:240
+msgid "Condition"
+msgstr "Kondition"
+
+#: models.py:241 models.py:350 models.py:373
+msgid "Permission"
+msgstr "Recht"
+
+#: models.py:258 models.py:282
+msgid "Content type"
+msgstr "Inhaltstyp"
+
+#: models.py:259 models.py:283
+msgid "Content id"
+msgstr "Inhalts-ID"
+
+#: models.py:261 models.py:349 models.py:372
+msgid "State"
+msgstr "Status"
+
+#: models.py:306
+msgid "Content Type"
+msgstr "Inhaltstyp"
+
+#: models.py:374
+msgid "Role"
+msgstr "Rolle"
diff --git a/workflows/models.py b/workflows/models.py
new file mode 100644
index 000000000..928e4ef0c
--- /dev/null
+++ b/workflows/models.py
@@ -0,0 +1,357 @@
+from django.db import models
+
+# django imports
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext_lazy as _
+
+# permissions imports
+import permissions.utils
+from permissions.models import Permission
+from permissions.models import Role
+
+class Workflow(models.Model):
+    """A workflow consists of a sequence of connected (through transitions)
+    states. It can be assigned to a model and / or model instances. If a
+    model instance has a workflow it takes precendence over the model's
+    workflow.
+
+    **Attributes:**
+
+    model
+        The model the workflow belongs to. Can be any
+
+    content
+        The object the workflow belongs to.
+
+    name
+        The unique name of the workflow.
+
+    states
+        The states of the workflow.
+
+    initial_state
+        The initial state the model / content gets if created.
+
+    """
+    name = models.CharField(_(u"Name"), max_length=100, unique=True)
+    initial_state = models.ForeignKey("State", related_name="workflow_state", blank=True, null=True)
+    permissions = models.ManyToManyField(Permission, symmetrical=False, through="WorkflowPermissionRelation")
+
+    def __unicode__(self):
+        return self.name
+
+    def get_initial_state(self):
+        """Returns the initial state of the workflow. Takes the first one if
+        no state has been defined.
+        """
+        if self.initial_state:
+            return self.initial_state
+        else:
+            try:
+                return self.states.all()[0]
+            except IndexError:
+                return None
+
+    def get_objects(self):
+        """Returns all objects which have this workflow assigned. Globally
+        (via the object's content type) or locally (via the object itself).
+        """
+        import workflows.utils
+        objs = []
+
+        # Get all objects whose content type has this workflow
+        for wmr in WorkflowModelRelation.objects.filter(workflow=self):
+            ctype = wmr.content_type
+            # We have also to check whether the global workflow is not
+            # overwritten.
+            for obj in ctype.model_class().objects.all():
+                if workflows.utils.get_workflow(obj) == self:
+                    objs.append(obj)
+
+        # Get all objects whose local workflow this workflow
+        for wor in WorkflowObjectRelation.objects.filter(workflow=self):
+            if wor.content not in objs:
+                objs.append(wor.content)
+
+        return objs
+
+    def set_to(self, ctype_or_obj):
+        """Sets the workflow to passed content type or object. See the specific
+        methods for more information.
+
+        **Parameters:**
+
+        ctype_or_obj
+            The content type or the object to which the workflow should be set.
+            Can be either a ContentType instance or any Django model instance.
+        """
+        if isinstance(ctype_or_obj, ContentType):
+            return self.set_to_model(ctype_or_obj)
+        else:
+            return self.set_to_object(ctype_or_obj)
+
+    def set_to_model(self, ctype):
+        """Sets the workflow to the passed content type. If the content
+        type has already an assigned workflow the workflow is overwritten.
+
+        **Parameters:**
+
+        ctype
+            The content type which gets the workflow. Can be any Django model
+            instance.
+        """
+        try:
+            wor = WorkflowModelRelation.objects.get(content_type=ctype)
+        except WorkflowModelRelation.DoesNotExist:
+            WorkflowModelRelation.objects.create(content_type=ctype, workflow=self)
+        else:
+            wor.workflow = self
+            wor.save()
+
+    def set_to_object(self, obj):
+        """Sets the workflow to the passed object.
+
+        If the object has already the given workflow nothing happens. Otherwise
+        the workflow is set to the objectthe state is set to the workflow's
+        initial state.
+
+        **Parameters:**
+
+        obj
+            The object which gets the workflow.
+        """
+        import workflows.utils
+
+        ctype = ContentType.objects.get_for_model(obj)
+        try:
+            wor = WorkflowObjectRelation.objects.get(content_type=ctype, content_id=obj.id)
+        except WorkflowObjectRelation.DoesNotExist:
+            WorkflowObjectRelation.objects.create(content = obj, workflow=self)
+            workflows.utils.set_state(obj, self.initial_state)
+        else:
+            if wor.workflow != self:
+                wor.workflow = self
+                wor.save()
+                workflows.utils.set_state(self.initial_state)
+
+class State(models.Model):
+    """A certain state within workflow.
+
+    **Attributes:**
+
+    name
+        The unique name of the state within the workflow.
+
+    workflow
+        The workflow to which the state belongs.
+
+    transitions
+        The transitions of a workflow state.
+
+    """
+    name = models.CharField(_(u"Name"), max_length=100)
+    workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="states")
+    transitions = models.ManyToManyField("Transition", verbose_name=_(u"Transitions"), blank=True, null=True, related_name="states")
+
+    class Meta:
+        ordering = ("name", )
+
+    def __unicode__(self):
+        return "%s (%s)" % (self.name, self.workflow.name)
+
+    def get_allowed_transitions(self, obj, user):
+        """Returns all allowed transitions for passed object and user.
+        """
+        transitions = []
+        for transition in self.transitions.all():
+            permission = transition.permission
+            if permission is None:
+               transitions.append(transition)
+            else:
+                # First we try to get the objects specific has_permission
+                # method (in case the object inherits from the PermissionBase
+                # class).
+                try:
+                    if obj.has_permission(user, permission.codename):
+                        transitions.append(transition)
+                except AttributeError:
+                    if permissions.utils.has_permission(obj, user, permission.codename):
+                        transitions.append(transition)
+        return transitions
+
+class Transition(models.Model):
+    """A transition from a source to a destination state. The transition can
+    be used from several source states.
+
+    **Attributes:**
+
+    name
+        The unique name of the transition within a workflow.
+
+    workflow
+        The workflow to which the transition belongs. Must be a Workflow
+        instance.
+
+    destination
+        The state after a transition has been processed. Must be a State
+        instance.
+
+    condition
+        The condition when the transition is available. Can be any python
+        expression.
+
+    permission
+        The necessary permission to process the transition. Must be a
+        Permission instance.
+
+    """
+    name = models.CharField(_(u"Name"), max_length=100)
+    workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="transitions")
+    destination = models.ForeignKey(State, verbose_name=_(u"Destination"), null=True, blank=True, related_name="destination_state")
+    condition = models.CharField(_(u"Condition"), blank=True, max_length=100)
+    permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"), blank=True, null=True)
+
+    def __unicode__(self):
+        return self.name
+
+class StateObjectRelation(models.Model):
+    """Stores the workflow state of an object.
+
+    Provides a way to give any object a workflow state without changing the
+    object's model.
+
+    **Attributes:**
+
+    content
+        The object for which the state is stored. This can be any instance of
+        a Django model.
+
+    state
+        The state of content. This must be a State instance.
+    """
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="state_object", blank=True, null=True)
+    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+    state = models.ForeignKey(State, verbose_name = _(u"State"))
+
+    def __unicode__(self):
+        return "%s %s - %s" % (self.content_type.name, self.content_id, self.state.name)
+
+    class Meta:
+        unique_together = ("content_type", "content_id", "state")
+
+class WorkflowObjectRelation(models.Model):
+    """Stores an workflow of an object.
+
+    Provides a way to give any object a workflow without changing the object's
+    model.
+
+    **Attributes:**
+
+    content
+        The object for which the workflow is stored. This can be any instance of
+        a Django model.
+
+    workflow
+        The workflow which is assigned to an object. This needs to be a workflow
+        instance.
+    """
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="workflow_object", blank=True, null=True)
+    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+    workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="wors")
+
+    class Meta:
+        unique_together = ("content_type", "content_id")
+
+    def __unicode__(self):
+        return "%s %s - %s" % (self.content_type.name, self.content_id, self.workflow.name)
+
+class WorkflowModelRelation(models.Model):
+    """Stores an workflow for a model (ContentType).
+
+    Provides a way to give any object a workflow without changing the model.
+
+    **Attributes:**
+
+    Content Type
+        The content type for which the workflow is stored. This can be any
+        instance of a Django model.
+
+    workflow
+        The workflow which is assigned to an object. This needs to be a
+        workflow instance.
+    """
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content Type"), unique=True)
+    workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="wmrs")
+
+    def __unicode__(self):
+        return "%s - %s" % (self.content_type.name, self.workflow.name)
+
+# Permissions relation #######################################################
+
+class WorkflowPermissionRelation(models.Model):
+    """Stores the permissions for which a workflow is responsible.
+
+    **Attributes:**
+
+    workflow
+        The workflow which is responsible for the permissions. Needs to be a
+        Workflow instance.
+
+    permission
+        The permission for which the workflow is responsible. Needs to be a
+        Permission instance.
+    """
+    workflow = models.ForeignKey(Workflow)
+    permission = models.ForeignKey(Permission, related_name="permissions")
+
+    class Meta:
+        unique_together = ("workflow", "permission")
+
+    def __unicode__(self):
+        return "%s %s" % (self.workflow.name, self.permission.name)
+
+class StateInheritanceBlock(models.Model):
+    """Stores inheritance block for state and permission.
+
+    **Attributes:**
+
+    state
+        The state for which the inheritance is blocked. Needs to be a State
+        instance.
+
+    permission
+        The permission for which the instance is blocked. Needs to be a
+        Permission instance.
+    """
+    state = models.ForeignKey(State, verbose_name=_(u"State"))
+    permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"))
+
+    def __unicode__(self):
+        return "%s %s" % (self.state.name, self.permission.name)
+
+class StatePermissionRelation(models.Model):
+    """Stores granted permission for state and role.
+
+    **Attributes:**
+
+    state
+        The state for which the role has the permission. Needs to be a State
+        instance.
+
+    permission
+        The permission for which the workflow is responsible. Needs to be a
+        Permission instance.
+
+    role
+        The role for which the state has the permission. Needs to be a lfc
+        Role instance.
+    """
+    state = models.ForeignKey(State, verbose_name=_(u"State"))
+    permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"))
+    role = models.ForeignKey(Role, verbose_name=_(u"Role"))
+
+    def __unicode__(self):
+        return "%s %s %s" % (self.state.name, self.role.name, self.permission.name)
\ No newline at end of file
diff --git a/workflows/templates/workflows/transitions.html b/workflows/templates/workflows/transitions.html
new file mode 100644
index 000000000..635cbba02
--- /dev/null
+++ b/workflows/templates/workflows/transitions.html
@@ -0,0 +1,10 @@
+{% load i18n %}
+<label>{{ state.name }}</label>
+<select name="transition">
+    {% for transition in transitions %}
+        <option {% if transition.selected %}selected="selected"{% endif %}
+                value="{{ transition.id }}">
+            {{ transition.name }}
+        </option>
+    {% endfor %}
+</select>
diff --git a/workflows/templatetags/__init__.py b/workflows/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/workflows/templatetags/workflows_tags.py b/workflows/templatetags/workflows_tags.py
new file mode 100644
index 000000000..e95357a32
--- /dev/null
+++ b/workflows/templatetags/workflows_tags.py
@@ -0,0 +1,18 @@
+# django imports
+from django import template
+
+# workflows imports
+import workflows.utils
+
+register = template.Library()
+
+@register.inclusion_tag('workflows/transitions.html', takes_context=True)
+def transitions(context, obj):
+    """
+    """
+    request = context.get("request")
+    
+    return {
+        "transitions" : workflows.utils.get_allowed_transitions(obj, request.user),
+        "state" : workflows.utils.get_state(obj),
+    }
diff --git a/workflows/tests.py b/workflows/tests.py
new file mode 100644
index 000000000..2fda0ad4b
--- /dev/null
+++ b/workflows/tests.py
@@ -0,0 +1,600 @@
+# django imports
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.flatpages.models import FlatPage
+from django.test import TestCase
+from django.contrib.auth.models import User
+from django.contrib.sessions.backends.file import SessionStore
+from django.core.handlers.wsgi import WSGIRequest
+from django.test.client import Client
+
+# workflows import
+import permissions.utils
+import workflows.utils
+from workflows.models import State
+from workflows.models import StateInheritanceBlock
+from workflows.models import StatePermissionRelation
+from workflows.models import StateObjectRelation
+from workflows.models import Transition
+from workflows.models import Workflow
+from workflows.models import WorkflowModelRelation
+from workflows.models import WorkflowObjectRelation
+from workflows.models import WorkflowPermissionRelation
+
+class WorkflowTestCase(TestCase):
+    """Tests a simple workflow without permissions.
+    """
+    def setUp(self):
+        """
+        """
+        create_workflow(self)
+
+    def test_get_states(self):
+        """
+        """
+        states = self.w.states.all()
+        self.assertEqual(states[0], self.private)
+        self.assertEqual(states[1], self.public)
+
+    def test_unicode(self):
+        """
+        """
+        self.assertEqual(self.w.__unicode__(), u"Standard")
+
+class PermissionsTestCase(TestCase):
+    """Tests a simple workflow with permissions.
+    """
+    def setUp(self):
+        """
+        """
+        create_workflow(self)
+
+        # Register roles
+        self.anonymous = permissions.utils.register_role("Anonymous")
+        self.owner = permissions.utils.register_role("Owner")
+
+        self.user = User.objects.create(username="john")
+        permissions.utils.add_role(self.user, self.owner)
+
+        # Example content type
+        self.page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+
+        # Registers permissions
+        self.view = permissions.utils.register_permission("View", "view")
+        self.edit = permissions.utils.register_permission("Edit", "edit")
+
+        # Add all permissions which are managed by the workflow
+        wpr = WorkflowPermissionRelation.objects.create(workflow=self.w, permission=self.view)
+        wpr = WorkflowPermissionRelation.objects.create(workflow=self.w, permission=self.edit)
+
+        # Add permissions for single states
+        spr = StatePermissionRelation.objects.create(state=self.public, permission=self.view, role=self.owner)
+        spr = StatePermissionRelation.objects.create(state=self.private, permission=self.view, role=self.owner)
+        spr = StatePermissionRelation.objects.create(state=self.private, permission=self.edit, role=self.owner)
+
+        # Add inheritance block for single states
+        sib = StateInheritanceBlock.objects.create(state=self.private, permission=self.view)
+        sib = StateInheritanceBlock.objects.create(state=self.private, permission=self.edit)
+        sib = StateInheritanceBlock.objects.create(state=self.public, permission=self.edit)
+
+        workflows.utils.set_workflow(self.page_1, self.w)
+
+    def test_set_state(self):
+        """
+        """
+        # Permissions
+        result = permissions.utils.has_permission(self.page_1, self.user, "edit")
+        self.assertEqual(result, True)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, True)
+
+        # Inheritance
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, False)
+
+        result = permissions.utils.is_inherited(self.page_1, "edit")
+        self.assertEqual(result, False)
+
+        # Change state
+        workflows.utils.set_state(self.page_1, self.public)
+
+        # Permissions
+        result = permissions.utils.has_permission(self.page_1, self.user, "edit")
+        self.assertEqual(result, False)
+
+        result = permissions.utils.has_permission(self.page_1, self.user, "view")
+        self.assertEqual(result, True)
+
+        # Inheritance
+        result = permissions.utils.is_inherited(self.page_1, "view")
+        self.assertEqual(result, True)
+
+        result = permissions.utils.is_inherited(self.page_1, "edit")
+        self.assertEqual(result, False)
+
+    def test_set_initial_state(self):
+        """
+        """
+        state = workflows.utils.get_state(self.page_1)
+        self.assertEqual(state.name, self.private.name)
+
+        workflows.utils.do_transition(self.page_1, self.make_public, self.user)
+        state = workflows.utils.get_state(self.page_1)
+        self.assertEqual(state.name, self.public.name)
+
+        workflows.utils.set_initial_state(self.page_1)
+        state = workflows.utils.get_state(self.page_1)
+        self.assertEqual(state.name, self.private.name)
+
+    def test_do_transition(self):
+        """
+        """
+        state = workflows.utils.get_state(self.page_1)
+        self.assertEqual(state.name, self.private.name)
+
+        # by transition
+        workflows.utils.do_transition(self.page_1, self.make_public, self.user)
+
+        state = workflows.utils.get_state(self.page_1)
+        self.assertEqual(state.name, self.public.name)
+
+        # by name
+        workflows.utils.do_transition(self.page_1, "Make private", self.user)
+
+        state = workflows.utils.get_state(self.page_1)
+        self.assertEqual(state.name, self.private.name)
+
+        # name which does not exist
+        result = workflows.utils.do_transition(self.page_1, "Make pending", self.user)
+        self.assertEqual(result, False)
+
+        wrong = Transition.objects.create(name="Wrong", workflow=self.w, destination = self.public)
+
+        # name which does not exist
+        result = workflows.utils.do_transition(self.page_1, wrong, self.user)
+        self.assertEqual(result, False)
+
+class UtilsTestCase(TestCase):
+    """Tests various methods of the utils module.
+    """
+    def setUp(self):
+        """
+        """
+        create_workflow(self)
+        self.user = User.objects.create()
+
+    def test_workflow(self):
+        """
+        """
+        workflows.utils.set_workflow(self.user, self.w)
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+    def test_state(self):
+        """
+        """
+        result = workflows.utils.get_state(self.user)
+        self.assertEqual(result, None)
+
+        workflows.utils.set_workflow(self.user, self.w)
+        result = workflows.utils.get_state(self.user)
+        self.assertEqual(result, self.w.initial_state)
+
+    def test_set_workflow_1(self):
+        """Set worklow by object
+        """
+        ctype = ContentType.objects.get_for_model(self.user)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, None)
+
+        wp = Workflow.objects.create(name="Portal")
+
+        # Set for model
+        workflows.utils.set_workflow_for_model(ctype, wp)
+
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, wp)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, wp)
+
+        # Set for object
+        workflows.utils.set_workflow_for_object(self.user, self.w)
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+        # The model still have wp
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, wp)
+
+    def test_set_workflow_2(self):
+        """Set worklow by name
+        """
+        ctype = ContentType.objects.get_for_model(self.user)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, None)
+
+        wp = Workflow.objects.create(name="Portal")
+
+        # Set for model
+        workflows.utils.set_workflow_for_model(ctype, "Portal")
+
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, wp)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, wp)
+
+        # Set for object
+        workflows.utils.set_workflow_for_object(self.user, "Standard")
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+        # The model still have wp
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, wp)
+
+        # Workflow which does not exist
+        result = workflows.utils.set_workflow_for_model(ctype, "Wrong")
+        self.assertEqual(result, False)
+
+        result = workflows.utils.set_workflow_for_object(self.user, "Wrong")
+        self.assertEqual(result, False)
+
+    def test_get_objects_for_workflow_1(self):
+        """Workflow is added to object.
+        """
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [])
+
+        workflows.utils.set_workflow(self.user, self.w)
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [self.user])
+
+    def test_get_objects_for_workflow_2(self):
+        """Workflow is added to content type.
+        """
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [])
+
+        ctype = ContentType.objects.get_for_model(self.user)
+        workflows.utils.set_workflow(ctype, self.w)
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [self.user])
+
+    def test_get_objects_for_workflow_3(self):
+        """Workflow is added to content type and object.
+        """
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [])
+
+        workflows.utils.set_workflow(self.user, self.w)
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [self.user])
+
+        ctype = ContentType.objects.get_for_model(self.user)
+        workflows.utils.set_workflow(ctype, self.w)
+        result = workflows.utils.get_objects_for_workflow(self.w)
+        self.assertEqual(result, [self.user])
+
+    def test_get_objects_for_workflow_4(self):
+        """Get workflow by name
+        """
+        result = workflows.utils.get_objects_for_workflow("Standard")
+        self.assertEqual(result, [])
+
+        workflows.utils.set_workflow(self.user, self.w)
+        result = workflows.utils.get_objects_for_workflow("Standard")
+        self.assertEqual(result, [self.user])
+
+        # Workflow which does not exist
+        result = workflows.utils.get_objects_for_workflow("Wrong")
+        self.assertEqual(result, [])
+
+    def test_remove_workflow_from_model(self):
+        """
+        """
+        ctype = ContentType.objects.get_for_model(self.user)
+
+        result = workflows.utils.get_workflow(ctype)
+        self.assertEqual(result, None)
+
+        workflows.utils.set_workflow_for_model(ctype, self.w)
+
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, self.w)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+        workflows.utils.remove_workflow_from_model(ctype)
+
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, None)
+
+        result = workflows.utils.get_workflow_for_object(self.user)
+        self.assertEqual(result, None)
+
+    def test_remove_workflow_from_object(self):
+        """
+        """
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, None)
+
+        workflows.utils.set_workflow_for_object(self.user, self.w)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+        result = workflows.utils.remove_workflow_from_object(self.user)
+        self.assertEqual(result, None)
+
+    def test_remove_workflow_1(self):
+        """Removes workflow from model
+        """
+        ctype = ContentType.objects.get_for_model(self.user)
+
+        result = workflows.utils.get_workflow(ctype)
+        self.assertEqual(result, None)
+
+        workflows.utils.set_workflow_for_model(ctype, self.w)
+
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, self.w)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+        workflows.utils.remove_workflow(ctype)
+
+        result = workflows.utils.get_workflow_for_model(ctype)
+        self.assertEqual(result, None)
+
+        result = workflows.utils.get_workflow_for_object(self.user)
+        self.assertEqual(result, None)
+
+    def test_remove_workflow_2(self):
+        """Removes workflow from object
+        """
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, None)
+
+        workflows.utils.set_workflow_for_object(self.user, self.w)
+
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, self.w)
+
+        result = workflows.utils.remove_workflow(self.user)
+        self.assertEqual(result, None)
+
+    def test_get_allowed_transitions(self):
+        """Tests get_allowed_transitions method
+        """
+        page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+        role_1 = permissions.utils.register_role("Role 1")
+        permissions.utils.add_role(self.user, role_1)
+
+        view = permissions.utils.register_permission("Publish", "publish")
+
+        transitions = self.private.get_allowed_transitions(page_1, self.user)
+        self.assertEqual(len(transitions), 1)
+
+        # protect the transition with a permission
+        self.make_public.permission = view        
+        self.make_public.save()
+
+        # user has no transition
+        transitions = self.private.get_allowed_transitions(page_1, self.user)
+        self.assertEqual(len(transitions), 0)
+
+        # grant permission
+        permissions.utils.grant_permission(page_1, role_1, view)
+
+        # user has transition again
+        transitions = self.private.get_allowed_transitions(page_1, self.user)
+        self.assertEqual(len(transitions), 1)
+
+    def test_get_workflow_for_object(self):
+        """
+        """
+        result = workflows.utils.get_workflow(self.user)
+        self.assertEqual(result, None)
+        
+        # Set workflow for a user
+        workflows.utils.set_workflow_for_object(self.user, self.w)
+        
+        # Get workflow for the user        
+        result = workflows.utils.get_workflow_for_object(self.user)
+        self.assertEqual(result, self.w)
+
+        # Set workflow for a FlatPage
+        page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+        workflows.utils.set_workflow_for_object(page_1, self.w)
+
+        result = workflows.utils.get_workflow_for_object(self.user)
+        self.assertEqual(result, self.w)
+
+        result = workflows.utils.get_workflow_for_object(page_1)
+        self.assertEqual(result, self.w)
+
+class StateTestCase(TestCase):
+    """Tests the State model
+    """
+    def setUp(self):
+        """
+        """
+        create_workflow(self)
+        self.user = User.objects.create()
+        self.role_1 = permissions.utils.register_role("Role 1")
+        permissions.utils.add_role(self.user, self.role_1)
+        self.page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+
+    def test_unicode(self):
+        """
+        """
+        self.assertEqual(self.private.__unicode__(), u"Private (Standard)")
+
+    def test_transitions(self):
+        """
+        """
+        transitions = self.public.transitions.all()
+        self.assertEqual(len(transitions), 1)
+        self.assertEqual(transitions[0], self.make_private)
+
+        transitions = self.private.transitions.all()
+        self.assertEqual(len(transitions), 1)
+        self.assertEqual(transitions[0], self.make_public)
+
+    def test_get_transitions(self):
+        """
+        """
+        transitions = self.private.get_allowed_transitions(self.page_1, self.user)
+        self.assertEqual(len(transitions), 1)
+        self.assertEqual(transitions[0], self.make_public)
+
+        transitions = self.public.get_allowed_transitions(self.page_1, self.user)
+        self.assertEqual(len(transitions), 1)
+        self.assertEqual(transitions[0], self.make_private)
+
+    def test_get_allowed_transitions(self):
+        """
+        """
+        self.view = permissions.utils.register_permission("Publish", "publish")
+        transitions = self.private.get_allowed_transitions(self.page_1, self.user)
+        self.assertEqual(len(transitions), 1)
+
+        # protect the transition with a permission
+        self.make_public.permission = self.view
+        self.make_public.save()
+
+        # user has no transition
+        transitions = self.private.get_allowed_transitions(self.page_1, self.user)
+        self.assertEqual(len(transitions), 0)
+
+        # grant permission
+        permissions.utils.grant_permission(self.page_1, self.role_1, self.view)
+
+        # user has transition again
+        transitions = self.private.get_allowed_transitions(self.page_1, self.user)
+        self.assertEqual(len(transitions), 1)
+
+class TransitionTestCase(TestCase):
+    """Tests the Transition model
+    """
+    def setUp(self):
+        """
+        """
+        create_workflow(self)
+
+    def test_unicode(self):
+        """
+        """
+        self.assertEqual(self.make_private.__unicode__(), u"Make private")
+
+class RelationsTestCase(TestCase):
+    """Tests various Relations models.
+    """
+    def setUp(self):
+        """
+        """
+        create_workflow(self)
+        self.page_1 = FlatPage.objects.create(url="/page-1/", title="Page 1")
+
+    def test_unicode(self):
+        """
+        """
+        # WorkflowObjectRelation
+        workflows.utils.set_workflow(self.page_1, self.w)
+        wor = WorkflowObjectRelation.objects.filter()[0]
+        self.assertEqual(wor.__unicode__(), "flat page 1 - Standard")
+
+        # StateObjectRelation
+        workflows.utils.set_state(self.page_1, self.public)
+        sor = StateObjectRelation.objects.filter()[0]
+        self.assertEqual(sor.__unicode__(), "flat page 1 - Public")
+
+        # WorkflowModelRelation
+        ctype = ContentType.objects.get_for_model(self.page_1)
+        workflows.utils.set_workflow(ctype, self.w)
+        wmr = WorkflowModelRelation.objects.filter()[0]
+        self.assertEqual(wmr.__unicode__(), "flat page - Standard")
+
+        # WorkflowPermissionRelation
+        self.view = permissions.utils.register_permission("View", "view")
+        wpr = WorkflowPermissionRelation.objects.create(workflow=self.w, permission=self.view)
+        self.assertEqual(wpr.__unicode__(), "Standard View")
+
+        # StatePermissionRelation
+        self.owner = permissions.utils.register_role("Owner")
+        spr = StatePermissionRelation.objects.create(state=self.public, permission=self.view, role=self.owner)
+        self.assertEqual(spr.__unicode__(), "Public Owner View")
+
+# Helpers ####################################################################
+
+def create_workflow(self):
+    self.w = Workflow.objects.create(name="Standard")
+
+    self.private = State.objects.create(name="Private", workflow= self.w)
+    self.public = State.objects.create(name="Public", workflow= self.w)
+
+    self.make_public = Transition.objects.create(name="Make public", workflow=self.w, destination = self.public)
+    self.make_private = Transition.objects.create(name="Make private", workflow=self.w, destination = self.private)
+
+    self.private.transitions.add(self.make_public)
+    self.public.transitions.add(self.make_private)
+
+    self.w.initial_state = self.private
+    self.w.save()
+
+# Taken from "http://www.djangosnippets.org/snippets/963/"
+class RequestFactory(Client):
+    """
+    Class that lets you create mock Request objects for use in testing.
+
+    Usage:
+
+    rf = RequestFactory()
+    get_request = rf.get('/hello/')
+    post_request = rf.post('/submit/', {'foo': 'bar'})
+
+    This class re-uses the django.test.client.Client interface, docs here:
+    http://www.djangoproject.com/documentation/testing/#the-test-client
+
+    Once you have a request object you can pass it to any view function,
+    just as if that view had been hooked up using a URLconf.
+
+    """
+    def request(self, **request):
+        """
+        Similar to parent class, but returns the request object as soon as it
+        has created it.
+        """
+        environ = {
+            'HTTP_COOKIE': self.cookies,
+            'PATH_INFO': '/',
+            'QUERY_STRING': '',
+            'REQUEST_METHOD': 'GET',
+            'SCRIPT_NAME': '',
+            'SERVER_NAME': 'testserver',
+            'SERVER_PORT': 80,
+            'SERVER_PROTOCOL': 'HTTP/1.1',
+        }
+        environ.update(self.defaults)
+        environ.update(request)
+        return WSGIRequest(environ)
+
+def create_request():
+    """
+    """
+    rf = RequestFactory()
+    request = rf.get('/')
+    request.session = SessionStore()
+
+    user = User()
+    user.is_superuser = True
+    user.save()
+    request.user = user
+
+    return request
diff --git a/workflows/urls.py b/workflows/urls.py
new file mode 100644
index 000000000..949c2346c
--- /dev/null
+++ b/workflows/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+
+# URL patterns for django-workflows
+
+urlpatterns = patterns('django-workflows.views',
+  # Add url patterns here
+)
diff --git a/workflows/utils.py b/workflows/utils.py
new file mode 100644
index 000000000..647d4c88f
--- /dev/null
+++ b/workflows/utils.py
@@ -0,0 +1,330 @@
+# django imports
+from django.contrib.contenttypes.models import ContentType
+
+# workflows imports
+from permissions.models import ObjectPermission
+from permissions.models import ObjectPermissionInheritanceBlock
+from workflows.models import StateInheritanceBlock
+from workflows.models import StateObjectRelation
+from workflows.models import StatePermissionRelation
+from workflows.models import Transition
+from workflows.models import Workflow
+from workflows.models import WorkflowModelRelation
+from workflows.models import WorkflowObjectRelation
+from workflows.models import WorkflowPermissionRelation
+
+# permissions imports
+import permissions.utils
+
+def get_objects_for_workflow(workflow):
+    """Returns all objects which have passed workflow.
+
+    **Parameters:**
+
+    workflow
+        The workflow for which the objects are returned. Can be a Workflow
+        instance or a string with the workflow name.
+    """
+    if not isinstance(workflow, Workflow):
+        try:
+            workflow = Workflow.objects.get(name=workflow)
+        except Workflow.DoesNotExist:
+            return []
+
+    return workflow.get_objects()
+
+def remove_workflow(ctype_or_obj):
+    """Removes the workflow from the passed content type or object. After this
+    function has been called the content type or object has no workflow
+    anymore.
+
+    If ctype_or_obj is an object the workflow is removed from the object not
+    from the belonging content type.
+
+    If ctype_or_obj is an content type the workflow is removed from the
+    content type not from instances of the content type (if they have an own
+    workflow)
+
+    ctype_or_obj
+        The content type or the object to which the passed workflow should be
+        set. Can be either a ContentType instance or any LFC Django model
+        instance.
+    """
+    if isinstance(ctype_or_obj, ContentType):
+        remove_workflow_from_model(ctype_or_obj)
+    else:
+        remove_workflow_from_object(ctype_or_obj)
+
+def remove_workflow_from_model(ctype):
+    """Removes the workflow from passed content type. After this function has
+    been called the content type has no workflow anymore (the instances might
+    have own ones).
+
+    ctype
+        The content type from which the passed workflow should be removed.
+        Must be a ContentType instance.
+    """
+    # First delete all states, inheritance blocks and permissions from ctype's
+    # instances which have passed workflow.
+    workflow = get_workflow_for_model(ctype)
+    for obj in get_objects_for_workflow(workflow):
+        try:
+            ctype = ContentType.objects.get_for_model(obj)
+            sor = StateObjectRelation.objects.get(content_id=obj.id, content_type=ctype)
+        except StateObjectRelation.DoesNotExist:
+            pass
+        else:
+            sor.delete()
+
+        # Reset all permissions
+        permissions.utils.reset(obj)
+
+    try:
+        wmr = WorkflowModelRelation.objects.get(content_type=ctype)
+    except WorkflowModelRelation.DoesNotExist:
+        pass
+    else:
+        wmr.delete()
+
+def remove_workflow_from_object(obj):
+    """Removes the workflow from the passed object. After this function has
+    been called the object has no *own* workflow anymore (it might have one
+    via its content type).
+
+    obj
+        The object from which the passed workflow should be set. Must be a
+        Django Model instance.
+    """
+    try:
+        wor = WorkflowObjectRelation.objects.get(content_type=obj)
+    except WorkflowObjectRelation.DoesNotExist:
+        pass
+    else:
+        wor.delete()
+
+    # Reset all permissions
+    permissions.utils.reset(obj)
+
+    # Set initial of object's content types workflow (if there is one)
+    set_initial_state(obj)
+
+def set_workflow(ctype_or_obj, workflow):
+    """Sets the workflow for passed content type or object. See the specific
+    methods for more information.
+
+    **Parameters:**
+
+    workflow
+        The workflow which should be set to the object or model.
+
+    ctype_or_obj
+        The content type or the object to which the passed workflow should be
+        set. Can be either a ContentType instance or any Django model
+        instance.
+    """
+    return workflow.set_to(ctype_or_obj)
+
+def set_workflow_for_object(obj, workflow):
+    """Sets the passed workflow to the passed object.
+
+    If the object has already the given workflow nothing happens. Otherwise
+    the object gets the passed workflow and the state is set to the workflow's
+    initial state.
+
+    **Parameters:**
+
+    workflow
+        The workflow which should be set to the object. Can be a Workflow
+        instance or a string with the workflow name.
+
+    obj
+        The object which gets the passed workflow.
+    """
+    if isinstance(workflow, Workflow) == False:
+        try:
+            workflow = Workflow.objects.get(name=workflow)
+        except Workflow.DoesNotExist:
+            return False
+
+    workflow.set_to_object(obj)
+
+def set_workflow_for_model(ctype, workflow):
+    """Sets the passed workflow to the passed content type. If the content
+    type has already an assigned workflow the workflow is overwritten.
+
+    The objects which had the old workflow must updated explicitely.
+
+    **Parameters:**
+
+    workflow
+        The workflow which should be set to passend content type. Must be a
+        Workflow instance.
+
+    ctype
+        The content type to which the passed workflow should be assigned. Can
+        be any Django model instance
+    """
+    if isinstance(workflow, Workflow) == False:
+        try:
+            workflow = Workflow.objects.get(name=workflow)
+        except Workflow.DoesNotExist:
+            return False
+
+    workflow.set_to_model(ctype)
+
+def get_workflow(obj):
+    """Returns the workflow for the passed object. It takes it either from
+    the passed object or - if the object doesn't have a workflow - from the
+    passed object's ContentType.
+
+    **Parameters:**
+
+    object
+        The object for which the workflow should be returend. Can be any
+        Django model instance.
+    """
+    workflow = get_workflow_for_object(obj)
+    if workflow is not None:
+        return workflow
+
+    ctype = ContentType.objects.get_for_model(obj)
+    return get_workflow_for_model(ctype)
+
+def get_workflow_for_object(obj):
+    """Returns the workflow for the passed object.
+
+    **Parameters:**
+
+    obj
+        The object for which the workflow should be returned. Can be any
+        Django model instance.
+    """
+    try:
+        ctype = ContentType.objects.get_for_model(obj)
+        wor = WorkflowObjectRelation.objects.get(content_id=obj.id, content_type=ctype)
+    except WorkflowObjectRelation.DoesNotExist:
+        return None
+    else:
+        return wor.workflow
+
+def get_workflow_for_model(ctype):
+    """Returns the workflow for the passed model.
+
+    **Parameters:**
+
+    ctype
+        The content type for which the workflow should be returned. Must be
+        a Django ContentType instance.
+    """
+    try:
+        wor = WorkflowModelRelation.objects.get(content_type=ctype)
+    except WorkflowModelRelation.DoesNotExist:
+        return None
+    else:
+        return wor.workflow
+
+def get_state(obj):
+    """Returns the current workflow state for the passed object.
+
+    **Parameters:**
+
+    obj
+        The object for which the workflow state should be returned. Can be any
+        Django model instance.
+    """
+    ctype = ContentType.objects.get_for_model(obj)
+    try:
+        sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.id)
+    except StateObjectRelation.DoesNotExist:
+        return None
+    else:
+        return sor.state
+
+def set_state(obj, state):
+    """Sets the state for the passed object to the passed state and updates
+    the permissions for the object.
+
+    **Parameters:**
+
+    obj
+        The object for which the workflow state should be set. Can be any
+        Django model instance.
+
+    state
+        The state which should be set to the passed object.
+    """
+    ctype = ContentType.objects.get_for_model(obj)
+    try:
+        sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.id)
+    except StateObjectRelation.DoesNotExist:
+        sor = StateObjectRelation.objects.create(content=obj, state=state)
+    else:
+        sor.state = state
+        sor.save()
+    update_permissions(obj)
+
+def set_initial_state(obj):
+    """Sets the initial state to the passed object.
+    """
+    wf = get_workflow(obj)
+    if wf is not None:
+        set_state(obj, wf.get_initial_state())
+
+def get_allowed_transitions(obj, user):
+    """Returns all allowed transitions for passed object and user. Takes the
+    current state of the object into account.
+
+    **Parameters:**
+
+    obj
+        The object for which the transitions should be returned.
+
+    user
+        The user for which the transitions are allowed.
+    """
+    state = get_state(obj)
+    if state is None:
+        return []
+
+    return state.get_allowed_transitions(obj, user)
+
+def do_transition(obj, transition, user):
+    """Processes the passed transition to the passed object (if allowed).
+    """
+    if not isinstance(transition, Transition):
+        try:
+            transition = Transition.objects.get(name=transition)
+        except Transition.DoesNotExist:
+            return False
+
+    transitions = get_allowed_transitions(obj, user)
+    if transition in transitions:
+        set_state(obj, transition.destination)
+        return True
+    else:
+        return False
+
+def update_permissions(obj):
+    """Updates the permissions of the passed object according to the object's
+    current workflow state.
+    """
+    workflow = get_workflow(obj)
+    state = get_state(obj)
+
+    # Remove all permissions for the workflow
+    ct = ContentType.objects.get_for_model(obj)
+    ps = [wpr.permission for wpr in WorkflowPermissionRelation.objects.filter(workflow=workflow)]
+
+    ObjectPermission.objects.filter(content_type = ct, content_id=obj.id, permission__in=ps).delete()
+            
+    # Grant permission for the state
+    for spr in StatePermissionRelation.objects.filter(state=state):
+        permissions.utils.grant_permission(obj, spr.role, spr.permission)
+    
+    # Remove all inheritance blocks from the object
+    ObjectPermissionInheritanceBlock.objects.filter(
+        content_type = ct, content_id=obj.id, permission__in=ps).delete()
+    
+    # Add inheritance blocks of this state to the object
+    for sib in StateInheritanceBlock.objects.filter(state=state):
+        permissions.utils.add_inheritance_block(obj, sib.permission)
\ No newline at end of file
diff --git a/workflows/views.py b/workflows/views.py
new file mode 100644
index 000000000..e69de29bb

From 51bb64b7eee7cca8569b133a1b7cdf2a2fcc7d84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Tue, 2 Nov 2010 16:11:59 +0000
Subject: [PATCH 02/57] Changed object.id to object.pk cause on ietf models
 there are tables without id. See #535  - Legacy-Id: 2599

---
 permissions/models.py |  6 +++---
 permissions/utils.py  | 38 +++++++++++++++++++-------------------
 workflows/utils.py    | 14 +++++++-------
 3 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/permissions/models.py b/permissions/models.py
index fdd96773a..f012f9f25 100644
--- a/permissions/models.py
+++ b/permissions/models.py
@@ -128,7 +128,7 @@ class Role(models.Model):
         if content:
             ctype = ContentType.objects.get_for_model(content)
             prrs = PrincipalRoleRelation.objects.filter(role=self,
-                content_id__in = (None, content.id),
+                content_id__in = (None, content.pk),
                 content_type__in = (None, ctype)).exclude(group=None)
         else:
             prrs = PrincipalRoleRelation.objects.filter(role=self,
@@ -143,7 +143,7 @@ class Role(models.Model):
         if content:
             ctype = ContentType.objects.get_for_model(content)
             prrs = PrincipalRoleRelation.objects.filter(role=self,
-                content_id__in = (None, content.id),
+                content_id__in = (None, content.pk),
                 content_type__in = (None, ctype)).exclude(user=None)
         else:
             prrs = PrincipalRoleRelation.objects.filter(role=self,
@@ -190,4 +190,4 @@ class PrincipalRoleRelation(models.Model):
         else:
             self.group = principal
 
-    principal = property(get_principal, set_principal)
\ No newline at end of file
+    principal = property(get_principal, set_principal)
diff --git a/permissions/utils.py b/permissions/utils.py
index 322076fb2..06183e946 100644
--- a/permissions/utils.py
+++ b/permissions/utils.py
@@ -62,13 +62,13 @@ def add_local_role(obj, principal, role):
     ctype = ContentType.objects.get_for_model(obj)
     if isinstance(principal, User):
         try:
-            ppr = PrincipalRoleRelation.objects.get(user=principal, role=role, content_id=obj.id, content_type=ctype)
+            ppr = PrincipalRoleRelation.objects.get(user=principal, role=role, content_id=obj.pk, content_type=ctype)
         except PrincipalRoleRelation.DoesNotExist:
             PrincipalRoleRelation.objects.create(user=principal, role=role, content=obj)
             return True
     else:
         try:
-            ppr = PrincipalRoleRelation.objects.get(group=principal, role=role, content_id=obj.id, content_type=ctype)
+            ppr = PrincipalRoleRelation.objects.get(group=principal, role=role, content_id=obj.pk, content_type=ctype)
         except PrincipalRoleRelation.DoesNotExist:
             PrincipalRoleRelation.objects.create(group=principal, role=role, content=obj)
             return True
@@ -120,10 +120,10 @@ def remove_local_role(obj, principal, role):
 
         if isinstance(principal, User):
             ppr = PrincipalRoleRelation.objects.get(
-                user=principal, role=role, content_id=obj.id, content_type=ctype)
+                user=principal, role=role, content_id=obj.pk, content_type=ctype)
         else:
             ppr = PrincipalRoleRelation.objects.get(
-                group=principal, role=role, content_id=obj.id, content_type=ctype)
+                group=principal, role=role, content_id=obj.pk, content_type=ctype)
 
     except PrincipalRoleRelation.DoesNotExist:
         return False
@@ -169,10 +169,10 @@ def remove_local_roles(obj, principal):
 
     if isinstance(principal, User):
         ppr = PrincipalRoleRelation.objects.filter(
-            user=principal, content_id=obj.id, content_type=ctype)
+            user=principal, content_id=obj.pk, content_type=ctype)
     else:
         ppr = PrincipalRoleRelation.objects.filter(
-            group=principal, content_id=obj.id, content_type=ctype)
+            group=principal, content_id=obj.pk, content_type=ctype)
 
     if ppr:
         ppr.delete()
@@ -222,7 +222,7 @@ def get_roles(user, obj=None):
                           FROM permissions_principalrolerelation
                           WHERE (user_id='%s' OR group_id IN (%s))
                           AND content_id='%s'
-                          AND content_type_id='%s'""" % (user.id, groups_ids_str, obj.id, ctype.id))
+                          AND content_type_id='%s'""" % (user.id, groups_ids_str, obj.pk, ctype.id))
 
         for row in cursor.fetchall():
             roles.append(row[0])
@@ -253,10 +253,10 @@ def get_local_roles(obj, principal):
 
     if isinstance(principal, User):
         return [prr.role for prr in PrincipalRoleRelation.objects.filter(
-            user=principal, content_id=obj.id, content_type=ctype)]
+            user=principal, content_id=obj.pk, content_type=ctype)]
     else:
         return [prr.role for prr in PrincipalRoleRelation.objects.filter(
-            group=principal, content_id=obj.id, content_type=ctype)]
+            group=principal, content_id=obj.pk, content_type=ctype)]
 
 # Permissions ################################################################
 
@@ -306,7 +306,7 @@ def grant_permission(obj, role, permission):
 
     ct = ContentType.objects.get_for_model(obj)
     try:
-        ObjectPermission.objects.get(role=role, content_type = ct, content_id=obj.id, permission=permission)
+        ObjectPermission.objects.get(role=role, content_type = ct, content_id=obj.pk, permission=permission)
     except ObjectPermission.DoesNotExist:
         ObjectPermission.objects.create(role=role, content=obj, permission=permission)
 
@@ -337,7 +337,7 @@ def remove_permission(obj, role, permission):
     ct = ContentType.objects.get_for_model(obj)
 
     try:
-        op = ObjectPermission.objects.get(role=role, content_type = ct, content_id=obj.id, permission = permission)
+        op = ObjectPermission.objects.get(role=role, content_type = ct, content_id=obj.pk, permission = permission)
     except ObjectPermission.DoesNotExist:
         return False
 
@@ -362,7 +362,7 @@ def has_permission(obj, user, codename, roles=None):
         If given these roles will be assigned to the user temporarily before
         the permissions are checked.
     """
-    cache_key = "%s-%s-%s" % (obj.content_type, obj.id, codename)
+    cache_key = "%s-%s-%s" % (obj.content_type, obj.pk, codename)
     result = _get_cached_permission(user, cache_key)
     if result is not None:
         return result
@@ -381,7 +381,7 @@ def has_permission(obj, user, codename, roles=None):
     result = False
     while obj is not None:
         p = ObjectPermission.objects.filter(
-            content_type=ct, content_id=obj.id, role__in=roles, permission__codename = codename).values("id")
+            content_type=ct, content_id=obj.pk, role__in=roles, permission__codename = codename).values("id")
 
         if len(p) > 0:
             result = True
@@ -422,7 +422,7 @@ def add_inheritance_block(obj, permission):
 
     ct = ContentType.objects.get_for_model(obj)
     try:
-        ObjectPermissionInheritanceBlock.objects.get(content_type = ct, content_id=obj.id, permission=permission)
+        ObjectPermissionInheritanceBlock.objects.get(content_type = ct, content_id=obj.pk, permission=permission)
     except ObjectPermissionInheritanceBlock.DoesNotExist:
         try:
             result = ObjectPermissionInheritanceBlock.objects.create(content=obj, permission=permission)
@@ -451,7 +451,7 @@ def remove_inheritance_block(obj, permission):
 
     ct = ContentType.objects.get_for_model(obj)
     try:
-        opi = ObjectPermissionInheritanceBlock.objects.get(content_type = ct, content_id=obj.id, permission=permission)
+        opi = ObjectPermissionInheritanceBlock.objects.get(content_type = ct, content_id=obj.pk, permission=permission)
     except ObjectPermissionInheritanceBlock.DoesNotExist:
         return False
 
@@ -473,7 +473,7 @@ def is_inherited(obj, codename):
     ct = ContentType.objects.get_for_model(obj)
     try:
         ObjectPermissionInheritanceBlock.objects.get(
-            content_type=ct, content_id=obj.id, permission__codename = codename)
+            content_type=ct, content_id=obj.pk, permission__codename = codename)
     except ObjectDoesNotExist:
         return True
     else:
@@ -515,8 +515,8 @@ def reset(obj):
     """Resets all permissions and inheritance blocks of passed object.
     """
     ctype = ContentType.objects.get_for_model(obj)
-    ObjectPermissionInheritanceBlock.objects.filter(content_id=obj.id, content_type=ctype).delete()
-    ObjectPermission.objects.filter(content_id=obj.id, content_type=ctype).delete()
+    ObjectPermissionInheritanceBlock.objects.filter(content_id=obj.pk, content_type=ctype).delete()
+    ObjectPermission.objects.filter(content_id=obj.pk, content_type=ctype).delete()
 
 # Registering ################################################################
 
@@ -662,4 +662,4 @@ def _get_cached_permission(user, cache_key):
     """
     permissions = getattr(user, "permissions", None)
     if permissions:
-        return user.permissions.get(cache_key, None)
\ No newline at end of file
+        return user.permissions.get(cache_key, None)
diff --git a/workflows/utils.py b/workflows/utils.py
index 647d4c88f..a51635dce 100644
--- a/workflows/utils.py
+++ b/workflows/utils.py
@@ -70,7 +70,7 @@ def remove_workflow_from_model(ctype):
     for obj in get_objects_for_workflow(workflow):
         try:
             ctype = ContentType.objects.get_for_model(obj)
-            sor = StateObjectRelation.objects.get(content_id=obj.id, content_type=ctype)
+            sor = StateObjectRelation.objects.get(content_id=obj.pk, content_type=ctype)
         except StateObjectRelation.DoesNotExist:
             pass
         else:
@@ -201,7 +201,7 @@ def get_workflow_for_object(obj):
     """
     try:
         ctype = ContentType.objects.get_for_model(obj)
-        wor = WorkflowObjectRelation.objects.get(content_id=obj.id, content_type=ctype)
+        wor = WorkflowObjectRelation.objects.get(content_id=obj.pk, content_type=ctype)
     except WorkflowObjectRelation.DoesNotExist:
         return None
     else:
@@ -234,7 +234,7 @@ def get_state(obj):
     """
     ctype = ContentType.objects.get_for_model(obj)
     try:
-        sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.id)
+        sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.pk)
     except StateObjectRelation.DoesNotExist:
         return None
     else:
@@ -255,7 +255,7 @@ def set_state(obj, state):
     """
     ctype = ContentType.objects.get_for_model(obj)
     try:
-        sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.id)
+        sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.pk)
     except StateObjectRelation.DoesNotExist:
         sor = StateObjectRelation.objects.create(content=obj, state=state)
     else:
@@ -315,7 +315,7 @@ def update_permissions(obj):
     ct = ContentType.objects.get_for_model(obj)
     ps = [wpr.permission for wpr in WorkflowPermissionRelation.objects.filter(workflow=workflow)]
 
-    ObjectPermission.objects.filter(content_type = ct, content_id=obj.id, permission__in=ps).delete()
+    ObjectPermission.objects.filter(content_type = ct, content_id=obj.pk, permission__in=ps).delete()
             
     # Grant permission for the state
     for spr in StatePermissionRelation.objects.filter(state=state):
@@ -323,8 +323,8 @@ def update_permissions(obj):
     
     # Remove all inheritance blocks from the object
     ObjectPermissionInheritanceBlock.objects.filter(
-        content_type = ct, content_id=obj.id, permission__in=ps).delete()
+        content_type = ct, content_id=obj.pk, permission__in=ps).delete()
     
     # Add inheritance blocks of this state to the object
     for sib in StateInheritanceBlock.objects.filter(state=state):
-        permissions.utils.add_inheritance_block(obj, sib.permission)
\ No newline at end of file
+        permissions.utils.add_inheritance_block(obj, sib.permission)

From baf4fa96ecd311cdae7e096b4357b6ade0f4bf0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 3 Nov 2010 16:50:22 +0000
Subject: [PATCH 03/57] Specific models in an IETF Workflow. Annotation tags
 and histories. See #535  - Legacy-Id: 2601

---
 ietf/ietfworkflows/__init__.py |  0
 ietf/ietfworkflows/admin.py    | 12 ++++++++
 ietf/ietfworkflows/models.py   | 50 ++++++++++++++++++++++++++++++++++
 ietf/settings.py               |  1 +
 4 files changed, 63 insertions(+)
 create mode 100644 ietf/ietfworkflows/__init__.py
 create mode 100644 ietf/ietfworkflows/admin.py
 create mode 100644 ietf/ietfworkflows/models.py

diff --git a/ietf/ietfworkflows/__init__.py b/ietf/ietfworkflows/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/ietfworkflows/admin.py b/ietf/ietfworkflows/admin.py
new file mode 100644
index 000000000..dab717d19
--- /dev/null
+++ b/ietf/ietfworkflows/admin.py
@@ -0,0 +1,12 @@
+from django.contrib import admin
+
+from ietf.ietfworkflows.models import AnnotationTag, WGWorkflow
+from workflows.admin import StateInline
+
+class AnnotationTagInline(admin.TabularInline):
+    model = AnnotationTag
+
+class IETFWorkflowAdmin(admin.ModelAdmin):
+    inlines = [StateInline, AnnotationTagInline]
+
+admin.site.register(WGWorkflow, IETFWorkflowAdmin)
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
new file mode 100644
index 000000000..b327e2b68
--- /dev/null
+++ b/ietf/ietfworkflows/models.py
@@ -0,0 +1,50 @@
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from workflows.models import Workflow
+from permissions.models import Permission
+
+
+class ObjectWorkflowHistoryEntry(models.Model):
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="workflow_history", blank=True, null=True)
+    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+
+    from_state = models.CharField(_('From state'), max_length=100)
+    to_state = models.CharField(_('To state'), max_length=100)
+    transition_date = models.DateTimeField(_('Transition date'))
+    comment = models.TextField(_('Comment'))
+
+
+class AnnotationTag(models.Model):
+    name = models.CharField(_(u"Name"), max_length=100)
+    workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="annotation_tags")
+    permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"), blank=True, null=True)
+
+    def __unicode__(self):
+        return self.name
+
+
+class AnnotationTagObjectRelation(models.Model):
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="annotation_tags", blank=True, null=True)
+    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+
+    annotation_tag = models.ForeignKey(AnnotationTag, verbose_name=_(u"Annotation tag"))
+
+
+class ObjectAnnotationTagHistoryEntry(models.Model):
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="annotation_tags_history", blank=True, null=True)
+    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
+    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
+
+    setted = models.TextField(_('Setted tags'), blank=True, null=True)
+    unsetted = models.TextField(_('Unsetted tags'), blank=True, null=True)
+    change_date = models.DateTimeField(_('Change date'))
+    comment = models.TextField(_('Comment'))
+
+
+class WGWorkflow(Workflow):
+    pass
diff --git a/ietf/settings.py b/ietf/settings.py
index e6d7f96c7..2edcbd880 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -133,6 +133,7 @@ INSTALLED_APPS = (
     'ietf.redirects',
     'ietf.idrfc',
     'ietf.wginfo',
+    'ietf.ietfworkflows',
 )
 
 INTERNAL_IPS = (

From 41790b9c21b07fb4e6d0cafc2e68a4dd51b83e96 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 3 Nov 2010 17:37:01 +0000
Subject: [PATCH 04/57] Basic default WG workflow. See #535  - Legacy-Id: 2602

---
 ietf/ietfworkflows/fixtures/initial_data.xml | 177 +++++++++++++++++++
 1 file changed, 177 insertions(+)
 create mode 100644 ietf/ietfworkflows/fixtures/initial_data.xml

diff --git a/ietf/ietfworkflows/fixtures/initial_data.xml b/ietf/ietfworkflows/fixtures/initial_data.xml
new file mode 100644
index 000000000..c58778c05
--- /dev/null
+++ b/ietf/ietfworkflows/fixtures/initial_data.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+    <object pk="8" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed - Issue raised by AD</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="7" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed - Issue raised by WGLC</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="6" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Referencing Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="5" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Referenced Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="4" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Author or Editor Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="3" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting Merge with Other Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="2" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting External Review/Resolution of Issues Raised</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="1" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting Expert Review/Resolution of Issues Raised</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="9" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed - Issue raised by IESG</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="10" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Doc Shepherd Follow-Up Underway</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="11" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Other - see Comment Log</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="1" model="ietfworkflows.wgworkflow">
+    </object>
+    <object pk="1" model="workflows.workflow">
+        <field type="CharField" name="name">Default WG Workflow</field>
+        <field to="workflows.state" name="initial_state" rel="ManyToOneRel">11</field>
+    </object>
+    <object pk="12" model="workflows.state">
+        <field type="CharField" name="name">Adopted by a WG</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
+    </object>
+    <object pk="13" model="workflows.state">
+        <field type="CharField" name="name">Adopted for WG Info Only</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="11" model="workflows.state">
+        <field type="CharField" name="name">Call For Adoption By WG Issued</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="10"></object><object pk="11"></object></field>
+    </object>
+    <object pk="16" model="workflows.state">
+        <field type="CharField" name="name">Dead WG Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
+    </object>
+    <object pk="17" model="workflows.state">
+        <field type="CharField" name="name">In WG Last Call</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object><object pk="17"></object><object pk="18"></object></field>
+    </object>
+    <object pk="15" model="workflows.state">
+        <field type="CharField" name="name">Parked WG Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
+    </object>
+    <object pk="20" model="workflows.state">
+        <field type="CharField" name="name">Submitted to IESG for Publication</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
+    </object>
+    <object pk="18" model="workflows.state">
+        <field type="CharField" name="name">Waiting for WG Chair Go-Ahead</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="16"></object><object pk="17"></object></field>
+    </object>
+    <object pk="19" model="workflows.state">
+        <field type="CharField" name="name">WG Consensus: Waiting for Write-Up</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="15"></object></field>
+    </object>
+    <object pk="14" model="workflows.state">
+        <field type="CharField" name="name">WG Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="13"></object><object pk="14"></object><object pk="16"></object><object pk="17"></object></field>
+    </object>
+    <object pk="16" model="workflows.transition">
+        <field type="CharField" name="name">Raise last call</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">17</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="15" model="workflows.transition">
+        <field type="CharField" name="name">Submit to IESG</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">20</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="14" model="workflows.transition">
+        <field type="CharField" name="name">Die</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">16</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="13" model="workflows.transition">
+        <field type="CharField" name="name">Park</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">15</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="12" model="workflows.transition">
+        <field type="CharField" name="name">Develop</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">14</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="11" model="workflows.transition">
+        <field type="CharField" name="name">Adopt for WG info only</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">13</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="10" model="workflows.transition">
+        <field type="CharField" name="name">Adopt</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">12</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="17" model="workflows.transition">
+        <field type="CharField" name="name">Reach consensus</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">19</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="18" model="workflows.transition">
+        <field type="CharField" name="name">Wait for go-ahead</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">18</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+</django-objects>

From c2209357e49ce67615ef30264e459f01769e0ae1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Sat, 6 Nov 2010 11:00:48 +0000
Subject: [PATCH 05/57] Initial migrations. See #535  - Legacy-Id: 2640

---
 ietf/ietfworkflows/migrations/0001_initial.py | 148 ++++++++++++
 ietf/ietfworkflows/migrations/__init__.py     |   0
 permissions/migrations/0001_initial.py        | 154 ++++++++++++
 permissions/migrations/__init__.py            |   0
 workflows/migrations/0001_initial.py          | 225 ++++++++++++++++++
 workflows/migrations/__init__.py              |   0
 6 files changed, 527 insertions(+)
 create mode 100644 ietf/ietfworkflows/migrations/0001_initial.py
 create mode 100644 ietf/ietfworkflows/migrations/__init__.py
 create mode 100644 permissions/migrations/0001_initial.py
 create mode 100644 permissions/migrations/__init__.py
 create mode 100644 workflows/migrations/0001_initial.py
 create mode 100644 workflows/migrations/__init__.py

diff --git a/ietf/ietfworkflows/migrations/0001_initial.py b/ietf/ietfworkflows/migrations/0001_initial.py
new file mode 100644
index 000000000..7d9f29e4f
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0001_initial.py
@@ -0,0 +1,148 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'WGWorkflow'
+        db.create_table('ietfworkflows_wgworkflow', (
+            ('workflow_ptr', orm['ietfworkflows.WGWorkflow:workflow_ptr']),
+        ))
+        db.send_create_signal('ietfworkflows', ['WGWorkflow'])
+        
+        # Adding model 'ObjectWorkflowHistoryEntry'
+        db.create_table('ietfworkflows_objectworkflowhistoryentry', (
+            ('id', orm['ietfworkflows.ObjectWorkflowHistoryEntry:id']),
+            ('content_type', orm['ietfworkflows.ObjectWorkflowHistoryEntry:content_type']),
+            ('content_id', orm['ietfworkflows.ObjectWorkflowHistoryEntry:content_id']),
+            ('from_state', orm['ietfworkflows.ObjectWorkflowHistoryEntry:from_state']),
+            ('to_state', orm['ietfworkflows.ObjectWorkflowHistoryEntry:to_state']),
+            ('transition_date', orm['ietfworkflows.ObjectWorkflowHistoryEntry:transition_date']),
+            ('comment', orm['ietfworkflows.ObjectWorkflowHistoryEntry:comment']),
+        ))
+        db.send_create_signal('ietfworkflows', ['ObjectWorkflowHistoryEntry'])
+        
+        # Adding model 'ObjectAnnotationTagHistoryEntry'
+        db.create_table('ietfworkflows_objectannotationtaghistoryentry', (
+            ('id', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:id']),
+            ('content_type', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:content_type']),
+            ('content_id', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:content_id']),
+            ('setted', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:setted']),
+            ('unsetted', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:unsetted']),
+            ('change_date', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:change_date']),
+            ('comment', orm['ietfworkflows.ObjectAnnotationTagHistoryEntry:comment']),
+        ))
+        db.send_create_signal('ietfworkflows', ['ObjectAnnotationTagHistoryEntry'])
+        
+        # Adding model 'AnnotationTag'
+        db.create_table('ietfworkflows_annotationtag', (
+            ('id', orm['ietfworkflows.AnnotationTag:id']),
+            ('name', orm['ietfworkflows.AnnotationTag:name']),
+            ('workflow', orm['ietfworkflows.AnnotationTag:workflow']),
+            ('permission', orm['ietfworkflows.AnnotationTag:permission']),
+        ))
+        db.send_create_signal('ietfworkflows', ['AnnotationTag'])
+        
+        # Adding model 'AnnotationTagObjectRelation'
+        db.create_table('ietfworkflows_annotationtagobjectrelation', (
+            ('id', orm['ietfworkflows.AnnotationTagObjectRelation:id']),
+            ('content_type', orm['ietfworkflows.AnnotationTagObjectRelation:content_type']),
+            ('content_id', orm['ietfworkflows.AnnotationTagObjectRelation:content_id']),
+            ('annotation_tag', orm['ietfworkflows.AnnotationTagObjectRelation:annotation_tag']),
+        ))
+        db.send_create_signal('ietfworkflows', ['AnnotationTagObjectRelation'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'WGWorkflow'
+        db.delete_table('ietfworkflows_wgworkflow')
+        
+        # Deleting model 'ObjectWorkflowHistoryEntry'
+        db.delete_table('ietfworkflows_objectworkflowhistoryentry')
+        
+        # Deleting model 'ObjectAnnotationTagHistoryEntry'
+        db.delete_table('ietfworkflows_objectannotationtaghistoryentry')
+        
+        # Deleting model 'AnnotationTag'
+        db.delete_table('ietfworkflows_annotationtag')
+        
+        # Deleting model 'AnnotationTagObjectRelation'
+        db.delete_table('ietfworkflows_annotationtagobjectrelation')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.wgworkflow': {
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/migrations/__init__.py b/ietf/ietfworkflows/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/permissions/migrations/0001_initial.py b/permissions/migrations/0001_initial.py
new file mode 100644
index 000000000..38e36b067
--- /dev/null
+++ b/permissions/migrations/0001_initial.py
@@ -0,0 +1,154 @@
+
+from south.db import db
+from django.db import models
+from permissions.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'Role'
+        db.create_table('permissions_role', (
+            ('id', orm['permissions.Role:id']),
+            ('name', orm['permissions.Role:name']),
+        ))
+        db.send_create_signal('permissions', ['Role'])
+        
+        # Adding model 'ObjectPermissionInheritanceBlock'
+        db.create_table('permissions_objectpermissioninheritanceblock', (
+            ('id', orm['permissions.ObjectPermissionInheritanceBlock:id']),
+            ('permission', orm['permissions.ObjectPermissionInheritanceBlock:permission']),
+            ('content_type', orm['permissions.ObjectPermissionInheritanceBlock:content_type']),
+            ('content_id', orm['permissions.ObjectPermissionInheritanceBlock:content_id']),
+        ))
+        db.send_create_signal('permissions', ['ObjectPermissionInheritanceBlock'])
+        
+        # Adding model 'ObjectPermission'
+        db.create_table('permissions_objectpermission', (
+            ('id', orm['permissions.ObjectPermission:id']),
+            ('role', orm['permissions.ObjectPermission:role']),
+            ('permission', orm['permissions.ObjectPermission:permission']),
+            ('content_type', orm['permissions.ObjectPermission:content_type']),
+            ('content_id', orm['permissions.ObjectPermission:content_id']),
+        ))
+        db.send_create_signal('permissions', ['ObjectPermission'])
+        
+        # Adding model 'Permission'
+        db.create_table('permissions_permission', (
+            ('id', orm['permissions.Permission:id']),
+            ('name', orm['permissions.Permission:name']),
+            ('codename', orm['permissions.Permission:codename']),
+        ))
+        db.send_create_signal('permissions', ['Permission'])
+        
+        # Adding model 'PrincipalRoleRelation'
+        db.create_table('permissions_principalrolerelation', (
+            ('id', orm['permissions.PrincipalRoleRelation:id']),
+            ('user', orm['permissions.PrincipalRoleRelation:user']),
+            ('group', orm['permissions.PrincipalRoleRelation:group']),
+            ('role', orm['permissions.PrincipalRoleRelation:role']),
+            ('content_type', orm['permissions.PrincipalRoleRelation:content_type']),
+            ('content_id', orm['permissions.PrincipalRoleRelation:content_id']),
+        ))
+        db.send_create_signal('permissions', ['PrincipalRoleRelation'])
+        
+        # Adding ManyToManyField 'Permission.content_types'
+        db.create_table('permissions_permission_content_types', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('permission', models.ForeignKey(orm.Permission, null=False)),
+            ('contenttype', models.ForeignKey(orm['contenttypes.ContentType'], null=False))
+        ))
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'Role'
+        db.delete_table('permissions_role')
+        
+        # Deleting model 'ObjectPermissionInheritanceBlock'
+        db.delete_table('permissions_objectpermissioninheritanceblock')
+        
+        # Deleting model 'ObjectPermission'
+        db.delete_table('permissions_objectpermission')
+        
+        # Deleting model 'Permission'
+        db.delete_table('permissions_permission')
+        
+        # Deleting model 'PrincipalRoleRelation'
+        db.delete_table('permissions_principalrolerelation')
+        
+        # Dropping ManyToManyField 'Permission.content_types'
+        db.delete_table('permissions_permission_content_types')
+        
+    
+    
+    models = {
+        'auth.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']", 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)"},
+            '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': {
+            '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']", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            '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']", 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'permissions.objectpermission': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']"}),
+            'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Role']", 'null': 'True', 'blank': 'True'})
+        },
+        'permissions.objectpermissioninheritanceblock': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']"})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'permissions.principalrolerelation': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Role']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'permissions.role': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        }
+    }
+    
+    complete_apps = ['permissions']
diff --git a/permissions/migrations/__init__.py b/permissions/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/workflows/migrations/0001_initial.py b/workflows/migrations/0001_initial.py
new file mode 100644
index 000000000..d12f59ff3
--- /dev/null
+++ b/workflows/migrations/0001_initial.py
@@ -0,0 +1,225 @@
+
+from south.db import db
+from django.db import models
+from workflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'Workflow'
+        db.create_table('workflows_workflow', (
+            ('id', orm['workflows.Workflow:id']),
+            ('name', orm['workflows.Workflow:name']),
+            ('initial_state', orm['workflows.Workflow:initial_state']),
+        ))
+        db.send_create_signal('workflows', ['Workflow'])
+        
+        # Adding model 'StatePermissionRelation'
+        db.create_table('workflows_statepermissionrelation', (
+            ('id', orm['workflows.StatePermissionRelation:id']),
+            ('state', orm['workflows.StatePermissionRelation:state']),
+            ('permission', orm['workflows.StatePermissionRelation:permission']),
+            ('role', orm['workflows.StatePermissionRelation:role']),
+        ))
+        db.send_create_signal('workflows', ['StatePermissionRelation'])
+        
+        # Adding model 'StateInheritanceBlock'
+        db.create_table('workflows_stateinheritanceblock', (
+            ('id', orm['workflows.StateInheritanceBlock:id']),
+            ('state', orm['workflows.StateInheritanceBlock:state']),
+            ('permission', orm['workflows.StateInheritanceBlock:permission']),
+        ))
+        db.send_create_signal('workflows', ['StateInheritanceBlock'])
+        
+        # Adding model 'WorkflowModelRelation'
+        db.create_table('workflows_workflowmodelrelation', (
+            ('id', orm['workflows.WorkflowModelRelation:id']),
+            ('content_type', orm['workflows.WorkflowModelRelation:content_type']),
+            ('workflow', orm['workflows.WorkflowModelRelation:workflow']),
+        ))
+        db.send_create_signal('workflows', ['WorkflowModelRelation'])
+        
+        # Adding model 'WorkflowPermissionRelation'
+        db.create_table('workflows_workflowpermissionrelation', (
+            ('id', orm['workflows.WorkflowPermissionRelation:id']),
+            ('workflow', orm['workflows.WorkflowPermissionRelation:workflow']),
+            ('permission', orm['workflows.WorkflowPermissionRelation:permission']),
+        ))
+        db.send_create_signal('workflows', ['WorkflowPermissionRelation'])
+        
+        # Adding model 'State'
+        db.create_table('workflows_state', (
+            ('id', orm['workflows.State:id']),
+            ('name', orm['workflows.State:name']),
+            ('workflow', orm['workflows.State:workflow']),
+        ))
+        db.send_create_signal('workflows', ['State'])
+        
+        # Adding model 'Transition'
+        db.create_table('workflows_transition', (
+            ('id', orm['workflows.Transition:id']),
+            ('name', orm['workflows.Transition:name']),
+            ('workflow', orm['workflows.Transition:workflow']),
+            ('destination', orm['workflows.Transition:destination']),
+            ('condition', orm['workflows.Transition:condition']),
+            ('permission', orm['workflows.Transition:permission']),
+        ))
+        db.send_create_signal('workflows', ['Transition'])
+        
+        # Adding model 'WorkflowObjectRelation'
+        db.create_table('workflows_workflowobjectrelation', (
+            ('id', orm['workflows.WorkflowObjectRelation:id']),
+            ('content_type', orm['workflows.WorkflowObjectRelation:content_type']),
+            ('content_id', orm['workflows.WorkflowObjectRelation:content_id']),
+            ('workflow', orm['workflows.WorkflowObjectRelation:workflow']),
+        ))
+        db.send_create_signal('workflows', ['WorkflowObjectRelation'])
+        
+        # Adding model 'StateObjectRelation'
+        db.create_table('workflows_stateobjectrelation', (
+            ('id', orm['workflows.StateObjectRelation:id']),
+            ('content_type', orm['workflows.StateObjectRelation:content_type']),
+            ('content_id', orm['workflows.StateObjectRelation:content_id']),
+            ('state', orm['workflows.StateObjectRelation:state']),
+        ))
+        db.send_create_signal('workflows', ['StateObjectRelation'])
+        
+        # Adding ManyToManyField 'State.transitions'
+        db.create_table('workflows_state_transitions', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('state', models.ForeignKey(orm.State, null=False)),
+            ('transition', models.ForeignKey(orm.Transition, null=False))
+        ))
+        
+        # Creating unique_together for [content_type, content_id] on WorkflowObjectRelation.
+        db.create_unique('workflows_workflowobjectrelation', ['content_type_id', 'content_id'])
+        
+        # Creating unique_together for [content_type, content_id, state] on StateObjectRelation.
+        db.create_unique('workflows_stateobjectrelation', ['content_type_id', 'content_id', 'state_id'])
+        
+        # Creating unique_together for [workflow, permission] on WorkflowPermissionRelation.
+        db.create_unique('workflows_workflowpermissionrelation', ['workflow_id', 'permission_id'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting unique_together for [workflow, permission] on WorkflowPermissionRelation.
+        db.delete_unique('workflows_workflowpermissionrelation', ['workflow_id', 'permission_id'])
+        
+        # Deleting unique_together for [content_type, content_id, state] on StateObjectRelation.
+        db.delete_unique('workflows_stateobjectrelation', ['content_type_id', 'content_id', 'state_id'])
+        
+        # Deleting unique_together for [content_type, content_id] on WorkflowObjectRelation.
+        db.delete_unique('workflows_workflowobjectrelation', ['content_type_id', 'content_id'])
+        
+        # Deleting model 'Workflow'
+        db.delete_table('workflows_workflow')
+        
+        # Deleting model 'StatePermissionRelation'
+        db.delete_table('workflows_statepermissionrelation')
+        
+        # Deleting model 'StateInheritanceBlock'
+        db.delete_table('workflows_stateinheritanceblock')
+        
+        # Deleting model 'WorkflowModelRelation'
+        db.delete_table('workflows_workflowmodelrelation')
+        
+        # Deleting model 'WorkflowPermissionRelation'
+        db.delete_table('workflows_workflowpermissionrelation')
+        
+        # Deleting model 'State'
+        db.delete_table('workflows_state')
+        
+        # Deleting model 'Transition'
+        db.delete_table('workflows_transition')
+        
+        # Deleting model 'WorkflowObjectRelation'
+        db.delete_table('workflows_workflowobjectrelation')
+        
+        # Deleting model 'StateObjectRelation'
+        db.delete_table('workflows_stateobjectrelation')
+        
+        # Dropping ManyToManyField 'State.transitions'
+        db.delete_table('workflows_state_transitions')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'permissions.role': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateinheritanceblock': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']"}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.statepermissionrelation': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']"}),
+            'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Role']"}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        },
+        'workflows.workflowmodelrelation': {
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wmrs'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflowobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wors'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflowpermissionrelation': {
+            'Meta': {'unique_together': "(('workflow', 'permission'),)"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'to': "orm['permissions.Permission']"}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.Workflow']"})
+        }
+    }
+    
+    complete_apps = ['workflows']
diff --git a/workflows/migrations/__init__.py b/workflows/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb

From 65209540055e21ac4c026bdc0551227a7a700468 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 29 Nov 2010 09:07:07 +0000
Subject: [PATCH 06/57] Model for managing WG delegates. See #557  - Legacy-Id:
 2687

---
 ietf/wgchairs/__init__.py |  0
 ietf/wgchairs/models.py   | 17 +++++++++++++++++
 2 files changed, 17 insertions(+)
 create mode 100644 ietf/wgchairs/__init__.py
 create mode 100644 ietf/wgchairs/models.py

diff --git a/ietf/wgchairs/__init__.py b/ietf/wgchairs/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/wgchairs/models.py b/ietf/wgchairs/models.py
new file mode 100644
index 000000000..fd32f7908
--- /dev/null
+++ b/ietf/wgchairs/models.py
@@ -0,0 +1,17 @@
+from django.db import models
+
+from ietf.idtracker.models import IETFWG, PersonOrOrgInfo
+
+
+class WGDelegate(models.Model):
+    person = models.ForeignKey(
+        PersonOrOrgInfo,
+        )
+
+    wg = models.ForeignKey(IETFWG)
+
+    def __unicode__(self):
+        return "%s" % self.person
+
+    class Meta:
+        verbose_name = "WG Delegate"

From d48a1ce3784376091fe9998f204c14b23889e610 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 29 Nov 2010 09:08:22 +0000
Subject: [PATCH 07/57] Initial migration. See #557  - Legacy-Id: 2688

---
 ietf/wgchairs/migrations/0001_initial.py | 110 +++++++++++++++++++++++
 ietf/wgchairs/migrations/__init__.py     |   0
 2 files changed, 110 insertions(+)
 create mode 100644 ietf/wgchairs/migrations/0001_initial.py
 create mode 100644 ietf/wgchairs/migrations/__init__.py

diff --git a/ietf/wgchairs/migrations/0001_initial.py b/ietf/wgchairs/migrations/0001_initial.py
new file mode 100644
index 000000000..6aa9fb58f
--- /dev/null
+++ b/ietf/wgchairs/migrations/0001_initial.py
@@ -0,0 +1,110 @@
+
+from south.db import db
+from django.db import models
+from ietf.wgchairs.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'WGDelegate'
+        db.create_table('wgchairs_wgdelegate', (
+            ('id', orm['wgchairs.WGDelegate:id']),
+            ('person', orm['wgchairs.WGDelegate:person']),
+            ('wg', orm['wgchairs.WGDelegate:wg']),
+        ))
+        db.send_create_signal('wgchairs', ['WGDelegate'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'WGDelegate'
+        db.delete_table('wgchairs_wgdelegate')
+        
+    
+    
+    models = {
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.area': {
+            'Meta': {'db_table': "'areas'"},
+            'area_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'extra_email_addresses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaStatus']"})
+        },
+        'idtracker.areadirector': {
+            'Meta': {'db_table': "'area_directors'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']", 'null': 'True', 'db_column': "'area_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.areastatus': {
+            'Meta': {'db_table': "'area_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.ietfwg': {
+            'Meta': {'db_table': "'groups_ietf'"},
+            'area_director': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaDirector']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'dormant_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'email_address': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
+            'email_archive': ('django.db.models.fields.CharField', [], {'max_length': '95', 'blank': 'True'}),
+            'email_keyword': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'email_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'group_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGType']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'meeting_scheduled': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'meeting_scheduled_old': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGStatus']"})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.wgstatus': {
+            'Meta': {'db_table': "'g_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.wgtype': {
+            'Meta': {'db_table': "'g_type'"},
+            'group_type_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'group_type'"})
+        },
+        'wgchairs.wgdelegate': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'wg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"})
+        }
+    }
+    
+    complete_apps = ['wgchairs']
diff --git a/ietf/wgchairs/migrations/__init__.py b/ietf/wgchairs/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb

From e32255576baeb56e58df6fa4c1ddc4fb7ddc7397 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 29 Nov 2010 09:09:46 +0000
Subject: [PATCH 08/57] Some permission related funcions. See #557  -
 Legacy-Id: 2689

---
 ietf/wgchairs/accounts.py | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 ietf/wgchairs/accounts.py

diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
new file mode 100644
index 000000000..4c519a288
--- /dev/null
+++ b/ietf/wgchairs/accounts.py
@@ -0,0 +1,39 @@
+def is_group_chair(person, group):
+    if group.chairs().filter(person=person):
+        return True
+    return False
+
+
+def get_person_for_user(user):
+    try:
+        return user.get_profile().person()
+    except:
+        return None
+
+
+def can_do_wg_workflow_in_group(user, group):
+    person = get_person_for_user(user)
+    if not person:
+        return False
+    return is_group_chair(person, group)
+
+
+def can_do_wg_workflow_in_document(user, document):
+    person = get_person_for_user(user)
+    if not person or not document.group:
+        return False
+    return can_do_wg_wgorkflow_in_group(document.group)
+
+
+def can_manage_workflow_in_group(user, group):
+    person = get_person_for_user(user)
+    if not person:
+        return False
+    return is_group_chair(person, group)
+
+
+def can_manage_delegates_in_group(user, group):
+    person = get_person_for_user(user)
+    if not person:
+        return False
+    return is_group_chair(person, group)

From 34cbc464cb2fedd4996ae4014d61816ca712d8cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 29 Nov 2010 09:15:52 +0000
Subject: [PATCH 09/57] Manage delegates views. See #557  - Legacy-Id: 2690

---
 ietf/templates/wgchairs/manage_delegates.html |  55 ++++++
 ietf/templates/wgchairs/notexistdelegate.html |  12 ++
 .../wgchairs/wgchairs_admin_options.html      |  15 ++
 ietf/templates/wginfo/wg_base.html            |   3 +-
 ietf/wgchairs/accounts.py                     |   2 +-
 ietf/wgchairs/forms.py                        | 179 ++++++++++++++++++
 ietf/wgchairs/templatetags/__init__.py        |   0
 ietf/wgchairs/templatetags/wgchairs_tags.py   |  19 ++
 ietf/wgchairs/urls.py                         |   8 +
 ietf/wgchairs/views.py                        |  38 ++++
 10 files changed, 329 insertions(+), 2 deletions(-)
 create mode 100644 ietf/templates/wgchairs/manage_delegates.html
 create mode 100644 ietf/templates/wgchairs/notexistdelegate.html
 create mode 100644 ietf/templates/wgchairs/wgchairs_admin_options.html
 create mode 100644 ietf/wgchairs/forms.py
 create mode 100644 ietf/wgchairs/templatetags/__init__.py
 create mode 100644 ietf/wgchairs/templatetags/wgchairs_tags.py
 create mode 100644 ietf/wgchairs/urls.py
 create mode 100644 ietf/wgchairs/views.py

diff --git a/ietf/templates/wgchairs/manage_delegates.html b/ietf/templates/wgchairs/manage_delegates.html
new file mode 100644
index 000000000..8afd102f0
--- /dev/null
+++ b/ietf/templates/wgchairs/manage_delegates.html
@@ -0,0 +1,55 @@
+{% extends "wginfo/wg_base.html" %}
+
+{% block morecss %}
+{{ block.super }}
+.wg-chair-management ul {
+    list-style: none;
+}
+
+.wg-chair-management input {
+    border: 1px solid green;
+}
+{% endblock %}
+
+{% block wg_titledetail %}Delegates{% endblock %}
+
+{% block wg_content %}
+<div class="wg-chair-management">
+<h2>Add new delegate</h2>
+{% if add_form.message %}
+<div class="message message-{{ add_form.message.type }}">
+    {{ add_form.message.value }}
+</div>
+{% endif %}
+{% if can_add %}
+  <form action="" method="POST">
+    {{ add_form.as_p }}
+    <p>
+    <input type="submit" value="{% if add_form.submit_msg %}{{ add_form.submit_msg }}{% else %}Add delegate{% endif %}" name="add" />
+    {% if add_form.can_cancel %}<a href="">No! I don't want to continue</a>{% endif %}
+    </p>
+  </form>
+{% else %}
+<p>
+You can only assign three delegates. Please remove delegates to add a new one.
+</p>
+{% endif %}
+
+<h2>Delegates</h2>
+{% if delegates %}
+  <form action="" method="POST">
+  <table>
+    <tr><th>Remove</th><th>Delegate name</th></tr>
+  {% for delegate in delegates %}
+    <tr><td><input type="checkbox" name="delete" value="{{ delegate.pk }}" /></td><td>{{ delegate.person }}</td></tr>
+  {% endfor %}
+  </table>
+  <input type="submit" value="Remove delegate(s)" name="remove" />
+  </form>
+{% else %}
+<p>
+No delegates
+</p>
+{% endif %}
+</div>
+{% endblock %}
diff --git a/ietf/templates/wgchairs/notexistdelegate.html b/ietf/templates/wgchairs/notexistdelegate.html
new file mode 100644
index 000000000..3a7fa6482
--- /dev/null
+++ b/ietf/templates/wgchairs/notexistdelegate.html
@@ -0,0 +1,12 @@
+<p>
+The delegate you are trying to designate does not have a personal user-id and password to log-on to the Datatracker.
+</p>
+<p>
+An email will be sent to the following address to inform that the person designated sould contact with the Secretariat
+to obtain their own user-id and password to the Datatracker.
+</p>
+<ul>
+{% for email in email_list %}
+<li>{{ email }}</li>
+{% endfor %}
+</ul>
diff --git a/ietf/templates/wgchairs/wgchairs_admin_options.html b/ietf/templates/wgchairs/wgchairs_admin_options.html
new file mode 100644
index 000000000..5a8501839
--- /dev/null
+++ b/ietf/templates/wgchairs/wgchairs_admin_options.html
@@ -0,0 +1,15 @@
+{% if can_manage_workflow %}
+  {% ifequal selected "manage_workflow" %}
+    <span class="selected">Manage workflow</span>
+  {% else %}
+    <a href="{% url manage_workflow wg.group_acronym.acronym %}">Manage workflow</a>
+  {% endifequal %} |
+{% endif %}
+
+{% if can_manage_delegates %}
+  {% ifequal selected "manage_delegates" %}
+    <span class="selected">Manage delegates</span>
+  {% else %}
+    <a href="{% url manage_delegates wg.group_acronym.acronym %}">Manage delegates</a>
+  {% endifequal %} |
+{% endif %}
diff --git a/ietf/templates/wginfo/wg_base.html b/ietf/templates/wginfo/wg_base.html
index 438724c11..853c3368e 100644
--- a/ietf/templates/wginfo/wg_base.html
+++ b/ietf/templates/wginfo/wg_base.html
@@ -32,7 +32,7 @@ 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 %}
+{% load ietf_filters wgchairs_tags %}
 {% block title %}{{wg.group_acronym.name}} ({{wg.group_acronym.acronym}}) - {% block wg_titledetail %}{% endblock %}{% endblock %}
 
 {% block morecss %}
@@ -58,6 +58,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 <div class="ietf-navset">
 {% ifequal selected "documents" %}<span class="selected">Documents</span>{% else %}<a href="/wg/{{wg}}/">Documents</a>{% endifequal %} | 
 {% ifequal selected "charter" %}<span class="selected">Charter</span>{% else %}<a href="/wg/{{wg}}/charter/">Charter</a>{% endifequal %} | 
+{% wgchairs_admin_options wg %}
 {% if wg.clean_email_archive|startswith:"http:" or wg.clean_email_archive|startswith:"ftp:" %}
 <a href="{{ wg.clean_email_archive }}">List Archive &#187;</a> | 
 {% endif %}
diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
index 4c519a288..95f45d147 100644
--- a/ietf/wgchairs/accounts.py
+++ b/ietf/wgchairs/accounts.py
@@ -22,7 +22,7 @@ def can_do_wg_workflow_in_document(user, document):
     person = get_person_for_user(user)
     if not person or not document.group:
         return False
-    return can_do_wg_wgorkflow_in_group(document.group)
+    return can_do_wg_workflow_in_group(document.group)
 
 
 def can_manage_workflow_in_group(user, group):
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
new file mode 100644
index 000000000..39903e73b
--- /dev/null
+++ b/ietf/wgchairs/forms.py
@@ -0,0 +1,179 @@
+from django import forms
+from django.conf import settings
+from django.core.mail import EmailMessage
+from django.template.loader import render_to_string
+
+from ietf.wgchairs.models import WGDelegate
+from ietf.wgchairs.accounts import get_person_for_user
+from ietf.idtracker.models import PersonOrOrgInfo
+
+
+class RelatedWGForm(forms.Form):
+
+    can_cancel = False
+
+    def __init__(self, *args, **kwargs):
+        self.wg = kwargs.pop('wg', None)
+        self.user = kwargs.pop('user', None)
+        self.message = {}
+        super(RelatedWGForm, self).__init__(*args, **kwargs)
+
+    def get_message(self):
+        return self.message
+
+    def set_message(self, msg_type, msg_value):
+        self.message = {'type': msg_type,
+                        'value': msg_value,
+                       }
+
+
+class RemoveDelegateForm(RelatedWGForm):
+
+    delete = forms.MultipleChoiceField()
+
+    def __init__(self, *args, **kwargs):
+        super(RemoveDelegateForm, self).__init__(*args, **kwargs)
+        self.fields['delete'].choices = [(i.pk, i.pk) for i in self.wg.wgdelegate_set.all()]
+
+    def save(self):
+        delegates = self.cleaned_data.get('delete')
+        WGDelegate.objects.filter(pk__in=delegates).delete()
+        self.set_message('success', 'Delegates removed')
+
+
+class AddDelegateForm(RelatedWGForm):
+
+    email = forms.EmailField()
+    form_type = forms.CharField(widget=forms.HiddenInput, initial='single')
+
+    def __init__(self, *args, **kwargs):
+        super(AddDelegateForm, self).__init__(*args, **kwargs)
+        self.next_form = self
+
+    def get_next_form(self):
+        return self.next_form
+
+    def get_person(self, email):
+        persons = PersonOrOrgInfo.objects.filter(emailaddress__address=email, iesglogin__isnull=False).distinct()
+        if not persons:
+            raise PersonOrOrgInfo.DoesNotExist
+        if len(persons) > 1:
+            raise PersonOrOrgInfo.MultipleObjectsReturned
+        return persons[0]
+
+    def save(self):
+        email = self.cleaned_data.get('email')
+        try:
+            person = self.get_person(email)
+        except PersonOrOrgInfo.DoesNotExist:
+            self.next_form = NotExistDelegateForm(wg=self.wg, user=self.user, email=email)
+            self.next_form.set_message('doesnotexist', 'There is no user with this email allowed to login to the system')
+            return
+        except PersonOrOrgInfo.MultipleObjectsReturned:
+            self.next_form = MultipleDelegateForm(wg=self.wg, user=self.user, email=email)
+            self.next_form.set_message('multiple', 'There are multiple users with this email in the system')
+            return
+        self.create_delegate(person)
+
+    def create_delegate(self, person):
+        (delegate, created) = WGDelegate.objects.get_or_create(wg=self.wg,
+                                                               person=person)
+        if not created:
+            self.set_message('error', 'The email belongs to a person who is already a delegate')
+        else:
+            self.next_form = AddDelegateForm(wg=self.wg, user=self.user)
+            self.next_form.set_message('success', 'A new delegate has been added')
+
+
+class MultipleDelegateForm(AddDelegateForm):
+
+    email = forms.EmailField(widget=forms.HiddenInput)
+    form_type = forms.CharField(widget=forms.HiddenInput, initial='multiple')
+    persons = forms.ChoiceField(widget=forms.RadioSelect, help_text='Please select one person from the list')
+    submit_msg = 'Designate as delegate'
+
+    def __init__(self, *args, **kwargs):
+        self.email = kwargs.pop('email', None)
+        super(MultipleDelegateForm, self).__init__(*args, **kwargs)
+        if not self.email:
+            self.email = self.data.get('email', None)
+        self.fields['email'].initial = self.email
+        self.fields['persons'].choices = [(i.pk, unicode(i)) for i in PersonOrOrgInfo.objects.filter(emailaddress__address=self.email, iesglogin__isnull=False).distinct().order_by('first_name')]
+
+    def save(self):
+        person_id = self.cleaned_data.get('persons')
+        person = PersonOrOrgInfo.objects.get(pk=person_id)
+        self.create_delegate(person)
+
+
+class NotExistDelegateForm(MultipleDelegateForm):
+
+    email = forms.EmailField(widget=forms.HiddenInput)
+    form_type = forms.CharField(widget=forms.HiddenInput, initial='notexist')
+    can_cancel = True
+    submit_msg = 'Send email to these addresses'
+
+    def __init__(self, *args, **kwargs):
+        super(NotExistDelegateForm, self).__init__(*args, **kwargs)
+        self.email_list = []
+
+    def get_email_list(self):
+        if self.email_list:
+            return self.email_list
+        email_list = [self.email]
+        email_list.append('IETF Secretariat <iesg-secretary@ietf.org>')
+        email_list += ['%s <%s>' % i.person.email() for i in self.wg.wgchair_set.all() if i.person.email()]
+        self.email_list = email_list
+        return email_list
+
+    def as_p(self):
+        email_list = self.get_email_list()
+        return render_to_string('wgchairs/notexistdelegate.html', {'email_list': email_list})
+
+    def send_email(self, email, template):
+        subject = 'WG Delegate needs system credentials'
+        persons = PersonOrOrgInfo.objects.filter(emailaddress__address=self.email).distinct()
+        body = render_to_string(template,
+                                {'chair': get_person_for_user(self.user),
+                                 'delegate_email': self.email,
+                                 'delegate_persons': persons,
+                                })
+        mail = EmailMessage(subject=subject,
+                            body=body,
+                            to=email,
+                            from_email=settings.DEFAULT_FROM_EMAIL)
+        return mail
+
+    def send_email_to_delegate(self, email):
+        self.send_email(email, 'wgchairs/notexistsdelegate_delegate_email.txt')
+
+    def send_email_to_secretariat(self, email):
+        self.send_email(email, 'wgchairs/notexistsdelegate_secretariat_email.txt')
+
+    def send_email_to_wgchairs(self, email):
+        self.send_email(email, 'wgchairs/notexistsdelegate_wgchairs_email.txt')
+
+    def save(self):
+        self.next_form = AddDelegateForm(wg=self.wg, user=self.user)
+        if settings.DEBUG:
+            self.next_form.set_message('warning', 'Email was not sent cause tool is in DEBUG mode')
+        else:
+            email_list = self.get_email_list()
+            self.send_email_to_delegate([email_list[0]])
+            self.send_email_to_secretariat([email_list[1]])
+            self.send_email_to_wgchairs(email_list[2:])
+            self.next_form.set_message('success', 'Email sent successfully')
+
+
+def add_form_factory(request, wg, user):
+    if request.method != 'POST':
+        return AddDelegateForm(wg=wg, user=user)
+
+    if request.POST.get('form_type', None) == 'multiple':
+        return MultipleDelegateForm(wg=wg, user=user, data=request.POST.copy())
+    elif request.POST.get('form_type', None) == 'notexist':
+        return NotExistDelegateForm(wg=wg, user=user, data=request.POST.copy())
+    elif request.POST.get('form_type', None) == 'single':
+        return AddDelegateForm(wg=wg, user=user, data=request.POST.copy())
+
+    return AddDelegateForm(wg=wg, user=user)
diff --git a/ietf/wgchairs/templatetags/__init__.py b/ietf/wgchairs/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/wgchairs/templatetags/wgchairs_tags.py b/ietf/wgchairs/templatetags/wgchairs_tags.py
new file mode 100644
index 000000000..abe633974
--- /dev/null
+++ b/ietf/wgchairs/templatetags/wgchairs_tags.py
@@ -0,0 +1,19 @@
+from django import template
+
+from ietf.wgchairs.accounts import (can_manage_workflow_in_group,
+                                    can_manage_delegates_in_group)
+
+
+register = template.Library()
+
+
+@register.inclusion_tag('wgchairs/wgchairs_admin_options.html', takes_context=True)
+def wgchairs_admin_options(context, wg):
+    request = context.get('request', None)
+    user = request and request.user
+    return {'user': user,
+            'can_manage_delegates': can_manage_delegates_in_group(user, wg),
+            'can_manage_workflow': can_manage_workflow_in_group(user, wg),
+            'wg': wg,
+            'selected': context.get('selected', None),
+           }
diff --git a/ietf/wgchairs/urls.py b/ietf/wgchairs/urls.py
new file mode 100644
index 000000000..916fa4ace
--- /dev/null
+++ b/ietf/wgchairs/urls.py
@@ -0,0 +1,8 @@
+# Copyright The IETF Trust 2008, All Rights Reserved
+
+from django.conf.urls.defaults import patterns, url
+
+urlpatterns = patterns('ietf.wgchairs.views',
+     url(r'^workflows/$', 'manage_workflow', name='manage_workflow'),
+     url(r'^delegates/$', 'manage_delegates', name='manage_delegates'),
+)
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
new file mode 100644
index 000000000..7da34a3dd
--- /dev/null
+++ b/ietf/wgchairs/views.py
@@ -0,0 +1,38 @@
+from ietf.idtracker.models import IETFWG
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.http import HttpResponseForbidden
+
+from ietf.wgchairs.forms import RemoveDelegateForm, add_form_factory
+from ietf.wgchairs.accounts import can_manage_delegates_in_group
+
+
+def manage_delegates(request, acronym):
+    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
+    user = request.user
+    if not can_manage_delegates_in_group(user, wg):
+        return HttpResponseForbidden('You have no permission to access this view')
+    delegates = wg.wgdelegate_set.all()
+    add_form = add_form_factory(request, wg, user)
+    if request.method == 'POST':
+        if request.POST.get('remove', None):
+            form = RemoveDelegateForm(wg=wg, data=request.POST.copy())
+            if form.is_valid():
+                form.save()
+        elif add_form.is_valid():
+            add_form.save()
+            add_form = add_form.get_next_form()
+    return render_to_response('wgchairs/manage_delegates.html',
+                              {'wg': wg,
+                               'delegates': delegates,
+                               'selected': 'manage_delegates',
+                               'can_add': delegates.count() < 3,
+                               'add_form': add_form,
+                              },
+                              RequestContext(request))
+
+
+def manage_workflow(request, acronym):
+    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
+    concluded = (wg.status_id != 1)
+    return render_to_response('wginfo/wg_charter.html', {'wg': wg, 'concluded': concluded, 'selected': 'manage_workflow'}, RequestContext(request))

From 0821250219691a71cc1e3e0cd85f664e1eba8364 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 29 Nov 2010 09:16:29 +0000
Subject: [PATCH 10/57] Enable wgchairs application urls. See #557  -
 Legacy-Id: 2691

---
 ietf/settings.py    | 1 +
 ietf/wginfo/urls.py | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/ietf/settings.py b/ietf/settings.py
index 2edcbd880..717c8328f 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -134,6 +134,7 @@ INSTALLED_APPS = (
     'ietf.idrfc',
     'ietf.wginfo',
     'ietf.ietfworkflows',
+    'ietf.wgchairs',
 )
 
 INTERNAL_IPS = (
diff --git a/ietf/wginfo/urls.py b/ietf/wginfo/urls.py
index 00475e7d9..d67fc4897 100644
--- a/ietf/wginfo/urls.py
+++ b/ietf/wginfo/urls.py
@@ -1,6 +1,6 @@
 # Copyright The IETF Trust 2008, All Rights Reserved
 
-from django.conf.urls.defaults import patterns
+from django.conf.urls.defaults import patterns, include
 from ietf.wginfo import views
 from django.views.generic.simple import redirect_to
 
@@ -15,4 +15,5 @@ urlpatterns = patterns('',
      (r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
      (r'^(?P<acronym>[^/]+)/$', views.wg_documents),
      (r'^(?P<acronym>[^/]+)/charter/$', views.wg_charter),
+     (r'^(?P<acronym>[^/]+)/management/', include('ietf.wgchairs.urls')),
 )

From 90a048eb442a2eb257ad492c79367dbf425fd4e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 29 Nov 2010 18:34:19 +0000
Subject: [PATCH 11/57] Added the diferent emails when a delegate does not have
 a system user/password. Fixes #557  - Legacy-Id: 2692

---
 .../wgchairs/notexistsdelegate_delegate_email.txt  |  9 +++++++++
 .../notexistsdelegate_secretariat_email.txt        | 14 ++++++++++++++
 .../wgchairs/notexistsdelegate_wgchairs_email.txt  | 11 +++++++++++
 ietf/wgchairs/forms.py                             |  8 ++++++--
 4 files changed, 40 insertions(+), 2 deletions(-)
 create mode 100644 ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt
 create mode 100644 ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt
 create mode 100644 ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt

diff --git a/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt b/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt
new file mode 100644
index 000000000..a79680663
--- /dev/null
+++ b/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt
@@ -0,0 +1,9 @@
+{{ chair }} as a WG Chair of {{ wg }} wants to add you as a {{ wg }} WG Delegate.
+
+You don't have an user/password to log into the datatracker so you must contact
+the Secretariat at iesg-secretary@ietf.org in order to get your credentials.
+
+When you get your credentials, please inform {{ chair }} at
+{{ chair.email.1 }} so he/she can finish the designate process.
+
+Thank you.
diff --git a/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt b/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt
new file mode 100644
index 000000000..d3d2b3d91
--- /dev/null
+++ b/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt
@@ -0,0 +1,14 @@
+{{ chair }} as a WG Chair of {{ wg }} wants to add a person with email
+{{ delegate_email }} as a WG Delegate.
+
+This person don't have an user/password to log into the datatracker so
+an email has been seent to {{ delegate_email }} in order to he/she contacs the
+Secretariat to request his/her credentials.
+
+{% if delegate_persons %}
+Please, note that the following persons with {{ delegate_email }} email address
+already exists in the system but they can not log in.
+{% for person in delegate_persons %}
+{{ person.pk }} - {{ person }}
+{% endfor %}
+{% endif %}
diff --git a/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt b/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt
new file mode 100644
index 000000000..fc9a19022
--- /dev/null
+++ b/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt
@@ -0,0 +1,11 @@
+{{ chair }} as a WG Chair of {{ wg }} wants to add a person with email
+{{ delegate_email }} as a WG Delegate.
+
+This person don't have an user/password to log into the datatracker so
+an email has been seent to {{ delegate_email }} in order to he/she contacs the
+Secretariat to request his/her credentials.
+
+When he/she gets her credentials then he/she will send an email to 
+{{ chair }} at {{ chair.email.1 }}. 
+
+{{ chair }} could then assign this person as WG Delegate.
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 39903e73b..b41960c19 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -116,6 +116,7 @@ class NotExistDelegateForm(MultipleDelegateForm):
     def __init__(self, *args, **kwargs):
         super(NotExistDelegateForm, self).__init__(*args, **kwargs)
         self.email_list = []
+        del(self.fields['persons'])
 
     def get_email_list(self):
         if self.email_list:
@@ -128,7 +129,8 @@ class NotExistDelegateForm(MultipleDelegateForm):
 
     def as_p(self):
         email_list = self.get_email_list()
-        return render_to_string('wgchairs/notexistdelegate.html', {'email_list': email_list})
+        info = render_to_string('wgchairs/notexistdelegate.html', {'email_list': email_list})
+        return info + super(NotExistDelegateForm, self).as_p()
 
     def send_email(self, email, template):
         subject = 'WG Delegate needs system credentials'
@@ -137,12 +139,14 @@ class NotExistDelegateForm(MultipleDelegateForm):
                                 {'chair': get_person_for_user(self.user),
                                  'delegate_email': self.email,
                                  'delegate_persons': persons,
+                                 'wg': self.wg,
                                 })
         mail = EmailMessage(subject=subject,
                             body=body,
                             to=email,
                             from_email=settings.DEFAULT_FROM_EMAIL)
-        return mail
+        mail.send()
+
 
     def send_email_to_delegate(self, email):
         self.send_email(email, 'wgchairs/notexistsdelegate_delegate_email.txt')

From 273ae037345949f6d2bb0410d392d7de16460073 Mon Sep 17 00:00:00 2001
From: Alexey Zarubin <azarubin@yaco.es>
Date: Tue, 30 Nov 2010 13:16:50 +0000
Subject: [PATCH 12/57] Fixes #562 creating migrations  - Legacy-Id: 2693

---
 ietf/.gitignore                            |    1 +
 ietf/idtracker/migrations/0001_initial.py  | 1064 ++++++++++++++++++++
 ietf/idtracker/migrations/0002_shepherd.py |  437 ++++++++
 ietf/idtracker/migrations/__init__.py      |    0
 ietf/idtracker/models.py                   |    1 +
 5 files changed, 1503 insertions(+)
 create mode 100644 ietf/idtracker/migrations/0001_initial.py
 create mode 100644 ietf/idtracker/migrations/0002_shepherd.py
 create mode 100644 ietf/idtracker/migrations/__init__.py

diff --git a/ietf/.gitignore b/ietf/.gitignore
index a74b07aee..c7013ced9 100644
--- a/ietf/.gitignore
+++ b/ietf/.gitignore
@@ -1 +1,2 @@
 /*.pyc
+/settings_local.py
diff --git a/ietf/idtracker/migrations/0001_initial.py b/ietf/idtracker/migrations/0001_initial.py
new file mode 100644
index 000000000..516926ad1
--- /dev/null
+++ b/ietf/idtracker/migrations/0001_initial.py
@@ -0,0 +1,1064 @@
+
+from south.db import db
+from django.db import models
+from ietf.idtracker.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'WGTechAdvisor'
+        db.create_table('g_tech_advisors', (
+            ('id', orm['idtracker.WGTechAdvisor:id']),
+            ('group_acronym', orm['idtracker.WGTechAdvisor:group_acronym']),
+            ('person', orm['idtracker.WGTechAdvisor:person']),
+        ))
+        db.send_create_signal('idtracker', ['WGTechAdvisor'])
+        
+        # Adding model 'IDState'
+        db.create_table('ref_doc_states_new', (
+            ('document_state_id', orm['idtracker.IDState:document_state_id']),
+            ('state', orm['idtracker.IDState:state']),
+            ('equiv_group_flag', orm['idtracker.IDState:equiv_group_flag']),
+            ('description', orm['idtracker.IDState:description']),
+        ))
+        db.send_create_signal('idtracker', ['IDState'])
+        
+        # Adding model 'BallotInfo'
+        db.create_table('ballot_info', (
+            ('ballot', orm['idtracker.BallotInfo:ballot']),
+            ('active', orm['idtracker.BallotInfo:active']),
+            ('an_sent', orm['idtracker.BallotInfo:an_sent']),
+            ('an_sent_date', orm['idtracker.BallotInfo:an_sent_date']),
+            ('an_sent_by', orm['idtracker.BallotInfo:an_sent_by']),
+            ('defer', orm['idtracker.BallotInfo:defer']),
+            ('defer_by', orm['idtracker.BallotInfo:defer_by']),
+            ('defer_date', orm['idtracker.BallotInfo:defer_date']),
+            ('approval_text', orm['idtracker.BallotInfo:approval_text']),
+            ('last_call_text', orm['idtracker.BallotInfo:last_call_text']),
+            ('ballot_writeup', orm['idtracker.BallotInfo:ballot_writeup']),
+            ('ballot_issued', orm['idtracker.BallotInfo:ballot_issued']),
+        ))
+        db.send_create_signal('idtracker', ['BallotInfo'])
+        
+        # Adding model 'AreaStatus'
+        db.create_table('area_status', (
+            ('status_id', orm['idtracker.AreaStatus:status_id']),
+            ('status', orm['idtracker.AreaStatus:status']),
+        ))
+        db.send_create_signal('idtracker', ['AreaStatus'])
+        
+        # Adding model 'RfcAuthor'
+        db.create_table('rfc_authors', (
+            ('id', orm['idtracker.RfcAuthor:id']),
+            ('rfc', orm['idtracker.RfcAuthor:rfc']),
+            ('person', orm['idtracker.RfcAuthor:person']),
+        ))
+        db.send_create_signal('idtracker', ['RfcAuthor'])
+        
+        # Adding model 'IDIntendedStatus'
+        db.create_table('id_intended_status', (
+            ('intended_status_id', orm['idtracker.IDIntendedStatus:intended_status_id']),
+            ('intended_status', orm['idtracker.IDIntendedStatus:intended_status']),
+        ))
+        db.send_create_signal('idtracker', ['IDIntendedStatus'])
+        
+        # Adding model 'IDNextState'
+        db.create_table('ref_next_states_new', (
+            ('id', orm['idtracker.IDNextState:id']),
+            ('cur_state', orm['idtracker.IDNextState:cur_state']),
+            ('next_state', orm['idtracker.IDNextState:next_state']),
+            ('condition', orm['idtracker.IDNextState:condition']),
+        ))
+        db.send_create_signal('idtracker', ['IDNextState'])
+        
+        # Adding model 'WGType'
+        db.create_table('g_type', (
+            ('group_type_id', orm['idtracker.WGType:group_type_id']),
+            ('type', orm['idtracker.WGType:type']),
+        ))
+        db.send_create_signal('idtracker', ['WGType'])
+        
+        # Adding model 'RfcObsolete'
+        db.create_table('rfcs_obsolete', (
+            ('id', orm['idtracker.RfcObsolete:id']),
+            ('rfc', orm['idtracker.RfcObsolete:rfc']),
+            ('action', orm['idtracker.RfcObsolete:action']),
+            ('rfc_acted_on', orm['idtracker.RfcObsolete:rfc_acted_on']),
+        ))
+        db.send_create_signal('idtracker', ['RfcObsolete'])
+        
+        # Adding model 'InternetDraft'
+        db.create_table('internet_drafts', (
+            ('id_document_tag', orm['idtracker.InternetDraft:id_document_tag']),
+            ('title', orm['idtracker.InternetDraft:title']),
+            ('id_document_key', orm['idtracker.InternetDraft:id_document_key']),
+            ('group', orm['idtracker.InternetDraft:group']),
+            ('filename', orm['idtracker.InternetDraft:filename']),
+            ('revision', orm['idtracker.InternetDraft:revision']),
+            ('revision_date', orm['idtracker.InternetDraft:revision_date']),
+            ('file_type', orm['idtracker.InternetDraft:file_type']),
+            ('txt_page_count', orm['idtracker.InternetDraft:txt_page_count']),
+            ('local_path', orm['idtracker.InternetDraft:local_path']),
+            ('start_date', orm['idtracker.InternetDraft:start_date']),
+            ('expiration_date', orm['idtracker.InternetDraft:expiration_date']),
+            ('abstract', orm['idtracker.InternetDraft:abstract']),
+            ('dunn_sent_date', orm['idtracker.InternetDraft:dunn_sent_date']),
+            ('extension_date', orm['idtracker.InternetDraft:extension_date']),
+            ('status', orm['idtracker.InternetDraft:status']),
+            ('intended_status', orm['idtracker.InternetDraft:intended_status']),
+            ('lc_sent_date', orm['idtracker.InternetDraft:lc_sent_date']),
+            ('lc_changes', orm['idtracker.InternetDraft:lc_changes']),
+            ('lc_expiration_date', orm['idtracker.InternetDraft:lc_expiration_date']),
+            ('b_sent_date', orm['idtracker.InternetDraft:b_sent_date']),
+            ('b_discussion_date', orm['idtracker.InternetDraft:b_discussion_date']),
+            ('b_approve_date', orm['idtracker.InternetDraft:b_approve_date']),
+            ('wgreturn_date', orm['idtracker.InternetDraft:wgreturn_date']),
+            ('rfc_number', orm['idtracker.InternetDraft:rfc_number']),
+            ('comments', orm['idtracker.InternetDraft:comments']),
+            ('last_modified_date', orm['idtracker.InternetDraft:last_modified_date']),
+            ('replaced_by', orm['idtracker.InternetDraft:replaced_by']),
+            ('review_by_rfc_editor', orm['idtracker.InternetDraft:review_by_rfc_editor']),
+            ('expired_tombstone', orm['idtracker.InternetDraft:expired_tombstone']),
+        ))
+        db.send_create_signal('idtracker', ['InternetDraft'])
+        
+        # Adding model 'IRTFChair'
+        db.create_table('irtf_chairs', (
+            ('id', orm['idtracker.IRTFChair:id']),
+            ('irtf', orm['idtracker.IRTFChair:irtf']),
+            ('person', orm['idtracker.IRTFChair:person']),
+        ))
+        db.send_create_signal('idtracker', ['IRTFChair'])
+        
+        # Adding model 'IETFWG'
+        db.create_table('groups_ietf', (
+            ('group_acronym', orm['idtracker.IETFWG:group_acronym']),
+            ('group_type', orm['idtracker.IETFWG:group_type']),
+            ('proposed_date', orm['idtracker.IETFWG:proposed_date']),
+            ('start_date', orm['idtracker.IETFWG:start_date']),
+            ('dormant_date', orm['idtracker.IETFWG:dormant_date']),
+            ('concluded_date', orm['idtracker.IETFWG:concluded_date']),
+            ('status', orm['idtracker.IETFWG:status']),
+            ('area_director', orm['idtracker.IETFWG:area_director']),
+            ('meeting_scheduled', orm['idtracker.IETFWG:meeting_scheduled']),
+            ('email_address', orm['idtracker.IETFWG:email_address']),
+            ('email_subscribe', orm['idtracker.IETFWG:email_subscribe']),
+            ('email_keyword', orm['idtracker.IETFWG:email_keyword']),
+            ('email_archive', orm['idtracker.IETFWG:email_archive']),
+            ('comments', orm['idtracker.IETFWG:comments']),
+            ('last_modified_date', orm['idtracker.IETFWG:last_modified_date']),
+            ('meeting_scheduled_old', orm['idtracker.IETFWG:meeting_scheduled_old']),
+        ))
+        db.send_create_signal('idtracker', ['IETFWG'])
+        
+        # Adding model 'PostalAddress'
+        db.create_table('postal_addresses', (
+            ('id', orm['idtracker.PostalAddress:id']),
+            ('address_type', orm['idtracker.PostalAddress:address_type']),
+            ('address_priority', orm['idtracker.PostalAddress:address_priority']),
+            ('person_or_org', orm['idtracker.PostalAddress:person_or_org']),
+            ('person_title', orm['idtracker.PostalAddress:person_title']),
+            ('affiliated_company', orm['idtracker.PostalAddress:affiliated_company']),
+            ('aff_company_key', orm['idtracker.PostalAddress:aff_company_key']),
+            ('department', orm['idtracker.PostalAddress:department']),
+            ('staddr1', orm['idtracker.PostalAddress:staddr1']),
+            ('staddr2', orm['idtracker.PostalAddress:staddr2']),
+            ('mail_stop', orm['idtracker.PostalAddress:mail_stop']),
+            ('city', orm['idtracker.PostalAddress:city']),
+            ('state_or_prov', orm['idtracker.PostalAddress:state_or_prov']),
+            ('postal_code', orm['idtracker.PostalAddress:postal_code']),
+            ('country', orm['idtracker.PostalAddress:country']),
+        ))
+        db.send_create_signal('idtracker', ['PostalAddress'])
+        
+        # Adding model 'RfcIntendedStatus'
+        db.create_table('rfc_intend_status', (
+            ('intended_status_id', orm['idtracker.RfcIntendedStatus:intended_status_id']),
+            ('status', orm['idtracker.RfcIntendedStatus:status']),
+        ))
+        db.send_create_signal('idtracker', ['RfcIntendedStatus'])
+        
+        # Adding model 'IDInternal'
+        db.create_table('id_internal', (
+            ('draft', orm['idtracker.IDInternal:draft']),
+            ('rfc_flag', orm['idtracker.IDInternal:rfc_flag']),
+            ('ballot', orm['idtracker.IDInternal:ballot']),
+            ('primary_flag', orm['idtracker.IDInternal:primary_flag']),
+            ('group_flag', orm['idtracker.IDInternal:group_flag']),
+            ('token_name', orm['idtracker.IDInternal:token_name']),
+            ('token_email', orm['idtracker.IDInternal:token_email']),
+            ('note', orm['idtracker.IDInternal:note']),
+            ('status_date', orm['idtracker.IDInternal:status_date']),
+            ('email_display', orm['idtracker.IDInternal:email_display']),
+            ('agenda', orm['idtracker.IDInternal:agenda']),
+            ('cur_state', orm['idtracker.IDInternal:cur_state']),
+            ('prev_state', orm['idtracker.IDInternal:prev_state']),
+            ('assigned_to', orm['idtracker.IDInternal:assigned_to']),
+            ('mark_by', orm['idtracker.IDInternal:mark_by']),
+            ('job_owner', orm['idtracker.IDInternal:job_owner']),
+            ('event_date', orm['idtracker.IDInternal:event_date']),
+            ('area_acronym', orm['idtracker.IDInternal:area_acronym']),
+            ('cur_sub_state', orm['idtracker.IDInternal:cur_sub_state']),
+            ('prev_sub_state', orm['idtracker.IDInternal:prev_sub_state']),
+            ('returning_item', orm['idtracker.IDInternal:returning_item']),
+            ('telechat_date', orm['idtracker.IDInternal:telechat_date']),
+            ('via_rfc_editor', orm['idtracker.IDInternal:via_rfc_editor']),
+            ('state_change_notice_to', orm['idtracker.IDInternal:state_change_notice_to']),
+            ('dnp', orm['idtracker.IDInternal:dnp']),
+            ('dnp_date', orm['idtracker.IDInternal:dnp_date']),
+            ('noproblem', orm['idtracker.IDInternal:noproblem']),
+            ('resurrect_requested_by', orm['idtracker.IDInternal:resurrect_requested_by']),
+            ('approved_in_minute', orm['idtracker.IDInternal:approved_in_minute']),
+        ))
+        db.send_create_signal('idtracker', ['IDInternal'])
+        
+        # Adding model 'IDAuthor'
+        db.create_table('id_authors', (
+            ('id', orm['idtracker.IDAuthor:id']),
+            ('document', orm['idtracker.IDAuthor:document']),
+            ('person', orm['idtracker.IDAuthor:person']),
+            ('author_order', orm['idtracker.IDAuthor:author_order']),
+        ))
+        db.send_create_signal('idtracker', ['IDAuthor'])
+        
+        # Adding model 'IDStatus'
+        db.create_table('id_status', (
+            ('status_id', orm['idtracker.IDStatus:status_id']),
+            ('status', orm['idtracker.IDStatus:status']),
+        ))
+        db.send_create_signal('idtracker', ['IDStatus'])
+        
+        # Adding model 'Role'
+        db.create_table('chairs', (
+            ('id', orm['idtracker.Role:id']),
+            ('person', orm['idtracker.Role:person']),
+            ('role_name', orm['idtracker.Role:role_name']),
+        ))
+        db.send_create_signal('idtracker', ['Role'])
+        
+        # Adding model 'AreaDirector'
+        db.create_table('area_directors', (
+            ('id', orm['idtracker.AreaDirector:id']),
+            ('area', orm['idtracker.AreaDirector:area']),
+            ('person', orm['idtracker.AreaDirector:person']),
+        ))
+        db.send_create_signal('idtracker', ['AreaDirector'])
+        
+        # Adding model 'Rfc'
+        db.create_table('rfcs', (
+            ('rfc_number', orm['idtracker.Rfc:rfc_number']),
+            ('title', orm['idtracker.Rfc:title']),
+            ('rfc_name_key', orm['idtracker.Rfc:rfc_name_key']),
+            ('group_acronym', orm['idtracker.Rfc:group_acronym']),
+            ('area_acronym', orm['idtracker.Rfc:area_acronym']),
+            ('status', orm['idtracker.Rfc:status']),
+            ('intended_status', orm['idtracker.Rfc:intended_status']),
+            ('fyi_number', orm['idtracker.Rfc:fyi_number']),
+            ('std_number', orm['idtracker.Rfc:std_number']),
+            ('txt_page_count', orm['idtracker.Rfc:txt_page_count']),
+            ('online_version', orm['idtracker.Rfc:online_version']),
+            ('rfc_published_date', orm['idtracker.Rfc:rfc_published_date']),
+            ('proposed_date', orm['idtracker.Rfc:proposed_date']),
+            ('draft_date', orm['idtracker.Rfc:draft_date']),
+            ('standard_date', orm['idtracker.Rfc:standard_date']),
+            ('historic_date', orm['idtracker.Rfc:historic_date']),
+            ('lc_sent_date', orm['idtracker.Rfc:lc_sent_date']),
+            ('lc_expiration_date', orm['idtracker.Rfc:lc_expiration_date']),
+            ('b_sent_date', orm['idtracker.Rfc:b_sent_date']),
+            ('b_approve_date', orm['idtracker.Rfc:b_approve_date']),
+            ('comments', orm['idtracker.Rfc:comments']),
+            ('last_modified_date', orm['idtracker.Rfc:last_modified_date']),
+        ))
+        db.send_create_signal('idtracker', ['Rfc'])
+        
+        # Adding model 'EmailAddress'
+        db.create_table('email_addresses', (
+            ('id', orm['idtracker.EmailAddress:id']),
+            ('person_or_org', orm['idtracker.EmailAddress:person_or_org']),
+            ('type', orm['idtracker.EmailAddress:type']),
+            ('priority', orm['idtracker.EmailAddress:priority']),
+            ('address', orm['idtracker.EmailAddress:address']),
+            ('comment', orm['idtracker.EmailAddress:comment']),
+        ))
+        db.send_create_signal('idtracker', ['EmailAddress'])
+        
+        # Adding model 'AreaGroup'
+        db.create_table('area_group', (
+            ('id', orm['idtracker.AreaGroup:id']),
+            ('area', orm['idtracker.AreaGroup:area']),
+            ('group', orm['idtracker.AreaGroup:group']),
+        ))
+        db.send_create_signal('idtracker', ['AreaGroup'])
+        
+        # Adding model 'IESGDiscuss'
+        db.create_table('ballots_discuss', (
+            ('id', orm['idtracker.IESGDiscuss:id']),
+            ('ballot', orm['idtracker.IESGDiscuss:ballot']),
+            ('ad', orm['idtracker.IESGDiscuss:ad']),
+            ('date', orm['idtracker.IESGDiscuss:date']),
+            ('revision', orm['idtracker.IESGDiscuss:revision']),
+            ('active', orm['idtracker.IESGDiscuss:active']),
+            ('text', orm['idtracker.IESGDiscuss:text']),
+        ))
+        db.send_create_signal('idtracker', ['IESGDiscuss'])
+        
+        # Adding model 'GoalMilestone'
+        db.create_table('goals_milestones', (
+            ('gm_id', orm['idtracker.GoalMilestone:gm_id']),
+            ('group_acronym', orm['idtracker.GoalMilestone:group_acronym']),
+            ('description', orm['idtracker.GoalMilestone:description']),
+            ('expected_due_date', orm['idtracker.GoalMilestone:expected_due_date']),
+            ('done_date', orm['idtracker.GoalMilestone:done_date']),
+            ('done', orm['idtracker.GoalMilestone:done']),
+            ('last_modified_date', orm['idtracker.GoalMilestone:last_modified_date']),
+        ))
+        db.send_create_signal('idtracker', ['GoalMilestone'])
+        
+        # Adding model 'PhoneNumber'
+        db.create_table('phone_numbers', (
+            ('id', orm['idtracker.PhoneNumber:id']),
+            ('person_or_org', orm['idtracker.PhoneNumber:person_or_org']),
+            ('phone_type', orm['idtracker.PhoneNumber:phone_type']),
+            ('phone_priority', orm['idtracker.PhoneNumber:phone_priority']),
+            ('phone_number', orm['idtracker.PhoneNumber:phone_number']),
+            ('phone_comment', orm['idtracker.PhoneNumber:phone_comment']),
+        ))
+        db.send_create_signal('idtracker', ['PhoneNumber'])
+        
+        # Adding model 'WGSecretary'
+        db.create_table('g_secretaries', (
+            ('id', orm['idtracker.WGSecretary:id']),
+            ('group_acronym', orm['idtracker.WGSecretary:group_acronym']),
+            ('person', orm['idtracker.WGSecretary:person']),
+        ))
+        db.send_create_signal('idtracker', ['WGSecretary'])
+        
+        # Adding model 'WGStatus'
+        db.create_table('g_status', (
+            ('status_id', orm['idtracker.WGStatus:status_id']),
+            ('status', orm['idtracker.WGStatus:status']),
+        ))
+        db.send_create_signal('idtracker', ['WGStatus'])
+        
+        # Adding model 'IRTF'
+        db.create_table('irtf', (
+            ('irtf_id', orm['idtracker.IRTF:irtf_id']),
+            ('acronym', orm['idtracker.IRTF:acronym']),
+            ('name', orm['idtracker.IRTF:name']),
+            ('charter_text', orm['idtracker.IRTF:charter_text']),
+            ('meeting_scheduled', orm['idtracker.IRTF:meeting_scheduled']),
+        ))
+        db.send_create_signal('idtracker', ['IRTF'])
+        
+        # Adding model 'RfcStatus'
+        db.create_table('rfc_status', (
+            ('status_id', orm['idtracker.RfcStatus:status_id']),
+            ('status', orm['idtracker.RfcStatus:status']),
+        ))
+        db.send_create_signal('idtracker', ['RfcStatus'])
+        
+        # Adding model 'Area'
+        db.create_table('areas', (
+            ('area_acronym', orm['idtracker.Area:area_acronym']),
+            ('start_date', orm['idtracker.Area:start_date']),
+            ('concluded_date', orm['idtracker.Area:concluded_date']),
+            ('status', orm['idtracker.Area:status']),
+            ('comments', orm['idtracker.Area:comments']),
+            ('last_modified_date', orm['idtracker.Area:last_modified_date']),
+            ('extra_email_addresses', orm['idtracker.Area:extra_email_addresses']),
+        ))
+        db.send_create_signal('idtracker', ['Area'])
+        
+        # Adding model 'ChairsHistory'
+        db.create_table('chairs_history', (
+            ('id', orm['idtracker.ChairsHistory:id']),
+            ('chair_type', orm['idtracker.ChairsHistory:chair_type']),
+            ('present_chair', orm['idtracker.ChairsHistory:present_chair']),
+            ('person', orm['idtracker.ChairsHistory:person']),
+            ('start_year', orm['idtracker.ChairsHistory:start_year']),
+            ('end_year', orm['idtracker.ChairsHistory:end_year']),
+        ))
+        db.send_create_signal('idtracker', ['ChairsHistory'])
+        
+        # Adding model 'Acronym'
+        db.create_table('acronym', (
+            ('acronym_id', orm['idtracker.Acronym:acronym_id']),
+            ('acronym', orm['idtracker.Acronym:acronym']),
+            ('name', orm['idtracker.Acronym:name']),
+            ('name_key', orm['idtracker.Acronym:name_key']),
+        ))
+        db.send_create_signal('idtracker', ['Acronym'])
+        
+        # Adding model 'WGChair'
+        db.create_table('g_chairs', (
+            ('id', orm['idtracker.WGChair:id']),
+            ('person', orm['idtracker.WGChair:person']),
+            ('group_acronym', orm['idtracker.WGChair:group_acronym']),
+        ))
+        db.send_create_signal('idtracker', ['WGChair'])
+        
+        # Adding model 'WGEditor'
+        db.create_table('g_editors', (
+            ('id', orm['idtracker.WGEditor:id']),
+            ('group_acronym', orm['idtracker.WGEditor:group_acronym']),
+            ('person', orm['idtracker.WGEditor:person']),
+        ))
+        db.send_create_signal('idtracker', ['WGEditor'])
+        
+        # Adding model 'PersonOrOrgInfo'
+        db.create_table('person_or_org_info', (
+            ('person_or_org_tag', orm['idtracker.PersonOrOrgInfo:person_or_org_tag']),
+            ('record_type', orm['idtracker.PersonOrOrgInfo:record_type']),
+            ('name_prefix', orm['idtracker.PersonOrOrgInfo:name_prefix']),
+            ('first_name', orm['idtracker.PersonOrOrgInfo:first_name']),
+            ('first_name_key', orm['idtracker.PersonOrOrgInfo:first_name_key']),
+            ('middle_initial', orm['idtracker.PersonOrOrgInfo:middle_initial']),
+            ('middle_initial_key', orm['idtracker.PersonOrOrgInfo:middle_initial_key']),
+            ('last_name', orm['idtracker.PersonOrOrgInfo:last_name']),
+            ('last_name_key', orm['idtracker.PersonOrOrgInfo:last_name_key']),
+            ('name_suffix', orm['idtracker.PersonOrOrgInfo:name_suffix']),
+            ('date_modified', orm['idtracker.PersonOrOrgInfo:date_modified']),
+            ('modified_by', orm['idtracker.PersonOrOrgInfo:modified_by']),
+            ('date_created', orm['idtracker.PersonOrOrgInfo:date_created']),
+            ('created_by', orm['idtracker.PersonOrOrgInfo:created_by']),
+            ('address_type', orm['idtracker.PersonOrOrgInfo:address_type']),
+        ))
+        db.send_create_signal('idtracker', ['PersonOrOrgInfo'])
+        
+        # Adding model 'Position'
+        db.create_table('ballots', (
+            ('id', orm['idtracker.Position:id']),
+            ('ballot', orm['idtracker.Position:ballot']),
+            ('ad', orm['idtracker.Position:ad']),
+            ('yes', orm['idtracker.Position:yes']),
+            ('noobj', orm['idtracker.Position:noobj']),
+            ('abstain', orm['idtracker.Position:abstain']),
+            ('approve', orm['idtracker.Position:approve']),
+            ('discuss', orm['idtracker.Position:discuss']),
+            ('recuse', orm['idtracker.Position:recuse']),
+        ))
+        db.send_create_signal('idtracker', ['Position'])
+        
+        # Adding model 'IESGComment'
+        db.create_table('ballots_comment', (
+            ('id', orm['idtracker.IESGComment:id']),
+            ('ballot', orm['idtracker.IESGComment:ballot']),
+            ('ad', orm['idtracker.IESGComment:ad']),
+            ('date', orm['idtracker.IESGComment:date']),
+            ('revision', orm['idtracker.IESGComment:revision']),
+            ('active', orm['idtracker.IESGComment:active']),
+            ('text', orm['idtracker.IESGComment:text']),
+        ))
+        db.send_create_signal('idtracker', ['IESGComment'])
+        
+        # Adding model 'IESGLogin'
+        db.create_table('iesg_login', (
+            ('id', orm['idtracker.IESGLogin:id']),
+            ('login_name', orm['idtracker.IESGLogin:login_name']),
+            ('password', orm['idtracker.IESGLogin:password']),
+            ('user_level', orm['idtracker.IESGLogin:user_level']),
+            ('first_name', orm['idtracker.IESGLogin:first_name']),
+            ('last_name', orm['idtracker.IESGLogin:last_name']),
+            ('person', orm['idtracker.IESGLogin:person']),
+            ('pgp_id', orm['idtracker.IESGLogin:pgp_id']),
+            ('default_search', orm['idtracker.IESGLogin:default_search']),
+        ))
+        db.send_create_signal('idtracker', ['IESGLogin'])
+        
+        # Adding model 'AreaWGURL'
+        db.create_table('wg_www_pages', (
+            ('id', orm['idtracker.AreaWGURL:id']),
+            ('name', orm['idtracker.AreaWGURL:name']),
+            ('url', orm['idtracker.AreaWGURL:url']),
+            ('description', orm['idtracker.AreaWGURL:description']),
+        ))
+        db.send_create_signal('idtracker', ['AreaWGURL'])
+        
+        # Adding model 'IDSubState'
+        db.create_table('sub_state', (
+            ('sub_state_id', orm['idtracker.IDSubState:sub_state_id']),
+            ('sub_state', orm['idtracker.IDSubState:sub_state']),
+            ('description', orm['idtracker.IDSubState:description']),
+        ))
+        db.send_create_signal('idtracker', ['IDSubState'])
+        
+        # Adding model 'DocumentComment'
+        db.create_table('document_comments', (
+            ('id', orm['idtracker.DocumentComment:id']),
+            ('document', orm['idtracker.DocumentComment:document']),
+            ('rfc_flag', orm['idtracker.DocumentComment:rfc_flag']),
+            ('public_flag', orm['idtracker.DocumentComment:public_flag']),
+            ('date', orm['idtracker.DocumentComment:date']),
+            ('time', orm['idtracker.DocumentComment:time']),
+            ('version', orm['idtracker.DocumentComment:version']),
+            ('comment_text', orm['idtracker.DocumentComment:comment_text']),
+            ('created_by', orm['idtracker.DocumentComment:created_by']),
+            ('result_state', orm['idtracker.DocumentComment:result_state']),
+            ('origin_state', orm['idtracker.DocumentComment:origin_state']),
+            ('ballot', orm['idtracker.DocumentComment:ballot']),
+        ))
+        db.send_create_signal('idtracker', ['DocumentComment'])
+        
+        # Creating unique_together for [ballot, ad] on IESGDiscuss.
+        db.create_unique('ballots_discuss', ['ballot_id', 'ad_id'])
+        
+        # Creating unique_together for [ballot, ad] on Position.
+        db.create_unique('ballots', ['ballot_id', 'ad_id'])
+        
+        # Creating unique_together for [ballot, ad] on IESGComment.
+        db.create_unique('ballots_comment', ['ballot_id', 'ad_id'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting unique_together for [ballot, ad] on IESGComment.
+        db.delete_unique('ballots_comment', ['ballot_id', 'ad_id'])
+        
+        # Deleting unique_together for [ballot, ad] on Position.
+        db.delete_unique('ballots', ['ballot_id', 'ad_id'])
+        
+        # Deleting unique_together for [ballot, ad] on IESGDiscuss.
+        db.delete_unique('ballots_discuss', ['ballot_id', 'ad_id'])
+        
+        # Deleting model 'WGTechAdvisor'
+        db.delete_table('g_tech_advisors')
+        
+        # Deleting model 'IDState'
+        db.delete_table('ref_doc_states_new')
+        
+        # Deleting model 'BallotInfo'
+        db.delete_table('ballot_info')
+        
+        # Deleting model 'AreaStatus'
+        db.delete_table('area_status')
+        
+        # Deleting model 'RfcAuthor'
+        db.delete_table('rfc_authors')
+        
+        # Deleting model 'IDIntendedStatus'
+        db.delete_table('id_intended_status')
+        
+        # Deleting model 'IDNextState'
+        db.delete_table('ref_next_states_new')
+        
+        # Deleting model 'WGType'
+        db.delete_table('g_type')
+        
+        # Deleting model 'RfcObsolete'
+        db.delete_table('rfcs_obsolete')
+        
+        # Deleting model 'InternetDraft'
+        db.delete_table('internet_drafts')
+        
+        # Deleting model 'IRTFChair'
+        db.delete_table('irtf_chairs')
+        
+        # Deleting model 'IETFWG'
+        db.delete_table('groups_ietf')
+        
+        # Deleting model 'PostalAddress'
+        db.delete_table('postal_addresses')
+        
+        # Deleting model 'RfcIntendedStatus'
+        db.delete_table('rfc_intend_status')
+        
+        # Deleting model 'IDInternal'
+        db.delete_table('id_internal')
+        
+        # Deleting model 'IDAuthor'
+        db.delete_table('id_authors')
+        
+        # Deleting model 'IDStatus'
+        db.delete_table('id_status')
+        
+        # Deleting model 'Role'
+        db.delete_table('chairs')
+        
+        # Deleting model 'AreaDirector'
+        db.delete_table('area_directors')
+        
+        # Deleting model 'Rfc'
+        db.delete_table('rfcs')
+        
+        # Deleting model 'EmailAddress'
+        db.delete_table('email_addresses')
+        
+        # Deleting model 'AreaGroup'
+        db.delete_table('area_group')
+        
+        # Deleting model 'IESGDiscuss'
+        db.delete_table('ballots_discuss')
+        
+        # Deleting model 'GoalMilestone'
+        db.delete_table('goals_milestones')
+        
+        # Deleting model 'PhoneNumber'
+        db.delete_table('phone_numbers')
+        
+        # Deleting model 'WGSecretary'
+        db.delete_table('g_secretaries')
+        
+        # Deleting model 'WGStatus'
+        db.delete_table('g_status')
+        
+        # Deleting model 'IRTF'
+        db.delete_table('irtf')
+        
+        # Deleting model 'RfcStatus'
+        db.delete_table('rfc_status')
+        
+        # Deleting model 'Area'
+        db.delete_table('areas')
+        
+        # Deleting model 'ChairsHistory'
+        db.delete_table('chairs_history')
+        
+        # Deleting model 'Acronym'
+        db.delete_table('acronym')
+        
+        # Deleting model 'WGChair'
+        db.delete_table('g_chairs')
+        
+        # Deleting model 'WGEditor'
+        db.delete_table('g_editors')
+        
+        # Deleting model 'PersonOrOrgInfo'
+        db.delete_table('person_or_org_info')
+        
+        # Deleting model 'Position'
+        db.delete_table('ballots')
+        
+        # Deleting model 'IESGComment'
+        db.delete_table('ballots_comment')
+        
+        # Deleting model 'IESGLogin'
+        db.delete_table('iesg_login')
+        
+        # Deleting model 'AreaWGURL'
+        db.delete_table('wg_www_pages')
+        
+        # Deleting model 'IDSubState'
+        db.delete_table('sub_state')
+        
+        # Deleting model 'DocumentComment'
+        db.delete_table('document_comments')
+        
+    
+    
+    models = {
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.area': {
+            'Meta': {'db_table': "'areas'"},
+            'area_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'extra_email_addresses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaStatus']"})
+        },
+        'idtracker.areadirector': {
+            'Meta': {'db_table': "'area_directors'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']", 'null': 'True', 'db_column': "'area_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.areagroup': {
+            'Meta': {'db_table': "'area_group'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'areagroup'", 'db_column': "'area_acronym_id'", 'to': "orm['idtracker.Area']"}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']", 'unique': 'True', 'db_column': "'group_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.areastatus': {
+            'Meta': {'db_table': "'area_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.areawgurl': {
+            'Meta': {'db_table': "'wg_www_pages'"},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'area_ID'"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'area_Name'"}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.ballotinfo': {
+            'Meta': {'db_table': "'ballot_info'"},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'an_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'an_sent_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ansent'", 'null': 'True', 'db_column': "'an_sent_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'an_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'approval_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'ballot': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'ballot_id'"}),
+            'ballot_issued': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'ballot_writeup': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'defer': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'defer_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deferred'", 'null': 'True', 'db_column': "'defer_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'defer_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'last_call_text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'idtracker.chairshistory': {
+            'Meta': {'db_table': "'chairs_history'"},
+            'chair_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Role']"}),
+            'end_year': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'present_chair': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'start_year': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'idtracker.documentcomment': {
+            'Meta': {'db_table': "'document_comments'"},
+            'ballot': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'comment_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'null': 'True', 'db_column': "'created_by'", 'null_values': '(0,999)'}),
+            'date': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'db_column': "'comment_date'"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDInternal']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'origin_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments_coming_from_state'", 'null': 'True', 'db_column': "'origin_state'", 'to': "orm['idtracker.IDState']"}),
+            'public_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'result_state': ('BrokenForeignKey', ["orm['idtracker.IDState']"], {'related_name': '"comments_leading_to_state"', 'null': 'True', 'db_column': "'result_state'", 'null_values': '(0,99)'}),
+            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'time': ('django.db.models.fields.CharField', [], {'default': "'05:06:33'", 'max_length': '20', 'db_column': "'comment_time'"}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'})
+        },
+        'idtracker.emailaddress': {
+            'Meta': {'db_table': "'email_addresses'"},
+            'address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'email_address'"}),
+            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_column': "'email_comment'", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'db_column': "'email_priority'"}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'db_column': "'email_type'"})
+        },
+        'idtracker.goalmilestone': {
+            'Meta': {'db_table': "'goals_milestones'"},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'done': ('django.db.models.fields.CharField', [], {'max_length': '4', 'blank': 'True'}),
+            'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expected_due_date': ('django.db.models.fields.DateField', [], {}),
+            'gm_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {})
+        },
+        'idtracker.idauthor': {
+            'Meta': {'db_table': "'id_authors'"},
+            'author_order': ('django.db.models.fields.IntegerField', [], {}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'id_document_tag'", 'to': "orm['idtracker.InternetDraft']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idinternal': {
+            'Meta': {'db_table': "'id_internal'"},
+            'agenda': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'approved_in_minute': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'area_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']"}),
+            'assigned_to': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'drafts'", 'db_column': "'ballot_id'", 'to': "orm['idtracker.BallotInfo']"}),
+            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs'", 'db_column': "'cur_state'", 'to': "orm['idtracker.IDState']"}),
+            'cur_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
+            'dnp': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'dnp_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'draft': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True', 'primary_key': 'True', 'db_column': "'id_document_tag'"}),
+            'email_display': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'event_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'group_flag': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'job_owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'db_column': "'job_owner'", 'to': "orm['idtracker.IESGLogin']"}),
+            'mark_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'marked'", 'db_column': "'mark_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'noproblem': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'prev_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs_prev'", 'db_column': "'prev_state'", 'to': "orm['idtracker.IDState']"}),
+            'prev_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs_prev'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
+            'primary_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'resurrect_requested_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'related_name': "'docsresurrected'", 'null': 'True', 'db_column': "'resurrect_requested_by'", 'blank': 'True'}),
+            'returning_item': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'state_change_notice_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'status_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'token_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'token_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'via_rfc_editor': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.idnextstate': {
+            'Meta': {'db_table': "'ref_next_states_new'"},
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nextstate'", 'to': "orm['idtracker.IDState']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'next_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'prevstate'", 'to': "orm['idtracker.IDState']"})
+        },
+        'idtracker.idstate': {
+            'Meta': {'db_table': "'ref_doc_states_new'"},
+            'description': ('django.db.models.fields.TextField', [], {'db_column': "'document_desc'", 'blank': 'True'}),
+            'document_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'equiv_group_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'document_state_val'"})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idsubstate': {
+            'Meta': {'db_table': "'sub_state'"},
+            'description': ('django.db.models.fields.TextField', [], {'db_column': "'sub_state_desc'", 'blank': 'True'}),
+            'sub_state': ('django.db.models.fields.CharField', [], {'max_length': '55', 'db_column': "'sub_state_val'"}),
+            'sub_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.iesgcomment': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_comment'"},
+            'active': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['idtracker.BallotInfo']"}),
+            'date': ('django.db.models.fields.DateField', [], {'db_column': "'comment_date'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'text': ('django.db.models.fields.TextField', [], {'db_column': "'comment_text'", 'blank': 'True'})
+        },
+        'idtracker.iesgdiscuss': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_discuss'"},
+            'active': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'discusses'", 'to': "orm['idtracker.BallotInfo']"}),
+            'date': ('django.db.models.fields.DateField', [], {'db_column': "'discuss_date'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'text': ('django.db.models.fields.TextField', [], {'db_column': "'discuss_text'", 'blank': 'True'})
+        },
+        'idtracker.iesglogin': {
+            'Meta': {'db_table': "'iesg_login'"},
+            'default_search': ('django.db.models.fields.NullBooleanField', [], {'null': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'login_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
+            'person': ('BrokenForeignKey', ["orm['idtracker.PersonOrOrgInfo']"], {'unique': 'True', 'null': 'True', 'db_column': "'person_or_org_tag'", 'null_values': '(0,888888)'}),
+            'pgp_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'user_level': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'idtracker.ietfwg': {
+            'Meta': {'db_table': "'groups_ietf'"},
+            'area_director': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaDirector']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'dormant_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'email_address': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
+            'email_archive': ('django.db.models.fields.CharField', [], {'max_length': '95', 'blank': 'True'}),
+            'email_keyword': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'email_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'group_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGType']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'meeting_scheduled': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'meeting_scheduled_old': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGStatus']"})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('BrokenForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.irtf': {
+            'Meta': {'db_table': "'irtf'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'irtf_acronym'", 'blank': 'True'}),
+            'charter_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'irtf_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting_scheduled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'irtf_name'", 'blank': 'True'})
+        },
+        'idtracker.irtfchair': {
+            'Meta': {'db_table': "'irtf_chairs'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'irtf': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IRTF']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.phonenumber': {
+            'Meta': {'db_table': "'phone_numbers'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'phone_comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'phone_priority': ('django.db.models.fields.IntegerField', [], {}),
+            'phone_type': ('django.db.models.fields.CharField', [], {'max_length': '3'})
+        },
+        'idtracker.position': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots'"},
+            'abstain': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'approve': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'positions'", 'to': "orm['idtracker.BallotInfo']"}),
+            'discuss': ('django.db.models.fields.IntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'noobj': ('django.db.models.fields.IntegerField', [], {'db_column': "'no_col'"}),
+            'recuse': ('django.db.models.fields.IntegerField', [], {}),
+            'yes': ('django.db.models.fields.IntegerField', [], {'db_column': "'yes_col'"})
+        },
+        'idtracker.postaladdress': {
+            'Meta': {'db_table': "'postal_addresses'"},
+            'address_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+            'aff_company_key': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'affiliated_company': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'city': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'country': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'department': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mail_stop': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'person_title': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'staddr1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'staddr2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
+            'state_or_prov': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        },
+        'idtracker.rfc': {
+            'Meta': {'db_table': "'rfcs'"},
+            'area_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'draft_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'fyi_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
+            'historic_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'default': '5', 'to': "orm['idtracker.RfcIntendedStatus']", 'db_column': "'intended_status_id'"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'online_version': ('django.db.models.fields.CharField', [], {'default': "'YES'", 'max_length': '3'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'rfc_name_key': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'rfc_published_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'standard_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.RfcStatus']", 'db_column': "'status_id'"}),
+            'std_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_column': "'rfc_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.rfcauthor': {
+            'Meta': {'db_table': "'rfc_authors'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"})
+        },
+        'idtracker.rfcintendedstatus': {
+            'Meta': {'db_table': "'rfc_intend_status'"},
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"})
+        },
+        'idtracker.rfcobsolete': {
+            'Meta': {'db_table': "'rfcs_obsolete'"},
+            'action': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates_or_obsoletes'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"}),
+            'rfc_acted_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updated_or_obsoleted_by'", 'db_column': "'rfc_acted_on'", 'to': "orm['idtracker.Rfc']"})
+        },
+        'idtracker.rfcstatus': {
+            'Meta': {'db_table': "'rfc_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.role': {
+            'Meta': {'db_table': "'chairs'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'role_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'chair_name'"})
+        },
+        'idtracker.wgchair': {
+            'Meta': {'db_table': "'g_chairs'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgeditor': {
+            'Meta': {'db_table': "'g_editors'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'unique': 'True', 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgsecretary': {
+            'Meta': {'db_table': "'g_secretaries'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgstatus': {
+            'Meta': {'db_table': "'g_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.wgtechadvisor': {
+            'Meta': {'db_table': "'g_tech_advisors'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgtype': {
+            'Meta': {'db_table': "'g_type'"},
+            'group_type_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'group_type'"})
+        }
+    }
+    
+    complete_apps = ['idtracker']
diff --git a/ietf/idtracker/migrations/0002_shepherd.py b/ietf/idtracker/migrations/0002_shepherd.py
new file mode 100644
index 000000000..091ab06f5
--- /dev/null
+++ b/ietf/idtracker/migrations/0002_shepherd.py
@@ -0,0 +1,437 @@
+
+from south.db import db
+from django.db import models
+from ietf.idtracker.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding field 'InternetDraft.shepherd'
+        db.add_column('internet_drafts', 'shepherd', orm['idtracker.internetdraft:shepherd'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting field 'InternetDraft.shepherd'
+        db.delete_column('internet_drafts', 'shepherd_id')
+        
+    
+    
+    models = {
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.area': {
+            'Meta': {'db_table': "'areas'"},
+            'area_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'extra_email_addresses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaStatus']"})
+        },
+        'idtracker.areadirector': {
+            'Meta': {'db_table': "'area_directors'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']", 'null': 'True', 'db_column': "'area_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.areagroup': {
+            'Meta': {'db_table': "'area_group'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'areagroup'", 'db_column': "'area_acronym_id'", 'to': "orm['idtracker.Area']"}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']", 'unique': 'True', 'db_column': "'group_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.areastatus': {
+            'Meta': {'db_table': "'area_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.areawgurl': {
+            'Meta': {'db_table': "'wg_www_pages'"},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'area_ID'"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'area_Name'"}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.ballotinfo': {
+            'Meta': {'db_table': "'ballot_info'"},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'an_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'an_sent_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ansent'", 'null': 'True', 'db_column': "'an_sent_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'an_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'approval_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'ballot': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'ballot_id'"}),
+            'ballot_issued': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'ballot_writeup': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'defer': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'defer_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deferred'", 'null': 'True', 'db_column': "'defer_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'defer_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'last_call_text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'idtracker.chairshistory': {
+            'Meta': {'db_table': "'chairs_history'"},
+            'chair_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Role']"}),
+            'end_year': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'present_chair': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'start_year': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'idtracker.documentcomment': {
+            'Meta': {'db_table': "'document_comments'"},
+            'ballot': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'comment_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'null': 'True', 'db_column': "'created_by'", 'null_values': '(0,999)'}),
+            'date': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'db_column': "'comment_date'"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDInternal']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'origin_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments_coming_from_state'", 'null': 'True', 'db_column': "'origin_state'", 'to': "orm['idtracker.IDState']"}),
+            'public_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'result_state': ('BrokenForeignKey', ["orm['idtracker.IDState']"], {'related_name': '"comments_leading_to_state"', 'null': 'True', 'db_column': "'result_state'", 'null_values': '(0,99)'}),
+            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'time': ('django.db.models.fields.CharField', [], {'default': "'05:10:39'", 'max_length': '20', 'db_column': "'comment_time'"}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'})
+        },
+        'idtracker.emailaddress': {
+            'Meta': {'db_table': "'email_addresses'"},
+            'address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'email_address'"}),
+            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_column': "'email_comment'", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'db_column': "'email_priority'"}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'db_column': "'email_type'"})
+        },
+        'idtracker.goalmilestone': {
+            'Meta': {'db_table': "'goals_milestones'"},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'done': ('django.db.models.fields.CharField', [], {'max_length': '4', 'blank': 'True'}),
+            'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expected_due_date': ('django.db.models.fields.DateField', [], {}),
+            'gm_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {})
+        },
+        'idtracker.idauthor': {
+            'Meta': {'db_table': "'id_authors'"},
+            'author_order': ('django.db.models.fields.IntegerField', [], {}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'id_document_tag'", 'to': "orm['idtracker.InternetDraft']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idinternal': {
+            'Meta': {'db_table': "'id_internal'"},
+            'agenda': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'approved_in_minute': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'area_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']"}),
+            'assigned_to': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'drafts'", 'db_column': "'ballot_id'", 'to': "orm['idtracker.BallotInfo']"}),
+            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs'", 'db_column': "'cur_state'", 'to': "orm['idtracker.IDState']"}),
+            'cur_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
+            'dnp': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'dnp_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'draft': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True', 'primary_key': 'True', 'db_column': "'id_document_tag'"}),
+            'email_display': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'event_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'group_flag': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'job_owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'db_column': "'job_owner'", 'to': "orm['idtracker.IESGLogin']"}),
+            'mark_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'marked'", 'db_column': "'mark_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'noproblem': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'prev_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs_prev'", 'db_column': "'prev_state'", 'to': "orm['idtracker.IDState']"}),
+            'prev_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs_prev'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
+            'primary_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'resurrect_requested_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'related_name': "'docsresurrected'", 'null': 'True', 'db_column': "'resurrect_requested_by'", 'blank': 'True'}),
+            'returning_item': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'state_change_notice_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'status_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'token_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'token_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'via_rfc_editor': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.idnextstate': {
+            'Meta': {'db_table': "'ref_next_states_new'"},
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nextstate'", 'to': "orm['idtracker.IDState']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'next_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'prevstate'", 'to': "orm['idtracker.IDState']"})
+        },
+        'idtracker.idstate': {
+            'Meta': {'db_table': "'ref_doc_states_new'"},
+            'description': ('django.db.models.fields.TextField', [], {'db_column': "'document_desc'", 'blank': 'True'}),
+            'document_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'equiv_group_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'document_state_val'"})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idsubstate': {
+            'Meta': {'db_table': "'sub_state'"},
+            'description': ('django.db.models.fields.TextField', [], {'db_column': "'sub_state_desc'", 'blank': 'True'}),
+            'sub_state': ('django.db.models.fields.CharField', [], {'max_length': '55', 'db_column': "'sub_state_val'"}),
+            'sub_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.iesgcomment': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_comment'"},
+            'active': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['idtracker.BallotInfo']"}),
+            'date': ('django.db.models.fields.DateField', [], {'db_column': "'comment_date'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'text': ('django.db.models.fields.TextField', [], {'db_column': "'comment_text'", 'blank': 'True'})
+        },
+        'idtracker.iesgdiscuss': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_discuss'"},
+            'active': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'discusses'", 'to': "orm['idtracker.BallotInfo']"}),
+            'date': ('django.db.models.fields.DateField', [], {'db_column': "'discuss_date'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'text': ('django.db.models.fields.TextField', [], {'db_column': "'discuss_text'", 'blank': 'True'})
+        },
+        'idtracker.iesglogin': {
+            'Meta': {'db_table': "'iesg_login'"},
+            'default_search': ('django.db.models.fields.NullBooleanField', [], {'null': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'login_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
+            'person': ('BrokenForeignKey', ["orm['idtracker.PersonOrOrgInfo']"], {'unique': 'True', 'null': 'True', 'db_column': "'person_or_org_tag'", 'null_values': '(0,888888)'}),
+            'pgp_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'user_level': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'idtracker.ietfwg': {
+            'Meta': {'db_table': "'groups_ietf'"},
+            'area_director': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaDirector']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'dormant_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'email_address': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
+            'email_archive': ('django.db.models.fields.CharField', [], {'max_length': '95', 'blank': 'True'}),
+            'email_keyword': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'email_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'group_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGType']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'meeting_scheduled': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'meeting_scheduled_old': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGStatus']"})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('BrokenForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.irtf': {
+            'Meta': {'db_table': "'irtf'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'irtf_acronym'", 'blank': 'True'}),
+            'charter_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'irtf_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting_scheduled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'irtf_name'", 'blank': 'True'})
+        },
+        'idtracker.irtfchair': {
+            'Meta': {'db_table': "'irtf_chairs'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'irtf': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IRTF']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.phonenumber': {
+            'Meta': {'db_table': "'phone_numbers'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'phone_comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'phone_priority': ('django.db.models.fields.IntegerField', [], {}),
+            'phone_type': ('django.db.models.fields.CharField', [], {'max_length': '3'})
+        },
+        'idtracker.position': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots'"},
+            'abstain': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'approve': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'positions'", 'to': "orm['idtracker.BallotInfo']"}),
+            'discuss': ('django.db.models.fields.IntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'noobj': ('django.db.models.fields.IntegerField', [], {'db_column': "'no_col'"}),
+            'recuse': ('django.db.models.fields.IntegerField', [], {}),
+            'yes': ('django.db.models.fields.IntegerField', [], {'db_column': "'yes_col'"})
+        },
+        'idtracker.postaladdress': {
+            'Meta': {'db_table': "'postal_addresses'"},
+            'address_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+            'aff_company_key': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'affiliated_company': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'city': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'country': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'department': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mail_stop': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'person_title': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'staddr1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'staddr2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
+            'state_or_prov': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        },
+        'idtracker.rfc': {
+            'Meta': {'db_table': "'rfcs'"},
+            'area_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'draft_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'fyi_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
+            'historic_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'default': '5', 'to': "orm['idtracker.RfcIntendedStatus']", 'db_column': "'intended_status_id'"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'online_version': ('django.db.models.fields.CharField', [], {'default': "'YES'", 'max_length': '3'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'rfc_name_key': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'rfc_published_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'standard_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.RfcStatus']", 'db_column': "'status_id'"}),
+            'std_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_column': "'rfc_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.rfcauthor': {
+            'Meta': {'db_table': "'rfc_authors'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"})
+        },
+        'idtracker.rfcintendedstatus': {
+            'Meta': {'db_table': "'rfc_intend_status'"},
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"})
+        },
+        'idtracker.rfcobsolete': {
+            'Meta': {'db_table': "'rfcs_obsolete'"},
+            'action': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates_or_obsoletes'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"}),
+            'rfc_acted_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updated_or_obsoleted_by'", 'db_column': "'rfc_acted_on'", 'to': "orm['idtracker.Rfc']"})
+        },
+        'idtracker.rfcstatus': {
+            'Meta': {'db_table': "'rfc_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.role': {
+            'Meta': {'db_table': "'chairs'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'role_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'chair_name'"})
+        },
+        'idtracker.wgchair': {
+            'Meta': {'db_table': "'g_chairs'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgeditor': {
+            'Meta': {'db_table': "'g_editors'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'unique': 'True', 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgsecretary': {
+            'Meta': {'db_table': "'g_secretaries'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgstatus': {
+            'Meta': {'db_table': "'g_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.wgtechadvisor': {
+            'Meta': {'db_table': "'g_tech_advisors'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgtype': {
+            'Meta': {'db_table': "'g_type'"},
+            'group_type_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'group_type'"})
+        }
+    }
+    
+    complete_apps = ['idtracker']
diff --git a/ietf/idtracker/migrations/__init__.py b/ietf/idtracker/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index f75f7f0f3..c1492ea56 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -167,6 +167,7 @@ class InternetDraft(models.Model):
     review_by_rfc_editor = models.BooleanField()
     expired_tombstone = models.BooleanField()
     idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
+    shepherd = models.ForeignKey('PersonOrOrgInfo')
     def __str__(self):
         return self.filename
     def save(self):

From 21e73c9d37f7098265f49d64c10ed75b8b0da4b2 Mon Sep 17 00:00:00 2001
From: Alexey Zarubin <azarubin@yaco.es>
Date: Thu, 2 Dec 2010 20:34:50 +0000
Subject: [PATCH 13/57] Fixes #563

edit form. the url for this described with  '^(?P<name>[^/]+)/edit/managing-shepherd/$'
 - Legacy-Id: 2694
---
 .gitignore                                    |  3 +
 ietf/.gitignore                               |  1 +
 ietf/idrfc/forms.py                           | 68 +++++++++++++++++++
 ietf/idrfc/urls.py                            |  2 +
 ietf/idrfc/views_edit.py                      | 22 +++++-
 ietf/idrfc/views_search.py                    |  8 ++-
 ietf/idtracker/admin.py                       |  2 +-
 ietf/idtracker/forms.py                       | 67 ++++++++++++++++++
 ietf/idtracker/models.py                      |  2 +-
 .../idrfc/edit_management_shepherd.html       | 50 ++++++++++++++
 .../idrfc/edit_management_shepherd_email.txt  |  1 +
 11 files changed, 222 insertions(+), 4 deletions(-)
 create mode 100644 ietf/idrfc/forms.py
 create mode 100644 ietf/idtracker/forms.py
 create mode 100644 ietf/templates/idrfc/edit_management_shepherd.html
 create mode 100644 ietf/templates/idrfc/edit_management_shepherd_email.txt

diff --git a/.gitignore b/.gitignore
index 674deb69e..fbd9b09c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,6 @@
 .*.swp
 .DS_store
 # Simulated Subversion default ignores end here
+/.project
+/.pydevproject
+/.settings
diff --git a/ietf/.gitignore b/ietf/.gitignore
index c7013ced9..2cc1317c1 100644
--- a/ietf/.gitignore
+++ b/ietf/.gitignore
@@ -1,2 +1,3 @@
 /*.pyc
 /settings_local.py
+/ietfdb.sql.gz
diff --git a/ietf/idrfc/forms.py b/ietf/idrfc/forms.py
new file mode 100644
index 000000000..eae1fc611
--- /dev/null
+++ b/ietf/idrfc/forms.py
@@ -0,0 +1,68 @@
+from django.conf import settings
+from django import forms
+from idtracker.models import PersonOrOrgInfo
+from django.db.models import Q
+from django.template.loader import render_to_string
+from django.core.mail import EmailMessage
+
+
+class ManagingShepherdForm(forms.Form):
+    email = forms.EmailField(required=False)
+    is_assign_current = forms.BooleanField(required=False)
+    
+    
+    def __init__(self, *args, **kwargs):
+        if kwargs.has_key('current_person'):
+            self.current_person = kwargs.pop('current_person')            
+        return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
+    
+    def clean_email(self):
+        email = self.cleaned_data.get('email')
+        if not email:
+            return None
+        
+        try:
+            PersonOrOrgInfo.objects. \
+                  filter(emailaddress__type__in=[ "INET", "Prim",], 
+                        emailaddress__address=email) \
+                        [:1].get()
+        except PersonOrOrgInfo.DoesNotExist:
+            if self.cleaned_data.get('is_assign_current'):
+                self._send_email(email)
+            raise forms.ValidationError("Person with such email does not exist")
+        return email
+    
+    def clean(self):
+        print self.cleaned_data.get('email') and self.cleaned_data.get('is_assign_current')
+        if self.cleaned_data.get('email') and \
+                                    self.cleaned_data.get('is_assign_current'):
+            raise forms.ValidationError("You should choose to assign to current \
+                        person or input the email. Not both at te same time. ")
+        
+        return self.cleaned_data
+    
+    def change_shepherd(self, document, save=True):
+        email = self.cleaned_data.get('email')        
+        if email:
+            person = PersonOrOrgInfo.objects. \
+                  filter(emailaddress__type__in=[ "INET", "Prim",], 
+                        emailaddress__address=email) \
+                        [:1].get()
+        else:
+            person = self.current_person        
+        document.shepherd = person 
+        if save: 
+            document.save()
+        return document
+    
+    def _send_email(self, email, 
+                        template='idrfc/edit_management_shepherd_email.txt'):
+        subject = 'WG Delegate needs system credentials'        
+        body = render_to_string(template,
+                                {'email': email,
+                                })
+        mail = EmailMessage(subject=subject,
+                            body=body,
+                            to=[email, settings.DEFAULT_FROM_EMAIL, ],
+                            from_email=settings.DEFAULT_FROM_EMAIL)
+        mail.send()
\ No newline at end of file
diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py
index 88bf31d16..128d6793c 100644
--- a/ietf/idrfc/urls.py
+++ b/ietf/idrfc/urls.py
@@ -51,6 +51,8 @@ urlpatterns = patterns('',
     url(r'^(?P<name>[^/]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'),
     url(r'^(?P<name>[^/]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'),                       
     url(r'^(?P<name>[^/]+)/edit/addcomment/$', views_edit.add_comment, name='doc_add_comment'),
+    url(r'^(?P<name>[^/]+)/edit/managing-shepherd/$', views_edit.managing_shepherd, name='doc_managing_shepherd'),
+    
     url(r'^(?P<name>[^/]+)/edit/position/$', views_ballot.edit_position, name='doc_edit_position'),
     url(r'^(?P<name>[^/]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),
     url(r'^(?P<name>[^/]+)/edit/undeferballot/$', views_ballot.undefer_ballot, name='doc_undefer_ballot'),
diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index bc39d3302..63b688b91 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -20,6 +20,7 @@ from ietf.idtracker.models import *
 from ietf.iesg.models import *
 from ietf.idrfc.mails import *
 from ietf.idrfc.utils import *
+from idrfc.forms import ManagingShepherdForm
 
     
 class ChangeStateForm(forms.Form):
@@ -31,6 +32,8 @@ def change_state(request, name):
     """Change state of Internet Draft, notifying parties as necessary
     and logging the change as a comment."""
     doc = get_object_or_404(InternetDraft, filename=name)
+    print doc.idinternal
+    print doc.status.status
     if not doc.idinternal or doc.status.status == "Expired":
         raise Http404()
 
@@ -381,4 +384,21 @@ def add_comment(request, name):
                                    form=form),
                               context_instance=RequestContext(request))
 
-
+def managing_shepherd(request, name):
+    """
+     View for managing the assigned shepherd of a document.
+    """
+    doc = get_object_or_404(InternetDraft, filename=name)
+    login = IESGLogin.objects.get(login_name=request.user.username)
+    form = ManagingShepherdForm()    
+    if request.method == "POST":
+        form = ManagingShepherdForm(request.POST, current_person=login.person)
+        if form.is_valid():
+            form.change_shepherd(doc)
+    
+    return render_to_response('idrfc/edit_management_shepherd.html',
+                              dict(doc=doc,
+                                   form=form,
+                                   user=request.user,
+                                   login=login),
+                              context_instance=RequestContext(request))
\ No newline at end of file
diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py
index 32c4dd1a8..eee1a792e 100644
--- a/ietf/idrfc/views_search.py
+++ b/ietf/idrfc/views_search.py
@@ -85,6 +85,9 @@ class SearchForm(forms.Form):
         return q
                                                                         
 def search_query(query_original):
+    """
+    @FIXME: This method should be re-factored !
+    """
     query = dict(query_original.items())
     drafts = query['activeDrafts'] or query['oldDrafts']
     if (not drafts) and (not query['rfcs']):
@@ -135,6 +138,7 @@ def search_query(query_original):
         matches = IDInternal.objects.filter(*q_objs)
     else:
         matches = InternetDraft.objects.filter(*q_objs)
+        print q_objs
     if not query['activeDrafts']:
         matches = matches.exclude(Q(**{prefix+"status":1}))
     if not query['rfcs']:
@@ -184,6 +188,7 @@ def search_query(query_original):
             numbers = IDInternal.objects.filter(*numbers_q).values_list('draft_id',flat=True)
             q_objs.append(Q(rfc_number__in=numbers))
 
+        
         if searchRfcIndex:
             matches = RfcIndex.objects.filter(*q_objs)[:MAX]
         else:
@@ -205,6 +210,7 @@ def search_query(query_original):
                 else:
                     rfcresults.append([rfc.rfc_number, None, rfc, None])
                     
+    
     # Find missing InternetDraft objects
     for r in rfcresults:
         if not r[1]:
@@ -229,7 +235,7 @@ def search_query(query_original):
 
     # TODO: require that RfcIndex is present?
 
-    results = []
+    results = []    
     for res in idresults+rfcresults:
         if len(res)==1:
             doc = IdRfcWrapper(IdWrapper(res[0]), None)
diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py
index 43f9abdb6..95a6cc1eb 100644
--- a/ietf/idtracker/admin.py
+++ b/ietf/idtracker/admin.py
@@ -101,7 +101,7 @@ class InternetDraftAdmin(admin.ModelAdmin):
     search_fields=['filename', 'title']
     list_filter=['status']
     raw_id_fields=['replaced_by']
-admin.site.register(InternetDraft, InternetDraftAdmin)
+admin.site.register(InternetDraft)
 
 class PersonOrOrgInfoAdmin(admin.ModelAdmin):
     fieldsets=((None, {'fields': (('first_name', 'middle_initial', 'last_name'), ('name_suffix', 'modified_by'))}), ('Obsolete Info', {'fields': ('record_type', 'created_by', 'address_type'), 'classes': 'collapse'}))
diff --git a/ietf/idtracker/forms.py b/ietf/idtracker/forms.py
new file mode 100644
index 000000000..a5f511d11
--- /dev/null
+++ b/ietf/idtracker/forms.py
@@ -0,0 +1,67 @@
+from django.conf import settings
+from django import forms
+from idtracker.models import PersonOrOrgInfo
+from django.db.models import Q
+from django.template.loader import render_to_string
+from django.core.mail import EmailMessage
+
+
+class ManagingShepherdForm(forms.Form):
+    email = forms.EmailField(required=False)
+    is_assign_current = forms.BooleanField(required=False)
+    
+    
+    def __init__(self, *args, **kwargs):
+        if kwargs.has_key('current_person'):
+            self.current_person = kwargs.pop('current_person')            
+        return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
+    
+    def clean_email(self):
+        email = self.cleaned_data.get('email')
+        if not email:
+            return None
+        
+        try:
+            PersonOrOrgInfo.objects. \
+                  filter(emailaddress__type__in=[ "INET", "Prim",], 
+                        emailaddress__address=email) \
+                        [:1].get()
+        except PersonOrOrgInfo.DoesNotExist:
+            if self.cleaned_data.get('is_assign_current'):
+                self._send_email(email)
+            raise forms.ValidationError("Person with such email does not exist")
+        return email
+    
+    def clean(self):
+        if self.cleaned_data.get('email') and \
+                                    self.cleaned_data.get('is_assign_current'):
+            raise forms.ValidationError("You should choose to assign to current \
+                        person or input the email. Not both at te same time. ")
+        
+        return self.cleaned_data
+    
+    def change_shepherd(self, document, save=True):
+        email = self.cleaned_data.get('email')        
+        if email:
+            person = PersonOrOrgInfo.objects. \
+                  filter(emailaddress__type__in=[ "INET", "Prim",], 
+                        emailaddress__address=email) \
+                        [:1].get()
+        else:
+            person = self.current_person        
+        document.shepherd = person 
+        if save: 
+            document.save()
+        return document
+    
+    def _send_email(self, email, 
+                        template='idrfc/edit_management_shepherd_email.txt'):
+        subject = 'WG Delegate needs system credentials'        
+        body = render_to_string(template,
+                                {'email': email,
+                                })
+        mail = EmailMessage(subject=subject,
+                            body=body,
+                            to=[email, settings.DEFAULT_FROM_EMAIL, ],
+                            from_email=settings.DEFAULT_FROM_EMAIL)
+        mail.send()
\ No newline at end of file
diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index c1492ea56..d5e4673f3 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -263,7 +263,7 @@ class PersonOrOrgInfo(models.Model):
 	    return u"(Person #%s)" % self.person_or_org_tag
         return u"%s %s" % ( self.first_name or u"<nofirst>", self.last_name or u"<nolast>")
     def email(self, priority=1, type=None):
-	name = str(self)
+        name = str(self)
         email = ''
         types = type and [ type ] or [ "INET", "Prim", None ]
         for type in types:
diff --git a/ietf/templates/idrfc/edit_management_shepherd.html b/ietf/templates/idrfc/edit_management_shepherd.html
new file mode 100644
index 000000000..8b8007b52
--- /dev/null
+++ b/ietf/templates/idrfc/edit_management_shepherd.html
@@ -0,0 +1,50 @@
+{% extends "base.html" %}
+
+{% block title %}Edit info on {{ doc }}{% endblock %}
+
+{% block morecss %}
+form.edit-info #id_state_change_notice_to {
+  width: 600px;
+}
+form.edit-info #id_note {
+  width: 600px;
+  height: 150px;
+}
+form.edit-info .actions {
+  padding-top: 20px;
+}
+{% endblock %}
+
+{% block content %}
+{% load ietf_filters %}
+<h1>Edit info on {{ doc }}</h1>
+
+Shepherd: {{ doc.shepherd }} 
+
+{{ form.non_field_errors }}
+<form class="edit-info" action="" method="POST">
+  <table>    
+    {% for field in form %}
+    <tr>
+      <th>{{ field.label_tag }}:</th>
+      <td>{{ field }}
+      {% ifequal field.name "telechat_date" %}{{ form.returning_item }} {{ form.returning_item.label_tag }} {{ form.returning_item.errors }}{% endifequal %}
+      {% ifequal field.name "job_owner" %}
+      {% if user|in_group:"Area_Director" %}
+      <label><input type="checkbox" name="job_owner" value="{{ login.id }}" /> Assign to me</label>
+      {% endif %}
+      {% endifequal %}      
+      {% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
+      {{ field.errors }}</td>
+    </tr>
+    {% endfor %}
+    <tr>
+      <td></td>
+      <td class="actions">
+        <a href="{{ doc.idinternal.get_absolute_url }}">Back</a>
+        <input type="submit" value="Save"/>
+      </td>
+    </tr>
+  </table>
+</form>
+{% endblock %}
diff --git a/ietf/templates/idrfc/edit_management_shepherd_email.txt b/ietf/templates/idrfc/edit_management_shepherd_email.txt
new file mode 100644
index 000000000..09d7215c1
--- /dev/null
+++ b/ietf/templates/idrfc/edit_management_shepherd_email.txt
@@ -0,0 +1 @@
+Designated person with email {{ email }} should get a user/password.
\ No newline at end of file

From ebb962c012d9351aa36a25f1cc3e1a238b6696d9 Mon Sep 17 00:00:00 2001
From: Alexey Zarubin <azarubin@yaco.es>
Date: Sun, 5 Dec 2010 17:19:45 +0000
Subject: [PATCH 14/57] Fixes #564  - Legacy-Id: 2695

---
 ...03_internet_draft_shepred_fk_blank_true.py | 439 ++++++++++++++++++
 ietf/idtracker/models.py                      |   3 +-
 ietf/templates/wginfo/wg_documents.html       |   2 +-
 .../wginfo/wg_shepherd_documents.html         |  76 +++
 ietf/wginfo/urls.py                           |   1 +
 ietf/wginfo/views.py                          |  20 +
 6 files changed, 539 insertions(+), 2 deletions(-)
 create mode 100644 ietf/idtracker/migrations/0003_internet_draft_shepred_fk_blank_true.py
 create mode 100644 ietf/templates/wginfo/wg_shepherd_documents.html

diff --git a/ietf/idtracker/migrations/0003_internet_draft_shepred_fk_blank_true.py b/ietf/idtracker/migrations/0003_internet_draft_shepred_fk_blank_true.py
new file mode 100644
index 000000000..d59878dad
--- /dev/null
+++ b/ietf/idtracker/migrations/0003_internet_draft_shepred_fk_blank_true.py
@@ -0,0 +1,439 @@
+
+from south.db import db
+from django.db import models
+from ietf.idtracker.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Changing field 'InternetDraft.shepherd'
+        # (to signature: django.db.models.fields.related.ForeignKey(to=orm['idtracker.PersonOrOrgInfo'], null=True, blank=True))
+        db.alter_column('internet_drafts', 'shepherd_id', orm['idtracker.internetdraft:shepherd'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Changing field 'InternetDraft.shepherd'
+        # (to signature: django.db.models.fields.related.ForeignKey(to=orm['idtracker.PersonOrOrgInfo']))
+        db.alter_column('internet_drafts', 'shepherd_id', orm['idtracker.internetdraft:shepherd'])
+        
+    
+    
+    models = {
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.area': {
+            'Meta': {'db_table': "'areas'"},
+            'area_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'extra_email_addresses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaStatus']"})
+        },
+        'idtracker.areadirector': {
+            'Meta': {'db_table': "'area_directors'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']", 'null': 'True', 'db_column': "'area_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.areagroup': {
+            'Meta': {'db_table': "'area_group'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'areagroup'", 'db_column': "'area_acronym_id'", 'to': "orm['idtracker.Area']"}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']", 'unique': 'True', 'db_column': "'group_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.areastatus': {
+            'Meta': {'db_table': "'area_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.areawgurl': {
+            'Meta': {'db_table': "'wg_www_pages'"},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'area_ID'"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'area_Name'"}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.ballotinfo': {
+            'Meta': {'db_table': "'ballot_info'"},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'an_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'an_sent_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ansent'", 'null': 'True', 'db_column': "'an_sent_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'an_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'approval_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'ballot': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'ballot_id'"}),
+            'ballot_issued': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'ballot_writeup': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'defer': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'defer_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deferred'", 'null': 'True', 'db_column': "'defer_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'defer_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'last_call_text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'idtracker.chairshistory': {
+            'Meta': {'db_table': "'chairs_history'"},
+            'chair_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Role']"}),
+            'end_year': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'present_chair': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'start_year': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'idtracker.documentcomment': {
+            'Meta': {'db_table': "'document_comments'"},
+            'ballot': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'comment_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'null': 'True', 'db_column': "'created_by'", 'null_values': '(0,999)'}),
+            'date': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'db_column': "'comment_date'"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDInternal']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'origin_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments_coming_from_state'", 'null': 'True', 'db_column': "'origin_state'", 'to': "orm['idtracker.IDState']"}),
+            'public_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'result_state': ('BrokenForeignKey', ["orm['idtracker.IDState']"], {'related_name': '"comments_leading_to_state"', 'null': 'True', 'db_column': "'result_state'", 'null_values': '(0,99)'}),
+            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'time': ('django.db.models.fields.CharField', [], {'default': "'08:36:20'", 'max_length': '20', 'db_column': "'comment_time'"}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'})
+        },
+        'idtracker.emailaddress': {
+            'Meta': {'db_table': "'email_addresses'"},
+            'address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'email_address'"}),
+            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_column': "'email_comment'", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'db_column': "'email_priority'"}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'db_column': "'email_type'"})
+        },
+        'idtracker.goalmilestone': {
+            'Meta': {'db_table': "'goals_milestones'"},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'done': ('django.db.models.fields.CharField', [], {'max_length': '4', 'blank': 'True'}),
+            'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expected_due_date': ('django.db.models.fields.DateField', [], {}),
+            'gm_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {})
+        },
+        'idtracker.idauthor': {
+            'Meta': {'db_table': "'id_authors'"},
+            'author_order': ('django.db.models.fields.IntegerField', [], {}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'id_document_tag'", 'to': "orm['idtracker.InternetDraft']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idinternal': {
+            'Meta': {'db_table': "'id_internal'"},
+            'agenda': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'approved_in_minute': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'area_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']"}),
+            'assigned_to': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'drafts'", 'db_column': "'ballot_id'", 'to': "orm['idtracker.BallotInfo']"}),
+            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs'", 'db_column': "'cur_state'", 'to': "orm['idtracker.IDState']"}),
+            'cur_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
+            'dnp': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'dnp_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'draft': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True', 'primary_key': 'True', 'db_column': "'id_document_tag'"}),
+            'email_display': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'event_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'group_flag': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'job_owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'db_column': "'job_owner'", 'to': "orm['idtracker.IESGLogin']"}),
+            'mark_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'marked'", 'db_column': "'mark_by'", 'to': "orm['idtracker.IESGLogin']"}),
+            'noproblem': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'prev_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs_prev'", 'db_column': "'prev_state'", 'to': "orm['idtracker.IDState']"}),
+            'prev_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs_prev'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
+            'primary_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'resurrect_requested_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'related_name': "'docsresurrected'", 'null': 'True', 'db_column': "'resurrect_requested_by'", 'blank': 'True'}),
+            'returning_item': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'state_change_notice_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'status_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'token_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'token_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'via_rfc_editor': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.idnextstate': {
+            'Meta': {'db_table': "'ref_next_states_new'"},
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nextstate'", 'to': "orm['idtracker.IDState']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'next_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'prevstate'", 'to': "orm['idtracker.IDState']"})
+        },
+        'idtracker.idstate': {
+            'Meta': {'db_table': "'ref_doc_states_new'"},
+            'description': ('django.db.models.fields.TextField', [], {'db_column': "'document_desc'", 'blank': 'True'}),
+            'document_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'equiv_group_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'document_state_val'"})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idsubstate': {
+            'Meta': {'db_table': "'sub_state'"},
+            'description': ('django.db.models.fields.TextField', [], {'db_column': "'sub_state_desc'", 'blank': 'True'}),
+            'sub_state': ('django.db.models.fields.CharField', [], {'max_length': '55', 'db_column': "'sub_state_val'"}),
+            'sub_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.iesgcomment': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_comment'"},
+            'active': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['idtracker.BallotInfo']"}),
+            'date': ('django.db.models.fields.DateField', [], {'db_column': "'comment_date'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'text': ('django.db.models.fields.TextField', [], {'db_column': "'comment_text'", 'blank': 'True'})
+        },
+        'idtracker.iesgdiscuss': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_discuss'"},
+            'active': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'discusses'", 'to': "orm['idtracker.BallotInfo']"}),
+            'date': ('django.db.models.fields.DateField', [], {'db_column': "'discuss_date'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'text': ('django.db.models.fields.TextField', [], {'db_column': "'discuss_text'", 'blank': 'True'})
+        },
+        'idtracker.iesglogin': {
+            'Meta': {'db_table': "'iesg_login'"},
+            'default_search': ('django.db.models.fields.NullBooleanField', [], {'null': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
+            'login_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
+            'person': ('BrokenForeignKey', ["orm['idtracker.PersonOrOrgInfo']"], {'unique': 'True', 'null': 'True', 'db_column': "'person_or_org_tag'", 'null_values': '(0,888888)'}),
+            'pgp_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'user_level': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'idtracker.ietfwg': {
+            'Meta': {'db_table': "'groups_ietf'"},
+            'area_director': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaDirector']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'dormant_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'email_address': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
+            'email_archive': ('django.db.models.fields.CharField', [], {'max_length': '95', 'blank': 'True'}),
+            'email_keyword': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'email_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'group_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGType']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'meeting_scheduled': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'meeting_scheduled_old': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGStatus']"})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('BrokenForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.irtf': {
+            'Meta': {'db_table': "'irtf'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'irtf_acronym'", 'blank': 'True'}),
+            'charter_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'irtf_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting_scheduled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'irtf_name'", 'blank': 'True'})
+        },
+        'idtracker.irtfchair': {
+            'Meta': {'db_table': "'irtf_chairs'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'irtf': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IRTF']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.phonenumber': {
+            'Meta': {'db_table': "'phone_numbers'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'phone_comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'phone_priority': ('django.db.models.fields.IntegerField', [], {}),
+            'phone_type': ('django.db.models.fields.CharField', [], {'max_length': '3'})
+        },
+        'idtracker.position': {
+            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots'"},
+            'abstain': ('django.db.models.fields.IntegerField', [], {}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
+            'approve': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'positions'", 'to': "orm['idtracker.BallotInfo']"}),
+            'discuss': ('django.db.models.fields.IntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'noobj': ('django.db.models.fields.IntegerField', [], {'db_column': "'no_col'"}),
+            'recuse': ('django.db.models.fields.IntegerField', [], {}),
+            'yes': ('django.db.models.fields.IntegerField', [], {'db_column': "'yes_col'"})
+        },
+        'idtracker.postaladdress': {
+            'Meta': {'db_table': "'postal_addresses'"},
+            'address_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+            'aff_company_key': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'affiliated_company': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'city': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'country': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'department': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mail_stop': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'person_title': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'staddr1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'staddr2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
+            'state_or_prov': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        },
+        'idtracker.rfc': {
+            'Meta': {'db_table': "'rfcs'"},
+            'area_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'draft_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'fyi_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
+            'historic_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'default': '5', 'to': "orm['idtracker.RfcIntendedStatus']", 'db_column': "'intended_status_id'"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'online_version': ('django.db.models.fields.CharField', [], {'default': "'YES'", 'max_length': '3'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'rfc_name_key': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'rfc_published_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'standard_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.RfcStatus']", 'db_column': "'status_id'"}),
+            'std_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_column': "'rfc_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.rfcauthor': {
+            'Meta': {'db_table': "'rfc_authors'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"})
+        },
+        'idtracker.rfcintendedstatus': {
+            'Meta': {'db_table': "'rfc_intend_status'"},
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"})
+        },
+        'idtracker.rfcobsolete': {
+            'Meta': {'db_table': "'rfcs_obsolete'"},
+            'action': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates_or_obsoletes'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"}),
+            'rfc_acted_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updated_or_obsoleted_by'", 'db_column': "'rfc_acted_on'", 'to': "orm['idtracker.Rfc']"})
+        },
+        'idtracker.rfcstatus': {
+            'Meta': {'db_table': "'rfc_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.role': {
+            'Meta': {'db_table': "'chairs'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
+            'role_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'chair_name'"})
+        },
+        'idtracker.wgchair': {
+            'Meta': {'db_table': "'g_chairs'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgeditor': {
+            'Meta': {'db_table': "'g_editors'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'unique': 'True', 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgsecretary': {
+            'Meta': {'db_table': "'g_secretaries'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgstatus': {
+            'Meta': {'db_table': "'g_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.wgtechadvisor': {
+            'Meta': {'db_table': "'g_tech_advisors'"},
+            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.wgtype': {
+            'Meta': {'db_table': "'g_type'"},
+            'group_type_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'group_type'"})
+        }
+    }
+    
+    complete_apps = ['idtracker']
diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index d5e4673f3..aaa1db268 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -167,9 +167,10 @@ class InternetDraft(models.Model):
     review_by_rfc_editor = models.BooleanField()
     expired_tombstone = models.BooleanField()
     idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
-    shepherd = models.ForeignKey('PersonOrOrgInfo')
+    shepherd = models.ForeignKey('PersonOrOrgInfo', null=True, blank=True)
     def __str__(self):
         return self.filename
+    
     def save(self):
         self.id_document_key = self.title.upper()
         super(InternetDraft, self).save()
diff --git a/ietf/templates/wginfo/wg_documents.html b/ietf/templates/wginfo/wg_documents.html
index 8b35f4973..b53a55ce3 100644
--- a/ietf/templates/wginfo/wg_documents.html
+++ b/ietf/templates/wginfo/wg_documents.html
@@ -58,7 +58,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 <tr class="header"><td colspan="6">{{doc_group.grouper}}s</td></tr>
 
 {% for doc in doc_group.list %}
-{% include "idrfc/search_result_row.html" %}
+  {% include "idrfc/search_result_row.html" %}
 {% endfor %}
 
 {% endfor %}
diff --git a/ietf/templates/wginfo/wg_shepherd_documents.html b/ietf/templates/wginfo/wg_shepherd_documents.html
new file mode 100644
index 000000000..6b9c0be3c
--- /dev/null
+++ b/ietf/templates/wginfo/wg_shepherd_documents.html
@@ -0,0 +1,76 @@
+{% 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 %}
+{% block wg_titledetail %}Documents{% endblock %}
+
+{% block content %}
+<div {% if concluded %}class="ietf-concluded-bg"{% endif %}>
+
+
+{% block wg_content %}
+<table class="ietf-table ietf-doctable" style="margin-top:16px;">
+    <tr>       
+       <th class="title">Title</th>
+       <th class="date">Date</th>
+       <th class="status" colspan="2">Status</th>
+       <th class="ad">Area Director</th>
+    </tr> 
+    
+    {% for group, documents in groupped_documents.items %}     
+      <tr class="header"><td colspan="5">{{ group }}</td></tr>
+    
+      {% for doc in documents %}
+        <tr class="{% cycle oddrow,evenrow %}">
+          <td class="title">
+            <a href="{% url doc_managing_shepherd doc %}">{{ doc.title }}</a>
+          </td>
+          <td class="date">
+             {{ doc.status.start_date|date:"Y-m" }}
+          </td>
+          <td class="status">
+              {{ doc.status.status }}
+          </td>        
+          <td class="ad">{{ doc.ad_name|default:"" }}</td>
+          
+          </tr>
+      {% endfor %}
+    {% endfor %}
+        
+</table>
+           
+{% endblock wg_content %}
+
+</div>
+{% endblock content %}
+
diff --git a/ietf/wginfo/urls.py b/ietf/wginfo/urls.py
index d67fc4897..f4f5fa475 100644
--- a/ietf/wginfo/urls.py
+++ b/ietf/wginfo/urls.py
@@ -13,6 +13,7 @@ urlpatterns = patterns('',
      (r'^1wg-summary-by-acronym.txt', views.wg_summary_acronym),
      (r'^1wg-charters.txt', views.wg_charters),
      (r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
+     (r'^shepherd/list/$', views.wg_shepherd_documents),     
      (r'^(?P<acronym>[^/]+)/$', views.wg_documents),
      (r'^(?P<acronym>[^/]+)/charter/$', views.wg_charter),
      (r'^(?P<acronym>[^/]+)/management/', include('ietf.wgchairs.urls')),
diff --git a/ietf/wginfo/views.py b/ietf/wginfo/views.py
index f0eb152cb..fd5e5dc7c 100644
--- a/ietf/wginfo/views.py
+++ b/ietf/wginfo/views.py
@@ -38,6 +38,8 @@ from django.template import RequestContext, loader
 from django.http import HttpResponse
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.idrfc.idrfc_wrapper import IdRfcWrapper
+from idtracker.models import InternetDraft, PersonOrOrgInfo, IESGLogin
+from django.db.models import Q
 
 def wg_summary_acronym(request):
     areas = Area.active_areas()
@@ -85,6 +87,24 @@ def wg_documents(request, acronym):
     return render_to_response('wginfo/wg_documents.html', {'wg': wg, 'concluded':concluded, 'selected':'documents', 'docs':docs,  'meta':meta, 
                                                            'docs_related':docs_related_pruned, 'meta_related':meta_related}, RequestContext(request))
 
+def wg_shepherd_documents(request):
+    current_person = PersonOrOrgInfo.objects. \
+                            get(iesglogin__login_name=request.user.username)
+                            
+    base_qs = InternetDraft.objects.select_related('status')
+    documents_no_shepherd = base_qs.filter(shepherd__isnull=True)    
+    documents_my = base_qs.filter(shepherd=current_person)
+    documents_other = base_qs.filter(~Q(shepherd=current_person))
+    context = {
+        'groupped_documents': {
+            'Documents without Shepherd': documents_no_shepherd,
+            'My documents': documents_my,
+            'Other documents': documents_other,        
+        }
+    }
+    return render_to_response('wginfo/wg_shepherd_documents.html', context, RequestContext(request))
+
+
 def wg_charter(request, acronym):
     wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
     concluded = (wg.status_id != 1)

From 3bc02edcac726b38960d6770fa333119ba467ac8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 12:29:44 +0000
Subject: [PATCH 15/57] Removed initial migration cause we work over a database
 in wich this application already exists. Fixes #562  - Legacy-Id: 2702

---
 ietf/idtracker/migrations/0001_initial.py | 1064 ---------------------
 ietf/ietfworkflows/models.py              |    4 +-
 ietf/wgchairs/views.py                    |   11 +-
 3 files changed, 10 insertions(+), 1069 deletions(-)
 delete mode 100644 ietf/idtracker/migrations/0001_initial.py

diff --git a/ietf/idtracker/migrations/0001_initial.py b/ietf/idtracker/migrations/0001_initial.py
deleted file mode 100644
index 516926ad1..000000000
--- a/ietf/idtracker/migrations/0001_initial.py
+++ /dev/null
@@ -1,1064 +0,0 @@
-
-from south.db import db
-from django.db import models
-from ietf.idtracker.models import *
-
-class Migration:
-    
-    def forwards(self, orm):
-        
-        # Adding model 'WGTechAdvisor'
-        db.create_table('g_tech_advisors', (
-            ('id', orm['idtracker.WGTechAdvisor:id']),
-            ('group_acronym', orm['idtracker.WGTechAdvisor:group_acronym']),
-            ('person', orm['idtracker.WGTechAdvisor:person']),
-        ))
-        db.send_create_signal('idtracker', ['WGTechAdvisor'])
-        
-        # Adding model 'IDState'
-        db.create_table('ref_doc_states_new', (
-            ('document_state_id', orm['idtracker.IDState:document_state_id']),
-            ('state', orm['idtracker.IDState:state']),
-            ('equiv_group_flag', orm['idtracker.IDState:equiv_group_flag']),
-            ('description', orm['idtracker.IDState:description']),
-        ))
-        db.send_create_signal('idtracker', ['IDState'])
-        
-        # Adding model 'BallotInfo'
-        db.create_table('ballot_info', (
-            ('ballot', orm['idtracker.BallotInfo:ballot']),
-            ('active', orm['idtracker.BallotInfo:active']),
-            ('an_sent', orm['idtracker.BallotInfo:an_sent']),
-            ('an_sent_date', orm['idtracker.BallotInfo:an_sent_date']),
-            ('an_sent_by', orm['idtracker.BallotInfo:an_sent_by']),
-            ('defer', orm['idtracker.BallotInfo:defer']),
-            ('defer_by', orm['idtracker.BallotInfo:defer_by']),
-            ('defer_date', orm['idtracker.BallotInfo:defer_date']),
-            ('approval_text', orm['idtracker.BallotInfo:approval_text']),
-            ('last_call_text', orm['idtracker.BallotInfo:last_call_text']),
-            ('ballot_writeup', orm['idtracker.BallotInfo:ballot_writeup']),
-            ('ballot_issued', orm['idtracker.BallotInfo:ballot_issued']),
-        ))
-        db.send_create_signal('idtracker', ['BallotInfo'])
-        
-        # Adding model 'AreaStatus'
-        db.create_table('area_status', (
-            ('status_id', orm['idtracker.AreaStatus:status_id']),
-            ('status', orm['idtracker.AreaStatus:status']),
-        ))
-        db.send_create_signal('idtracker', ['AreaStatus'])
-        
-        # Adding model 'RfcAuthor'
-        db.create_table('rfc_authors', (
-            ('id', orm['idtracker.RfcAuthor:id']),
-            ('rfc', orm['idtracker.RfcAuthor:rfc']),
-            ('person', orm['idtracker.RfcAuthor:person']),
-        ))
-        db.send_create_signal('idtracker', ['RfcAuthor'])
-        
-        # Adding model 'IDIntendedStatus'
-        db.create_table('id_intended_status', (
-            ('intended_status_id', orm['idtracker.IDIntendedStatus:intended_status_id']),
-            ('intended_status', orm['idtracker.IDIntendedStatus:intended_status']),
-        ))
-        db.send_create_signal('idtracker', ['IDIntendedStatus'])
-        
-        # Adding model 'IDNextState'
-        db.create_table('ref_next_states_new', (
-            ('id', orm['idtracker.IDNextState:id']),
-            ('cur_state', orm['idtracker.IDNextState:cur_state']),
-            ('next_state', orm['idtracker.IDNextState:next_state']),
-            ('condition', orm['idtracker.IDNextState:condition']),
-        ))
-        db.send_create_signal('idtracker', ['IDNextState'])
-        
-        # Adding model 'WGType'
-        db.create_table('g_type', (
-            ('group_type_id', orm['idtracker.WGType:group_type_id']),
-            ('type', orm['idtracker.WGType:type']),
-        ))
-        db.send_create_signal('idtracker', ['WGType'])
-        
-        # Adding model 'RfcObsolete'
-        db.create_table('rfcs_obsolete', (
-            ('id', orm['idtracker.RfcObsolete:id']),
-            ('rfc', orm['idtracker.RfcObsolete:rfc']),
-            ('action', orm['idtracker.RfcObsolete:action']),
-            ('rfc_acted_on', orm['idtracker.RfcObsolete:rfc_acted_on']),
-        ))
-        db.send_create_signal('idtracker', ['RfcObsolete'])
-        
-        # Adding model 'InternetDraft'
-        db.create_table('internet_drafts', (
-            ('id_document_tag', orm['idtracker.InternetDraft:id_document_tag']),
-            ('title', orm['idtracker.InternetDraft:title']),
-            ('id_document_key', orm['idtracker.InternetDraft:id_document_key']),
-            ('group', orm['idtracker.InternetDraft:group']),
-            ('filename', orm['idtracker.InternetDraft:filename']),
-            ('revision', orm['idtracker.InternetDraft:revision']),
-            ('revision_date', orm['idtracker.InternetDraft:revision_date']),
-            ('file_type', orm['idtracker.InternetDraft:file_type']),
-            ('txt_page_count', orm['idtracker.InternetDraft:txt_page_count']),
-            ('local_path', orm['idtracker.InternetDraft:local_path']),
-            ('start_date', orm['idtracker.InternetDraft:start_date']),
-            ('expiration_date', orm['idtracker.InternetDraft:expiration_date']),
-            ('abstract', orm['idtracker.InternetDraft:abstract']),
-            ('dunn_sent_date', orm['idtracker.InternetDraft:dunn_sent_date']),
-            ('extension_date', orm['idtracker.InternetDraft:extension_date']),
-            ('status', orm['idtracker.InternetDraft:status']),
-            ('intended_status', orm['idtracker.InternetDraft:intended_status']),
-            ('lc_sent_date', orm['idtracker.InternetDraft:lc_sent_date']),
-            ('lc_changes', orm['idtracker.InternetDraft:lc_changes']),
-            ('lc_expiration_date', orm['idtracker.InternetDraft:lc_expiration_date']),
-            ('b_sent_date', orm['idtracker.InternetDraft:b_sent_date']),
-            ('b_discussion_date', orm['idtracker.InternetDraft:b_discussion_date']),
-            ('b_approve_date', orm['idtracker.InternetDraft:b_approve_date']),
-            ('wgreturn_date', orm['idtracker.InternetDraft:wgreturn_date']),
-            ('rfc_number', orm['idtracker.InternetDraft:rfc_number']),
-            ('comments', orm['idtracker.InternetDraft:comments']),
-            ('last_modified_date', orm['idtracker.InternetDraft:last_modified_date']),
-            ('replaced_by', orm['idtracker.InternetDraft:replaced_by']),
-            ('review_by_rfc_editor', orm['idtracker.InternetDraft:review_by_rfc_editor']),
-            ('expired_tombstone', orm['idtracker.InternetDraft:expired_tombstone']),
-        ))
-        db.send_create_signal('idtracker', ['InternetDraft'])
-        
-        # Adding model 'IRTFChair'
-        db.create_table('irtf_chairs', (
-            ('id', orm['idtracker.IRTFChair:id']),
-            ('irtf', orm['idtracker.IRTFChair:irtf']),
-            ('person', orm['idtracker.IRTFChair:person']),
-        ))
-        db.send_create_signal('idtracker', ['IRTFChair'])
-        
-        # Adding model 'IETFWG'
-        db.create_table('groups_ietf', (
-            ('group_acronym', orm['idtracker.IETFWG:group_acronym']),
-            ('group_type', orm['idtracker.IETFWG:group_type']),
-            ('proposed_date', orm['idtracker.IETFWG:proposed_date']),
-            ('start_date', orm['idtracker.IETFWG:start_date']),
-            ('dormant_date', orm['idtracker.IETFWG:dormant_date']),
-            ('concluded_date', orm['idtracker.IETFWG:concluded_date']),
-            ('status', orm['idtracker.IETFWG:status']),
-            ('area_director', orm['idtracker.IETFWG:area_director']),
-            ('meeting_scheduled', orm['idtracker.IETFWG:meeting_scheduled']),
-            ('email_address', orm['idtracker.IETFWG:email_address']),
-            ('email_subscribe', orm['idtracker.IETFWG:email_subscribe']),
-            ('email_keyword', orm['idtracker.IETFWG:email_keyword']),
-            ('email_archive', orm['idtracker.IETFWG:email_archive']),
-            ('comments', orm['idtracker.IETFWG:comments']),
-            ('last_modified_date', orm['idtracker.IETFWG:last_modified_date']),
-            ('meeting_scheduled_old', orm['idtracker.IETFWG:meeting_scheduled_old']),
-        ))
-        db.send_create_signal('idtracker', ['IETFWG'])
-        
-        # Adding model 'PostalAddress'
-        db.create_table('postal_addresses', (
-            ('id', orm['idtracker.PostalAddress:id']),
-            ('address_type', orm['idtracker.PostalAddress:address_type']),
-            ('address_priority', orm['idtracker.PostalAddress:address_priority']),
-            ('person_or_org', orm['idtracker.PostalAddress:person_or_org']),
-            ('person_title', orm['idtracker.PostalAddress:person_title']),
-            ('affiliated_company', orm['idtracker.PostalAddress:affiliated_company']),
-            ('aff_company_key', orm['idtracker.PostalAddress:aff_company_key']),
-            ('department', orm['idtracker.PostalAddress:department']),
-            ('staddr1', orm['idtracker.PostalAddress:staddr1']),
-            ('staddr2', orm['idtracker.PostalAddress:staddr2']),
-            ('mail_stop', orm['idtracker.PostalAddress:mail_stop']),
-            ('city', orm['idtracker.PostalAddress:city']),
-            ('state_or_prov', orm['idtracker.PostalAddress:state_or_prov']),
-            ('postal_code', orm['idtracker.PostalAddress:postal_code']),
-            ('country', orm['idtracker.PostalAddress:country']),
-        ))
-        db.send_create_signal('idtracker', ['PostalAddress'])
-        
-        # Adding model 'RfcIntendedStatus'
-        db.create_table('rfc_intend_status', (
-            ('intended_status_id', orm['idtracker.RfcIntendedStatus:intended_status_id']),
-            ('status', orm['idtracker.RfcIntendedStatus:status']),
-        ))
-        db.send_create_signal('idtracker', ['RfcIntendedStatus'])
-        
-        # Adding model 'IDInternal'
-        db.create_table('id_internal', (
-            ('draft', orm['idtracker.IDInternal:draft']),
-            ('rfc_flag', orm['idtracker.IDInternal:rfc_flag']),
-            ('ballot', orm['idtracker.IDInternal:ballot']),
-            ('primary_flag', orm['idtracker.IDInternal:primary_flag']),
-            ('group_flag', orm['idtracker.IDInternal:group_flag']),
-            ('token_name', orm['idtracker.IDInternal:token_name']),
-            ('token_email', orm['idtracker.IDInternal:token_email']),
-            ('note', orm['idtracker.IDInternal:note']),
-            ('status_date', orm['idtracker.IDInternal:status_date']),
-            ('email_display', orm['idtracker.IDInternal:email_display']),
-            ('agenda', orm['idtracker.IDInternal:agenda']),
-            ('cur_state', orm['idtracker.IDInternal:cur_state']),
-            ('prev_state', orm['idtracker.IDInternal:prev_state']),
-            ('assigned_to', orm['idtracker.IDInternal:assigned_to']),
-            ('mark_by', orm['idtracker.IDInternal:mark_by']),
-            ('job_owner', orm['idtracker.IDInternal:job_owner']),
-            ('event_date', orm['idtracker.IDInternal:event_date']),
-            ('area_acronym', orm['idtracker.IDInternal:area_acronym']),
-            ('cur_sub_state', orm['idtracker.IDInternal:cur_sub_state']),
-            ('prev_sub_state', orm['idtracker.IDInternal:prev_sub_state']),
-            ('returning_item', orm['idtracker.IDInternal:returning_item']),
-            ('telechat_date', orm['idtracker.IDInternal:telechat_date']),
-            ('via_rfc_editor', orm['idtracker.IDInternal:via_rfc_editor']),
-            ('state_change_notice_to', orm['idtracker.IDInternal:state_change_notice_to']),
-            ('dnp', orm['idtracker.IDInternal:dnp']),
-            ('dnp_date', orm['idtracker.IDInternal:dnp_date']),
-            ('noproblem', orm['idtracker.IDInternal:noproblem']),
-            ('resurrect_requested_by', orm['idtracker.IDInternal:resurrect_requested_by']),
-            ('approved_in_minute', orm['idtracker.IDInternal:approved_in_minute']),
-        ))
-        db.send_create_signal('idtracker', ['IDInternal'])
-        
-        # Adding model 'IDAuthor'
-        db.create_table('id_authors', (
-            ('id', orm['idtracker.IDAuthor:id']),
-            ('document', orm['idtracker.IDAuthor:document']),
-            ('person', orm['idtracker.IDAuthor:person']),
-            ('author_order', orm['idtracker.IDAuthor:author_order']),
-        ))
-        db.send_create_signal('idtracker', ['IDAuthor'])
-        
-        # Adding model 'IDStatus'
-        db.create_table('id_status', (
-            ('status_id', orm['idtracker.IDStatus:status_id']),
-            ('status', orm['idtracker.IDStatus:status']),
-        ))
-        db.send_create_signal('idtracker', ['IDStatus'])
-        
-        # Adding model 'Role'
-        db.create_table('chairs', (
-            ('id', orm['idtracker.Role:id']),
-            ('person', orm['idtracker.Role:person']),
-            ('role_name', orm['idtracker.Role:role_name']),
-        ))
-        db.send_create_signal('idtracker', ['Role'])
-        
-        # Adding model 'AreaDirector'
-        db.create_table('area_directors', (
-            ('id', orm['idtracker.AreaDirector:id']),
-            ('area', orm['idtracker.AreaDirector:area']),
-            ('person', orm['idtracker.AreaDirector:person']),
-        ))
-        db.send_create_signal('idtracker', ['AreaDirector'])
-        
-        # Adding model 'Rfc'
-        db.create_table('rfcs', (
-            ('rfc_number', orm['idtracker.Rfc:rfc_number']),
-            ('title', orm['idtracker.Rfc:title']),
-            ('rfc_name_key', orm['idtracker.Rfc:rfc_name_key']),
-            ('group_acronym', orm['idtracker.Rfc:group_acronym']),
-            ('area_acronym', orm['idtracker.Rfc:area_acronym']),
-            ('status', orm['idtracker.Rfc:status']),
-            ('intended_status', orm['idtracker.Rfc:intended_status']),
-            ('fyi_number', orm['idtracker.Rfc:fyi_number']),
-            ('std_number', orm['idtracker.Rfc:std_number']),
-            ('txt_page_count', orm['idtracker.Rfc:txt_page_count']),
-            ('online_version', orm['idtracker.Rfc:online_version']),
-            ('rfc_published_date', orm['idtracker.Rfc:rfc_published_date']),
-            ('proposed_date', orm['idtracker.Rfc:proposed_date']),
-            ('draft_date', orm['idtracker.Rfc:draft_date']),
-            ('standard_date', orm['idtracker.Rfc:standard_date']),
-            ('historic_date', orm['idtracker.Rfc:historic_date']),
-            ('lc_sent_date', orm['idtracker.Rfc:lc_sent_date']),
-            ('lc_expiration_date', orm['idtracker.Rfc:lc_expiration_date']),
-            ('b_sent_date', orm['idtracker.Rfc:b_sent_date']),
-            ('b_approve_date', orm['idtracker.Rfc:b_approve_date']),
-            ('comments', orm['idtracker.Rfc:comments']),
-            ('last_modified_date', orm['idtracker.Rfc:last_modified_date']),
-        ))
-        db.send_create_signal('idtracker', ['Rfc'])
-        
-        # Adding model 'EmailAddress'
-        db.create_table('email_addresses', (
-            ('id', orm['idtracker.EmailAddress:id']),
-            ('person_or_org', orm['idtracker.EmailAddress:person_or_org']),
-            ('type', orm['idtracker.EmailAddress:type']),
-            ('priority', orm['idtracker.EmailAddress:priority']),
-            ('address', orm['idtracker.EmailAddress:address']),
-            ('comment', orm['idtracker.EmailAddress:comment']),
-        ))
-        db.send_create_signal('idtracker', ['EmailAddress'])
-        
-        # Adding model 'AreaGroup'
-        db.create_table('area_group', (
-            ('id', orm['idtracker.AreaGroup:id']),
-            ('area', orm['idtracker.AreaGroup:area']),
-            ('group', orm['idtracker.AreaGroup:group']),
-        ))
-        db.send_create_signal('idtracker', ['AreaGroup'])
-        
-        # Adding model 'IESGDiscuss'
-        db.create_table('ballots_discuss', (
-            ('id', orm['idtracker.IESGDiscuss:id']),
-            ('ballot', orm['idtracker.IESGDiscuss:ballot']),
-            ('ad', orm['idtracker.IESGDiscuss:ad']),
-            ('date', orm['idtracker.IESGDiscuss:date']),
-            ('revision', orm['idtracker.IESGDiscuss:revision']),
-            ('active', orm['idtracker.IESGDiscuss:active']),
-            ('text', orm['idtracker.IESGDiscuss:text']),
-        ))
-        db.send_create_signal('idtracker', ['IESGDiscuss'])
-        
-        # Adding model 'GoalMilestone'
-        db.create_table('goals_milestones', (
-            ('gm_id', orm['idtracker.GoalMilestone:gm_id']),
-            ('group_acronym', orm['idtracker.GoalMilestone:group_acronym']),
-            ('description', orm['idtracker.GoalMilestone:description']),
-            ('expected_due_date', orm['idtracker.GoalMilestone:expected_due_date']),
-            ('done_date', orm['idtracker.GoalMilestone:done_date']),
-            ('done', orm['idtracker.GoalMilestone:done']),
-            ('last_modified_date', orm['idtracker.GoalMilestone:last_modified_date']),
-        ))
-        db.send_create_signal('idtracker', ['GoalMilestone'])
-        
-        # Adding model 'PhoneNumber'
-        db.create_table('phone_numbers', (
-            ('id', orm['idtracker.PhoneNumber:id']),
-            ('person_or_org', orm['idtracker.PhoneNumber:person_or_org']),
-            ('phone_type', orm['idtracker.PhoneNumber:phone_type']),
-            ('phone_priority', orm['idtracker.PhoneNumber:phone_priority']),
-            ('phone_number', orm['idtracker.PhoneNumber:phone_number']),
-            ('phone_comment', orm['idtracker.PhoneNumber:phone_comment']),
-        ))
-        db.send_create_signal('idtracker', ['PhoneNumber'])
-        
-        # Adding model 'WGSecretary'
-        db.create_table('g_secretaries', (
-            ('id', orm['idtracker.WGSecretary:id']),
-            ('group_acronym', orm['idtracker.WGSecretary:group_acronym']),
-            ('person', orm['idtracker.WGSecretary:person']),
-        ))
-        db.send_create_signal('idtracker', ['WGSecretary'])
-        
-        # Adding model 'WGStatus'
-        db.create_table('g_status', (
-            ('status_id', orm['idtracker.WGStatus:status_id']),
-            ('status', orm['idtracker.WGStatus:status']),
-        ))
-        db.send_create_signal('idtracker', ['WGStatus'])
-        
-        # Adding model 'IRTF'
-        db.create_table('irtf', (
-            ('irtf_id', orm['idtracker.IRTF:irtf_id']),
-            ('acronym', orm['idtracker.IRTF:acronym']),
-            ('name', orm['idtracker.IRTF:name']),
-            ('charter_text', orm['idtracker.IRTF:charter_text']),
-            ('meeting_scheduled', orm['idtracker.IRTF:meeting_scheduled']),
-        ))
-        db.send_create_signal('idtracker', ['IRTF'])
-        
-        # Adding model 'RfcStatus'
-        db.create_table('rfc_status', (
-            ('status_id', orm['idtracker.RfcStatus:status_id']),
-            ('status', orm['idtracker.RfcStatus:status']),
-        ))
-        db.send_create_signal('idtracker', ['RfcStatus'])
-        
-        # Adding model 'Area'
-        db.create_table('areas', (
-            ('area_acronym', orm['idtracker.Area:area_acronym']),
-            ('start_date', orm['idtracker.Area:start_date']),
-            ('concluded_date', orm['idtracker.Area:concluded_date']),
-            ('status', orm['idtracker.Area:status']),
-            ('comments', orm['idtracker.Area:comments']),
-            ('last_modified_date', orm['idtracker.Area:last_modified_date']),
-            ('extra_email_addresses', orm['idtracker.Area:extra_email_addresses']),
-        ))
-        db.send_create_signal('idtracker', ['Area'])
-        
-        # Adding model 'ChairsHistory'
-        db.create_table('chairs_history', (
-            ('id', orm['idtracker.ChairsHistory:id']),
-            ('chair_type', orm['idtracker.ChairsHistory:chair_type']),
-            ('present_chair', orm['idtracker.ChairsHistory:present_chair']),
-            ('person', orm['idtracker.ChairsHistory:person']),
-            ('start_year', orm['idtracker.ChairsHistory:start_year']),
-            ('end_year', orm['idtracker.ChairsHistory:end_year']),
-        ))
-        db.send_create_signal('idtracker', ['ChairsHistory'])
-        
-        # Adding model 'Acronym'
-        db.create_table('acronym', (
-            ('acronym_id', orm['idtracker.Acronym:acronym_id']),
-            ('acronym', orm['idtracker.Acronym:acronym']),
-            ('name', orm['idtracker.Acronym:name']),
-            ('name_key', orm['idtracker.Acronym:name_key']),
-        ))
-        db.send_create_signal('idtracker', ['Acronym'])
-        
-        # Adding model 'WGChair'
-        db.create_table('g_chairs', (
-            ('id', orm['idtracker.WGChair:id']),
-            ('person', orm['idtracker.WGChair:person']),
-            ('group_acronym', orm['idtracker.WGChair:group_acronym']),
-        ))
-        db.send_create_signal('idtracker', ['WGChair'])
-        
-        # Adding model 'WGEditor'
-        db.create_table('g_editors', (
-            ('id', orm['idtracker.WGEditor:id']),
-            ('group_acronym', orm['idtracker.WGEditor:group_acronym']),
-            ('person', orm['idtracker.WGEditor:person']),
-        ))
-        db.send_create_signal('idtracker', ['WGEditor'])
-        
-        # Adding model 'PersonOrOrgInfo'
-        db.create_table('person_or_org_info', (
-            ('person_or_org_tag', orm['idtracker.PersonOrOrgInfo:person_or_org_tag']),
-            ('record_type', orm['idtracker.PersonOrOrgInfo:record_type']),
-            ('name_prefix', orm['idtracker.PersonOrOrgInfo:name_prefix']),
-            ('first_name', orm['idtracker.PersonOrOrgInfo:first_name']),
-            ('first_name_key', orm['idtracker.PersonOrOrgInfo:first_name_key']),
-            ('middle_initial', orm['idtracker.PersonOrOrgInfo:middle_initial']),
-            ('middle_initial_key', orm['idtracker.PersonOrOrgInfo:middle_initial_key']),
-            ('last_name', orm['idtracker.PersonOrOrgInfo:last_name']),
-            ('last_name_key', orm['idtracker.PersonOrOrgInfo:last_name_key']),
-            ('name_suffix', orm['idtracker.PersonOrOrgInfo:name_suffix']),
-            ('date_modified', orm['idtracker.PersonOrOrgInfo:date_modified']),
-            ('modified_by', orm['idtracker.PersonOrOrgInfo:modified_by']),
-            ('date_created', orm['idtracker.PersonOrOrgInfo:date_created']),
-            ('created_by', orm['idtracker.PersonOrOrgInfo:created_by']),
-            ('address_type', orm['idtracker.PersonOrOrgInfo:address_type']),
-        ))
-        db.send_create_signal('idtracker', ['PersonOrOrgInfo'])
-        
-        # Adding model 'Position'
-        db.create_table('ballots', (
-            ('id', orm['idtracker.Position:id']),
-            ('ballot', orm['idtracker.Position:ballot']),
-            ('ad', orm['idtracker.Position:ad']),
-            ('yes', orm['idtracker.Position:yes']),
-            ('noobj', orm['idtracker.Position:noobj']),
-            ('abstain', orm['idtracker.Position:abstain']),
-            ('approve', orm['idtracker.Position:approve']),
-            ('discuss', orm['idtracker.Position:discuss']),
-            ('recuse', orm['idtracker.Position:recuse']),
-        ))
-        db.send_create_signal('idtracker', ['Position'])
-        
-        # Adding model 'IESGComment'
-        db.create_table('ballots_comment', (
-            ('id', orm['idtracker.IESGComment:id']),
-            ('ballot', orm['idtracker.IESGComment:ballot']),
-            ('ad', orm['idtracker.IESGComment:ad']),
-            ('date', orm['idtracker.IESGComment:date']),
-            ('revision', orm['idtracker.IESGComment:revision']),
-            ('active', orm['idtracker.IESGComment:active']),
-            ('text', orm['idtracker.IESGComment:text']),
-        ))
-        db.send_create_signal('idtracker', ['IESGComment'])
-        
-        # Adding model 'IESGLogin'
-        db.create_table('iesg_login', (
-            ('id', orm['idtracker.IESGLogin:id']),
-            ('login_name', orm['idtracker.IESGLogin:login_name']),
-            ('password', orm['idtracker.IESGLogin:password']),
-            ('user_level', orm['idtracker.IESGLogin:user_level']),
-            ('first_name', orm['idtracker.IESGLogin:first_name']),
-            ('last_name', orm['idtracker.IESGLogin:last_name']),
-            ('person', orm['idtracker.IESGLogin:person']),
-            ('pgp_id', orm['idtracker.IESGLogin:pgp_id']),
-            ('default_search', orm['idtracker.IESGLogin:default_search']),
-        ))
-        db.send_create_signal('idtracker', ['IESGLogin'])
-        
-        # Adding model 'AreaWGURL'
-        db.create_table('wg_www_pages', (
-            ('id', orm['idtracker.AreaWGURL:id']),
-            ('name', orm['idtracker.AreaWGURL:name']),
-            ('url', orm['idtracker.AreaWGURL:url']),
-            ('description', orm['idtracker.AreaWGURL:description']),
-        ))
-        db.send_create_signal('idtracker', ['AreaWGURL'])
-        
-        # Adding model 'IDSubState'
-        db.create_table('sub_state', (
-            ('sub_state_id', orm['idtracker.IDSubState:sub_state_id']),
-            ('sub_state', orm['idtracker.IDSubState:sub_state']),
-            ('description', orm['idtracker.IDSubState:description']),
-        ))
-        db.send_create_signal('idtracker', ['IDSubState'])
-        
-        # Adding model 'DocumentComment'
-        db.create_table('document_comments', (
-            ('id', orm['idtracker.DocumentComment:id']),
-            ('document', orm['idtracker.DocumentComment:document']),
-            ('rfc_flag', orm['idtracker.DocumentComment:rfc_flag']),
-            ('public_flag', orm['idtracker.DocumentComment:public_flag']),
-            ('date', orm['idtracker.DocumentComment:date']),
-            ('time', orm['idtracker.DocumentComment:time']),
-            ('version', orm['idtracker.DocumentComment:version']),
-            ('comment_text', orm['idtracker.DocumentComment:comment_text']),
-            ('created_by', orm['idtracker.DocumentComment:created_by']),
-            ('result_state', orm['idtracker.DocumentComment:result_state']),
-            ('origin_state', orm['idtracker.DocumentComment:origin_state']),
-            ('ballot', orm['idtracker.DocumentComment:ballot']),
-        ))
-        db.send_create_signal('idtracker', ['DocumentComment'])
-        
-        # Creating unique_together for [ballot, ad] on IESGDiscuss.
-        db.create_unique('ballots_discuss', ['ballot_id', 'ad_id'])
-        
-        # Creating unique_together for [ballot, ad] on Position.
-        db.create_unique('ballots', ['ballot_id', 'ad_id'])
-        
-        # Creating unique_together for [ballot, ad] on IESGComment.
-        db.create_unique('ballots_comment', ['ballot_id', 'ad_id'])
-        
-    
-    
-    def backwards(self, orm):
-        
-        # Deleting unique_together for [ballot, ad] on IESGComment.
-        db.delete_unique('ballots_comment', ['ballot_id', 'ad_id'])
-        
-        # Deleting unique_together for [ballot, ad] on Position.
-        db.delete_unique('ballots', ['ballot_id', 'ad_id'])
-        
-        # Deleting unique_together for [ballot, ad] on IESGDiscuss.
-        db.delete_unique('ballots_discuss', ['ballot_id', 'ad_id'])
-        
-        # Deleting model 'WGTechAdvisor'
-        db.delete_table('g_tech_advisors')
-        
-        # Deleting model 'IDState'
-        db.delete_table('ref_doc_states_new')
-        
-        # Deleting model 'BallotInfo'
-        db.delete_table('ballot_info')
-        
-        # Deleting model 'AreaStatus'
-        db.delete_table('area_status')
-        
-        # Deleting model 'RfcAuthor'
-        db.delete_table('rfc_authors')
-        
-        # Deleting model 'IDIntendedStatus'
-        db.delete_table('id_intended_status')
-        
-        # Deleting model 'IDNextState'
-        db.delete_table('ref_next_states_new')
-        
-        # Deleting model 'WGType'
-        db.delete_table('g_type')
-        
-        # Deleting model 'RfcObsolete'
-        db.delete_table('rfcs_obsolete')
-        
-        # Deleting model 'InternetDraft'
-        db.delete_table('internet_drafts')
-        
-        # Deleting model 'IRTFChair'
-        db.delete_table('irtf_chairs')
-        
-        # Deleting model 'IETFWG'
-        db.delete_table('groups_ietf')
-        
-        # Deleting model 'PostalAddress'
-        db.delete_table('postal_addresses')
-        
-        # Deleting model 'RfcIntendedStatus'
-        db.delete_table('rfc_intend_status')
-        
-        # Deleting model 'IDInternal'
-        db.delete_table('id_internal')
-        
-        # Deleting model 'IDAuthor'
-        db.delete_table('id_authors')
-        
-        # Deleting model 'IDStatus'
-        db.delete_table('id_status')
-        
-        # Deleting model 'Role'
-        db.delete_table('chairs')
-        
-        # Deleting model 'AreaDirector'
-        db.delete_table('area_directors')
-        
-        # Deleting model 'Rfc'
-        db.delete_table('rfcs')
-        
-        # Deleting model 'EmailAddress'
-        db.delete_table('email_addresses')
-        
-        # Deleting model 'AreaGroup'
-        db.delete_table('area_group')
-        
-        # Deleting model 'IESGDiscuss'
-        db.delete_table('ballots_discuss')
-        
-        # Deleting model 'GoalMilestone'
-        db.delete_table('goals_milestones')
-        
-        # Deleting model 'PhoneNumber'
-        db.delete_table('phone_numbers')
-        
-        # Deleting model 'WGSecretary'
-        db.delete_table('g_secretaries')
-        
-        # Deleting model 'WGStatus'
-        db.delete_table('g_status')
-        
-        # Deleting model 'IRTF'
-        db.delete_table('irtf')
-        
-        # Deleting model 'RfcStatus'
-        db.delete_table('rfc_status')
-        
-        # Deleting model 'Area'
-        db.delete_table('areas')
-        
-        # Deleting model 'ChairsHistory'
-        db.delete_table('chairs_history')
-        
-        # Deleting model 'Acronym'
-        db.delete_table('acronym')
-        
-        # Deleting model 'WGChair'
-        db.delete_table('g_chairs')
-        
-        # Deleting model 'WGEditor'
-        db.delete_table('g_editors')
-        
-        # Deleting model 'PersonOrOrgInfo'
-        db.delete_table('person_or_org_info')
-        
-        # Deleting model 'Position'
-        db.delete_table('ballots')
-        
-        # Deleting model 'IESGComment'
-        db.delete_table('ballots_comment')
-        
-        # Deleting model 'IESGLogin'
-        db.delete_table('iesg_login')
-        
-        # Deleting model 'AreaWGURL'
-        db.delete_table('wg_www_pages')
-        
-        # Deleting model 'IDSubState'
-        db.delete_table('sub_state')
-        
-        # Deleting model 'DocumentComment'
-        db.delete_table('document_comments')
-        
-    
-    
-    models = {
-        'idtracker.acronym': {
-            'Meta': {'db_table': "'acronym'"},
-            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
-            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        },
-        'idtracker.area': {
-            'Meta': {'db_table': "'areas'"},
-            'area_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
-            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'extra_email_addresses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
-            'last_modified_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}),
-            'start_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaStatus']"})
-        },
-        'idtracker.areadirector': {
-            'Meta': {'db_table': "'area_directors'"},
-            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']", 'null': 'True', 'db_column': "'area_acronym_id'"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.areagroup': {
-            'Meta': {'db_table': "'area_group'"},
-            'area': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'areagroup'", 'db_column': "'area_acronym_id'", 'to': "orm['idtracker.Area']"}),
-            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']", 'unique': 'True', 'db_column': "'group_acronym_id'"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.areastatus': {
-            'Meta': {'db_table': "'area_status'"},
-            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
-            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.areawgurl': {
-            'Meta': {'db_table': "'wg_www_pages'"},
-            'description': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'area_ID'"}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'area_Name'"}),
-            'url': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        },
-        'idtracker.ballotinfo': {
-            'Meta': {'db_table': "'ballot_info'"},
-            'active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'an_sent': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'an_sent_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ansent'", 'null': 'True', 'db_column': "'an_sent_by'", 'to': "orm['idtracker.IESGLogin']"}),
-            'an_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'approval_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'ballot': ('django.db.models.fields.AutoField', [], {'primary_key': 'True', 'db_column': "'ballot_id'"}),
-            'ballot_issued': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'ballot_writeup': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'defer': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'defer_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deferred'", 'null': 'True', 'db_column': "'defer_by'", 'to': "orm['idtracker.IESGLogin']"}),
-            'defer_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'last_call_text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
-        },
-        'idtracker.chairshistory': {
-            'Meta': {'db_table': "'chairs_history'"},
-            'chair_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Role']"}),
-            'end_year': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
-            'present_chair': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'start_year': ('django.db.models.fields.IntegerField', [], {})
-        },
-        'idtracker.documentcomment': {
-            'Meta': {'db_table': "'document_comments'"},
-            'ballot': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
-            'comment_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'created_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'null': 'True', 'db_column': "'created_by'", 'null_values': '(0,999)'}),
-            'date': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'db_column': "'comment_date'"}),
-            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDInternal']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'origin_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments_coming_from_state'", 'null': 'True', 'db_column': "'origin_state'", 'to': "orm['idtracker.IDState']"}),
-            'public_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'result_state': ('BrokenForeignKey', ["orm['idtracker.IDState']"], {'related_name': '"comments_leading_to_state"', 'null': 'True', 'db_column': "'result_state'", 'null_values': '(0,99)'}),
-            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'time': ('django.db.models.fields.CharField', [], {'default': "'05:06:33'", 'max_length': '20', 'db_column': "'comment_time'"}),
-            'version': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'})
-        },
-        'idtracker.emailaddress': {
-            'Meta': {'db_table': "'email_addresses'"},
-            'address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'email_address'"}),
-            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_column': "'email_comment'", 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
-            'priority': ('django.db.models.fields.IntegerField', [], {'db_column': "'email_priority'"}),
-            'type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'db_column': "'email_type'"})
-        },
-        'idtracker.goalmilestone': {
-            'Meta': {'db_table': "'goals_milestones'"},
-            'description': ('django.db.models.fields.TextField', [], {}),
-            'done': ('django.db.models.fields.CharField', [], {'max_length': '4', 'blank': 'True'}),
-            'done_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'expected_due_date': ('django.db.models.fields.DateField', [], {}),
-            'gm_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
-            'last_modified_date': ('django.db.models.fields.DateField', [], {})
-        },
-        'idtracker.idauthor': {
-            'Meta': {'db_table': "'id_authors'"},
-            'author_order': ('django.db.models.fields.IntegerField', [], {}),
-            'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'id_document_tag'", 'to': "orm['idtracker.InternetDraft']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.idintendedstatus': {
-            'Meta': {'db_table': "'id_intended_status'"},
-            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
-            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.idinternal': {
-            'Meta': {'db_table': "'id_internal'"},
-            'agenda': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'approved_in_minute': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'area_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']"}),
-            'assigned_to': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
-            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'drafts'", 'db_column': "'ballot_id'", 'to': "orm['idtracker.BallotInfo']"}),
-            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs'", 'db_column': "'cur_state'", 'to': "orm['idtracker.IDState']"}),
-            'cur_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
-            'dnp': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'dnp_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'draft': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True', 'primary_key': 'True', 'db_column': "'id_document_tag'"}),
-            'email_display': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
-            'event_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
-            'group_flag': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
-            'job_owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'db_column': "'job_owner'", 'to': "orm['idtracker.IESGLogin']"}),
-            'mark_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'marked'", 'db_column': "'mark_by'", 'to': "orm['idtracker.IESGLogin']"}),
-            'noproblem': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'prev_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'docs_prev'", 'db_column': "'prev_state'", 'to': "orm['idtracker.IDState']"}),
-            'prev_sub_state': ('BrokenForeignKey', ["orm['idtracker.IDSubState']"], {'related_name': "'docs_prev'", 'null': 'True', 'null_values': '(0,-1)', 'blank': 'True'}),
-            'primary_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'resurrect_requested_by': ('BrokenForeignKey', ["orm['idtracker.IESGLogin']"], {'related_name': "'docsresurrected'", 'null': 'True', 'db_column': "'resurrect_requested_by'", 'blank': 'True'}),
-            'returning_item': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'rfc_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
-            'state_change_notice_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'status_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'token_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'token_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
-            'via_rfc_editor': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
-        },
-        'idtracker.idnextstate': {
-            'Meta': {'db_table': "'ref_next_states_new'"},
-            'condition': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'cur_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nextstate'", 'to': "orm['idtracker.IDState']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'next_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'prevstate'", 'to': "orm['idtracker.IDState']"})
-        },
-        'idtracker.idstate': {
-            'Meta': {'db_table': "'ref_doc_states_new'"},
-            'description': ('django.db.models.fields.TextField', [], {'db_column': "'document_desc'", 'blank': 'True'}),
-            'document_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'equiv_group_flag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'state': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_column': "'document_state_val'"})
-        },
-        'idtracker.idstatus': {
-            'Meta': {'db_table': "'id_status'"},
-            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
-            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.idsubstate': {
-            'Meta': {'db_table': "'sub_state'"},
-            'description': ('django.db.models.fields.TextField', [], {'db_column': "'sub_state_desc'", 'blank': 'True'}),
-            'sub_state': ('django.db.models.fields.CharField', [], {'max_length': '55', 'db_column': "'sub_state_val'"}),
-            'sub_state_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.iesgcomment': {
-            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_comment'"},
-            'active': ('django.db.models.fields.IntegerField', [], {}),
-            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
-            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['idtracker.BallotInfo']"}),
-            'date': ('django.db.models.fields.DateField', [], {'db_column': "'comment_date'"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
-            'text': ('django.db.models.fields.TextField', [], {'db_column': "'comment_text'", 'blank': 'True'})
-        },
-        'idtracker.iesgdiscuss': {
-            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots_discuss'"},
-            'active': ('django.db.models.fields.IntegerField', [], {}),
-            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
-            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'discusses'", 'to': "orm['idtracker.BallotInfo']"}),
-            'date': ('django.db.models.fields.DateField', [], {'db_column': "'discuss_date'"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
-            'text': ('django.db.models.fields.TextField', [], {'db_column': "'discuss_text'", 'blank': 'True'})
-        },
-        'idtracker.iesglogin': {
-            'Meta': {'db_table': "'iesg_login'"},
-            'default_search': ('django.db.models.fields.NullBooleanField', [], {'null': 'True'}),
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
-            'login_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
-            'person': ('BrokenForeignKey', ["orm['idtracker.PersonOrOrgInfo']"], {'unique': 'True', 'null': 'True', 'db_column': "'person_or_org_tag'", 'null_values': '(0,888888)'}),
-            'pgp_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
-            'user_level': ('django.db.models.fields.IntegerField', [], {})
-        },
-        'idtracker.ietfwg': {
-            'Meta': {'db_table': "'groups_ietf'"},
-            'area_director': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaDirector']", 'null': 'True'}),
-            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'dormant_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'email_address': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
-            'email_archive': ('django.db.models.fields.CharField', [], {'max_length': '95', 'blank': 'True'}),
-            'email_keyword': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
-            'email_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
-            'group_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
-            'group_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGType']"}),
-            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
-            'meeting_scheduled': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
-            'meeting_scheduled_old': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
-            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGStatus']"})
-        },
-        'idtracker.internetdraft': {
-            'Meta': {'db_table': "'internet_drafts'"},
-            'abstract': ('django.db.models.fields.TextField', [], {}),
-            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
-            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
-            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
-            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
-            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
-            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
-            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
-            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
-            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
-            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
-            'replaced_by': ('BrokenForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
-            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
-            'revision_date': ('django.db.models.fields.DateField', [], {}),
-            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
-            'start_date': ('django.db.models.fields.DateField', [], {}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
-            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
-            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
-        },
-        'idtracker.irtf': {
-            'Meta': {'db_table': "'irtf'"},
-            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'irtf_acronym'", 'blank': 'True'}),
-            'charter_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
-            'irtf_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'meeting_scheduled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'irtf_name'", 'blank': 'True'})
-        },
-        'idtracker.irtfchair': {
-            'Meta': {'db_table': "'irtf_chairs'"},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'irtf': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IRTF']"}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.personororginfo': {
-            'Meta': {'db_table': "'person_or_org_info'"},
-            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
-            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
-            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
-            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
-            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
-            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
-            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
-            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
-            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
-            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
-            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
-        },
-        'idtracker.phonenumber': {
-            'Meta': {'db_table': "'phone_numbers'"},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
-            'phone_comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'phone_priority': ('django.db.models.fields.IntegerField', [], {}),
-            'phone_type': ('django.db.models.fields.CharField', [], {'max_length': '3'})
-        },
-        'idtracker.position': {
-            'Meta': {'unique_together': "(('ballot', 'ad'),)", 'db_table': "'ballots'"},
-            'abstain': ('django.db.models.fields.IntegerField', [], {}),
-            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IESGLogin']"}),
-            'approve': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
-            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'positions'", 'to': "orm['idtracker.BallotInfo']"}),
-            'discuss': ('django.db.models.fields.IntegerField', [], {}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'noobj': ('django.db.models.fields.IntegerField', [], {'db_column': "'no_col'"}),
-            'recuse': ('django.db.models.fields.IntegerField', [], {}),
-            'yes': ('django.db.models.fields.IntegerField', [], {'db_column': "'yes_col'"})
-        },
-        'idtracker.postaladdress': {
-            'Meta': {'db_table': "'postal_addresses'"},
-            'address_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
-            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
-            'aff_company_key': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
-            'affiliated_company': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
-            'city': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'country': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'department': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'mail_stop': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'person_or_org': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
-            'person_title': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
-            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'staddr1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
-            'staddr2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
-            'state_or_prov': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
-        },
-        'idtracker.rfc': {
-            'Meta': {'db_table': "'rfcs'"},
-            'area_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
-            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'draft_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'fyi_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'group_acronym': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
-            'historic_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'default': '5', 'to': "orm['idtracker.RfcIntendedStatus']", 'db_column': "'intended_status_id'"}),
-            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
-            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'online_version': ('django.db.models.fields.CharField', [], {'default': "'YES'", 'max_length': '3'}),
-            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'rfc_name_key': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
-            'rfc_number': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
-            'rfc_published_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'standard_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.RfcStatus']", 'db_column': "'status_id'"}),
-            'std_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_column': "'rfc_name'"}),
-            'txt_page_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
-        },
-        'idtracker.rfcauthor': {
-            'Meta': {'db_table': "'rfc_authors'"},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
-            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'authors'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"})
-        },
-        'idtracker.rfcintendedstatus': {
-            'Meta': {'db_table': "'rfc_intend_status'"},
-            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"})
-        },
-        'idtracker.rfcobsolete': {
-            'Meta': {'db_table': "'rfcs_obsolete'"},
-            'action': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'rfc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates_or_obsoletes'", 'db_column': "'rfc_number'", 'to': "orm['idtracker.Rfc']"}),
-            'rfc_acted_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updated_or_obsoleted_by'", 'db_column': "'rfc_acted_on'", 'to': "orm['idtracker.Rfc']"})
-        },
-        'idtracker.rfcstatus': {
-            'Meta': {'db_table': "'rfc_status'"},
-            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
-            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.role': {
-            'Meta': {'db_table': "'chairs'"},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
-            'role_name': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'chair_name'"})
-        },
-        'idtracker.wgchair': {
-            'Meta': {'db_table': "'g_chairs'"},
-            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.wgeditor': {
-            'Meta': {'db_table': "'g_editors'"},
-            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'unique': 'True', 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.wgsecretary': {
-            'Meta': {'db_table': "'g_secretaries'"},
-            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.wgstatus': {
-            'Meta': {'db_table': "'g_status'"},
-            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
-            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'idtracker.wgtechadvisor': {
-            'Meta': {'db_table': "'g_tech_advisors'"},
-            'group_acronym': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
-        },
-        'idtracker.wgtype': {
-            'Meta': {'db_table': "'g_type'"},
-            'group_type_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'type': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'group_type'"})
-        }
-    }
-    
-    complete_apps = ['idtracker']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index b327e2b68..1d5399d48 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -47,4 +47,6 @@ class ObjectAnnotationTagHistoryEntry(models.Model):
 
 
 class WGWorkflow(Workflow):
-    pass
+
+    class Meta:
+        proxy = True
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index 7da34a3dd..646aa9ad5 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -5,6 +5,7 @@ from django.http import HttpResponseForbidden
 
 from ietf.wgchairs.forms import RemoveDelegateForm, add_form_factory
 from ietf.wgchairs.accounts import can_manage_delegates_in_group
+from ietf.ietfworkflows.utils import get_workflow_for_wg
 
 
 def manage_delegates(request, acronym):
@@ -28,11 +29,13 @@ def manage_delegates(request, acronym):
                                'selected': 'manage_delegates',
                                'can_add': delegates.count() < 3,
                                'add_form': add_form,
-                              },
-                              RequestContext(request))
+                              }, RequestContext(request))
 
 
 def manage_workflow(request, acronym):
     wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
-    concluded = (wg.status_id != 1)
-    return render_to_response('wginfo/wg_charter.html', {'wg': wg, 'concluded': concluded, 'selected': 'manage_workflow'}, RequestContext(request))
+    workflow = get_workflow_for_wg(wg)
+    return render_to_response('wgchairs/manage_workflow.html',
+                              {'wg': wg,
+                               'workflow': workflow,
+                              }, RequestContext(request))

From 32f1db57a62b588f620b5461cfc4aceca747060e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 12:37:38 +0000
Subject: [PATCH 16/57] Util functions to manage wg workflows. See #559  -
 Legacy-Id: 2703

---
 ietf/ietfworkflows/utils.py | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 ietf/ietfworkflows/utils.py

diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
new file mode 100644
index 000000000..0cb5d3737
--- /dev/null
+++ b/ietf/ietfworkflows/utils.py
@@ -0,0 +1,15 @@
+from workflows.utils import get_workflow_for_object, set_workflow_for_object
+
+from ietf.ietfworkflows.models import WGWorkflow
+
+
+def get_workflow_for_wg(wg):
+    workflow = get_workflow_for_object(wg)
+    if not workflow:
+        try:
+            workflow = WGWorkflow.objects.get(name='Default WG Workflow')
+            set_workflow_for_object(wg, workflow)
+        except WGWorkflow.DoesNotExist:
+            return None
+    return workflow
+

From a7347380cfc1001df83af8f8c26753a70a90fc47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 12:41:25 +0000
Subject: [PATCH 17/57] Register InternetDraft with InternetDraftAdmin, removed
 by error. See #563  - Legacy-Id: 2704

---
 ietf/idtracker/admin.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py
index 95a6cc1eb..43f9abdb6 100644
--- a/ietf/idtracker/admin.py
+++ b/ietf/idtracker/admin.py
@@ -101,7 +101,7 @@ class InternetDraftAdmin(admin.ModelAdmin):
     search_fields=['filename', 'title']
     list_filter=['status']
     raw_id_fields=['replaced_by']
-admin.site.register(InternetDraft)
+admin.site.register(InternetDraft, InternetDraftAdmin)
 
 class PersonOrOrgInfoAdmin(admin.ModelAdmin):
     fieldsets=((None, {'fields': (('first_name', 'middle_initial', 'last_name'), ('name_suffix', 'modified_by'))}), ('Obsolete Info', {'fields': ('record_type', 'created_by', 'address_type'), 'classes': 'collapse'}))

From 53eccb9b0dc64da11a41ab4ec310ad76ac8731a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 12:43:06 +0000
Subject: [PATCH 18/57] Removed print sentences. See #563  - Legacy-Id: 2705

---
 ietf/idrfc/views_edit.py   | 4 +---
 ietf/idrfc/views_search.py | 4 ----
 2 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 63b688b91..5dd7c786e 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -32,8 +32,6 @@ def change_state(request, name):
     """Change state of Internet Draft, notifying parties as necessary
     and logging the change as a comment."""
     doc = get_object_or_404(InternetDraft, filename=name)
-    print doc.idinternal
-    print doc.status.status
     if not doc.idinternal or doc.status.status == "Expired":
         raise Http404()
 
@@ -401,4 +399,4 @@ def managing_shepherd(request, name):
                                    form=form,
                                    user=request.user,
                                    login=login),
-                              context_instance=RequestContext(request))
\ No newline at end of file
+                              context_instance=RequestContext(request))
diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py
index eee1a792e..1be14924c 100644
--- a/ietf/idrfc/views_search.py
+++ b/ietf/idrfc/views_search.py
@@ -85,9 +85,6 @@ class SearchForm(forms.Form):
         return q
                                                                         
 def search_query(query_original):
-    """
-    @FIXME: This method should be re-factored !
-    """
     query = dict(query_original.items())
     drafts = query['activeDrafts'] or query['oldDrafts']
     if (not drafts) and (not query['rfcs']):
@@ -138,7 +135,6 @@ def search_query(query_original):
         matches = IDInternal.objects.filter(*q_objs)
     else:
         matches = InternetDraft.objects.filter(*q_objs)
-        print q_objs
     if not query['activeDrafts']:
         matches = matches.exclude(Q(**{prefix+"status":1}))
     if not query['rfcs']:

From c14031d6eb17e8268628985c705f730cf255dc7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 12:45:49 +0000
Subject: [PATCH 19/57] This file is not needed. It is duplicated in
 idrfc.forms that is the one in use. See #563  - Legacy-Id: 2706

---
 ietf/idtracker/forms.py | 67 -----------------------------------------
 1 file changed, 67 deletions(-)
 delete mode 100644 ietf/idtracker/forms.py

diff --git a/ietf/idtracker/forms.py b/ietf/idtracker/forms.py
deleted file mode 100644
index a5f511d11..000000000
--- a/ietf/idtracker/forms.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from django.conf import settings
-from django import forms
-from idtracker.models import PersonOrOrgInfo
-from django.db.models import Q
-from django.template.loader import render_to_string
-from django.core.mail import EmailMessage
-
-
-class ManagingShepherdForm(forms.Form):
-    email = forms.EmailField(required=False)
-    is_assign_current = forms.BooleanField(required=False)
-    
-    
-    def __init__(self, *args, **kwargs):
-        if kwargs.has_key('current_person'):
-            self.current_person = kwargs.pop('current_person')            
-        return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
-    
-    def clean_email(self):
-        email = self.cleaned_data.get('email')
-        if not email:
-            return None
-        
-        try:
-            PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=[ "INET", "Prim",], 
-                        emailaddress__address=email) \
-                        [:1].get()
-        except PersonOrOrgInfo.DoesNotExist:
-            if self.cleaned_data.get('is_assign_current'):
-                self._send_email(email)
-            raise forms.ValidationError("Person with such email does not exist")
-        return email
-    
-    def clean(self):
-        if self.cleaned_data.get('email') and \
-                                    self.cleaned_data.get('is_assign_current'):
-            raise forms.ValidationError("You should choose to assign to current \
-                        person or input the email. Not both at te same time. ")
-        
-        return self.cleaned_data
-    
-    def change_shepherd(self, document, save=True):
-        email = self.cleaned_data.get('email')        
-        if email:
-            person = PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=[ "INET", "Prim",], 
-                        emailaddress__address=email) \
-                        [:1].get()
-        else:
-            person = self.current_person        
-        document.shepherd = person 
-        if save: 
-            document.save()
-        return document
-    
-    def _send_email(self, email, 
-                        template='idrfc/edit_management_shepherd_email.txt'):
-        subject = 'WG Delegate needs system credentials'        
-        body = render_to_string(template,
-                                {'email': email,
-                                })
-        mail = EmailMessage(subject=subject,
-                            body=body,
-                            to=[email, settings.DEFAULT_FROM_EMAIL, ],
-                            from_email=settings.DEFAULT_FROM_EMAIL)
-        mail.send()
\ No newline at end of file

From 34f9cec92af155b00c7a42a109bab9945eaf631d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 12:47:04 +0000
Subject: [PATCH 20/57] Fix import. Don't forget to input the project module.
 See #563  - Legacy-Id: 2707

---
 ietf/idrfc/views_edit.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 5dd7c786e..3a1e37fef 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -20,7 +20,7 @@ from ietf.idtracker.models import *
 from ietf.iesg.models import *
 from ietf.idrfc.mails import *
 from ietf.idrfc.utils import *
-from idrfc.forms import ManagingShepherdForm
+from ietf.idrfc.forms import ManagingShepherdForm
 
     
 class ChangeStateForm(forms.Form):

From ebaf7ae436aa82cee256e30ec27e31aafd49feef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 13:00:43 +0000
Subject: [PATCH 21/57] Moved shepherd management into wgchairs application.
 See #563  - Legacy-Id: 2708

---
 ietf/idrfc/forms.py                           | 68 -------------------
 ietf/idrfc/urls.py                            |  2 -
 ietf/idrfc/views_edit.py                      | 20 ------
 .../edit_management_shepherd.html             |  0
 .../edit_management_shepherd_email.txt        |  0
 ietf/wgchairs/forms.py                        | 63 +++++++++++++++++
 ietf/wgchairs/urls.py                         |  1 +
 ietf/wgchairs/views.py                        | 25 ++++++-
 ietf/wginfo/views.py                          |  2 +-
 9 files changed, 88 insertions(+), 93 deletions(-)
 delete mode 100644 ietf/idrfc/forms.py
 rename ietf/templates/{idrfc => wgchairs}/edit_management_shepherd.html (100%)
 rename ietf/templates/{idrfc => wgchairs}/edit_management_shepherd_email.txt (100%)

diff --git a/ietf/idrfc/forms.py b/ietf/idrfc/forms.py
deleted file mode 100644
index eae1fc611..000000000
--- a/ietf/idrfc/forms.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from django.conf import settings
-from django import forms
-from idtracker.models import PersonOrOrgInfo
-from django.db.models import Q
-from django.template.loader import render_to_string
-from django.core.mail import EmailMessage
-
-
-class ManagingShepherdForm(forms.Form):
-    email = forms.EmailField(required=False)
-    is_assign_current = forms.BooleanField(required=False)
-    
-    
-    def __init__(self, *args, **kwargs):
-        if kwargs.has_key('current_person'):
-            self.current_person = kwargs.pop('current_person')            
-        return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
-    
-    def clean_email(self):
-        email = self.cleaned_data.get('email')
-        if not email:
-            return None
-        
-        try:
-            PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=[ "INET", "Prim",], 
-                        emailaddress__address=email) \
-                        [:1].get()
-        except PersonOrOrgInfo.DoesNotExist:
-            if self.cleaned_data.get('is_assign_current'):
-                self._send_email(email)
-            raise forms.ValidationError("Person with such email does not exist")
-        return email
-    
-    def clean(self):
-        print self.cleaned_data.get('email') and self.cleaned_data.get('is_assign_current')
-        if self.cleaned_data.get('email') and \
-                                    self.cleaned_data.get('is_assign_current'):
-            raise forms.ValidationError("You should choose to assign to current \
-                        person or input the email. Not both at te same time. ")
-        
-        return self.cleaned_data
-    
-    def change_shepherd(self, document, save=True):
-        email = self.cleaned_data.get('email')        
-        if email:
-            person = PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=[ "INET", "Prim",], 
-                        emailaddress__address=email) \
-                        [:1].get()
-        else:
-            person = self.current_person        
-        document.shepherd = person 
-        if save: 
-            document.save()
-        return document
-    
-    def _send_email(self, email, 
-                        template='idrfc/edit_management_shepherd_email.txt'):
-        subject = 'WG Delegate needs system credentials'        
-        body = render_to_string(template,
-                                {'email': email,
-                                })
-        mail = EmailMessage(subject=subject,
-                            body=body,
-                            to=[email, settings.DEFAULT_FROM_EMAIL, ],
-                            from_email=settings.DEFAULT_FROM_EMAIL)
-        mail.send()
\ No newline at end of file
diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py
index 128d6793c..88bf31d16 100644
--- a/ietf/idrfc/urls.py
+++ b/ietf/idrfc/urls.py
@@ -51,8 +51,6 @@ urlpatterns = patterns('',
     url(r'^(?P<name>[^/]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'),
     url(r'^(?P<name>[^/]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'),                       
     url(r'^(?P<name>[^/]+)/edit/addcomment/$', views_edit.add_comment, name='doc_add_comment'),
-    url(r'^(?P<name>[^/]+)/edit/managing-shepherd/$', views_edit.managing_shepherd, name='doc_managing_shepherd'),
-    
     url(r'^(?P<name>[^/]+)/edit/position/$', views_ballot.edit_position, name='doc_edit_position'),
     url(r'^(?P<name>[^/]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),
     url(r'^(?P<name>[^/]+)/edit/undeferballot/$', views_ballot.undefer_ballot, name='doc_undefer_ballot'),
diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 3a1e37fef..9a928a0b5 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -20,7 +20,6 @@ from ietf.idtracker.models import *
 from ietf.iesg.models import *
 from ietf.idrfc.mails import *
 from ietf.idrfc.utils import *
-from ietf.idrfc.forms import ManagingShepherdForm
 
     
 class ChangeStateForm(forms.Form):
@@ -381,22 +380,3 @@ def add_comment(request, name):
                               dict(doc=doc,
                                    form=form),
                               context_instance=RequestContext(request))
-
-def managing_shepherd(request, name):
-    """
-     View for managing the assigned shepherd of a document.
-    """
-    doc = get_object_or_404(InternetDraft, filename=name)
-    login = IESGLogin.objects.get(login_name=request.user.username)
-    form = ManagingShepherdForm()    
-    if request.method == "POST":
-        form = ManagingShepherdForm(request.POST, current_person=login.person)
-        if form.is_valid():
-            form.change_shepherd(doc)
-    
-    return render_to_response('idrfc/edit_management_shepherd.html',
-                              dict(doc=doc,
-                                   form=form,
-                                   user=request.user,
-                                   login=login),
-                              context_instance=RequestContext(request))
diff --git a/ietf/templates/idrfc/edit_management_shepherd.html b/ietf/templates/wgchairs/edit_management_shepherd.html
similarity index 100%
rename from ietf/templates/idrfc/edit_management_shepherd.html
rename to ietf/templates/wgchairs/edit_management_shepherd.html
diff --git a/ietf/templates/idrfc/edit_management_shepherd_email.txt b/ietf/templates/wgchairs/edit_management_shepherd_email.txt
similarity index 100%
rename from ietf/templates/idrfc/edit_management_shepherd_email.txt
rename to ietf/templates/wgchairs/edit_management_shepherd_email.txt
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index b41960c19..0818cd4d8 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -1,4 +1,5 @@
 from django import forms
+from django.db.models import Q
 from django.conf import settings
 from django.core.mail import EmailMessage
 from django.template.loader import render_to_string
@@ -181,3 +182,65 @@ def add_form_factory(request, wg, user):
         return AddDelegateForm(wg=wg, user=user, data=request.POST.copy())
 
     return AddDelegateForm(wg=wg, user=user)
+
+
+class ManagingShepherdForm(forms.Form):
+    email = forms.EmailField(required=False)
+    is_assign_current = forms.BooleanField(required=False)
+    
+    
+    def __init__(self, *args, **kwargs):
+        if kwargs.has_key('current_person'):
+            self.current_person = kwargs.pop('current_person')            
+        return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
+    
+    def clean_email(self):
+        email = self.cleaned_data.get('email')
+        if not email:
+            return None
+        
+        try:
+            PersonOrOrgInfo.objects. \
+                  filter(emailaddress__type__in=[ "INET", "Prim",], 
+                        emailaddress__address=email) \
+                        [:1].get()
+        except PersonOrOrgInfo.DoesNotExist:
+            if self.cleaned_data.get('is_assign_current'):
+                self._send_email(email)
+            raise forms.ValidationError("Person with such email does not exist")
+        return email
+    
+    def clean(self):
+        print self.cleaned_data.get('email') and self.cleaned_data.get('is_assign_current')
+        if self.cleaned_data.get('email') and \
+                                    self.cleaned_data.get('is_assign_current'):
+            raise forms.ValidationError("You should choose to assign to current \
+                        person or input the email. Not both at te same time. ")
+        
+        return self.cleaned_data
+    
+    def change_shepherd(self, document, save=True):
+        email = self.cleaned_data.get('email')        
+        if email:
+            person = PersonOrOrgInfo.objects. \
+                  filter(emailaddress__type__in=[ "INET", "Prim",], 
+                        emailaddress__address=email) \
+                        [:1].get()
+        else:
+            person = self.current_person        
+        document.shepherd = person 
+        if save: 
+            document.save()
+        return document
+    
+    def _send_email(self, email, 
+                        template='wgchairs/edit_management_shepherd_email.txt'):
+        subject = 'WG Delegate needs system credentials'        
+        body = render_to_string(template,
+                                {'email': email,
+                                })
+        mail = EmailMessage(subject=subject,
+                            body=body,
+                            to=[email, settings.DEFAULT_FROM_EMAIL, ],
+                            from_email=settings.DEFAULT_FROM_EMAIL)
+        mail.send()
diff --git a/ietf/wgchairs/urls.py b/ietf/wgchairs/urls.py
index 916fa4ace..a64747c1d 100644
--- a/ietf/wgchairs/urls.py
+++ b/ietf/wgchairs/urls.py
@@ -5,4 +5,5 @@ from django.conf.urls.defaults import patterns, url
 urlpatterns = patterns('ietf.wgchairs.views',
      url(r'^workflows/$', 'manage_workflow', name='manage_workflow'),
      url(r'^delegates/$', 'manage_delegates', name='manage_delegates'),
+     url(r'shepherds/(?P<name>[^/]+)/$', 'managing_shepherd', name='doc_managing_shepherd'),
 )
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index 646aa9ad5..9970a1ed4 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -1,9 +1,10 @@
-from ietf.idtracker.models import IETFWG
+from ietf.idtracker.models import IETFWG, InternetDraft, IESGLogin
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 from django.http import HttpResponseForbidden
 
-from ietf.wgchairs.forms import RemoveDelegateForm, add_form_factory
+from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
+                                 ManagingShepherdForm)
 from ietf.wgchairs.accounts import can_manage_delegates_in_group
 from ietf.ietfworkflows.utils import get_workflow_for_wg
 
@@ -39,3 +40,23 @@ def manage_workflow(request, acronym):
                               {'wg': wg,
                                'workflow': workflow,
                               }, RequestContext(request))
+
+
+def managing_shepherd(request, acronym, name):
+    """
+     View for managing the assigned shepherd of a document.
+    """
+    doc = get_object_or_404(InternetDraft, filename=name)
+    login = IESGLogin.objects.get(login_name=request.user.username)
+    form = ManagingShepherdForm()
+    if request.method == "POST":
+        form = ManagingShepherdForm(request.POST, current_person=login.person)
+        if form.is_valid():
+            form.change_shepherd(doc)
+
+    return render_to_response('wgchairs/edit_management_shepherd.html',
+                              dict(doc=doc,
+                                   form=form,
+                                   user=request.user,
+                                   login=login),
+                              context_instance=RequestContext(request))
diff --git a/ietf/wginfo/views.py b/ietf/wginfo/views.py
index fd5e5dc7c..61b341b6d 100644
--- a/ietf/wginfo/views.py
+++ b/ietf/wginfo/views.py
@@ -38,7 +38,7 @@ from django.template import RequestContext, loader
 from django.http import HttpResponse
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.idrfc.idrfc_wrapper import IdRfcWrapper
-from idtracker.models import InternetDraft, PersonOrOrgInfo, IESGLogin
+from ietf.idtracker.models import InternetDraft, PersonOrOrgInfo, IESGLogin
 from django.db.models import Q
 
 def wg_summary_acronym(request):

From a0b799216913fbddc51cdd3529898026f551b0fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 13:22:03 +0000
Subject: [PATCH 22/57] Move shepherd list of documents view into wgchais
 application. See #564  - Legacy-Id: 2709

---
 .../wg_shepherd_documents.html                |  0
 ietf/wgchairs/urls.py                         |  3 ++-
 ietf/wgchairs/views.py                        | 20 +++++++++++++++++++
 ietf/wginfo/urls.py                           |  1 -
 ietf/wginfo/views.py                          | 20 -------------------
 5 files changed, 22 insertions(+), 22 deletions(-)
 rename ietf/templates/{wginfo => wgchairs}/wg_shepherd_documents.html (100%)

diff --git a/ietf/templates/wginfo/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
similarity index 100%
rename from ietf/templates/wginfo/wg_shepherd_documents.html
rename to ietf/templates/wgchairs/wg_shepherd_documents.html
diff --git a/ietf/wgchairs/urls.py b/ietf/wgchairs/urls.py
index a64747c1d..ab69bf8d6 100644
--- a/ietf/wgchairs/urls.py
+++ b/ietf/wgchairs/urls.py
@@ -5,5 +5,6 @@ from django.conf.urls.defaults import patterns, url
 urlpatterns = patterns('ietf.wgchairs.views',
      url(r'^workflows/$', 'manage_workflow', name='manage_workflow'),
      url(r'^delegates/$', 'manage_delegates', name='manage_delegates'),
-     url(r'shepherds/(?P<name>[^/]+)/$', 'managing_shepherd', name='doc_managing_shepherd'),
+     url(r'^shepherds/$', 'wg_shepherd_documents', name='manage_shepherds'),
+     url(r'^shepherds/(?P<name>[^/]+)/$', 'managing_shepherd', name='doc_managing_shepherd'),
 )
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index 9970a1ed4..69da491af 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -7,6 +7,8 @@ from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
                                  ManagingShepherdForm)
 from ietf.wgchairs.accounts import can_manage_delegates_in_group
 from ietf.ietfworkflows.utils import get_workflow_for_wg
+from ietf.idtracker.models import InternetDraft, PersonOrOrgInfo, IESGLogin
+from django.db.models import Q
 
 
 def manage_delegates(request, acronym):
@@ -60,3 +62,21 @@ def managing_shepherd(request, acronym, name):
                                    user=request.user,
                                    login=login),
                               context_instance=RequestContext(request))
+
+
+def wg_shepherd_documents(request, acronym):
+    current_person = PersonOrOrgInfo.objects. \
+                            get(iesglogin__login_name=request.user.username)
+
+    base_qs = InternetDraft.objects.select_related('status')
+    documents_no_shepherd = base_qs.filter(shepherd__isnull=True)
+    documents_my = base_qs.filter(shepherd=current_person)
+    documents_other = base_qs.filter(~Q(shepherd=current_person))
+    context = {
+        'groupped_documents': {
+            'Documents without Shepherd': documents_no_shepherd,
+            'My documents': documents_my,
+            'Other documents': documents_other,
+        }
+    }
+    return render_to_response('wgchairs/wg_shepherd_documents.html', context, RequestContext(request))
diff --git a/ietf/wginfo/urls.py b/ietf/wginfo/urls.py
index f4f5fa475..d67fc4897 100644
--- a/ietf/wginfo/urls.py
+++ b/ietf/wginfo/urls.py
@@ -13,7 +13,6 @@ urlpatterns = patterns('',
      (r'^1wg-summary-by-acronym.txt', views.wg_summary_acronym),
      (r'^1wg-charters.txt', views.wg_charters),
      (r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
-     (r'^shepherd/list/$', views.wg_shepherd_documents),     
      (r'^(?P<acronym>[^/]+)/$', views.wg_documents),
      (r'^(?P<acronym>[^/]+)/charter/$', views.wg_charter),
      (r'^(?P<acronym>[^/]+)/management/', include('ietf.wgchairs.urls')),
diff --git a/ietf/wginfo/views.py b/ietf/wginfo/views.py
index 61b341b6d..f0eb152cb 100644
--- a/ietf/wginfo/views.py
+++ b/ietf/wginfo/views.py
@@ -38,8 +38,6 @@ from django.template import RequestContext, loader
 from django.http import HttpResponse
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.idrfc.idrfc_wrapper import IdRfcWrapper
-from ietf.idtracker.models import InternetDraft, PersonOrOrgInfo, IESGLogin
-from django.db.models import Q
 
 def wg_summary_acronym(request):
     areas = Area.active_areas()
@@ -87,24 +85,6 @@ def wg_documents(request, acronym):
     return render_to_response('wginfo/wg_documents.html', {'wg': wg, 'concluded':concluded, 'selected':'documents', 'docs':docs,  'meta':meta, 
                                                            'docs_related':docs_related_pruned, 'meta_related':meta_related}, RequestContext(request))
 
-def wg_shepherd_documents(request):
-    current_person = PersonOrOrgInfo.objects. \
-                            get(iesglogin__login_name=request.user.username)
-                            
-    base_qs = InternetDraft.objects.select_related('status')
-    documents_no_shepherd = base_qs.filter(shepherd__isnull=True)    
-    documents_my = base_qs.filter(shepherd=current_person)
-    documents_other = base_qs.filter(~Q(shepherd=current_person))
-    context = {
-        'groupped_documents': {
-            'Documents without Shepherd': documents_no_shepherd,
-            'My documents': documents_my,
-            'Other documents': documents_other,        
-        }
-    }
-    return render_to_response('wginfo/wg_shepherd_documents.html', context, RequestContext(request))
-
-
 def wg_charter(request, acronym):
     wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
     concluded = (wg.status_id != 1)

From 1e5f2df379af7fc798a7e95e32f8aa1bf9a4677b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 9 Dec 2010 14:37:44 +0000
Subject: [PATCH 23/57] Restrict access to sheperd document list. See #564  -
 Legacy-Id: 2710

---
 ietf/templates/wgchairs/wg_shepherd_documents.html |  2 +-
 ietf/wgchairs/accounts.py                          |  7 +++++++
 ietf/wgchairs/views.py                             | 14 +++++++++-----
 3 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/ietf/templates/wgchairs/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
index 6b9c0be3c..5c76b0300 100644
--- a/ietf/templates/wgchairs/wg_shepherd_documents.html
+++ b/ietf/templates/wgchairs/wg_shepherd_documents.html
@@ -53,7 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       {% for doc in documents %}
         <tr class="{% cycle oddrow,evenrow %}">
           <td class="title">
-            <a href="{% url doc_managing_shepherd doc %}">{{ doc.title }}</a>
+            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
           </td>
           <td class="date">
              {{ doc.status.start_date|date:"Y-m" }}
diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
index 95f45d147..6177cbca0 100644
--- a/ietf/wgchairs/accounts.py
+++ b/ietf/wgchairs/accounts.py
@@ -37,3 +37,10 @@ def can_manage_delegates_in_group(user, group):
     if not person:
         return False
     return is_group_chair(person, group)
+
+
+def can_manage_shepherds_in_group(user, group):
+    person = get_person_for_user(user)
+    if not person:
+        return False
+    return is_group_chair(person, group)
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index 69da491af..f04a36298 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -5,9 +5,9 @@ from django.http import HttpResponseForbidden
 
 from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
                                  ManagingShepherdForm)
-from ietf.wgchairs.accounts import can_manage_delegates_in_group
+from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
+                                    can_manage_shepherds_in_group)
 from ietf.ietfworkflows.utils import get_workflow_for_wg
-from ietf.idtracker.models import InternetDraft, PersonOrOrgInfo, IESGLogin
 from django.db.models import Q
 
 
@@ -65,8 +65,11 @@ def managing_shepherd(request, acronym, name):
 
 
 def wg_shepherd_documents(request, acronym):
-    current_person = PersonOrOrgInfo.objects. \
-                            get(iesglogin__login_name=request.user.username)
+    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
+    user = request.user
+    if not can_manage_shepherds_in_group(user, wg):
+        return HttpResponseForbidden('You have no permission to access this view')
+    current_person = get_person_for_user(user)
 
     base_qs = InternetDraft.objects.select_related('status')
     documents_no_shepherd = base_qs.filter(shepherd__isnull=True)
@@ -77,6 +80,7 @@ def wg_shepherd_documents(request, acronym):
             'Documents without Shepherd': documents_no_shepherd,
             'My documents': documents_my,
             'Other documents': documents_other,
-        }
+        },
+        'wg': wg,
     }
     return render_to_response('wgchairs/wg_shepherd_documents.html', context, RequestContext(request))

From 82a852a5d5f8a0c1068acdae01d3582a67452a9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 24 Dec 2010 10:25:07 +0000
Subject: [PATCH 24/57] Allow a wgworkflow to define a subset of states and
 annotation tags. See #559. Fixes #560  - Legacy-Id: 2731

---
 .../0002_add_selected_states_and_tags.py      | 107 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |   7 +-
 ietf/ietfworkflows/utils.py                   |  48 +++++++-
 3 files changed, 153 insertions(+), 9 deletions(-)
 create mode 100644 ietf/ietfworkflows/migrations/0002_add_selected_states_and_tags.py

diff --git a/ietf/ietfworkflows/migrations/0002_add_selected_states_and_tags.py b/ietf/ietfworkflows/migrations/0002_add_selected_states_and_tags.py
new file mode 100644
index 000000000..7545559b7
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0002_add_selected_states_and_tags.py
@@ -0,0 +1,107 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding ManyToManyField 'WGWorkflow.selected_tags'
+        db.create_table('ietfworkflows_wgworkflow_selected_tags', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('wgworkflow', models.ForeignKey(orm.WGWorkflow, null=False)),
+            ('annotationtag', models.ForeignKey(orm.AnnotationTag, null=False))
+        ))
+        
+        # Adding ManyToManyField 'WGWorkflow.selected_states'
+        db.create_table('ietfworkflows_wgworkflow_selected_states', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('wgworkflow', models.ForeignKey(orm.WGWorkflow, null=False)),
+            ('state', models.ForeignKey(orm['workflows.State'], null=False))
+        ))
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Dropping ManyToManyField 'WGWorkflow.selected_tags'
+        db.delete_table('ietfworkflows_wgworkflow_selected_tags')
+        
+        # Dropping ManyToManyField 'WGWorkflow.selected_states'
+        db.delete_table('ietfworkflows_wgworkflow_selected_states')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.State']"}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index 1d5399d48..404fb1328 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
-from workflows.models import Workflow
+from workflows.models import Workflow, State
 from permissions.models import Permission
 
 
@@ -47,6 +47,5 @@ class ObjectAnnotationTagHistoryEntry(models.Model):
 
 
 class WGWorkflow(Workflow):
-
-    class Meta:
-        proxy = True
+    selected_states = models.ManyToManyField(State)
+    selected_tags = models.ManyToManyField(AnnotationTag)
diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 0cb5d3737..5096d7bb9 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -1,15 +1,53 @@
+import copy
+
 from workflows.utils import get_workflow_for_object, set_workflow_for_object
 
 from ietf.ietfworkflows.models import WGWorkflow
 
 
+def get_default_workflow_for_wg():
+    try:
+        workflow = WGWorkflow.objects.get(name='Default WG Workflow')
+        return workflow
+    except WGWorkflow.DoesNotExist:
+        return None
+    
+def clone_transition(transition):
+    new = copy.copy(transition)
+    new.pk = None
+    new.save()
+
+    # Reference original initial states
+    for state in transition.states.all():
+        new.states.add(state)
+    return new
+
+def clone_workflow(workflow, name):
+    new = WGWorkflow.objects.create(name=name, initial_state=workflow.initial_state)
+
+    # Reference default states
+    for state in workflow.states.all():
+        new.selected_states.add(state)
+
+    # Reference default annotation tags
+    for tag in workflow.annotation_tags.all():
+        new.selected_tags.add(tag)
+
+    # Reference cloned transitions
+    for transition in workflow.transitions.all():
+        new.transitions.add(clone_transition(transition))
+    return new
+
 def get_workflow_for_wg(wg):
     workflow = get_workflow_for_object(wg)
+    try:
+        workflow = workflow and workflow.wgworkflow
+    except WGWorkflow.DoesNotExist:
+        workflow = None
     if not workflow:
-        try:
-            workflow = WGWorkflow.objects.get(name='Default WG Workflow')
-            set_workflow_for_object(wg, workflow)
-        except WGWorkflow.DoesNotExist:
+        workflow = get_default_workflow_for_wg()
+        if not workflow:
             return None
+        workflow = clone_workflow(workflow, name='%s workflow' % wg)
+        set_workflow_for_object(wg, workflow)
     return workflow
-

From c5dd835e410c0af11e0bf9f67b4d1c74ccf82407 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 24 Dec 2010 10:32:15 +0000
Subject: [PATCH 25/57] Reorder general shepherd view. See #558  - Legacy-Id:
 2732

---
 .../wgchairs/wg_shepherd_documents.html       | 76 +++++++++++++++----
 ietf/wgchairs/views.py                        | 30 +++-----
 2 files changed, 73 insertions(+), 33 deletions(-)

diff --git a/ietf/templates/wgchairs/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
index 5c76b0300..8d0487afa 100644
--- a/ietf/templates/wgchairs/wg_shepherd_documents.html
+++ b/ietf/templates/wgchairs/wg_shepherd_documents.html
@@ -34,23 +34,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 {% endcomment %}
 {% block wg_titledetail %}Documents{% endblock %}
 
-{% block content %}
-<div {% if concluded %}class="ietf-concluded-bg"{% endif %}>
-
-
 {% block wg_content %}
+           
 <table class="ietf-table ietf-doctable" style="margin-top:16px;">
     <tr>       
-       <th class="title">Title</th>
+       <th class="title">Documents I shepherd</th>
        <th class="date">Date</th>
-       <th class="status" colspan="2">Status</th>
+       <th class="status">Status</th>
        <th class="ad">Area Director</th>
     </tr> 
     
-    {% for group, documents in groupped_documents.items %}     
-      <tr class="header"><td colspan="5">{{ group }}</td></tr>
-    
-      {% for doc in documents %}
+      {% for doc in my_documents %}
         <tr class="{% cycle oddrow,evenrow %}">
           <td class="title">
             <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
@@ -65,12 +59,64 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
           
           </tr>
       {% endfor %}
-    {% endfor %}
         
 </table>
-           
+
+<table class="ietf-table ietf-doctable" style="margin-top:16px;">
+    <tr>       
+       <th class="title">Documents whithout shepherd</th>
+       <th class="date">Date</th>
+       <th class="status">Status</th>
+       <th class="ad">Area Director</th>
+    </tr> 
+    
+      {% for doc in no_shepherd %}
+        <tr class="{% cycle oddrow,evenrow %}">
+          <td class="title">
+            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
+          </td>
+          <td class="date">
+             {{ doc.status.start_date|date:"Y-m" }}
+          </td>
+          <td class="status">
+              {{ doc.status.status }}
+          </td>        
+          <td class="ad">{{ doc.ad_name|default:"" }}</td>
+          
+          </tr>
+      {% endfor %}
+        
+</table>
+
+<table class="ietf-table ietf-doctable" style="margin-top:16px;">
+    <tr>       
+       <th class="title">Documents by other shepherds</th>
+       <th class="date">Date</th>
+       <th class="status">Status</th>
+       <th class="ad">Area Director</th>
+    </tr> 
+    
+      {% regroup other_shepherds by shepherd as regrouped %}
+      {% for documents in regrouped %}
+
+        <tr class="header"><td colspan="4">{{ documents.grouper }}</td></tr>
+
+        {% for doc in documents.list %}
+        <tr class="{% cycle oddrow,evenrow %}">
+          <td class="title">
+            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
+          </td>
+          <td class="date">
+             {{ doc.status.start_date|date:"Y-m" }}
+          </td>
+          <td class="status">
+              {{ doc.status.status }}
+          </td>        
+          <td class="ad">{{ doc.ad_name|default:"" }}</td>
+          
+          </tr>
+        {% endfor %}
+      {% endfor %}
+</table>
 {% endblock wg_content %}
 
-</div>
-{% endblock content %}
-
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index f04a36298..e2229aeab 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -3,12 +3,11 @@ from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 from django.http import HttpResponseForbidden
 
+from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
                                  ManagingShepherdForm)
 from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
                                     can_manage_shepherds_in_group)
-from ietf.ietfworkflows.utils import get_workflow_for_wg
-from django.db.models import Q
 
 
 def manage_delegates(request, acronym):
@@ -35,15 +34,6 @@ def manage_delegates(request, acronym):
                               }, RequestContext(request))
 
 
-def manage_workflow(request, acronym):
-    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
-    workflow = get_workflow_for_wg(wg)
-    return render_to_response('wgchairs/manage_workflow.html',
-                              {'wg': wg,
-                               'workflow': workflow,
-                              }, RequestContext(request))
-
-
 def managing_shepherd(request, acronym, name):
     """
      View for managing the assigned shepherd of a document.
@@ -71,16 +61,20 @@ def wg_shepherd_documents(request, acronym):
         return HttpResponseForbidden('You have no permission to access this view')
     current_person = get_person_for_user(user)
 
-    base_qs = InternetDraft.objects.select_related('status')
+    form = SearchForm({'by':'group', 'group':str(wg.group_acronym.acronym),
+                       'activeDrafts':'on'})
+    if not form.is_valid():
+        raise ValueError("form did not validate")
+    (docs,meta) = search_query(form.cleaned_data)
+
+    base_qs = InternetDraft.objects.filter(pk__in=[i.id._draft.pk for i in docs if i.id]).select_related('status')
     documents_no_shepherd = base_qs.filter(shepherd__isnull=True)
     documents_my = base_qs.filter(shepherd=current_person)
-    documents_other = base_qs.filter(~Q(shepherd=current_person))
+    documents_other = base_qs.exclude(shepherd__isnull=True).exclude(shepherd__pk__in=[current_person.pk, 0])
     context = {
-        'groupped_documents': {
-            'Documents without Shepherd': documents_no_shepherd,
-            'My documents': documents_my,
-            'Other documents': documents_other,
-        },
+        'no_shepherd': documents_no_shepherd,
+        'my_documents': documents_my,
+        'other_shepherds': documents_other,
         'wg': wg,
     }
     return render_to_response('wgchairs/wg_shepherd_documents.html', context, RequestContext(request))

From 204a4dd72b9f2e03db8f3d18b4a26c4a7dea853a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 24 Dec 2010 10:37:01 +0000
Subject: [PATCH 26/57] Allow to edit the workflow used in a wg. Fixes #559  -
 Legacy-Id: 2733

---
 ietf/templates/wgchairs/manage_workflow.html | 135 +++++++++++++++
 ietf/wgchairs/forms.py                       | 172 ++++++++++++++++---
 ietf/wgchairs/views.py                       |  53 +++++-
 3 files changed, 332 insertions(+), 28 deletions(-)
 create mode 100644 ietf/templates/wgchairs/manage_workflow.html

diff --git a/ietf/templates/wgchairs/manage_workflow.html b/ietf/templates/wgchairs/manage_workflow.html
new file mode 100644
index 000000000..ade831bea
--- /dev/null
+++ b/ietf/templates/wgchairs/manage_workflow.html
@@ -0,0 +1,135 @@
+{% extends "wginfo/wg_base.html" %}
+
+{% block wg_titledetail %}Manage Workflow{% endblock %}
+
+{% block wg_content %}
+<div class="wg-workflow-management">
+<h2>Edit workflow</h2>
+<div id="mytabs" class="yui-navset">
+<ul class="yui-nav">
+  <li class="selected"><a href="#info"><em>Info</em></a></li>
+  <li><a href="#states"><em>Select states</em></a></li>
+  <li><a href="#transitions"><em>Edit transitions</em></a></li>
+  <li><a href="#tags"><em>Select Annotation Tags</em></a></li>
+</ul>
+
+<div class="yui-content">
+<div id="info">
+<table style="width: 100%;"><tr style="vertical-align: top;"><td>
+<table class="ietf-table" style="width: 100%">
+  <tr> 
+    <th>States used in {{ wg }} Working Group</th>
+  </tr>
+  {% for state in states %}
+  <tr class="{% cycle "oddrow" "evenrow" %}">
+    <td>{{ state.name }}</td>
+  </tr>
+{% endfor %}
+</table><br />
+<table class="ietf-table" style="width: 100%;">
+  <tr> 
+    <th>Annotation tags used in {{ wg }} Working Group</th>
+  </tr>
+  {% for tag in tags %}
+  <tr class="{% cycle "oddrow" "evenrow" %}">
+    <td>{{ tag.name }}</td>
+  </tr>
+{% endfor %}
+</table>
+</td><td>
+<table class="ietf-table" style="width: 100%;">
+  <tr>
+    <th>Transition name</th><th>Initial states</th><th>Destination state</th>
+  </tr>
+  {% for transition in workflow.transitions.all %}
+  <tr class="{% cycle "oddrow" "evenrow" %}">
+    <td>
+      {{ transition.name }}
+    </td>
+    <td>
+    {% for state in transition.states.all %}
+      {{state.name }}{% if not forloop.last %}<br />{% endif %}
+    {% endfor %}
+    </td>
+    <td>
+      {{ transition.destination.name }}
+    </td>
+  </tr>
+  {% endfor %}
+  {% if not workflow.transitions.all.count %}
+  <tr class="oddrow"><td colspan="3">There are no transitions defined so any state change is allowed</td></tr>
+  {% endif %}
+</table>
+</td></tr></table>
+</div>
+
+<div id="states">
+<form action="#info" method="POST">
+<table class="ietf-table">
+  <tr> 
+    <th>Used in {{ wg }}</th><th>Available states</th>
+  </tr>
+  {% for state in default_states %}
+  <tr class="{% cycle "oddrow" "evenrow" %}">
+    <td><input type="checkbox" id="id_states_{{ state.pk }}" name="states" value="{{ state.pk }}" {% if state.used %}checked="checked" {% endif %}/></td>
+    <td><label for="id_states_{{ state.pk }}">{{ state.name }}</label></td>
+  </tr>
+{% endfor %}
+</table>
+<input type="submit" name="update_states" value="Update states" />
+</form>
+</div>
+
+<div id="transitions">
+<form action="#transitions" method="POST">
+<table class="ietf-table">
+  <tr>
+    <th>Delete</th><th>Transition name</th><th>Initial states</th><th>Destination state</th>
+  </tr>
+{{ formset.as_table }}
+</table>
+<input type="submit" name="update_transitions" value="Update transitions" />
+</form>
+</div>
+
+<div id="tags">
+<form action="#info" method="POST">
+<table class="ietf-table">
+  <tr> 
+    <th>Used in {{ wg }}</th><th>Available annotation tags</th>
+  </tr>
+  {% for tag in default_tags %}
+  <tr class="{% cycle "oddrow" "evenrow" %}">
+    <td><input type="checkbox" id="id_tags_{{ tag.pk }}" name="tags" value="{{ tag.pk }}" {% if tag.used %}checked="checked" {% endif %}/></td>
+    <td><label for="id_tags_{{ tag.pk }}">{{ tag.name }}</label></td>
+  </tr>
+{% endfor %}
+</table>
+<input type="submit" name="update_tags" value="Update tags" />
+</form>
+</div>
+</div>
+</div>
+
+<script type="text/javascript" src="/js/lib/jquery-1.4.2.min.js"></script>
+<script type="text/javascript" src="/js/yui/yui-20100305.js"></script>
+<script type="text/javascript" src="/js/base.js"></script>
+
+<script type="text/javascript">
+//<![CDATA[
+var tabView = new YAHOO.widget.TabView('mytabs');
+var url = location.href.split('#'); 
+if (url[1]) { 
+  url[1] = "#"+url[1];
+  var tabs = tabView.get('tabs'); 
+  for (var i = 0; i < tabs.length; i++) { 
+    if (url[1].indexOf(tabs[i].get('href')) == 0) {
+      tabView.set('activeIndex', i);
+      break; 
+     } 
+  } 
+} 
+//]]>
+</script>
+
+{% endblock %}
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 0818cd4d8..15c534698 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -2,12 +2,17 @@ from django import forms
 from django.db.models import Q
 from django.conf import settings
 from django.core.mail import EmailMessage
+from django.forms.models import BaseModelFormSet
 from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
 
 from ietf.wgchairs.models import WGDelegate
 from ietf.wgchairs.accounts import get_person_for_user
+from ietf.ietfworkflows.utils import get_default_workflow_for_wg, get_workflow_for_wg
 from ietf.idtracker.models import PersonOrOrgInfo
 
+from workflows.models import Transition
+
 
 class RelatedWGForm(forms.Form):
 
@@ -28,6 +33,131 @@ class RelatedWGForm(forms.Form):
                        }
 
 
+class TagForm(RelatedWGForm):
+
+    tags = forms.ModelMultipleChoiceField(get_default_workflow_for_wg().annotation_tags.all(),
+                                          widget=forms.CheckboxSelectMultiple)
+
+    def save(self):
+        workflow = get_workflow_for_wg(self.wg)
+        workflow.selected_tags.clear()
+        for tag in self.cleaned_data['tags']:
+            workflow.selected_tags.add(tag)
+        return workflow
+
+
+class StateForm(RelatedWGForm):
+
+    states = forms.ModelMultipleChoiceField(get_default_workflow_for_wg().states.all(),
+                                            widget=forms.CheckboxSelectMultiple)
+
+    def update_transitions(self, workflow):
+        for transition in workflow.transitions.all():
+            if not workflow.selected_states.filter(pk=transition.destination.pk).count():
+                transition.delete()
+                continue
+            for state in transition.states.all():
+                if not workflow.selected_states.filter(pk=state.pk).count():
+                    transition.states.remove(state)
+            if not transition.states.count():
+                transition.delete()
+                continue
+
+    def save(self):
+        workflow = get_workflow_for_wg(self.wg)
+        workflow.selected_states.clear()
+        for state in self.cleaned_data['states']:
+            workflow.selected_states.add(state)
+        self.update_transitions(workflow)
+        return workflow
+
+
+class DeleteTransitionForm(RelatedWGForm):
+
+    transitions = forms.ModelMultipleChoiceField(Transition.objects.all(),
+                                                 widget=forms.CheckboxSelectMultiple)
+
+    def __init__(self, *args, **kwargs):
+        super(DeleteTransitionForm, self).__init__(*args, **kwargs)
+        workflow = get_workflow_for_wg(self.wg)
+        self.fields['transitions'].queryset = self.fields['transitions'].queryset.filter(workflow=workflow)
+
+    def save(self):
+        for transition in self.cleaned_data['transitions']:
+            transition.delete()
+
+
+class TransitionForm(forms.ModelForm):
+
+    states = forms.ModelMultipleChoiceField(get_default_workflow_for_wg().states.all())
+
+    class Meta:
+        model = Transition
+        fields = ('DELETE', 'name', 'states', 'destination', )
+
+    def __init__(self, *args, **kwargs):
+        self.wg = kwargs.pop('wg', None)
+        self.user = kwargs.pop('user', None)
+        super(TransitionForm, self).__init__(*args, **kwargs)
+        workflow = get_workflow_for_wg(self.wg)
+        self.fields['states'].queryset = workflow.selected_states.all()
+        self.fields['destination'].queryset = workflow.selected_states.all()
+        self.fields['destination'].required = True
+        if self.instance.pk:
+            self.fields['states'].initial = [i.pk for i in self.instance.states.all()]
+        self.instance.workflow = workflow
+
+    def as_row(self):
+        return self._html_output(u'<td>%(errors)s%(field)s%(help_text)s</td>', u'<td colspan="2">%s</td>', '</td>', u'<br />%s', False)
+
+    def save(self, *args, **kwargs):
+        instance = super(TransitionForm, self).save(*args, **kwargs)
+        for state in self.cleaned_data['states']:
+            state.transitions.add(instance)
+
+
+class TransitionFormSet(BaseModelFormSet):
+
+    form = TransitionForm
+    can_delete = True
+    extra = 2
+    max_num = 0
+    can_order = False
+    model = Transition
+
+    def __init__(self, *args, **kwargs):
+        self.wg = kwargs.pop('wg', None)
+        self.user = kwargs.pop('user', None)
+        super(TransitionFormSet, self).__init__(*args, **kwargs)
+
+    def _construct_form(self, i, **kwargs):
+        kwargs = kwargs or {}
+        kwargs.update({'wg': self.wg, 'user': self.user})
+        return super(TransitionFormSet, self)._construct_form(i, **kwargs)
+
+    def as_table(self):
+        html = u''
+        csscl = 'oddrow'
+        for form in self.forms:
+            html += u'<tr class="%s">' % csscl
+            html += form.as_row()
+            html += u'</tr>'
+            if csscl == 'oddrow':
+                csscl = 'evenrow'
+            else:
+                csscl = 'oddrow'
+        return mark_safe(u'\n'.join([unicode(self.management_form), html]))
+
+
+def workflow_form_factory(request, wg, user):
+
+    if request.POST.get('update_transitions', None):
+        return TransitionFormSet(wg=wg, user=user, data=request.POST)
+    elif request.POST.get('update_states', None):
+        return StateForm(wg=wg, user=user, data=request.POST)
+    return TagForm(wg=wg, user=user, data=request.POST)
+
+
 class RemoveDelegateForm(RelatedWGForm):
 
     delete = forms.MultipleChoiceField()
@@ -148,7 +278,6 @@ class NotExistDelegateForm(MultipleDelegateForm):
                             from_email=settings.DEFAULT_FROM_EMAIL)
         mail.send()
 
-
     def send_email_to_delegate(self, email):
         self.send_email(email, 'wgchairs/notexistsdelegate_delegate_email.txt')
 
@@ -187,55 +316,52 @@ def add_form_factory(request, wg, user):
 class ManagingShepherdForm(forms.Form):
     email = forms.EmailField(required=False)
     is_assign_current = forms.BooleanField(required=False)
-    
-    
+
     def __init__(self, *args, **kwargs):
-        if kwargs.has_key('current_person'):
-            self.current_person = kwargs.pop('current_person')            
+        if 'current_person' in kwargs.keys():
+            self.current_person = kwargs.pop('current_person')
         return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
-    
+
     def clean_email(self):
         email = self.cleaned_data.get('email')
         if not email:
             return None
-        
+
         try:
             PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=[ "INET", "Prim",], 
-                        emailaddress__address=email) \
-                        [:1].get()
+                  filter(emailaddress__type__in=["INET", "Prim", ],
+                        emailaddress__address=email)[:1].get()
         except PersonOrOrgInfo.DoesNotExist:
             if self.cleaned_data.get('is_assign_current'):
                 self._send_email(email)
             raise forms.ValidationError("Person with such email does not exist")
         return email
-    
+
     def clean(self):
         print self.cleaned_data.get('email') and self.cleaned_data.get('is_assign_current')
         if self.cleaned_data.get('email') and \
                                     self.cleaned_data.get('is_assign_current'):
             raise forms.ValidationError("You should choose to assign to current \
                         person or input the email. Not both at te same time. ")
-        
+
         return self.cleaned_data
-    
+
     def change_shepherd(self, document, save=True):
-        email = self.cleaned_data.get('email')        
+        email = self.cleaned_data.get('email')
         if email:
             person = PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=[ "INET", "Prim",], 
-                        emailaddress__address=email) \
-                        [:1].get()
+                  filter(emailaddress__type__in=["INET", "Prim", ],
+                        emailaddress__address=email)[:1].get()
         else:
-            person = self.current_person        
-        document.shepherd = person 
-        if save: 
+            person = self.current_person
+        document.shepherd = person
+        if save:
             document.save()
         return document
-    
-    def _send_email(self, email, 
+
+    def _send_email(self, email,
                         template='wgchairs/edit_management_shepherd_email.txt'):
-        subject = 'WG Delegate needs system credentials'        
+        subject = 'WG Delegate needs system credentials'
         body = render_to_string(template,
                                 {'email': email,
                                 })
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index e2229aeab..25759d0cc 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -5,9 +5,13 @@ from django.http import HttpResponseForbidden
 
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
-                                 ManagingShepherdForm)
+                                 ManagingShepherdForm, workflow_form_factory,
+                                 TransitionFormSet)
 from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
-                                    can_manage_shepherds_in_group)
+                                    can_manage_shepherds_in_group,
+                                    can_manage_workflow_in_group)
+from ietf.ietfworkflows.utils import (get_workflow_for_wg,
+                                      get_default_workflow_for_wg)
 
 
 def manage_delegates(request, acronym):
@@ -34,6 +38,45 @@ def manage_delegates(request, acronym):
                               }, RequestContext(request))
 
 
+def manage_workflow(request, acronym):
+    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
+    user = request.user
+    if not can_manage_workflow_in_group(user, wg):
+        return HttpResponseForbidden('You have no permission to access this view')
+    workflow = get_workflow_for_wg(wg)
+    default_workflow = get_default_workflow_for_wg()
+    formset = None
+    if request.method == 'POST':
+        form = workflow_form_factory(request, wg=wg, user=user)
+        if form.is_valid():
+            form.save()
+        elif isinstance(form, TransitionFormSet):
+            formset = form
+    tags = workflow.selected_tags.all()
+    default_tags = default_workflow.annotation_tags.all()
+    states = workflow.selected_states.all()
+    default_states = default_workflow.states.all()
+    for i in default_states:
+        if states.filter(name=i.name).count() == 1:
+            i.used = True
+    for i in default_tags:
+        if tags.filter(name=i.name).count() == 1:
+            i.used = True
+    if not formset:
+        formset = TransitionFormSet(queryset=workflow.transitions.all(), user=user, wg=wg)
+
+    return render_to_response('wgchairs/manage_workflow.html',
+                              {'wg': wg,
+                               'workflow': workflow,
+                               'default_workflow': default_workflow,
+                               'states': states,
+                               'tags': tags,
+                               'default_states': default_states,
+                               'default_tags': default_tags,
+                               'formset': formset,
+                              }, RequestContext(request))
+
+
 def managing_shepherd(request, acronym, name):
     """
      View for managing the assigned shepherd of a document.
@@ -61,11 +104,11 @@ def wg_shepherd_documents(request, acronym):
         return HttpResponseForbidden('You have no permission to access this view')
     current_person = get_person_for_user(user)
 
-    form = SearchForm({'by':'group', 'group':str(wg.group_acronym.acronym),
-                       'activeDrafts':'on'})
+    form = SearchForm({'by': 'group', 'group': str(wg.group_acronym.acronym),
+                       'activeDrafts': 'on'})
     if not form.is_valid():
         raise ValueError("form did not validate")
-    (docs,meta) = search_query(form.cleaned_data)
+    (docs, meta) = search_query(form.cleaned_data)
 
     base_qs = InternetDraft.objects.filter(pk__in=[i.id._draft.pk for i in docs if i.id]).select_related('status')
     documents_no_shepherd = base_qs.filter(shepherd__isnull=True)

From 61697ed096eddfba55e4431c21fa313b877f0700 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 24 Dec 2010 13:38:34 +0000
Subject: [PATCH 27/57] Improve shepherd assignation by email.

  Check if multiple persons have the same email.
  If person exist assign him/her directly.
  If person doesn't exists inform by email: person, wg chairs and secretariat.
  Inform the user what's going on in each step.
See #558
 - Legacy-Id: 2734
---
 .../wgchairs/edit_management_shepherd.html    |  80 ++++++-------
 .../edit_management_shepherd_email.txt        |   1 -
 ietf/templates/wgchairs/manage_delegates.html |  66 ++++++-----
 ietf/templates/wgchairs/manage_workflow.html  |  11 +-
 .../notexistsdelegate_delegate_email.txt      |   2 +-
 .../notexistsdelegate_secretariat_email.txt   |   2 +-
 .../notexistsdelegate_wgchairs_email.txt      |   4 +-
 .../wgchairs/wg_shepherd_documents.html       | 107 ++++++++++++------
 .../wgchairs/wgchairs_admin_options.html      |   8 ++
 ietf/wgchairs/forms.py                        | 100 +++++-----------
 ietf/wgchairs/templatetags/wgchairs_tags.py   |   4 +-
 ietf/wgchairs/views.py                        |  36 ++++--
 12 files changed, 213 insertions(+), 208 deletions(-)
 delete mode 100644 ietf/templates/wgchairs/edit_management_shepherd_email.txt

diff --git a/ietf/templates/wgchairs/edit_management_shepherd.html b/ietf/templates/wgchairs/edit_management_shepherd.html
index 8b8007b52..372fea6c8 100644
--- a/ietf/templates/wgchairs/edit_management_shepherd.html
+++ b/ietf/templates/wgchairs/edit_management_shepherd.html
@@ -1,50 +1,40 @@
-{% extends "base.html" %}
-
-{% block title %}Edit info on {{ doc }}{% endblock %}
-
-{% block morecss %}
-form.edit-info #id_state_change_notice_to {
-  width: 600px;
-}
-form.edit-info #id_note {
-  width: 600px;
-  height: 150px;
-}
-form.edit-info .actions {
-  padding-top: 20px;
-}
-{% endblock %}
-
-{% block content %}
+{% extends "wginfo/wg_base.html" %}
 {% load ietf_filters %}
-<h1>Edit info on {{ doc }}</h1>
 
-Shepherd: {{ doc.shepherd }} 
+{% block title %}Chage shepherd for {{ doc }}{% endblock %}
 
-{{ form.non_field_errors }}
-<form class="edit-info" action="" method="POST">
-  <table>    
-    {% for field in form %}
-    <tr>
-      <th>{{ field.label_tag }}:</th>
-      <td>{{ field }}
-      {% ifequal field.name "telechat_date" %}{{ form.returning_item }} {{ form.returning_item.label_tag }} {{ form.returning_item.errors }}{% endifequal %}
-      {% ifequal field.name "job_owner" %}
-      {% if user|in_group:"Area_Director" %}
-      <label><input type="checkbox" name="job_owner" value="{{ login.id }}" /> Assign to me</label>
-      {% endif %}
-      {% endifequal %}      
-      {% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
-      {{ field.errors }}</td>
-    </tr>
-    {% endfor %}
-    <tr>
-      <td></td>
-      <td class="actions">
-        <a href="{{ doc.idinternal.get_absolute_url }}">Back</a>
-        <input type="submit" value="Save"/>
-      </td>
-    </tr>
-  </table>
+{% block wg_content %}
+
+<p>
+<a href="{% url manage_shepherds wg.group_acronym.acronym %}">Return to shepherd list</a>
+</p>
+
+<h1>Change shepherd for {{ doc }}</h1>
+
+<table style="width: 100%;"><tr style="vertical-align: top;"><td style="width: 50%;">
+<form action="" method="POST">
+<table class="ietf-table" style="width: 100%;">
+<tr><th>Actual shepherd</th></tr>
+<tr><td>{% if doc.shepherd %}{{ doc.shepherd }}{% else %}No shephered assigned{% endif %}</td></tr>
+</table>
+    <input type="hidden" name="update_shepherd" value="1" />
+    <input type="submit" name="remove_shepherd" value="Unassign shepherd" />
+    <input type="submit" name="setme" value="Set me as shepherd of this document" />
 </form>
+</td><td style="width: 50%;">
+<form action="" method="POST">
+  <table class="ietf-table" style="width: 100%;">    
+  <tr><th>Change shepherd</th></tr>
+{% if form.message %}
+<tr><td class="message message-{{ form.message.type }}">
+    {{ form.message.value }}
+</td></tr>
+{% endif %}
+
+  <tr><td>{{ form.as_p }}</td></tr>
+  </table>
+    <input type="submit" name="change_sheperd" value="Change shepherd" />
+    {% if form.can_cancel %}<a href="">No! I don't want to continue</a>{% endif %}
+</form>
+</td></tr></table>
 {% endblock %}
diff --git a/ietf/templates/wgchairs/edit_management_shepherd_email.txt b/ietf/templates/wgchairs/edit_management_shepherd_email.txt
deleted file mode 100644
index 09d7215c1..000000000
--- a/ietf/templates/wgchairs/edit_management_shepherd_email.txt
+++ /dev/null
@@ -1 +0,0 @@
-Designated person with email {{ email }} should get a user/password.
\ No newline at end of file
diff --git a/ietf/templates/wgchairs/manage_delegates.html b/ietf/templates/wgchairs/manage_delegates.html
index 8afd102f0..fb58fc0fe 100644
--- a/ietf/templates/wgchairs/manage_delegates.html
+++ b/ietf/templates/wgchairs/manage_delegates.html
@@ -1,26 +1,39 @@
 {% extends "wginfo/wg_base.html" %}
 
-{% block morecss %}
-{{ block.super }}
-.wg-chair-management ul {
-    list-style: none;
-}
-
-.wg-chair-management input {
-    border: 1px solid green;
-}
-{% endblock %}
 
 {% block wg_titledetail %}Delegates{% endblock %}
 
 {% block wg_content %}
 <div class="wg-chair-management">
-<h2>Add new delegate</h2>
-{% if add_form.message %}
-<div class="message message-{{ add_form.message.type }}">
-    {{ add_form.message.value }}
-</div>
+
+<h2>Manage delegates</h2>
+<p><strong>Please,</strong> rembember that you only can assign a maximum of three delegates.</p>
+
+<table style="width: 100%;">
+<tr style="vertical-align: top;"><td>
+{% if delegates %}
+  <form action="" method="POST">
+  <table class="ietf-table" style="width: 100%">
+    <tr><th>Remove</th><th style="Width: 100%">Delegate name</th></tr>
+  {% for delegate in delegates %}
+    <tr class="{% cycle "oddrow" "evenrow" %}"><td><input type="checkbox" name="delete" value="{{ delegate.pk }}" /></td><td>{{ delegate.person }}</td></tr>
+  {% endfor %}
+  </table>
+  <input type="submit" value="Remove delegate(s)" name="remove" />
+  </form>
+{% else %}
+No delegates
 {% endif %}
+</td>
+<td>
+<table class="ietf-table" style="width: 100%;">
+<tr><th>Add new delegate</th></tr>
+{% if add_form.message %}
+<tr><td class="message message-{{ add_form.message.type }}">
+    {{ add_form.message.value }}
+</td></tr>
+{% endif %}
+<tr><td>
 {% if can_add %}
   <form action="" method="POST">
     {{ add_form.as_p }}
@@ -30,26 +43,11 @@
     </p>
   </form>
 {% else %}
-<p>
 You can only assign three delegates. Please remove delegates to add a new one.
-</p>
-{% endif %}
-
-<h2>Delegates</h2>
-{% if delegates %}
-  <form action="" method="POST">
-  <table>
-    <tr><th>Remove</th><th>Delegate name</th></tr>
-  {% for delegate in delegates %}
-    <tr><td><input type="checkbox" name="delete" value="{{ delegate.pk }}" /></td><td>{{ delegate.person }}</td></tr>
-  {% endfor %}
-  </table>
-  <input type="submit" value="Remove delegate(s)" name="remove" />
-  </form>
-{% else %}
-<p>
-No delegates
-</p>
 {% endif %}
+</td></tr>
+</table>
+</td></tr>
+</table>
 </div>
 {% endblock %}
diff --git a/ietf/templates/wgchairs/manage_workflow.html b/ietf/templates/wgchairs/manage_workflow.html
index ade831bea..5ac578ffd 100644
--- a/ietf/templates/wgchairs/manage_workflow.html
+++ b/ietf/templates/wgchairs/manage_workflow.html
@@ -2,6 +2,13 @@
 
 {% block wg_titledetail %}Manage Workflow{% endblock %}
 
+{% block pagehead %}
+{{ block.super }}
+<script type="text/javascript" src="/js/lib/jquery-1.4.2.min.js"></script>
+<script type="text/javascript" src="/js/yui/yui-20100305.js"></script>
+<script type="text/javascript" src="/js/base.js"></script>
+{% endblock pagehead %}
+
 {% block wg_content %}
 <div class="wg-workflow-management">
 <h2>Edit workflow</h2>
@@ -111,10 +118,6 @@
 </div>
 </div>
 
-<script type="text/javascript" src="/js/lib/jquery-1.4.2.min.js"></script>
-<script type="text/javascript" src="/js/yui/yui-20100305.js"></script>
-<script type="text/javascript" src="/js/base.js"></script>
-
 <script type="text/javascript">
 //<![CDATA[
 var tabView = new YAHOO.widget.TabView('mytabs');
diff --git a/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt b/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt
index a79680663..74ae07117 100644
--- a/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt
+++ b/ietf/templates/wgchairs/notexistsdelegate_delegate_email.txt
@@ -1,4 +1,4 @@
-{{ chair }} as a WG Chair of {{ wg }} wants to add you as a {{ wg }} WG Delegate.
+{{ chair }} as a WG Chair of {{ wg }} wants to add you as a {{ wg }} {% if shepherd %}shepherd of document {{ shepherd }}{% else %}WG Delegate{% endif %}.
 
 You don't have an user/password to log into the datatracker so you must contact
 the Secretariat at iesg-secretary@ietf.org in order to get your credentials.
diff --git a/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt b/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt
index d3d2b3d91..6f2e59fac 100644
--- a/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt
+++ b/ietf/templates/wgchairs/notexistsdelegate_secretariat_email.txt
@@ -1,5 +1,5 @@
 {{ chair }} as a WG Chair of {{ wg }} wants to add a person with email
-{{ delegate_email }} as a WG Delegate.
+{{ delegate_email }} as a {% if shepherd %}shepherd of document {{ shepherd }}{% else %}WG Delegate{% endif %}.
 
 This person don't have an user/password to log into the datatracker so
 an email has been seent to {{ delegate_email }} in order to he/she contacs the
diff --git a/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt b/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt
index fc9a19022..e47c161b5 100644
--- a/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt
+++ b/ietf/templates/wgchairs/notexistsdelegate_wgchairs_email.txt
@@ -1,5 +1,5 @@
 {{ chair }} as a WG Chair of {{ wg }} wants to add a person with email
-{{ delegate_email }} as a WG Delegate.
+{{ delegate_email }} as a {% if shepherd %}shepherd of document {{ shepherd }}{% else %}WG Delegate{% endif %}.
 
 This person don't have an user/password to log into the datatracker so
 an email has been seent to {{ delegate_email }} in order to he/she contacs the
@@ -8,4 +8,4 @@ Secretariat to request his/her credentials.
 When he/she gets her credentials then he/she will send an email to 
 {{ chair }} at {{ chair.email.1 }}. 
 
-{{ chair }} could then assign this person as WG Delegate.
+{{ chair }} could then assign this person as {% if shepherd %}shepherd of document {{ shepherd }}{% else %}WG Delegate{% endif %}.
diff --git a/ietf/templates/wgchairs/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
index 8d0487afa..468970da2 100644
--- a/ietf/templates/wgchairs/wg_shepherd_documents.html
+++ b/ietf/templates/wgchairs/wg_shepherd_documents.html
@@ -34,37 +34,28 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 {% endcomment %}
 {% block wg_titledetail %}Documents{% endblock %}
 
+{% block pagehead %}
+{{ block.super }}
+<script type="text/javascript" src="/js/lib/jquery-1.4.2.min.js"></script>
+<script type="text/javascript" src="/js/yui/yui-20100305.js"></script>
+<script type="text/javascript" src="/js/base.js"></script>
+{% endblock pagehead %}
+
 {% block wg_content %}
            
-<table class="ietf-table ietf-doctable" style="margin-top:16px;">
-    <tr>       
-       <th class="title">Documents I shepherd</th>
-       <th class="date">Date</th>
-       <th class="status">Status</th>
-       <th class="ad">Area Director</th>
-    </tr> 
-    
-      {% for doc in my_documents %}
-        <tr class="{% cycle oddrow,evenrow %}">
-          <td class="title">
-            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
-          </td>
-          <td class="date">
-             {{ doc.status.start_date|date:"Y-m" }}
-          </td>
-          <td class="status">
-              {{ doc.status.status }}
-          </td>        
-          <td class="ad">{{ doc.ad_name|default:"" }}</td>
-          
-          </tr>
-      {% endfor %}
-        
-</table>
+<h2>Documets by its shepherd</h2>
+<div id="mytabs" class="yui-navset">
+<ul class="yui-nav">
+  <li class="selected"><a href="#noshepherd"><em>Without shepherd</em></a></li>
+  <li><a href="#mydocs"><em>I Shepherd</em></a></li>
+  <li><a href="#othershepherds"><em>Other shepherds</em></a></li>
+</ul>
 
-<table class="ietf-table ietf-doctable" style="margin-top:16px;">
+<div class="yui-content">
+<div id="noshepherd">
+<table class="ietf-table ietf-doctable" style="margin-top:16px; width: 100%;">
     <tr>       
-       <th class="title">Documents whithout shepherd</th>
+       <th class="title">Document</th>
        <th class="date">Date</th>
        <th class="status">Status</th>
        <th class="ad">Area Director</th>
@@ -87,21 +78,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       {% endfor %}
         
 </table>
+</div>
 
-<table class="ietf-table ietf-doctable" style="margin-top:16px;">
+<div id="mydocs">
+<table class="ietf-table ietf-doctable" style="margin-top:16px; width: 100%;">
     <tr>       
-       <th class="title">Documents by other shepherds</th>
+       <th class="title">Document</th>
        <th class="date">Date</th>
        <th class="status">Status</th>
        <th class="ad">Area Director</th>
     </tr> 
     
-      {% regroup other_shepherds by shepherd as regrouped %}
-      {% for documents in regrouped %}
-
-        <tr class="header"><td colspan="4">{{ documents.grouper }}</td></tr>
-
-        {% for doc in documents.list %}
+      {% for doc in my_documents %}
         <tr class="{% cycle oddrow,evenrow %}">
           <td class="title">
             <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
@@ -115,8 +103,55 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
           <td class="ad">{{ doc.ad_name|default:"" }}</td>
           
           </tr>
-        {% endfor %}
       {% endfor %}
+        
 </table>
+</div>
+
+<div id="othershepherds">
+{% regroup other_shepherds by shepherd as regrouped %}
+{% for documents in regrouped %}
+  <h3 style="margin-bottom: 0px;">{{ documents.grouper }}</h3>
+  <table class="ietf-table ietf-doctable" style="width: 100%;">
+    <tr>       
+       <th class="title">Document</th>
+       <th class="date">Date</th>
+       <th class="status">Status</th>
+       <th class="ad">Area Director</th>
+    </tr> 
+    {% for doc in documents.list %}
+    <tr class="{% cycle oddrow,evenrow %}">
+      <td class="title">
+        <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
+      </td>
+      <td class="date">
+         {{ doc.status.start_date|date:"Y-m" }}
+      </td>
+      <td class="status">
+          {{ doc.status.status }}
+      </td>        
+      <td class="ad">{{ doc.ad_name|default:"" }}</td>
+    </tr>
+    {% endfor %}
+  </table>
+{% endfor %}
+</div>
+
+<script type="text/javascript">
+//<![CDATA[
+var tabView = new YAHOO.widget.TabView('mytabs');
+var url = location.href.split('#'); 
+if (url[1]) { 
+  url[1] = "#"+url[1];
+  var tabs = tabView.get('tabs'); 
+  for (var i = 0; i < tabs.length; i++) { 
+    if (url[1].indexOf(tabs[i].get('href')) == 0) {
+      tabView.set('activeIndex', i);
+      break; 
+     } 
+  } 
+} 
+//]]>
+</script>
 {% endblock wg_content %}
 
diff --git a/ietf/templates/wgchairs/wgchairs_admin_options.html b/ietf/templates/wgchairs/wgchairs_admin_options.html
index 5a8501839..7df492513 100644
--- a/ietf/templates/wgchairs/wgchairs_admin_options.html
+++ b/ietf/templates/wgchairs/wgchairs_admin_options.html
@@ -13,3 +13,11 @@
     <a href="{% url manage_delegates wg.group_acronym.acronym %}">Manage delegates</a>
   {% endifequal %} |
 {% endif %}
+
+{% if can_manage_shepherds %}
+  {% ifequal selected "manage_shepherds" %}
+    <span class="selected">Manage shepherds</span>
+  {% else %}
+    <a href="{% url manage_shepherds wg.group_acronym.acronym %}">Manage shepherds</a>
+  {% endifequal %} |
+{% endif %}
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 15c534698..19b4965c5 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -178,6 +178,7 @@ class AddDelegateForm(RelatedWGForm):
     form_type = forms.CharField(widget=forms.HiddenInput, initial='single')
 
     def __init__(self, *args, **kwargs):
+        self.shepherd = kwargs.pop('shepherd', False)
         super(AddDelegateForm, self).__init__(*args, **kwargs)
         self.next_form = self
 
@@ -197,14 +198,23 @@ class AddDelegateForm(RelatedWGForm):
         try:
             person = self.get_person(email)
         except PersonOrOrgInfo.DoesNotExist:
-            self.next_form = NotExistDelegateForm(wg=self.wg, user=self.user, email=email)
+            self.next_form = NotExistDelegateForm(wg=self.wg, user=self.user, email=email, shepherd=self.shepherd)
             self.next_form.set_message('doesnotexist', 'There is no user with this email allowed to login to the system')
             return
         except PersonOrOrgInfo.MultipleObjectsReturned:
-            self.next_form = MultipleDelegateForm(wg=self.wg, user=self.user, email=email)
+            self.next_form = MultipleDelegateForm(wg=self.wg, user=self.user, email=email, shepherd=self.shepherd)
             self.next_form.set_message('multiple', 'There are multiple users with this email in the system')
             return
-        self.create_delegate(person)
+        if self.shepherd:
+            self.assign_shepherd(person)
+        else:
+            self.create_delegate(person)
+
+    def assign_shepherd(self, person):
+        self.shepherd.shepherd = person
+        self.shepherd.save()
+        self.next_form = AddDelegateForm(wg=self.wg, user=self.user, shepherd=self.shepherd)
+        self.next_form.set_message('success', 'Shepherd assigned successfully')
 
     def create_delegate(self, person):
         (delegate, created) = WGDelegate.objects.get_or_create(wg=self.wg,
@@ -234,7 +244,10 @@ class MultipleDelegateForm(AddDelegateForm):
     def save(self):
         person_id = self.cleaned_data.get('persons')
         person = PersonOrOrgInfo.objects.get(pk=person_id)
-        self.create_delegate(person)
+        if self.shepherd:
+            self.assign_shepherd(person)
+        else:
+            self.create_delegate(person)
 
 
 class NotExistDelegateForm(MultipleDelegateForm):
@@ -264,11 +277,15 @@ class NotExistDelegateForm(MultipleDelegateForm):
         return info + super(NotExistDelegateForm, self).as_p()
 
     def send_email(self, email, template):
-        subject = 'WG Delegate needs system credentials'
+        if self.shepherd:
+            subject = 'WG shepherd needs system credentials'
+        else:
+            subject = 'WG Delegate needs system credentials'
         persons = PersonOrOrgInfo.objects.filter(emailaddress__address=self.email).distinct()
         body = render_to_string(template,
                                 {'chair': get_person_for_user(self.user),
                                  'delegate_email': self.email,
+                                 'shepherd': self.shepherd,
                                  'delegate_persons': persons,
                                  'wg': self.wg,
                                 })
@@ -299,74 +316,15 @@ class NotExistDelegateForm(MultipleDelegateForm):
             self.next_form.set_message('success', 'Email sent successfully')
 
 
-def add_form_factory(request, wg, user):
-    if request.method != 'POST':
-        return AddDelegateForm(wg=wg, user=user)
+def add_form_factory(request, wg, user, shepherd=False):
+    if request.method != 'POST' or request.POST.get('update_shepehrd'):
+        return AddDelegateForm(wg=wg, user=user, shepherd=shepherd)
 
     if request.POST.get('form_type', None) == 'multiple':
-        return MultipleDelegateForm(wg=wg, user=user, data=request.POST.copy())
+        return MultipleDelegateForm(wg=wg, user=user, data=request.POST.copy(), shepherd=shepherd)
     elif request.POST.get('form_type', None) == 'notexist':
-        return NotExistDelegateForm(wg=wg, user=user, data=request.POST.copy())
+        return NotExistDelegateForm(wg=wg, user=user, data=request.POST.copy(), shepherd=shepherd)
     elif request.POST.get('form_type', None) == 'single':
-        return AddDelegateForm(wg=wg, user=user, data=request.POST.copy())
+        return AddDelegateForm(wg=wg, user=user, data=request.POST.copy(), shepherd=shepherd)
 
-    return AddDelegateForm(wg=wg, user=user)
-
-
-class ManagingShepherdForm(forms.Form):
-    email = forms.EmailField(required=False)
-    is_assign_current = forms.BooleanField(required=False)
-
-    def __init__(self, *args, **kwargs):
-        if 'current_person' in kwargs.keys():
-            self.current_person = kwargs.pop('current_person')
-        return super(ManagingShepherdForm, self).__init__(*args, **kwargs)
-
-    def clean_email(self):
-        email = self.cleaned_data.get('email')
-        if not email:
-            return None
-
-        try:
-            PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=["INET", "Prim", ],
-                        emailaddress__address=email)[:1].get()
-        except PersonOrOrgInfo.DoesNotExist:
-            if self.cleaned_data.get('is_assign_current'):
-                self._send_email(email)
-            raise forms.ValidationError("Person with such email does not exist")
-        return email
-
-    def clean(self):
-        print self.cleaned_data.get('email') and self.cleaned_data.get('is_assign_current')
-        if self.cleaned_data.get('email') and \
-                                    self.cleaned_data.get('is_assign_current'):
-            raise forms.ValidationError("You should choose to assign to current \
-                        person or input the email. Not both at te same time. ")
-
-        return self.cleaned_data
-
-    def change_shepherd(self, document, save=True):
-        email = self.cleaned_data.get('email')
-        if email:
-            person = PersonOrOrgInfo.objects. \
-                  filter(emailaddress__type__in=["INET", "Prim", ],
-                        emailaddress__address=email)[:1].get()
-        else:
-            person = self.current_person
-        document.shepherd = person
-        if save:
-            document.save()
-        return document
-
-    def _send_email(self, email,
-                        template='wgchairs/edit_management_shepherd_email.txt'):
-        subject = 'WG Delegate needs system credentials'
-        body = render_to_string(template,
-                                {'email': email,
-                                })
-        mail = EmailMessage(subject=subject,
-                            body=body,
-                            to=[email, settings.DEFAULT_FROM_EMAIL, ],
-                            from_email=settings.DEFAULT_FROM_EMAIL)
-        mail.send()
+    return AddDelegateForm(wg=wg, user=user, shepherd=shepherd)
diff --git a/ietf/wgchairs/templatetags/wgchairs_tags.py b/ietf/wgchairs/templatetags/wgchairs_tags.py
index abe633974..8cf9d9783 100644
--- a/ietf/wgchairs/templatetags/wgchairs_tags.py
+++ b/ietf/wgchairs/templatetags/wgchairs_tags.py
@@ -1,7 +1,8 @@
 from django import template
 
 from ietf.wgchairs.accounts import (can_manage_workflow_in_group,
-                                    can_manage_delegates_in_group)
+                                    can_manage_delegates_in_group,
+                                    can_manage_shepherds_in_group)
 
 
 register = template.Library()
@@ -14,6 +15,7 @@ def wgchairs_admin_options(context, wg):
     return {'user': user,
             'can_manage_delegates': can_manage_delegates_in_group(user, wg),
             'can_manage_workflow': can_manage_workflow_in_group(user, wg),
+            'can_manage_shepherds': can_manage_shepherds_in_group(user, wg),
             'wg': wg,
             'selected': context.get('selected', None),
            }
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index 25759d0cc..f147c1289 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -5,8 +5,7 @@ from django.http import HttpResponseForbidden
 
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
-                                 ManagingShepherdForm, workflow_form_factory,
-                                 TransitionFormSet)
+                                 workflow_form_factory, TransitionFormSet)
 from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
                                     can_manage_shepherds_in_group,
                                     can_manage_workflow_in_group)
@@ -74,6 +73,7 @@ def manage_workflow(request, acronym):
                                'default_states': default_states,
                                'default_tags': default_tags,
                                'formset': formset,
+                               'selected': 'manage_workflow',
                               }, RequestContext(request))
 
 
@@ -81,19 +81,30 @@ def managing_shepherd(request, acronym, name):
     """
      View for managing the assigned shepherd of a document.
     """
+    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
+    user = request.user
+    person = get_person_for_user(user)
+    if not can_manage_shepherds_in_group(user, wg):
+        return HttpResponseForbidden('You have no permission to access this view')
     doc = get_object_or_404(InternetDraft, filename=name)
-    login = IESGLogin.objects.get(login_name=request.user.username)
-    form = ManagingShepherdForm()
-    if request.method == "POST":
-        form = ManagingShepherdForm(request.POST, current_person=login.person)
-        if form.is_valid():
-            form.change_shepherd(doc)
-
+    add_form = add_form_factory(request, wg, user, shepherd=doc)
+    if request.method == 'POST':
+        if request.POST.get('remove_shepherd'):
+            doc.shepherd = None
+            doc.save()
+        elif request.POST.get('setme'):
+            doc.shepherd = person
+            doc.save()
+        elif add_form.is_valid():
+            add_form.save()
+            add_form = add_form.get_next_form()
     return render_to_response('wgchairs/edit_management_shepherd.html',
                               dict(doc=doc,
-                                   form=form,
-                                   user=request.user,
-                                   login=login),
+                                   form=add_form,
+                                   user=user,
+                                   selected='manage_shepherds',
+                                   wg=wg,
+                                   ),
                               context_instance=RequestContext(request))
 
 
@@ -118,6 +129,7 @@ def wg_shepherd_documents(request, acronym):
         'no_shepherd': documents_no_shepherd,
         'my_documents': documents_my,
         'other_shepherds': documents_other,
+        'selected': 'manage_shepherds',
         'wg': wg,
     }
     return render_to_response('wgchairs/wg_shepherd_documents.html', context, RequestContext(request))

From 6729180833b4d3ddf7c0cc37d840c1b15a583568 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 24 Dec 2010 13:50:09 +0000
Subject: [PATCH 28/57] Do not allow access to other wg documents. Fixes #558 
 - Legacy-Id: 2735

---
 ietf/wgchairs/accounts.py | 9 ++++++++-
 ietf/wgchairs/views.py    | 7 +++++--
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
index 6177cbca0..2f751c00a 100644
--- a/ietf/wgchairs/accounts.py
+++ b/ietf/wgchairs/accounts.py
@@ -22,7 +22,7 @@ def can_do_wg_workflow_in_document(user, document):
     person = get_person_for_user(user)
     if not person or not document.group:
         return False
-    return can_do_wg_workflow_in_group(document.group)
+    return can_do_wg_workflow_in_group(document.group.ietfwg)
 
 
 def can_manage_workflow_in_group(user, group):
@@ -44,3 +44,10 @@ def can_manage_shepherds_in_group(user, group):
     if not person:
         return False
     return is_group_chair(person, group)
+
+
+def can_manage_shepherd_of_a_document(user, document):
+    person = get_person_for_user(user)
+    if not person or not document.group:
+        return False
+    return can_manage_shepherds_in_group(user, document.group.ietfwg)
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index f147c1289..ce353e511 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -1,14 +1,15 @@
 from ietf.idtracker.models import IETFWG, InternetDraft, IESGLogin
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
-from django.http import HttpResponseForbidden
+from django.http import HttpResponseForbidden, Http404
 
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
                                  workflow_form_factory, TransitionFormSet)
 from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
                                     can_manage_shepherds_in_group,
-                                    can_manage_workflow_in_group)
+                                    can_manage_workflow_in_group,
+                                    can_manage_shepherd_of_a_document)
 from ietf.ietfworkflows.utils import (get_workflow_for_wg,
                                       get_default_workflow_for_wg)
 
@@ -87,6 +88,8 @@ def managing_shepherd(request, acronym, name):
     if not can_manage_shepherds_in_group(user, wg):
         return HttpResponseForbidden('You have no permission to access this view')
     doc = get_object_or_404(InternetDraft, filename=name)
+    if not can_manage_shepherd_of_a_document(user, doc):
+        raise Http404
     add_form = add_form_factory(request, wg, user, shepherd=doc)
     if request.method == 'POST':
         if request.POST.get('remove_shepherd'):

From 6afd71b6283a03e14050e1fcfc68d255c62095b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 18:16:57 +0000
Subject: [PATCH 29/57] Util functions to get workflows, states and annotation
 tags for a draft. See #543  - Legacy-Id: 2743

---
 ietf/ietfworkflows/utils.py | 96 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 94 insertions(+), 2 deletions(-)

diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 5096d7bb9..4dff8d10b 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -1,8 +1,18 @@
 import copy
+import datetime
 
-from workflows.utils import get_workflow_for_object, set_workflow_for_object
+from django.contrib.contenttypes.models import ContentType
 
-from ietf.ietfworkflows.models import WGWorkflow
+from workflows.models import State
+from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
+                             get_state)
+
+from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
+                                       AnnotationTag, ObjectAnnotationTagHistoryEntry)
+
+
+WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
+FOLLOWUP_TAG = 'Doc Shepherd Follow-up Underway'
 
 
 def get_default_workflow_for_wg():
@@ -51,3 +61,85 @@ def get_workflow_for_wg(wg):
         workflow = clone_workflow(workflow, name='%s workflow' % wg)
         set_workflow_for_object(wg, workflow)
     return workflow
+
+def get_workflow_for_draft(draft):
+    workflow = get_workflow_for_object(draft)
+    try:
+        workflow = workflow and workflow.wgworkflow
+    except WGWorkflow.DoesNotExist:
+        workflow = None
+    if not workflow:
+        workflow = get_workflow_for_wg(draft.group.ietfwg)
+        set_workflow_for_object(draft, workflow)
+    return workflow
+
+
+def get_annotation_tags_for_draft(draft):
+    ctype = ContentType.objects.get_for_model(draft)
+    tags = AnnotationTagObjectRelation.objects.filter(content_type=ctype, content_id=draft.pk)
+    return tags
+
+
+def get_state_for_draft(draft):
+    return get_state(draft)
+
+
+def get_state_by_name(state_name):
+    try:
+        return State.objects.get(name=state_name)
+    except State.DoesNotExist:
+        return None
+
+
+def get_annotation_tag_by_name(tag_name):
+    try:
+        return AnnotationTag.objects.get(name=tag_name)
+    except AnnotationTag.DoesNotExist:
+        return None
+
+def set_tag_by_name(obj, tag_name):
+    ctype = ContentType.objects.get_for_model(obj)
+    try:
+        tag = AnnotationTag.objects.get(name=tag_name)
+        (relation, created) = AnnotationTagObjectRelation.objects.get_or_create(
+            content_type=ctype,
+            content_id=obj.pk,
+            annotation_tag=tag)
+    except AnnotationTag.DoesNotExist:
+        return None
+    return relation
+
+
+def reset_tag_by_name(obj, tag_name):
+    ctype = ContentType.objects.get_for_model(obj)
+    try:
+        tag = AnnotationTag.objects.get(name=tag_name)
+        tag_relation = AnnotationTagObjectRelation.objects.get(
+            content_type=ctype,
+            content_id=obj.pk,
+            annotation_tag=tag)
+        tag_relation.delete()
+        return True
+    except AnnotationTagObjectRelation.DoesNotExist:
+        return False
+    except AnnotationTag.DoesNotExist:
+        return False
+
+
+def update_tags(obj, comment, set_tags=[], reset_tags=[]):
+    ctype = ContentType.objects.get_for_model(obj)
+    setted = []
+    resetted = []
+    for name in set_tags:
+        if set_tag_by_name(obj, name):
+            setted.append(name)
+    for name in reset_tags:
+        if reset_tag_by_name(obj, name):
+            resetted.append(name)
+    ObjectAnnotationTagHistoryEntry.objects.create(
+        content_type=ctype,
+        content_id=obj.pk,
+        setted = ','.join(setted),
+        unsetted = ','.join(resetted),
+        change_date = datetime.datetime.now(),
+        comment = comment)

From d0e9d9515050b6985d88365506cf0f9063f84b60 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 18:18:59 +0000
Subject: [PATCH 30/57] Add protocol writeup model. See #561  - Legacy-Id: 2744

---
 ietf/wgchairs/migrations/0002_add_writeup.py | 163 +++++++++++++++++++
 ietf/wgchairs/models.py                      |  30 +++-
 2 files changed, 192 insertions(+), 1 deletion(-)
 create mode 100644 ietf/wgchairs/migrations/0002_add_writeup.py

diff --git a/ietf/wgchairs/migrations/0002_add_writeup.py b/ietf/wgchairs/migrations/0002_add_writeup.py
new file mode 100644
index 000000000..e1754ed22
--- /dev/null
+++ b/ietf/wgchairs/migrations/0002_add_writeup.py
@@ -0,0 +1,163 @@
+
+from south.db import db
+from django.db import models
+from ietf.wgchairs.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'ProtoWriteUp'
+        db.create_table('wgchairs_protowriteup', (
+            ('id', orm['wgchairs.protowriteup:id']),
+            ('person', orm['wgchairs.protowriteup:person']),
+            ('draft', orm['wgchairs.protowriteup:draft']),
+            ('date', orm['wgchairs.protowriteup:date']),
+            ('writeup', orm['wgchairs.protowriteup:writeup']),
+        ))
+        db.send_create_signal('wgchairs', ['ProtoWriteUp'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'ProtoWriteUp'
+        db.delete_table('wgchairs_protowriteup')
+        
+    
+    
+    models = {
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.area': {
+            'Meta': {'db_table': "'areas'"},
+            'area_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'extra_email_addresses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaStatus']"})
+        },
+        'idtracker.areadirector': {
+            'Meta': {'db_table': "'area_directors'"},
+            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Area']", 'null': 'True', 'db_column': "'area_acronym_id'"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
+        },
+        'idtracker.areastatus': {
+            'Meta': {'db_table': "'area_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.ietfwg': {
+            'Meta': {'db_table': "'groups_ietf'"},
+            'area_director': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.AreaDirector']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'concluded_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'dormant_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'email_address': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
+            'email_archive': ('django.db.models.fields.CharField', [], {'max_length': '95', 'blank': 'True'}),
+            'email_keyword': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'email_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}),
+            'group_acronym': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.Acronym']", 'unique': 'True', 'primary_key': 'True'}),
+            'group_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGType']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'meeting_scheduled': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'meeting_scheduled_old': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
+            'proposed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.WGStatus']"})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.wgstatus': {
+            'Meta': {'db_table': "'g_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.wgtype': {
+            'Meta': {'db_table': "'g_type'"},
+            'group_type_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'group_type'"})
+        },
+        'wgchairs.protowriteup': {
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now()'}),
+            'draft': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.InternetDraft']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'writeup': ('django.db.models.fields.TextField', [], {})
+        },
+        'wgchairs.wgdelegate': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'wg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IETFWG']"})
+        }
+    }
+    
+    complete_apps = ['wgchairs']
diff --git a/ietf/wgchairs/models.py b/ietf/wgchairs/models.py
index fd32f7908..b40748908 100644
--- a/ietf/wgchairs/models.py
+++ b/ietf/wgchairs/models.py
@@ -1,6 +1,9 @@
+import datetime
+
 from django.db import models
 
-from ietf.idtracker.models import IETFWG, PersonOrOrgInfo
+from ietf.idtracker.models import (IETFWG, PersonOrOrgInfo,
+                                   InternetDraft)
 
 
 class WGDelegate(models.Model):
@@ -15,3 +18,28 @@ class WGDelegate(models.Model):
 
     class Meta:
         verbose_name = "WG Delegate"
+
+
+class ProtoWriteUp(models.Model):
+    person = models.ForeignKey(
+        PersonOrOrgInfo,
+        blank=False,
+        null=False,
+        )
+
+    draft = models.ForeignKey(
+        InternetDraft,
+        blank=False,
+        null=False,
+        )
+
+    date = models.DateTimeField(
+        default=datetime.datetime.now(),
+        blank=False,
+        null=False,
+        )
+
+    writeup = models.TextField(
+        blank = False,
+        null = False,
+        )

From 242796253e245f16b90feeb6a1f40396b69a4e04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 18:20:06 +0000
Subject: [PATCH 31/57] Form to edit or upload a protocol write-up. See #561  -
 Legacy-Id: 2745

---
 .../wgchairs/confirm_management_writeup.html  | 55 ++++++++++++++
 ietf/templates/wgchairs/draft_state.html      |  1 +
 .../wgchairs/edit_management_writeup.html     | 65 ++++++++++++++++
 .../wgchairs/shepherd_document_row.html       | 17 +++++
 .../wgchairs/wg_shepherd_documents.html       | 53 +++----------
 ietf/wgchairs/accounts.py                     | 31 ++++++++
 ietf/wgchairs/forms.py                        | 50 +++++++++++-
 ietf/wgchairs/templatetags/wgchairs_tags.py   | 25 ++++++
 ietf/wgchairs/urls.py                         |  1 +
 ietf/wgchairs/views.py                        | 76 ++++++++++++++++++-
 10 files changed, 325 insertions(+), 49 deletions(-)
 create mode 100644 ietf/templates/wgchairs/confirm_management_writeup.html
 create mode 100644 ietf/templates/wgchairs/draft_state.html
 create mode 100644 ietf/templates/wgchairs/edit_management_writeup.html
 create mode 100644 ietf/templates/wgchairs/shepherd_document_row.html

diff --git a/ietf/templates/wgchairs/confirm_management_writeup.html b/ietf/templates/wgchairs/confirm_management_writeup.html
new file mode 100644
index 000000000..ae23f4a97
--- /dev/null
+++ b/ietf/templates/wgchairs/confirm_management_writeup.html
@@ -0,0 +1,55 @@
+{% extends "wginfo/wg_base.html" %}
+{% load ietf_filters wgchairs_tags %}
+
+{% block title %}Chage shepherd for {{ doc }}{% endblock %}
+
+{% block wg_content %}
+
+<p>
+<a href="{% url manage_shepherds wg.group_acronym.acronym %}">Return to shepherd list</a>
+</p>
+
+<h1>Updatting write-up for {{ doc }}</h1>
+
+<p>
+Before you modify the protocol write-up <strong>please revise the 'Doc Shepherd Follow-up Underway' annotation tag and set or reset it if appropriate</strong>. 
+</p>
+<p>
+Remember that you must provide a comment if you change the annotation tag state.
+</p>
+
+<form action="" method="POST">
+<table style="width: 100%;"><tr style="vertical-align: top;"><td style="width: 50%;">
+  <table class="ietf-table" style="width: 100%;">    
+  <tr><th>Doc Shepherd Follow-up Underway</th></tr>
+{% if form.message %}
+<tr><td class="message message-{{ form.message.type }}">
+    {{ form.message.value }}
+</td></tr>
+{% endif %}
+
+  <tr><td>
+<input type="hidden" value="{{ form.get_writeup }}" name="writeup" />
+<input type="hidden" value="confirm" name="confirm" />
+<input type="checkbox" name="followup" id="followup_id" value="1"{% if followup %} checked="checked"{% endif %} />
+<label for="followup_id">Doc Shepherd Follow-up Underway</label>
+</td></tr><tr><td>
+{{ form.comment }}
+</td></tr>
+  </table>
+  <p>
+  <strong>Change write-up and ...</strong><br />
+  <input type="submit" name="modify_tag" value="Modify 'Doc Shepherd Follow-up Underway'" />
+  <input type="submit" name="change_writeup" value="Leave 'Doc Shepherd Follow-up Underway' untouched" /><br />
+    <a href="">Cancel, I don't want to do any change!</a>
+  </p>
+
+</td><td style="width: 50%;">
+<table class="ietf-table" style="width: 100%;">
+<tr><th>New protocol write-up</th></tr>
+<tr style="vertical-align: top;"><td>{{ form.get_writeup|linebreaksbr }}</td></tr>
+</table>
+
+</td></tr></table>
+</form>
+{% endblock %}
diff --git a/ietf/templates/wgchairs/draft_state.html b/ietf/templates/wgchairs/draft_state.html
new file mode 100644
index 000000000..956ebd44f
--- /dev/null
+++ b/ietf/templates/wgchairs/draft_state.html
@@ -0,0 +1 @@
+{{ state.name }}
diff --git a/ietf/templates/wgchairs/edit_management_writeup.html b/ietf/templates/wgchairs/edit_management_writeup.html
new file mode 100644
index 000000000..3f8cc801b
--- /dev/null
+++ b/ietf/templates/wgchairs/edit_management_writeup.html
@@ -0,0 +1,65 @@
+{% extends "wginfo/wg_base.html" %}
+{% load ietf_filters wgchairs_tags %}
+
+{% block title %}Chage shepherd for {{ doc }}{% endblock %}
+
+{% block wg_content %}
+
+<p>
+<a href="{% url manage_shepherds wg.group_acronym.acronym %}">Return to shepherd list</a>
+</p>
+
+<h1>Change protocol write-up for {{ doc }}</h1>
+
+<table class="ietf-table" style="width: 100%;">
+<tr><th>Draft state</th><th>Actual protocol write-up</th><th>Last updated</th></tr>
+<tr style="vertical-align: top;"><td>{% show_state doc %}</td><td>{{ writeup.writeup|linebreaksbr }}</td><td>{% if writeup %}{{ writeup.date }} by {{ writeup.person }}{% endif %}</td></tr>
+</table>
+
+<p>
+Please, <strong>note</strong> that the <strong>'Doc Shepherd Follow-up Underway'</strong> annotation tag is {% if not followup %}<strong>NOT</strong>{% endif %} setted for {{ doc }}.
+</p>
+
+{% if can_edit %}
+<table style="width: 100%;"><tr style="vertical-align: top;"><td style="width: 75%;">
+<form action="" method="POST">
+  <table class="ietf-table" style="width: 100%;">    
+  <tr><th>Edit protocol write-up</th></tr>
+{% if form.message %}
+<tr><td class="message message-{{ form.message.type }}">
+    {{ form.message.value }}
+</td></tr>
+{% endif %}
+
+  <tr><td>
+  <textarea name="writeup" style="border: 1px solid #cccccc; width: 100%; height: 15em;">{{ form.get_writeup }}</textarea></td></tr>
+  </table>
+    <input type="submit" name="change_writeup" value="Change write-up" />
+</form>
+
+</td><td style="width: 25%;">
+
+<form action="" method="POST" enctype="multipart/form-data">
+  <table class="ietf-table" style="width: 100%;">    
+  <tr><th>Upload a new protocol write-up</th></tr>
+  <tr><td>
+  <p>Replace the current write-up with the contents of a plain ascii file:</p>
+  <input type="file" name="uploaded_writeup" /></td></tr>
+  </table>
+  <input type="submit" name="upload_writeup" value="Upload write-up" />
+</form>
+</td></tr></table>
+{% else %}
+<table class="ietf-table" style="width: 100%;">    
+  <tr><th>Edit protocol write-up</th></tr>
+  <tr><td>
+  <p>
+  You can not edit or upload the protocol write-up for {{ doc }} cause the draft is not on "WG Consensus: Waiting for Write-Up" state.
+  </p>
+  <p>
+  Please contact with the {{ wg }} Working Group chair.
+  </p>
+  </td></tr>
+</table>
+{% endif %}
+{% endblock %}
diff --git a/ietf/templates/wgchairs/shepherd_document_row.html b/ietf/templates/wgchairs/shepherd_document_row.html
new file mode 100644
index 000000000..dec1a12dc
--- /dev/null
+++ b/ietf/templates/wgchairs/shepherd_document_row.html
@@ -0,0 +1,17 @@
+{% load wgchairs_tags %}
+
+        <tr class="{% cycle oddrow,evenrow %}">
+          <td class="title">
+            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
+          </td>
+          <td class="status">
+              {{ doc.status.status }}
+          </td>        
+          <td class="writeup">
+             {% writeup doc %}
+             <a href="{% url doc_managing_writeup wg.group_acronym.acronym doc %}" style="display: block;" href="">[Edit]</a>
+          </td>
+          <td class="writeup_date">
+             {% writeupdate doc %}
+          </td>
+          </tr>
diff --git a/ietf/templates/wgchairs/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
index 468970da2..9cb5fff36 100644
--- a/ietf/templates/wgchairs/wg_shepherd_documents.html
+++ b/ietf/templates/wgchairs/wg_shepherd_documents.html
@@ -56,25 +56,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 <table class="ietf-table ietf-doctable" style="margin-top:16px; width: 100%;">
     <tr>       
        <th class="title">Document</th>
-       <th class="date">Date</th>
        <th class="status">Status</th>
-       <th class="ad">Area Director</th>
+       <th class="writeup">Protocol write-up</th>
+       <th class="writeup_date">Protocol write-up last update</th>
     </tr> 
     
       {% for doc in no_shepherd %}
-        <tr class="{% cycle oddrow,evenrow %}">
-          <td class="title">
-            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
-          </td>
-          <td class="date">
-             {{ doc.status.start_date|date:"Y-m" }}
-          </td>
-          <td class="status">
-              {{ doc.status.status }}
-          </td>        
-          <td class="ad">{{ doc.ad_name|default:"" }}</td>
-          
-          </tr>
+        {% include "wgchairs/shepherd_document_row.html" %}
       {% endfor %}
         
 </table>
@@ -84,25 +72,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 <table class="ietf-table ietf-doctable" style="margin-top:16px; width: 100%;">
     <tr>       
        <th class="title">Document</th>
-       <th class="date">Date</th>
        <th class="status">Status</th>
-       <th class="ad">Area Director</th>
+       <th class="writeup">Protocol write-up</th>
+       <th class="writeup_date">Protocol write-up last update</th>
     </tr> 
     
       {% for doc in my_documents %}
-        <tr class="{% cycle oddrow,evenrow %}">
-          <td class="title">
-            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
-          </td>
-          <td class="date">
-             {{ doc.status.start_date|date:"Y-m" }}
-          </td>
-          <td class="status">
-              {{ doc.status.status }}
-          </td>        
-          <td class="ad">{{ doc.ad_name|default:"" }}</td>
-          
-          </tr>
+        {% include "wgchairs/shepherd_document_row.html" %}
       {% endfor %}
         
 </table>
@@ -115,23 +91,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   <table class="ietf-table ietf-doctable" style="width: 100%;">
     <tr>       
        <th class="title">Document</th>
-       <th class="date">Date</th>
        <th class="status">Status</th>
-       <th class="ad">Area Director</th>
+       <th class="writeup">Protocol write-up</th>
+       <th class="writeup_date">Protocol write-up last update</th>
     </tr> 
     {% for doc in documents.list %}
-    <tr class="{% cycle oddrow,evenrow %}">
-      <td class="title">
-        <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
-      </td>
-      <td class="date">
-         {{ doc.status.start_date|date:"Y-m" }}
-      </td>
-      <td class="status">
-          {{ doc.status.status }}
-      </td>        
-      <td class="ad">{{ doc.ad_name|default:"" }}</td>
-    </tr>
+        {% include "wgchairs/shepherd_document_row.html" %}
     {% endfor %}
   </table>
 {% endfor %}
diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
index 2f751c00a..dd8ec110b 100644
--- a/ietf/wgchairs/accounts.py
+++ b/ietf/wgchairs/accounts.py
@@ -1,9 +1,21 @@
+def is_area_director_for_group(person, group):
+    return bool(group.area.area.areadirector_set.filter(person=person).count())
+
+
 def is_group_chair(person, group):
     if group.chairs().filter(person=person):
         return True
     return False
 
 
+def is_group_delegate(person, group):
+    return bool(group.wgdelegate_set.filter(person=person).count())
+
+
+def is_document_shepherd(person, document):
+    return person == document.shepherd
+
+
 def get_person_for_user(user):
     try:
         return user.get_profile().person()
@@ -51,3 +63,22 @@ def can_manage_shepherd_of_a_document(user, document):
     if not person or not document.group:
         return False
     return can_manage_shepherds_in_group(user, document.group.ietfwg)
+
+
+def can_manage_writeup_of_a_document_no_state(user, document):
+    person = get_person_for_user(user)
+    if not person or not document.group:
+        return False
+    group = document.group.ietfwg
+    return (is_group_chair(person, group) or
+            is_areadirector_for_group(person, group) or
+            is_group_delegate(person, group))
+
+
+def can_manage_writeup_of_a_document(user, document):
+    person = get_person_for_user(user)
+    if not person or not document.group:
+        return False
+    group = document.group.ietfwg
+    return (can_manage_writeup_of_a_document_no_state(user, document) or
+            is_document_shepherd(person, doc))
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 19b4965c5..fa7dcf3cc 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -6,9 +6,10 @@ from django.forms.models import BaseModelFormSet
 from django.template.loader import render_to_string
 from django.utils.safestring import mark_safe
 
-from ietf.wgchairs.models import WGDelegate
+from ietf.wgchairs.models import WGDelegate, ProtoWriteUp
 from ietf.wgchairs.accounts import get_person_for_user
-from ietf.ietfworkflows.utils import get_default_workflow_for_wg, get_workflow_for_wg
+from ietf.ietfworkflows.utils import (get_default_workflow_for_wg, get_workflow_for_wg,
+                                      update_tags, FOLLOWUP_TAG)
 from ietf.idtracker.models import PersonOrOrgInfo
 
 from workflows.models import Transition
@@ -328,3 +329,48 @@ def add_form_factory(request, wg, user, shepherd=False):
         return AddDelegateForm(wg=wg, user=user, data=request.POST.copy(), shepherd=shepherd)
 
     return AddDelegateForm(wg=wg, user=user, shepherd=shepherd)
+
+
+class WriteUpEditForm(RelatedWGForm):
+
+    writeup = forms.CharField(widget=forms.Textarea, required=False)
+    followup = forms.BooleanField(required=False)
+    comment = forms.CharField(widget=forms.Textarea, required=False)
+
+
+    def __init__(self, *args, **kwargs):
+        self.doc = kwargs.pop('doc', None)
+        self.doc_writeup = self.doc.protowriteup_set.all()
+        if self.doc_writeup.count():
+            self.doc_writeup=self.doc_writeup[0]
+        else:
+            self.doc_writeup=None
+        super(WriteUpEditForm, self).__init__(*args, **kwargs)
+
+    def get_writeup(self):
+        return self.data.get('writeup', self.doc_writeup and self.doc_writeup.writeup or '')
+
+    def save(self):
+        if not self.doc_writeup:
+            self.doc_writeup = ProtoWriteUp.objects.create(
+                person=get_person_for_user(self.user),
+                draft=self.doc,
+                writeup=self.cleaned_data['writeup'])
+        else:
+            self.doc_writeup.writeup = self.cleaned_data['writeup']
+            self.doc_writeup.save()
+        if self.data.get('modify_tag', False):
+            followup = self.cleaned_data.get('followup', False)
+            comment = self.cleaned_data.get('comment', False)
+            if followup:
+                update_tags(self.doc, comment, set_tags=[FOLLOWUP_TAG])
+            else:
+                update_tags(self.doc, comment, reset_tags=[FOLLOWUP_TAG])
+        return self.doc_writeup
+
+    def is_valid(self):
+        if self.data.get('confirm', False) and self.data.get('modify_tag', False):
+            self.fields['comment'].required = True
+        else:
+            self.fields['comment'].required = False
+        return super(WriteUpEditForm, self).is_valid()
diff --git a/ietf/wgchairs/templatetags/wgchairs_tags.py b/ietf/wgchairs/templatetags/wgchairs_tags.py
index 8cf9d9783..e81301c78 100644
--- a/ietf/wgchairs/templatetags/wgchairs_tags.py
+++ b/ietf/wgchairs/templatetags/wgchairs_tags.py
@@ -1,5 +1,6 @@
 from django import template
 
+from ietf.ietfworkflows.utils import get_state_for_draft
 from ietf.wgchairs.accounts import (can_manage_workflow_in_group,
                                     can_manage_delegates_in_group,
                                     can_manage_shepherds_in_group)
@@ -19,3 +20,27 @@ def wgchairs_admin_options(context, wg):
             'wg': wg,
             'selected': context.get('selected', None),
            }
+
+@register.simple_tag
+def writeup(doc):
+    writeup = doc.protowriteup_set.all()
+    if not writeup.count():
+        return ''
+    else:
+        return writeup[0].writeup
+
+
+@register.simple_tag
+def writeupdate(doc):
+    writeup = doc.protowriteup_set.all()
+    if not writeup.count():
+        return ''
+    else:
+        return writeup[0].date
+
+
+@register.inclusion_tag('wgchairs/draft_state.html', takes_context=True)
+def show_state(context, doc):
+    return {'doc': doc,
+            'state': get_state_for_draft(doc),
+           }
diff --git a/ietf/wgchairs/urls.py b/ietf/wgchairs/urls.py
index ab69bf8d6..d60c7b61d 100644
--- a/ietf/wgchairs/urls.py
+++ b/ietf/wgchairs/urls.py
@@ -7,4 +7,5 @@ urlpatterns = patterns('ietf.wgchairs.views',
      url(r'^delegates/$', 'manage_delegates', name='manage_delegates'),
      url(r'^shepherds/$', 'wg_shepherd_documents', name='manage_shepherds'),
      url(r'^shepherds/(?P<name>[^/]+)/$', 'managing_shepherd', name='doc_managing_shepherd'),
+     url(r'^shepherds/(?P<name>[^/]+)/writeup/$', 'managing_writeup', name='doc_managing_writeup'),
 )
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index ce353e511..b03c91b29 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -5,13 +5,21 @@ from django.http import HttpResponseForbidden, Http404
 
 from ietf.idrfc.views_search import SearchForm, search_query
 from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
-                                 workflow_form_factory, TransitionFormSet)
+                                 workflow_form_factory, TransitionFormSet,
+                                 WriteUpEditForm)
 from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
                                     can_manage_shepherds_in_group,
                                     can_manage_workflow_in_group,
-                                    can_manage_shepherd_of_a_document)
+                                    can_manage_shepherd_of_a_document,
+                                    can_manage_writeup_of_a_document,
+                                    can_manage_writeup_of_a_document_no_state,
+                                    )
 from ietf.ietfworkflows.utils import (get_workflow_for_wg,
-                                      get_default_workflow_for_wg)
+                                      get_default_workflow_for_wg,
+                                      get_state_by_name,
+                                      get_annotation_tags_for_draft,
+                                      get_state_for_draft, WAITING_WRITEUP,
+                                      FOLLOWUP_TAG)
 
 
 def manage_delegates(request, acronym):
@@ -136,3 +144,65 @@ def wg_shepherd_documents(request, acronym):
         'wg': wg,
     }
     return render_to_response('wgchairs/wg_shepherd_documents.html', context, RequestContext(request))
+
+def managing_writeup(request, acronym, name):
+    wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
+    user = request.user
+    person = get_person_for_user(user)
+    doc = get_object_or_404(InternetDraft, filename=name)
+    if not can_manage_writeup_of_a_document(user, doc):
+        raise Http404
+    current_state = get_state_for_draft(doc)
+    can_edit = True
+    if current_state != get_state_by_name(WAITING_WRITEUP) and not can_manage_writeup_of_a_document_no_state(user,doc):
+        can_edit = False
+    writeup = doc.protowriteup_set.all()
+    if writeup.count():
+        writeup = writeup[0]
+    else:
+        writeup = None
+    error = False
+    followup_tag = get_annotation_tags_for_draft(doc).filter(annotation_tag__name=FOLLOWUP_TAG)
+    followup = bool(followup_tag.count())
+    if request.method == 'POST':
+        form = WriteUpEditForm(wg=wg, doc=doc, user=user, data=request.POST, files=request.FILES)
+        if request.FILES.get('uploaded_writeup', None):
+            try:
+                newwriteup = request.FILES['uploaded_writeup'].read().encode('ascii')
+                form.data.update({'writeup': newwriteup})
+            except:
+                form.set_message('error', 'You have try to upload a non ascii file')
+                error = True
+        valid = form.is_valid()
+        if (valid and not error and not request.POST.get('confirm', None)) or (not valid and not error):
+            if not valid:
+                form.set_message('error', 'You have to specify a comment')
+            return render_to_response('wgchairs/confirm_management_writeup.html',
+                                      dict(doc=doc,
+                                           user=user,
+                                           selected='manage_shepherds',
+                                           wg=wg,
+                                           followup=followup,
+                                           form=form,
+                                           writeup=writeup,
+                                           can_edit=can_edit,
+                                           ),
+                                      context_instance=RequestContext(request))
+        elif valid and not error:
+            writeup = form.save()
+            form = WriteUpEditForm(wg=wg, doc=doc, user=user)
+            followup_tag = get_annotation_tags_for_draft(doc).filter(annotation_tag__name=FOLLOWUP_TAG)
+            followup = bool(followup_tag.count())
+    else:
+        form = WriteUpEditForm(wg=wg, doc=doc, user=user)
+    return render_to_response('wgchairs/edit_management_writeup.html',
+                              dict(doc=doc,
+                                   user=user,
+                                   selected='manage_shepherds',
+                                   wg=wg,
+                                   form=form,
+                                   writeup=writeup,
+                                   followup=followup,
+                                   can_edit=can_edit,
+                                   ),
+                              context_instance=RequestContext(request))

From 75e33a6ba99d3ee89a949fb168a1d3b3c66369f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 18:38:13 +0000
Subject: [PATCH 32/57] Add person to state/tags object history. Fixes #543  -
 Legacy-Id: 2746

---
 .../migrations/0003_add_person_to_history.py  | 119 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |   3 +
 ietf/ietfworkflows/utils.py                   |   5 +-
 3 files changed, 125 insertions(+), 2 deletions(-)
 create mode 100644 ietf/ietfworkflows/migrations/0003_add_person_to_history.py

diff --git a/ietf/ietfworkflows/migrations/0003_add_person_to_history.py b/ietf/ietfworkflows/migrations/0003_add_person_to_history.py
new file mode 100644
index 000000000..85f634656
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0003_add_person_to_history.py
@@ -0,0 +1,119 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding field 'ObjectWorkflowHistoryEntry.person'
+        db.add_column('ietfworkflows_objectworkflowhistoryentry', 'person', orm['ietfworkflows.objectworkflowhistoryentry:person'])
+        
+        # Adding field 'ObjectAnnotationTagHistoryEntry.person'
+        db.add_column('ietfworkflows_objectannotationtaghistoryentry', 'person', orm['ietfworkflows.objectannotationtaghistoryentry:person'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting field 'ObjectWorkflowHistoryEntry.person'
+        db.delete_column('ietfworkflows_objectworkflowhistoryentry', 'person_id')
+        
+        # Deleting field 'ObjectAnnotationTagHistoryEntry.person'
+        db.delete_column('ietfworkflows_objectannotationtaghistoryentry', 'person_id')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.State']", 'symmetrical': 'False'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ietfworkflows.AnnotationTag']", 'symmetrical': 'False'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index 404fb1328..6eaaff76c 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
+from ietf.idtracker.models import PersonOrOrgInfo
 from workflows.models import Workflow, State
 from permissions.models import Permission
 
@@ -16,6 +17,7 @@ class ObjectWorkflowHistoryEntry(models.Model):
     to_state = models.CharField(_('To state'), max_length=100)
     transition_date = models.DateTimeField(_('Transition date'))
     comment = models.TextField(_('Comment'))
+    person = models.ForeignKey(PersonOrOrgInfo)
 
 
 class AnnotationTag(models.Model):
@@ -44,6 +46,7 @@ class ObjectAnnotationTagHistoryEntry(models.Model):
     unsetted = models.TextField(_('Unsetted tags'), blank=True, null=True)
     change_date = models.DateTimeField(_('Change date'))
     comment = models.TextField(_('Comment'))
+    person = models.ForeignKey(PersonOrOrgInfo)
 
 
 class WGWorkflow(Workflow):
diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 4dff8d10b..ed0128870 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -126,7 +126,7 @@ def reset_tag_by_name(obj, tag_name):
         return False
 
 
-def update_tags(obj, comment, set_tags=[], reset_tags=[]):
+def update_tags(obj, comment, person, set_tags=[], reset_tags=[]):
     ctype = ContentType.objects.get_for_model(obj)
     setted = []
     resetted = []
@@ -142,4 +142,5 @@ def update_tags(obj, comment, set_tags=[], reset_tags=[]):
         setted = ','.join(setted),
         unsetted = ','.join(resetted),
         change_date = datetime.datetime.now(),
-        comment = comment)
+        comment = comment,
+        person=person)

From 837d9f6293c4e8a8dfd6d8872ca8d882a65a3a38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 19:44:36 +0000
Subject: [PATCH 33/57] Notify by email of state/tag updates. Fixes #568  -
 Legacy-Id: 2747

---
 ietf/ietfworkflows/utils.py                   | 39 ++++++++++++++++++-
 .../annotation_tags_updated_mail.txt          |  9 +++++
 .../ietfworkflows/state_updated_mail.txt      |  9 +++++
 3 files changed, 55 insertions(+), 2 deletions(-)
 create mode 100644 ietf/templates/ietfworkflows/annotation_tags_updated_mail.txt
 create mode 100644 ietf/templates/ietfworkflows/state_updated_mail.txt

diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index ed0128870..e0a10ffb3 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -1,7 +1,10 @@
 import copy
 import datetime
 
+from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
+from django.core.mail import EmailMessage
+from django.template.loader import render_to_string
 
 from workflows.models import State
 from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
@@ -126,7 +129,38 @@ def reset_tag_by_name(obj, tag_name):
         return False
 
 
-def update_tags(obj, comment, person, set_tags=[], reset_tags=[]):
+def notify_entry(entry, template, extra_notify=[]):
+    doc = entry.content
+    wg = doc.group.ietfwg
+    mail_list = set(['%s <%s>' % i.person.email() for i in wg.wgchair_set.all() if i.person.email()])
+    mail_list = mail_list.union(['%s <%s>' % i.person.email() for i in wg.wgdelegate_set.all() if i.person.email()])
+    mail_list = mail_list.union(extra_notify)
+    mail_list = list(mail_list)
+
+    subject = 'Annotation tags have changed for draft %s' % doc
+    body = render_to_string(template, {'doc': doc,
+                                       'entry': entry,
+                                      })
+    mail = EmailMessage(subject=subject,
+        body=body,
+        to=mail_list,
+        from_email=settings.DEFAULT_FROM_EMAIL)
+    # Only send emails if we are not debug mode
+    print body
+    if not settings.DEBUG:
+        mail.send()
+    return mail
+
+
+def notify_tag_entry(entry, extra_notify=[]):
+    return notify_entry(entry, 'ietfworkflows/annotation_tags_updated_mail.txt', extra_notify)
+
+
+def notify_state_entry(entry, extra_notify=[]):
+    return notify_entry(entry, 'ietfworkflows/state_updated_mail.txt', extra_notify)
+
+    
+def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
     ctype = ContentType.objects.get_for_model(obj)
     setted = []
     resetted = []
@@ -136,7 +170,7 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[]):
     for name in reset_tags:
         if reset_tag_by_name(obj, name):
             resetted.append(name)
-    ObjectAnnotationTagHistoryEntry.objects.create(
+    entry = ObjectAnnotationTagHistoryEntry.objects.create(
         content_type=ctype,
         content_id=obj.pk,
         setted = ','.join(setted),
@@ -144,3 +178,4 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[]):
         change_date = datetime.datetime.now(),
         comment = comment,
         person=person)
+    notify_tag_entry(entry, extra_notify)
diff --git a/ietf/templates/ietfworkflows/annotation_tags_updated_mail.txt b/ietf/templates/ietfworkflows/annotation_tags_updated_mail.txt
new file mode 100644
index 000000000..5c755817c
--- /dev/null
+++ b/ietf/templates/ietfworkflows/annotation_tags_updated_mail.txt
@@ -0,0 +1,9 @@
+The annotation tags of document {{ doc }} have been updated. See more information below.
+
+Annotation tags set: {{ entry.setted }}
+Annotation tags reset: {{ entry.unsetted }}
+Date of the change: {{ entry.change_date }}
+Author of the change: {{ entry.person }}
+
+Comment:
+{{ entry.comment }}
diff --git a/ietf/templates/ietfworkflows/state_updated_mail.txt b/ietf/templates/ietfworkflows/state_updated_mail.txt
new file mode 100644
index 000000000..95e1d7dd6
--- /dev/null
+++ b/ietf/templates/ietfworkflows/state_updated_mail.txt
@@ -0,0 +1,9 @@
+The state of document {{ doc }} has been updated. See more information below.
+
+Previous state: {{ entry.from_state }}
+Current state: {{ entry.to_state }}
+Transition date: {{ entry.transition_date }}
+Author of the change: {{ entry.person }}
+
+Comment:
+{{ entry.comment }}

From 049b649eb76ae4409bb53acd65f1330fd9d4e0c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 19:49:00 +0000
Subject: [PATCH 34/57] Notify shepherd (and wg chairs and delegates) by email
 if 'Doc Shepherd Follow-up Underway' is updated. Fixes #561  - Legacy-Id:
 2748

---
 ietf/ietfworkflows/utils.py |  1 -
 ietf/wgchairs/forms.py      | 14 ++++++++++----
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index e0a10ffb3..1ec56558c 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -146,7 +146,6 @@ def notify_entry(entry, template, extra_notify=[]):
         to=mail_list,
         from_email=settings.DEFAULT_FROM_EMAIL)
     # Only send emails if we are not debug mode
-    print body
     if not settings.DEBUG:
         mail.send()
     return mail
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index fa7dcf3cc..2d12e1bd1 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -346,6 +346,7 @@ class WriteUpEditForm(RelatedWGForm):
         else:
             self.doc_writeup=None
         super(WriteUpEditForm, self).__init__(*args, **kwargs)
+        self.person = get_person_for_user(self.user)
 
     def get_writeup(self):
         return self.data.get('writeup', self.doc_writeup and self.doc_writeup.writeup or '')
@@ -353,7 +354,7 @@ class WriteUpEditForm(RelatedWGForm):
     def save(self):
         if not self.doc_writeup:
             self.doc_writeup = ProtoWriteUp.objects.create(
-                person=get_person_for_user(self.user),
+                person=self.person,
                 draft=self.doc,
                 writeup=self.cleaned_data['writeup'])
         else:
@@ -362,10 +363,15 @@ class WriteUpEditForm(RelatedWGForm):
         if self.data.get('modify_tag', False):
             followup = self.cleaned_data.get('followup', False)
             comment = self.cleaned_data.get('comment', False)
-            if followup:
-                update_tags(self.doc, comment, set_tags=[FOLLOWUP_TAG])
+            shepherd = self.doc.shepherd
+            if shepherd:
+                extra_notify = ['%s <%s>' % shepherd.email()]
             else:
-                update_tags(self.doc, comment, reset_tags=[FOLLOWUP_TAG])
+                extra_notify = []
+            if followup:
+                update_tags(self.doc, comment, self.person, set_tags=[FOLLOWUP_TAG], extra_notify=extra_notify)
+            else:
+                update_tags(self.doc, comment, self.person, reset_tags=[FOLLOWUP_TAG], extra_notify=extra_notify)
         return self.doc_writeup
 
     def is_valid(self):

From 92e9360cea3cb026c0dae771cc57e74911944685 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 17 Jan 2011 19:53:55 +0000
Subject: [PATCH 35/57] Added I-D authors to the list of persons to be notified
 of state/tags updates. Fixes #568  - Legacy-Id: 2749

---
 ietf/ietfworkflows/utils.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 1ec56558c..bef2c064c 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -134,6 +134,7 @@ def notify_entry(entry, template, extra_notify=[]):
     wg = doc.group.ietfwg
     mail_list = set(['%s <%s>' % i.person.email() for i in wg.wgchair_set.all() if i.person.email()])
     mail_list = mail_list.union(['%s <%s>' % i.person.email() for i in wg.wgdelegate_set.all() if i.person.email()])
+    mail_list = mail_list.union(['%s <%s>' % i.person.email() for i in doc.authors.all() if i.person.email()])
     mail_list = mail_list.union(extra_notify)
     mail_list = list(mail_list)
 

From 6c2621ca18e4074ec8021cacfae55a64ed46cdc8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 21 Jan 2011 08:32:31 +0000
Subject: [PATCH 36/57] Replace 'id' with 'pk'. Fixes #570  - Legacy-Id: 2750

---
 workflows/models.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/workflows/models.py b/workflows/models.py
index 928e4ef0c..d7b5ac68d 100644
--- a/workflows/models.py
+++ b/workflows/models.py
@@ -125,7 +125,7 @@ class Workflow(models.Model):
 
         ctype = ContentType.objects.get_for_model(obj)
         try:
-            wor = WorkflowObjectRelation.objects.get(content_type=ctype, content_id=obj.id)
+            wor = WorkflowObjectRelation.objects.get(content_type=ctype, content_id=obj.pk)
         except WorkflowObjectRelation.DoesNotExist:
             WorkflowObjectRelation.objects.create(content = obj, workflow=self)
             workflows.utils.set_state(obj, self.initial_state)
@@ -354,4 +354,4 @@ class StatePermissionRelation(models.Model):
     role = models.ForeignKey(Role, verbose_name=_(u"Role"))
 
     def __unicode__(self):
-        return "%s %s %s" % (self.state.name, self.role.name, self.permission.name)
\ No newline at end of file
+        return "%s %s %s" % (self.state.name, self.role.name, self.permission.name)

From b09a27b6a4858892c03a327d5608860a60cb1bc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 21 Jan 2011 08:36:10 +0000
Subject: [PATCH 37/57] Add enter date and estimated date in object/state
 relation. Fixes #571  - Legacy-Id: 2751

---
 .../migrations/0004_add_object_state_dates.py | 132 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |  18 ++-
 2 files changed, 146 insertions(+), 4 deletions(-)
 create mode 100644 ietf/ietfworkflows/migrations/0004_add_object_state_dates.py

diff --git a/ietf/ietfworkflows/migrations/0004_add_object_state_dates.py b/ietf/ietfworkflows/migrations/0004_add_object_state_dates.py
new file mode 100644
index 000000000..c71d2a08e
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0004_add_object_state_dates.py
@@ -0,0 +1,132 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'StateObjectRelationMetadata'
+        db.create_table('ietfworkflows_stateobjectrelationmetadata', (
+            ('id', orm['ietfworkflows.stateobjectrelationmetadata:id']),
+            ('relation', orm['ietfworkflows.stateobjectrelationmetadata:relation']),
+            ('from_date', orm['ietfworkflows.stateobjectrelationmetadata:from_date']),
+            ('estimated_date', orm['ietfworkflows.stateobjectrelationmetadata:estimated_date']),
+        ))
+        db.send_create_signal('ietfworkflows', ['StateObjectRelationMetadata'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'StateObjectRelationMetadata'
+        db.delete_table('ietfworkflows_stateobjectrelationmetadata')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.State']", 'symmetrical': 'False'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ietfworkflows.AnnotationTag']", 'symmetrical': 'False'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index 6eaaff76c..e7e1a9bd6 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -3,8 +3,8 @@ from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
-from ietf.idtracker.models import PersonOrOrgInfo
-from workflows.models import Workflow, State
+from ietf.idtracker.models import PersonOrOrgInfo, InternetDraft
+from workflows.models import Workflow, State, StateObjectRelation
 from permissions.models import Permission
 
 
@@ -49,6 +49,16 @@ class ObjectAnnotationTagHistoryEntry(models.Model):
     person = models.ForeignKey(PersonOrOrgInfo)
 
 
+class StateObjectRelationMetadata(models.Model):
+    relation = models.ForeignKey(StateObjectRelation)
+    from_date = models.DateTimeField(_('Initial date'))
+    estimated_date = models.DateTimeField(_('Estimated date'), blank=True, null=True)
+
+
 class WGWorkflow(Workflow):
-    selected_states = models.ManyToManyField(State)
-    selected_tags = models.ManyToManyField(AnnotationTag)
+    selected_states = models.ManyToManyField(State, blank=True, null=True)
+    selected_tags = models.ManyToManyField(AnnotationTag, blank=True, null=True)
+
+    class Meta:
+        verbose_name = 'IETF Workflow'
+        verbose_name_plural = 'IETF Workflows'

From 0c0ee8212a1cbe85b52c0abcfe3ce8b05bfc6574 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 21 Jan 2011 08:39:30 +0000
Subject: [PATCH 38/57] Added models for basic stream configuration. See #569 
 - Legacy-Id: 2752

---
 ietf/ietfworkflows/admin.py                   |   5 +-
 .../migrations/0005_add_streams.py            | 209 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |  20 ++
 3 files changed, 233 insertions(+), 1 deletion(-)
 create mode 100644 ietf/ietfworkflows/migrations/0005_add_streams.py

diff --git a/ietf/ietfworkflows/admin.py b/ietf/ietfworkflows/admin.py
index dab717d19..d282d7aa9 100644
--- a/ietf/ietfworkflows/admin.py
+++ b/ietf/ietfworkflows/admin.py
@@ -1,6 +1,7 @@
 from django.contrib import admin
 
-from ietf.ietfworkflows.models import AnnotationTag, WGWorkflow
+from ietf.ietfworkflows.models import (AnnotationTag, WGWorkflow,
+                                       Stream)
 from workflows.admin import StateInline
 
 class AnnotationTagInline(admin.TabularInline):
@@ -9,4 +10,6 @@ class AnnotationTagInline(admin.TabularInline):
 class IETFWorkflowAdmin(admin.ModelAdmin):
     inlines = [StateInline, AnnotationTagInline]
 
+    
 admin.site.register(WGWorkflow, IETFWorkflowAdmin)
+admin.site.register(Stream, admin.ModelAdmin)
diff --git a/ietf/ietfworkflows/migrations/0005_add_streams.py b/ietf/ietfworkflows/migrations/0005_add_streams.py
new file mode 100644
index 000000000..1e3de388c
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0005_add_streams.py
@@ -0,0 +1,209 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'StreamedID'
+        db.create_table('ietfworkflows_streamedid', (
+            ('id', orm['ietfworkflows.streamedid:id']),
+            ('draft', orm['ietfworkflows.streamedid:draft']),
+            ('stream', orm['ietfworkflows.streamedid:stream']),
+        ))
+        db.send_create_signal('ietfworkflows', ['StreamedID'])
+        
+        # Adding model 'Stream'
+        db.create_table('ietfworkflows_stream', (
+            ('id', orm['ietfworkflows.stream:id']),
+            ('name', orm['ietfworkflows.stream:name']),
+            ('with_groups', orm['ietfworkflows.stream:with_groups']),
+            ('group_model', orm['ietfworkflows.stream:group_model']),
+            ('group_chair_model', orm['ietfworkflows.stream:group_chair_model']),
+            ('workflow', orm['ietfworkflows.stream:workflow']),
+        ))
+        db.send_create_signal('ietfworkflows', ['Stream'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'StreamedID'
+        db.delete_table('ietfworkflows_streamedid')
+        
+        # Deleting model 'Stream'
+        db.delete_table('ietfworkflows_stream')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.stream': {
+            'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
+        },
+        'ietfworkflows.streamedid': {
+            'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']"})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index e7e1a9bd6..bb6a7c505 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -62,3 +62,23 @@ class WGWorkflow(Workflow):
     class Meta:
         verbose_name = 'IETF Workflow'
         verbose_name_plural = 'IETF Workflows'
+
+
+class Stream(models.Model):
+    name = models.CharField(_(u"Name"), max_length=100)
+    with_groups = models.BooleanField(_(u'With groups'), default=False)
+    group_model = models.CharField(_(u'Group model'), max_length=100, blank=True, null=True)
+    group_chair_model = models.CharField(_(u'Group chair model'), max_length=100, blank=True, null=True)
+    workflow = models.ForeignKey(WGWorkflow)
+
+    def __unicode__(self):
+        return u'%s stream' % self.name
+
+
+class StreamedID(models.Model):
+    draft = models.OneToOneField(InternetDraft)
+    stream = models.ForeignKey(Stream)
+    
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="annotation_tags_history", blank=True, null=True)
+    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
+    group = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")

From a4c08f099cc6fafc7548bd8f50f4ff5a400d0956 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 21 Jan 2011 08:44:27 +0000
Subject: [PATCH 39/57] Add wich group of the stream is responsible of the ID.
 See #569  - Legacy-Id: 2753

---
 .../0006_add_group_to_streamed_id.py          | 198 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |   2 +-
 2 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 ietf/ietfworkflows/migrations/0006_add_group_to_streamed_id.py

diff --git a/ietf/ietfworkflows/migrations/0006_add_group_to_streamed_id.py b/ietf/ietfworkflows/migrations/0006_add_group_to_streamed_id.py
new file mode 100644
index 000000000..9460f0ac6
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0006_add_group_to_streamed_id.py
@@ -0,0 +1,198 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding field 'StreamedID.content_type'
+        db.add_column('ietfworkflows_streamedid', 'content_type', orm['ietfworkflows.streamedid:content_type'])
+        
+        # Adding field 'StreamedID.content_id'
+        db.add_column('ietfworkflows_streamedid', 'content_id', orm['ietfworkflows.streamedid:content_id'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting field 'StreamedID.content_type'
+        db.delete_column('ietfworkflows_streamedid', 'content_type_id')
+        
+        # Deleting field 'StreamedID.content_id'
+        db.delete_column('ietfworkflows_streamedid', 'content_id')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.stream': {
+            'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
+        },
+        'ietfworkflows.streamedid': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'streamed_id'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']"})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index bb6a7c505..33f03e906 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -79,6 +79,6 @@ class StreamedID(models.Model):
     draft = models.OneToOneField(InternetDraft)
     stream = models.ForeignKey(Stream)
     
-    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="annotation_tags_history", blank=True, null=True)
+    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="streamed_id", blank=True, null=True)
     content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
     group = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")

From a241c2095d6899ac41acd2f002f6ce7fd801f5a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 21 Jan 2011 09:03:46 +0000
Subject: [PATCH 40/57] Updated fixture with IETF, IAB, IRTF, ISE streams and
 its corresponding workflows. Fixes #569  - Legacy-Id: 2754

---
 ietf/ietfworkflows/fixtures/initial_data.xml | 494 +++++++++++++++----
 1 file changed, 389 insertions(+), 105 deletions(-)

diff --git a/ietf/ietfworkflows/fixtures/initial_data.xml b/ietf/ietfworkflows/fixtures/initial_data.xml
index c58778c05..f78fdce7f 100644
--- a/ietf/ietfworkflows/fixtures/initial_data.xml
+++ b/ietf/ietfworkflows/fixtures/initial_data.xml
@@ -1,66 +1,31 @@
 <?xml version="1.0" encoding="utf-8"?>
 <django-objects version="1.0">
-    <object pk="8" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Revised I-D Needed - Issue raised by AD</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="7" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Revised I-D Needed - Issue raised by WGLC</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="6" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Waiting for Referencing Document</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="5" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Waiting for Referenced Document</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="4" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Author or Editor Needed</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="3" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Awaiting Merge with Other Document</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="2" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Awaiting External Review/Resolution of Issues Raised</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="1" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Awaiting Expert Review/Resolution of Issues Raised</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="9" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Revised I-D Needed - Issue raised by IESG</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="10" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Doc Shepherd Follow-Up Underway</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="11" model="ietfworkflows.annotationtag">
-        <field type="CharField" name="name">Other - see Comment Log</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="1" model="ietfworkflows.wgworkflow">
-    </object>
     <object pk="1" model="workflows.workflow">
         <field type="CharField" name="name">Default WG Workflow</field>
         <field to="workflows.state" name="initial_state" rel="ManyToOneRel">11</field>
     </object>
+    <object pk="2" model="workflows.workflow">
+        <field type="CharField" name="name">IAB Workflow</field>
+        <field to="workflows.state" name="initial_state" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="3" model="workflows.workflow">
+        <field type="CharField" name="name">IRTF Workflow</field>
+        <field to="workflows.state" name="initial_state" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="4" model="workflows.workflow">
+        <field type="CharField" name="name">ISE Workflow</field>
+        <field to="workflows.state" name="initial_state" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="32" model="workflows.state">
+        <field type="CharField" name="name">Active IAB Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="42" model="workflows.state">
+        <field type="CharField" name="name">Active RG Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="12" model="workflows.state">
         <field type="CharField" name="name">Adopted by a WG</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
@@ -71,31 +36,181 @@
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
     </object>
+    <object pk="36" model="workflows.state">
+        <field type="CharField" name="name">Approved by IAB, To Be Sent to RFC Editor</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="47" model="workflows.state">
+        <field type="CharField" name="name">Awaiting IRSG Reviews</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="11" model="workflows.state">
         <field type="CharField" name="name">Call For Adoption By WG Issued</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="10"></object><object pk="11"></object></field>
     </object>
+    <object pk="31" model="workflows.state">
+        <field type="CharField" name="name">Candidate IAB Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="41" model="workflows.state">
+        <field type="CharField" name="name">Candidate RG Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="35" model="workflows.state">
+        <field type="CharField" name="name">Community Review</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="40" model="workflows.state">
+        <field type="CharField" name="name">Dead IAB Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="53" model="workflows.state">
+        <field type="CharField" name="name">Dead IRTF Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="16" model="workflows.state">
         <field type="CharField" name="name">Dead WG Document</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
     </object>
+    <object pk="51" model="workflows.state">
+        <field type="CharField" name="name">Document on Hold Based On IESG Request</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="62" model="workflows.state">
+        <field type="CharField" name="name">Document on Hold Based On IESG Request</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="55" model="workflows.state">
+        <field type="CharField" name="name">Finding Reviewers</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="34" model="workflows.state">
+        <field type="CharField" name="name">IAB Review</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="49" model="workflows.state">
+        <field type="CharField" name="name">In IESG Review</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="58" model="workflows.state">
+        <field type="CharField" name="name">In IESG Review</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="48" model="workflows.state">
+        <field type="CharField" name="name">In IRSG Poll</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="56" model="workflows.state">
+        <field type="CharField" name="name">In ISE Review</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="44" model="workflows.state">
+        <field type="CharField" name="name">In RG Last Call</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="17" model="workflows.state">
         <field type="CharField" name="name">In WG Last Call</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object><object pk="17"></object><object pk="18"></object></field>
     </object>
+    <object pk="61" model="workflows.state">
+        <field type="CharField" name="name">No Longer In Independent Submission Stream</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="33" model="workflows.state">
+        <field type="CharField" name="name">Parked IAB Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="43" model="workflows.state">
+        <field type="CharField" name="name">Parked RG Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="15" model="workflows.state">
         <field type="CharField" name="name">Parked WG Document</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
     </object>
+    <object pk="52" model="workflows.state">
+        <field type="CharField" name="name">Published RFC</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="60" model="workflows.state">
+        <field type="CharField" name="name">Published RFC</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="39" model="workflows.state">
+        <field type="CharField" name="name">Published RFC</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="57" model="workflows.state">
+        <field type="CharField" name="name">Response to Review Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="37" model="workflows.state">
+        <field type="CharField" name="name">Sent to a Different Organization for Publication</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="38" model="workflows.state">
+        <field type="CharField" name="name">Sent to the RFC Editor</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="59" model="workflows.state">
+        <field type="CharField" name="name">Sent to the RFC Editor</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="50" model="workflows.state">
+        <field type="CharField" name="name">Sent to the RFC Editor</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="54" model="workflows.state">
+        <field type="CharField" name="name">Submission Received</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="20" model="workflows.state">
         <field type="CharField" name="name">Submitted to IESG for Publication</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="12"></object></field>
     </object>
+    <object pk="45" model="workflows.state">
+        <field type="CharField" name="name">Waiting for Document Shepherd</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="46" model="workflows.state">
+        <field type="CharField" name="name">Waiting for IRTF Chair</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="workflows.transition" name="transitions" rel="ManyToManyRel"></field>
+    </object>
     <object pk="18" model="workflows.state">
         <field type="CharField" name="name">Waiting for WG Chair Go-Ahead</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
@@ -111,52 +226,10 @@
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
         <field to="workflows.transition" name="transitions" rel="ManyToManyRel"><object pk="13"></object><object pk="14"></object><object pk="16"></object><object pk="17"></object></field>
     </object>
-    <object pk="16" model="workflows.transition">
-        <field type="CharField" name="name">Raise last call</field>
+    <object pk="18" model="workflows.transition">
+        <field type="CharField" name="name">Wait for go-ahead</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">17</field>
-        <field type="CharField" name="condition"></field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="15" model="workflows.transition">
-        <field type="CharField" name="name">Submit to IESG</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">20</field>
-        <field type="CharField" name="condition"></field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="14" model="workflows.transition">
-        <field type="CharField" name="name">Die</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">16</field>
-        <field type="CharField" name="condition"></field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="13" model="workflows.transition">
-        <field type="CharField" name="name">Park</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">15</field>
-        <field type="CharField" name="condition"></field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="12" model="workflows.transition">
-        <field type="CharField" name="name">Develop</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">14</field>
-        <field type="CharField" name="condition"></field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="11" model="workflows.transition">
-        <field type="CharField" name="name">Adopt for WG info only</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">13</field>
-        <field type="CharField" name="condition"></field>
-        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
-    </object>
-    <object pk="10" model="workflows.transition">
-        <field type="CharField" name="name">Adopt</field>
-        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">12</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">18</field>
         <field type="CharField" name="condition"></field>
         <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
     </object>
@@ -167,11 +240,222 @@
         <field type="CharField" name="condition"></field>
         <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
     </object>
-    <object pk="18" model="workflows.transition">
-        <field type="CharField" name="name">Wait for go-ahead</field>
+    <object pk="10" model="workflows.transition">
+        <field type="CharField" name="name">Adopt</field>
         <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
-        <field to="workflows.state" name="destination" rel="ManyToOneRel">18</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">12</field>
         <field type="CharField" name="condition"></field>
         <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
     </object>
+    <object pk="11" model="workflows.transition">
+        <field type="CharField" name="name">Adopt for WG info only</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">13</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="12" model="workflows.transition">
+        <field type="CharField" name="name">Develop</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">14</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="13" model="workflows.transition">
+        <field type="CharField" name="name">Park</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">15</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="14" model="workflows.transition">
+        <field type="CharField" name="name">Die</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">16</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="15" model="workflows.transition">
+        <field type="CharField" name="name">Submit to IESG</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">20</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="16" model="workflows.transition">
+        <field type="CharField" name="name">Raise last call</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="workflows.state" name="destination" rel="ManyToOneRel">17</field>
+        <field type="CharField" name="condition"></field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="31" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="29" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Shepherd Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="30" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Dependency on Other Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="26" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="27" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Document Shepherd Followup</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="28" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Editor Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="25" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting Reviews</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="24" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Partner Feedback</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="11" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Other - see Comment Log</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="23" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Editor Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">2</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="10" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Doc Shepherd Follow-Up Underway</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="9" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed - Issue raised by IESG</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="1" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting Expert Review/Resolution of Issues Raised</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="2" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting External Review/Resolution of Issues Raised</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="3" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting Merge with Other Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="4" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Author or Editor Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="5" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Referenced Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="8" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed - Issue raised by AD</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="7" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed - Issue raised by WGLC</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="6" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Referencing Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">1</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="32" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">IESG Review Completed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">3</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="33" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Waiting for Dependency on Other Document</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="34" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Awaiting Reviews</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="35" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">Revised I-D Needed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="36" model="ietfworkflows.annotationtag">
+        <field type="CharField" name="name">IESG Review Completed</field>
+        <field to="workflows.workflow" name="workflow" rel="ManyToOneRel">4</field>
+        <field to="permissions.permission" name="permission" rel="ManyToOneRel"><None></None></field>
+    </object>
+    <object pk="1" model="ietfworkflows.wgworkflow">
+        <field to="workflows.state" name="selected_states" rel="ManyToManyRel"></field>
+        <field to="ietfworkflows.annotationtag" name="selected_tags" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="2" model="ietfworkflows.wgworkflow">
+        <field to="workflows.state" name="selected_states" rel="ManyToManyRel"></field>
+        <field to="ietfworkflows.annotationtag" name="selected_tags" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="3" model="ietfworkflows.wgworkflow">
+        <field to="workflows.state" name="selected_states" rel="ManyToManyRel"></field>
+        <field to="ietfworkflows.annotationtag" name="selected_tags" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="4" model="ietfworkflows.wgworkflow">
+        <field to="workflows.state" name="selected_states" rel="ManyToManyRel"></field>
+        <field to="ietfworkflows.annotationtag" name="selected_tags" rel="ManyToManyRel"></field>
+    </object>
+    <object pk="1" model="ietfworkflows.stream">
+        <field type="CharField" name="name">IETF</field>
+        <field type="BooleanField" name="with_groups">True</field>
+        <field type="CharField" name="group_model">idtracker.IETFWG</field>
+        <field type="CharField" name="group_chair_model">idtracker.WGChair</field>
+        <field to="ietfworkflows.wgworkflow" name="workflow" rel="ManyToOneRel">1</field>
+    </object>
+    <object pk="2" model="ietfworkflows.stream">
+        <field type="CharField" name="name">IAB</field>
+        <field type="BooleanField" name="with_groups">False</field>
+        <field type="CharField" name="group_model"></field>
+        <field type="CharField" name="group_chair_model"></field>
+        <field to="ietfworkflows.wgworkflow" name="workflow" rel="ManyToOneRel">2</field>
+    </object>
+    <object pk="3" model="ietfworkflows.stream">
+        <field type="CharField" name="name">IRTF</field>
+        <field type="BooleanField" name="with_groups">True</field>
+        <field type="CharField" name="group_model">idtracker.IRTF</field>
+        <field type="CharField" name="group_chair_model">idtracker.IRTFChair</field>
+        <field to="ietfworkflows.wgworkflow" name="workflow" rel="ManyToOneRel">3</field>
+    </object>
+    <object pk="4" model="ietfworkflows.stream">
+        <field type="CharField" name="name">ISE</field>
+        <field type="BooleanField" name="with_groups">False</field>
+        <field type="CharField" name="group_model"></field>
+        <field type="CharField" name="group_chair_model"></field>
+        <field to="ietfworkflows.wgworkflow" name="workflow" rel="ManyToOneRel">4</field>
+    </object>
 </django-objects>

From 649079c59311afbdeb74273f3430729d6aa21e37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Sat, 22 Jan 2011 09:07:11 +0000
Subject: [PATCH 41/57] Remove state functionallity in base workflows. Fixes
 #572  - Legacy-Id: 2756

---
 workflows/utils.py | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/workflows/utils.py b/workflows/utils.py
index a51635dce..ef2d32596 100644
--- a/workflows/utils.py
+++ b/workflows/utils.py
@@ -253,14 +253,35 @@ def set_state(obj, state):
     state
         The state which should be set to the passed object.
     """
+    if not state:
+        remove_state(obj)
+    else:
+        ctype = ContentType.objects.get_for_model(obj)
+        try:
+            sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.pk)
+        except StateObjectRelation.DoesNotExist:
+            sor = StateObjectRelation.objects.create(content=obj, state=state)
+        else:
+            sor.state = state
+            sor.save()
+        update_permissions(obj)
+
+def remove_state(obj):
+    """Removes the current state for the passed object.
+
+    **Parameters:**
+
+    obj
+        The object for which the workflow state should be set. Can be any
+        Django model instance.
+
+    """
     ctype = ContentType.objects.get_for_model(obj)
     try:
         sor = StateObjectRelation.objects.get(content_type=ctype, content_id=obj.pk)
+        sor.delete()
     except StateObjectRelation.DoesNotExist:
-        sor = StateObjectRelation.objects.create(content=obj, state=state)
-    else:
-        sor.state = state
-        sor.save()
+        pass
     update_permissions(obj)
 
 def set_initial_state(obj):

From d516f4327136b1ec6a52ac617a35ebdc4ab4a332 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Sat, 22 Jan 2011 17:00:58 +0000
Subject: [PATCH 42/57] Refactor workflow related history entries. Fixes #574 
 - Legacy-Id: 2757

---
 .../migrations/0007_do_stream_optional.py     | 194 +++++++++++++
 .../0008_refactor_history_entries.py          | 267 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |  59 ++--
 3 files changed, 503 insertions(+), 17 deletions(-)
 create mode 100644 ietf/ietfworkflows/migrations/0007_do_stream_optional.py
 create mode 100644 ietf/ietfworkflows/migrations/0008_refactor_history_entries.py

diff --git a/ietf/ietfworkflows/migrations/0007_do_stream_optional.py b/ietf/ietfworkflows/migrations/0007_do_stream_optional.py
new file mode 100644
index 000000000..bcb758aab
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0007_do_stream_optional.py
@@ -0,0 +1,194 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Changing field 'StreamedID.stream'
+        # (to signature: django.db.models.fields.related.ForeignKey(to=orm['ietfworkflows.Stream'], null=True, blank=True))
+        db.alter_column('ietfworkflows_streamedid', 'stream_id', orm['ietfworkflows.streamedid:stream'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Changing field 'StreamedID.stream'
+        # (to signature: django.db.models.fields.related.ForeignKey(to=orm['ietfworkflows.Stream']))
+        db.alter_column('ietfworkflows_streamedid', 'stream_id', orm['ietfworkflows.streamedid:stream'])
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'change_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transition_date': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.stream': {
+            'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
+        },
+        'ietfworkflows.streamedid': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'streamed_id'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']", 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/migrations/0008_refactor_history_entries.py b/ietf/ietfworkflows/migrations/0008_refactor_history_entries.py
new file mode 100644
index 000000000..dfb18c6dc
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0008_refactor_history_entries.py
@@ -0,0 +1,267 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Deleting model 'objectworkflowhistoryentry'
+        db.delete_table('ietfworkflows_objectworkflowhistoryentry')
+        
+        # Deleting model 'objectannotationtaghistoryentry'
+        db.delete_table('ietfworkflows_objectannotationtaghistoryentry')
+        
+        # Adding model 'ObjectAnnotationTagHistoryEntry'
+        db.create_table('ietfworkflows_objectannotationtaghistoryentry', (
+            ('objecthistoryentry_ptr', orm['ietfworkflows.objectannotationtaghistoryentry:objecthistoryentry_ptr']),
+            ('setted', orm['ietfworkflows.objectannotationtaghistoryentry:setted']),
+            ('unsetted', orm['ietfworkflows.objectannotationtaghistoryentry:unsetted']),
+        ))
+        db.send_create_signal('ietfworkflows', ['ObjectAnnotationTagHistoryEntry'])
+        
+        # Adding model 'ObjectHistoryEntry'
+        db.create_table('ietfworkflows_objecthistoryentry', (
+            ('id', orm['ietfworkflows.objecthistoryentry:id']),
+            ('content_type', orm['ietfworkflows.objecthistoryentry:content_type']),
+            ('content_id', orm['ietfworkflows.objecthistoryentry:content_id']),
+            ('date', orm['ietfworkflows.objecthistoryentry:date']),
+            ('comment', orm['ietfworkflows.objecthistoryentry:comment']),
+            ('person', orm['ietfworkflows.objecthistoryentry:person']),
+        ))
+        db.send_create_signal('ietfworkflows', ['ObjectHistoryEntry'])
+        
+        # Adding model 'ObjectStreamHistoryEntry'
+        db.create_table('ietfworkflows_objectstreamhistoryentry', (
+            ('objecthistoryentry_ptr', orm['ietfworkflows.objectstreamhistoryentry:objecthistoryentry_ptr']),
+            ('from_stream', orm['ietfworkflows.objectstreamhistoryentry:from_stream']),
+            ('to_stream', orm['ietfworkflows.objectstreamhistoryentry:to_stream']),
+        ))
+        db.send_create_signal('ietfworkflows', ['ObjectStreamHistoryEntry'])
+        
+        # Adding model 'ObjectWorkflowHistoryEntry'
+        db.create_table('ietfworkflows_objectworkflowhistoryentry', (
+            ('objecthistoryentry_ptr', orm['ietfworkflows.objectworkflowhistoryentry:objecthistoryentry_ptr']),
+            ('from_state', orm['ietfworkflows.objectworkflowhistoryentry:from_state']),
+            ('to_state', orm['ietfworkflows.objectworkflowhistoryentry:to_state']),
+        ))
+        db.send_create_signal('ietfworkflows', ['ObjectWorkflowHistoryEntry'])
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'ObjectAnnotationTagHistoryEntry'
+        db.delete_table('ietfworkflows_objectannotationtaghistoryentry')
+        
+        # Deleting model 'ObjectHistoryEntry'
+        db.delete_table('ietfworkflows_objecthistoryentry')
+        
+        # Deleting model 'ObjectStreamHistoryEntry'
+        db.delete_table('ietfworkflows_objectstreamhistoryentry')
+        
+        # Deleting model 'ObjectWorkflowHistoryEntry'
+        db.delete_table('ietfworkflows_objectworkflowhistoryentry')
+        
+        # Adding model 'objectworkflowhistoryentry'
+        db.create_table('ietfworkflows_objectworkflowhistoryentry', (
+            ('comment', orm['ietfworkflows.objectworkflowhistoryentry:comment']),
+            ('from_state', orm['ietfworkflows.objectworkflowhistoryentry:from_state']),
+            ('to_state', orm['ietfworkflows.objectworkflowhistoryentry:to_state']),
+            ('content_type', orm['ietfworkflows.objectworkflowhistoryentry:content_type']),
+            ('person', orm['ietfworkflows.objectworkflowhistoryentry:person']),
+            ('content_id', orm['ietfworkflows.objectworkflowhistoryentry:content_id']),
+            ('id', orm['ietfworkflows.objectworkflowhistoryentry:id']),
+            ('transition_date', orm['ietfworkflows.objectworkflowhistoryentry:transition_date']),
+        ))
+        db.send_create_signal('ietfworkflows', ['objectworkflowhistoryentry'])
+        
+        # Adding model 'objectannotationtaghistoryentry'
+        db.create_table('ietfworkflows_objectannotationtaghistoryentry', (
+            ('comment', orm['ietfworkflows.objectworkflowhistoryentry:comment']),
+            ('person', orm['ietfworkflows.objectworkflowhistoryentry:person']),
+            ('unsetted', orm['ietfworkflows.objectworkflowhistoryentry:unsetted']),
+            ('content_type', orm['ietfworkflows.objectworkflowhistoryentry:content_type']),
+            ('change_date', orm['ietfworkflows.objectworkflowhistoryentry:change_date']),
+            ('setted', orm['ietfworkflows.objectworkflowhistoryentry:setted']),
+            ('content_id', orm['ietfworkflows.objectworkflowhistoryentry:content_id']),
+            ('id', orm['ietfworkflows.objectworkflowhistoryentry:id']),
+        ))
+        db.send_create_signal('ietfworkflows', ['objectannotationtaghistoryentry'])
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objecthistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"})
+        },
+        'ietfworkflows.objectstreamhistoryentry': {
+            'from_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'to_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.stream': {
+            'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
+        },
+        'ietfworkflows.streamedid': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'streamed_id'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']", 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index 33f03e906..99634d769 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -8,23 +8,53 @@ from workflows.models import Workflow, State, StateObjectRelation
 from permissions.models import Permission
 
 
-class ObjectWorkflowHistoryEntry(models.Model):
+class ObjectHistoryEntry(models.Model):
     content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="workflow_history", blank=True, null=True)
     content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
     content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
 
-    from_state = models.CharField(_('From state'), max_length=100)
-    to_state = models.CharField(_('To state'), max_length=100)
-    transition_date = models.DateTimeField(_('Transition date'))
+    date = models.DateTimeField(_('Date'), auto_now_add=True)
     comment = models.TextField(_('Comment'))
     person = models.ForeignKey(PersonOrOrgInfo)
 
+    def get_real_instance(self):
+        if hasattr(self, '_real_instance'): 
+            return self._real_instance
+        for i in ('objectworkflowhistoryentry', 'objectannotationtaghistoryentry', 'objectstreamhistoryentry'):
+            try:
+                real_instance = getattr(self, 'objectworkflowhistoryentry', None)
+                if real_instance:
+                    self._real_instance = real_instance
+                    return real_instance
+            except models.ObjectDoesNotExist:
+                continue
+        self._real_instance = self
+        return self
+
+
+class ObjectWorkflowHistoryEntry(ObjectHistoryEntry):
+    from_state = models.CharField(_('From state'), max_length=100)
+    to_state = models.CharField(_('To state'), max_length=100)
+
+
+class ObjectAnnotationTagHistoryEntry(ObjectHistoryEntry):
+    setted = models.TextField(_('Setted tags'), blank=True, null=True)
+    unsetted = models.TextField(_('Unsetted tags'), blank=True, null=True)
+
+
+class ObjectStreamHistoryEntry(ObjectHistoryEntry):
+    from_stream = models.TextField(_('From stream'), blank=True, null=True)
+    to_stream = models.TextField(_('To stream'), blank=True, null=True)
+
 
 class AnnotationTag(models.Model):
     name = models.CharField(_(u"Name"), max_length=100)
     workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="annotation_tags")
     permission = models.ForeignKey(Permission, verbose_name=_(u"Permission"), blank=True, null=True)
 
+    class Meta:
+        ordering = ('name', )
+
     def __unicode__(self):
         return self.name
 
@@ -37,18 +67,6 @@ class AnnotationTagObjectRelation(models.Model):
     annotation_tag = models.ForeignKey(AnnotationTag, verbose_name=_(u"Annotation tag"))
 
 
-class ObjectAnnotationTagHistoryEntry(models.Model):
-    content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="annotation_tags_history", blank=True, null=True)
-    content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
-    content = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
-
-    setted = models.TextField(_('Setted tags'), blank=True, null=True)
-    unsetted = models.TextField(_('Unsetted tags'), blank=True, null=True)
-    change_date = models.DateTimeField(_('Change date'))
-    comment = models.TextField(_('Comment'))
-    person = models.ForeignKey(PersonOrOrgInfo)
-
-
 class StateObjectRelationMetadata(models.Model):
     relation = models.ForeignKey(StateObjectRelation)
     from_date = models.DateTimeField(_('Initial date'))
@@ -63,6 +81,13 @@ class WGWorkflow(Workflow):
         verbose_name = 'IETF Workflow'
         verbose_name_plural = 'IETF Workflows'
 
+    def get_tags(self):
+        tags = self.annotation_tags.all()
+        if tags.count():
+            return tags
+        else:
+            return self.selected_tags.all()
+
 
 class Stream(models.Model):
     name = models.CharField(_(u"Name"), max_length=100)
@@ -77,7 +102,7 @@ class Stream(models.Model):
 
 class StreamedID(models.Model):
     draft = models.OneToOneField(InternetDraft)
-    stream = models.ForeignKey(Stream)
+    stream = models.ForeignKey(Stream, blank=True, null=True)
     
     content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="streamed_id", blank=True, null=True)
     content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)

From 256dd73dd43c24ebe01d8c285bcfcc294a7ece84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Sun, 23 Jan 2011 10:23:45 +0000
Subject: [PATCH 43/57] Detailed stream info view. Fixes #573  - Legacy-Id:
 2758

---
 ietf/ietfworkflows/models.py                  | 32 ++++++-
 ietf/ietfworkflows/streams.py                 | 84 +++++++++++++++++++
 ietf/ietfworkflows/templatetags/__init__.py   |  0
 .../templatetags/ietf_streams.py              | 48 +++++++++++
 ietf/ietfworkflows/urls.py                    |  7 ++
 ietf/ietfworkflows/utils.py                   | 48 ++++++++---
 ietf/ietfworkflows/views.py                   | 60 +++++++++++++
 ietf/templates/idrfc/status_columns.html      |  3 +-
 .../ietfworkflows/stream_history.html         | 44 ++++++++++
 .../templates/ietfworkflows/stream_state.html | 10 +++
 .../ietfworkflows/workflow_history_entry.html | 12 +++
 ietf/urls.py                                  |  1 +
 ietf/wgchairs/forms.py                        |  5 +-
 static/css/base2.css                          | 11 +++
 static/js/base.js                             | 50 +++++++++++
 15 files changed, 402 insertions(+), 13 deletions(-)
 create mode 100644 ietf/ietfworkflows/streams.py
 create mode 100644 ietf/ietfworkflows/templatetags/__init__.py
 create mode 100644 ietf/ietfworkflows/templatetags/ietf_streams.py
 create mode 100644 ietf/ietfworkflows/urls.py
 create mode 100644 ietf/ietfworkflows/views.py
 create mode 100644 ietf/templates/ietfworkflows/stream_history.html
 create mode 100644 ietf/templates/ietfworkflows/stream_state.html
 create mode 100644 ietf/templates/ietfworkflows/workflow_history_entry.html

diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index 99634d769..ede8ac995 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -17,12 +17,16 @@ class ObjectHistoryEntry(models.Model):
     comment = models.TextField(_('Comment'))
     person = models.ForeignKey(PersonOrOrgInfo)
 
+    class Meta:
+        ordering = ('-date', )
+
+
     def get_real_instance(self):
         if hasattr(self, '_real_instance'): 
             return self._real_instance
         for i in ('objectworkflowhistoryentry', 'objectannotationtaghistoryentry', 'objectstreamhistoryentry'):
             try:
-                real_instance = getattr(self, 'objectworkflowhistoryentry', None)
+                real_instance = getattr(self, i, None)
                 if real_instance:
                     self._real_instance = real_instance
                     return real_instance
@@ -36,16 +40,42 @@ class ObjectWorkflowHistoryEntry(ObjectHistoryEntry):
     from_state = models.CharField(_('From state'), max_length=100)
     to_state = models.CharField(_('To state'), max_length=100)
 
+    def describe_change(self):
+        html = '<p class="describe_state_change">'
+        html += 'Changed state <i>%s</i> to <b>%s</b>' % (self.from_state, self.to_state)
+        html += '</p>'
+        return html
+
 
 class ObjectAnnotationTagHistoryEntry(ObjectHistoryEntry):
     setted = models.TextField(_('Setted tags'), blank=True, null=True)
     unsetted = models.TextField(_('Unsetted tags'), blank=True, null=True)
 
+    def describe_change(self):
+        html = ''
+        if self.setted:
+            html += '<p class="describe_tags_set">'
+            html += 'Annotation tags set: '
+            html += self.setted
+            html += '</p>'
+        if self.unsetted:
+            html += '<p class="describe_tags_reset">'
+            html += 'Annotation tags reset: '
+            html += self.unsetted
+            html += '</p>'
+        return html
+
 
 class ObjectStreamHistoryEntry(ObjectHistoryEntry):
     from_stream = models.TextField(_('From stream'), blank=True, null=True)
     to_stream = models.TextField(_('To stream'), blank=True, null=True)
 
+    def describe_change(self):
+        html = '<p class="describe_stream_change">'
+        html += 'Changed doc from stream <i>%s</i> to <b>%s</b>' % (self.from_stream, self.to_stream)
+        html += '</p>'
+        return html
+
 
 class AnnotationTag(models.Model):
     name = models.CharField(_(u"Name"), max_length=100)
diff --git a/ietf/ietfworkflows/streams.py b/ietf/ietfworkflows/streams.py
new file mode 100644
index 000000000..18ce32f36
--- /dev/null
+++ b/ietf/ietfworkflows/streams.py
@@ -0,0 +1,84 @@
+from django.db import models
+
+from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
+from ietf.ietfworkflows.models import StreamedID, Stream
+
+
+def get_streamed_draft(draft):
+    if not draft:
+        return None
+    try:
+        return draft.streamedid
+    except StreamedID.DoesNotExist:
+        return None
+
+
+def get_stream_from_draft(draft):
+    streamedid = get_streamed_draft(draft)
+    if streamedid:
+        return streamedid.stream
+    return False
+
+
+def get_stream_from_id(stream_id):
+    try:
+        return Stream.objects.get(id=stream_id)
+    except Stream.DoesNotExist:
+        return None
+
+
+def _get_group_from_acronym(group_model_str, acronym):
+    try:
+        app, model = group_model_str.split('.', 1)
+    except ValueError:
+        return None
+    group_model = models.get_model(app, model)
+    if not group_model:
+        return
+    if 'acronym' in group_model._meta.get_all_field_names():
+        try:
+            return group_model._default_manager.get(acronym=acronym)
+        except group_model.DoesNotExist:
+            return None
+    elif 'group_acronym' in group_model._meta.get_all_field_names():
+        try:
+            return group_model._default_manager.get(group_acronym__acronym=acronym)
+        except group_model.DoesNotExist:
+            return None
+    else:
+        return None
+
+
+def _set_stream_automatically(draft, stream):
+    streamed = StreamedID.objects.create(stream=stream, draft=draft)
+    if not stream or not stream.with_groups:
+        return
+    try:
+        draft_literal, stream_name, group_name, extra = draft.filename.split('-', 3)
+        if stream_name.lower() == stream.name.lower():
+            group = _get_group_from_acronym(stream.group_model, group_name)
+            if group:
+                streamed.group = group
+                streamed.save()
+    except ValueError:
+        return
+
+
+def get_stream_from_wrapper(idrfc_wrapper):
+    idwrapper = None
+    if isinstance(idrfc_wrapper, IdRfcWrapper):
+        idwrapper = idrfc_wrapper.id
+    elif isinstance(idrfc_wrapper, IdWrapper):
+        idwrapper = idrfc_wrapper
+    if not idwrapper:
+        return None
+    draft = idwrapper._draft
+    stream = get_stream_from_draft(draft)
+    if stream == False:
+        stream_id = idwrapper.stream_id()
+        stream = get_stream_from_id(stream_id)
+        _set_stream_automatically(draft, stream)
+        return stream
+    else:
+        return stream
+    return None
diff --git a/ietf/ietfworkflows/templatetags/__init__.py b/ietf/ietfworkflows/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/ietfworkflows/templatetags/ietf_streams.py b/ietf/ietfworkflows/templatetags/ietf_streams.py
new file mode 100644
index 000000000..f18d7709e
--- /dev/null
+++ b/ietf/ietfworkflows/templatetags/ietf_streams.py
@@ -0,0 +1,48 @@
+from django import template
+
+from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
+from ietf.ietfworkflows.utils import (get_workflow_for_draft,
+                                      get_state_for_draft)
+from ietf.ietfworkflows.streams import get_stream_from_wrapper
+
+
+register = template.Library()
+
+
+@register.inclusion_tag('ietfworkflows/stream_state.html', takes_context=True)
+def stream_state(context, doc):
+    request = context.get('request', None)
+    user = request and request.user
+    data = {}
+    stream = get_stream_from_wrapper(doc)
+    data.update({'stream': stream})
+    if not stream:
+        return data
+
+    idwrapper = None
+    if isinstance(doc, IdRfcWrapper):
+        idwrapper = doc.id
+    elif isinstance(doc, IdWrapper):
+        idwrapper = doc
+    if not idwrapper:
+        return data
+
+    draft = getattr(idwrapper, '_draft', None)
+    if not draft:
+        return data
+
+    workflow = get_workflow_for_draft(draft)
+    state = get_state_for_draft(draft)
+
+    data.update({'workflow': workflow,
+                 'draft': draft,
+                 'state': state})
+
+    return data
+
+
+@register.inclusion_tag('ietfworkflows/workflow_history_entry.html', takes_context=True)
+def workflow_history_entry(context, entry):
+    real_entry = entry.get_real_instance()
+    return {'entry': real_entry,
+            'entry_class': real_entry.__class__.__name__.lower()}
diff --git a/ietf/ietfworkflows/urls.py b/ietf/ietfworkflows/urls.py
new file mode 100644
index 000000000..7ea564563
--- /dev/null
+++ b/ietf/ietfworkflows/urls.py
@@ -0,0 +1,7 @@
+# Copyright The IETF Trust 2008, All Rights Reserved
+
+from django.conf.urls.defaults import patterns, url
+
+urlpatterns = patterns('ietf.ietfworkflows.views',
+     url(r'^(?P<name>[^/]+)/history/$', 'stream_history', name='stream_history'),
+)
diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index bef2c064c..3f40b614b 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -10,8 +10,10 @@ from workflows.models import State
 from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
                              get_state)
 
+from ietf.ietfworkflows.streams import get_streamed_draft
 from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
-                                       AnnotationTag, ObjectAnnotationTagHistoryEntry)
+                                       AnnotationTag, ObjectAnnotationTagHistoryEntry,
+                                       ObjectHistoryEntry)
 
 
 WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
@@ -24,7 +26,8 @@ def get_default_workflow_for_wg():
         return workflow
     except WGWorkflow.DoesNotExist:
         return None
-    
+
+
 def clone_transition(transition):
     new = copy.copy(transition)
     new.pk = None
@@ -35,6 +38,7 @@ def clone_transition(transition):
         new.states.add(state)
     return new
 
+
 def clone_workflow(workflow, name):
     new = WGWorkflow.objects.create(name=name, initial_state=workflow.initial_state)
 
@@ -51,20 +55,25 @@ def clone_workflow(workflow, name):
         new.transitions.add(clone_transition(transition))
     return new
 
-def get_workflow_for_wg(wg):
+
+def get_workflow_for_wg(wg, default=None):
     workflow = get_workflow_for_object(wg)
     try:
         workflow = workflow and workflow.wgworkflow
     except WGWorkflow.DoesNotExist:
         workflow = None
     if not workflow:
-        workflow = get_default_workflow_for_wg()
+        if default:
+            workflow = default
+        else:
+            workflow = get_default_workflow_for_wg()
         if not workflow:
             return None
         workflow = clone_workflow(workflow, name='%s workflow' % wg)
         set_workflow_for_object(wg, workflow)
     return workflow
 
+
 def get_workflow_for_draft(draft):
     workflow = get_workflow_for_object(draft)
     try:
@@ -72,11 +81,29 @@ def get_workflow_for_draft(draft):
     except WGWorkflow.DoesNotExist:
         workflow = None
     if not workflow:
-        workflow = get_workflow_for_wg(draft.group.ietfwg)
+        streamed_draft = get_streamed_draft(draft)
+        if not streamed_draft or not streamed_draft.stream:
+            return None
+        stream = streamed_draft.stream
+        if stream.with_groups:
+            if not streamed_draft.group:
+                return None
+            else:
+                workflow = get_workflow_for_wg(streamed_draft.group, streamed_draft.stream.workflow)
+        else:
+            workflow = stream.workflow
         set_workflow_for_object(draft, workflow)
     return workflow
 
 
+def get_workflow_history_for_draft(draft):
+    ctype = ContentType.objects.get_for_model(draft)
+    history = ObjectHistoryEntry.objects.filter(content_type=ctype, content_id=draft.pk).\
+        select_related('objectworkflowhistoryentry', 'objectannotationtaghistoryentry',
+                       'objectstreamhistoryentry')
+    return history
+
+
 def get_annotation_tags_for_draft(draft):
     ctype = ContentType.objects.get_for_model(draft)
     tags = AnnotationTagObjectRelation.objects.filter(content_type=ctype, content_id=draft.pk)
@@ -100,6 +127,7 @@ def get_annotation_tag_by_name(tag_name):
     except AnnotationTag.DoesNotExist:
         return None
 
+
 def set_tag_by_name(obj, tag_name):
     ctype = ContentType.objects.get_for_model(obj)
     try:
@@ -159,7 +187,7 @@ def notify_tag_entry(entry, extra_notify=[]):
 def notify_state_entry(entry, extra_notify=[]):
     return notify_entry(entry, 'ietfworkflows/state_updated_mail.txt', extra_notify)
 
-    
+
 def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
     ctype = ContentType.objects.get_for_model(obj)
     setted = []
@@ -173,9 +201,9 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[
     entry = ObjectAnnotationTagHistoryEntry.objects.create(
         content_type=ctype,
         content_id=obj.pk,
-        setted = ','.join(setted),
-        unsetted = ','.join(resetted),
-        change_date = datetime.datetime.now(),
-        comment = comment,
+        setted=','.join(setted),
+        unsetted=','.join(resetted),
+        date=datetime.datetime.now(),
+        comment=comment,
         person=person)
     notify_tag_entry(entry, extra_notify)
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
new file mode 100644
index 000000000..a11f3b3d0
--- /dev/null
+++ b/ietf/ietfworkflows/views.py
@@ -0,0 +1,60 @@
+from ietf.idtracker.models import IETFWG, InternetDraft, IESGLogin
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.http import HttpResponseForbidden, Http404
+
+from ietf.idrfc.views_search import SearchForm, search_query
+from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
+                                 workflow_form_factory, TransitionFormSet,
+                                 WriteUpEditForm)
+from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
+                                    can_manage_shepherds_in_group,
+                                    can_manage_workflow_in_group,
+                                    can_manage_shepherd_of_a_document,
+                                    can_manage_writeup_of_a_document,
+                                    can_manage_writeup_of_a_document_no_state,
+                                    )
+from ietf.ietfworkflows.streams import (get_stream_from_draft,
+                                        get_streamed_draft)
+from ietf.ietfworkflows.utils import (get_workflow_for_wg,
+                                      get_default_workflow_for_wg,
+                                      get_workflow_history_for_draft,
+                                      get_workflow_for_draft,
+                                      get_state_by_name,
+                                      get_annotation_tags_for_draft,
+                                      get_state_for_draft, WAITING_WRITEUP,
+                                      FOLLOWUP_TAG)
+
+
+REDUCED_HISTORY_LEN = 20
+
+
+def stream_history(request, name):
+    user = request.user
+    person = get_person_for_user(user)
+    draft = get_object_or_404(InternetDraft, filename=name)
+    streamed = get_streamed_draft(draft)
+    stream = get_stream_from_draft(draft)
+    workflow = get_workflow_for_draft(draft)
+    tags = []
+    if workflow:
+        tags_setted = [i.annotation_tag.pk for i in get_annotation_tags_for_draft(draft)]
+        for tag in workflow.get_tags():
+            tag.setted = tag.pk in tags_setted
+            tags.append(tag)
+    state = get_state_for_draft(draft)
+    history = get_workflow_history_for_draft(draft)
+    show_more = False
+    if history.count > REDUCED_HISTORY_LEN:
+        show_more = True
+    return render_to_response('ietfworkflows/stream_history.html',
+                              {'stream': stream,
+                               'streamed': streamed,
+                               'draft': draft,
+                               'tags': tags,
+                               'state': state,
+                               'workflow': workflow,
+                               'show_more': show_more,
+                               'history': history[:REDUCED_HISTORY_LEN],
+                              },
+                              context_instance=RequestContext(request))
diff --git a/ietf/templates/idrfc/status_columns.html b/ietf/templates/idrfc/status_columns.html
index 973db5844..00491018e 100644
--- a/ietf/templates/idrfc/status_columns.html
+++ b/ietf/templates/idrfc/status_columns.html
@@ -31,7 +31,7 @@ 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 %}{% load ballot_icon %}
+{% load ietf_filters ietf_streams %}{% load ballot_icon %}
 <td class="status">
 {{ doc.friendly_state|safe }} {% if not doc.rfc %}{{ doc.id|state_age_colored|safe }}{% endif %}
 {% if not hide_telechat_date %}{% if doc.telechat_date %}<br/>IESG Telechat: {{ doc.telechat_date }}{% endif %}{% endif %}
@@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 {% if doc.rfc.has_errata %}<br /><a href="http://www.rfc-editor.org/errata_search.php?rfc={{doc.rfc.rfc_number}}" rel="nofollow">Errata</a>{% endif %}
 {% else %}{# not rfc #}
 {% if doc.id.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.id.draft_name}}">{{ doc.id.rfc_editor_state|escape }}</a>{% endif %}
+{% stream_state doc %}
 {% endif %}
 </td>
 <td class="ballot">
diff --git a/ietf/templates/ietfworkflows/stream_history.html b/ietf/templates/ietfworkflows/stream_history.html
new file mode 100644
index 000000000..ae4fb7b19
--- /dev/null
+++ b/ietf/templates/ietfworkflows/stream_history.html
@@ -0,0 +1,44 @@
+{% load ietf_streams %}
+<table class="ietf-ballot ietf-stream">
+<tr>
+  <td class="left">
+    {% if workflow %}
+      <ul class="ietf-stream-tag-list">
+      {% for tag in tags %}
+        <li{% if tag.setted %} class="tag_set"{% endif %}>{{ tag }}</li>
+      {% endfor %}
+      </ul>
+    {% endif %}
+  </td>
+  <td class="right">
+    <div class="ietf-stream-head">
+      {% if stream %}
+        <h2>
+          {{ stream|default:"Without stream" }}{% if stream.with_groups and streamed.group %} :: {{ streamed.group|default:"" }}{% endif %}
+        </h2>
+        <h3>Current state: {{ state|default:"None" }}</h3>
+      {% else %}
+        <h2>Without stream</h2>
+      {% endif %}
+    </div>
+
+    {% if history %}
+    <div class="ietf-stream-history">
+      {% if show_more %}
+        <p> Viewing the last 20 entries. <a href="">Show full log</a>.</p>
+      {% endif %}
+      {% for entry in history %}
+        {% workflow_history_entry entry %}
+      {% endfor %}
+      {% if show_more %}
+        <p> Viewing the last 20 entries. <a href="">Show full log</a>.</p>
+      {% endif %}
+    </div>
+    {% else %}
+    <p>
+        There is no stream history for this document.
+    </p>
+    {% endif %}
+  </td>
+</tr>
+</table>
diff --git a/ietf/templates/ietfworkflows/stream_state.html b/ietf/templates/ietfworkflows/stream_state.html
new file mode 100644
index 000000000..f4ffbf4b3
--- /dev/null
+++ b/ietf/templates/ietfworkflows/stream_state.html
@@ -0,0 +1,10 @@
+{% if draft %}
+<div class="stream_state" style="border: 1px solid black; padding: 0px 5px; margin: 5px 0px;">
+<div class="stream_state_more" style="float: right; margin-left: 2em;"><a href="{% url stream_history draft.filename %}" class="show_stream_info" title="Stream information for {{ draft.filename }}" style="text-decoration: none; color: black;">+</a></div>
+{% if stream %}
+  {{ stream }} {% if state %}:: {{ state }}{% endif %}
+{% else %}
+  No stream asigned
+{% endif %}
+</div>
+{% endif %}
diff --git a/ietf/templates/ietfworkflows/workflow_history_entry.html b/ietf/templates/ietfworkflows/workflow_history_entry.html
new file mode 100644
index 000000000..a18302c44
--- /dev/null
+++ b/ietf/templates/ietfworkflows/workflow_history_entry.html
@@ -0,0 +1,12 @@
+<div class="workflow-history-entry workflow-history-entry-{{ entry_class }}">
+  <div class="entry-title">
+    <span class="entry-date">{{ entry.date }}</span>
+    {{ entry.person }}
+  </div>
+  <div class="entry-action">
+    {{ entry.describe_change|safe }}
+  </div>
+  <div class="entry-comment">
+    {{ entry.comment }}
+  </div>
+</pre>
diff --git a/ietf/urls.py b/ietf/urls.py
index 41ce897bc..c9b75c256 100644
--- a/ietf/urls.py
+++ b/ietf/urls.py
@@ -55,6 +55,7 @@ urlpatterns = patterns('',
     (r'^accounts/', include('ietf.ietfauth.urls')),
     (r'^doc/', include('ietf.idrfc.urls')),
     (r'^wg/', include('ietf.wginfo.urls')),
+    (r'^streams/', include('ietf.ietfworkflows.urls')),
 
     (r'^$', 'ietf.idrfc.views.main'),
     ('^admin/', include(admin.site.urls)),
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 2d12e1bd1..9132a9c63 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -363,7 +363,10 @@ class WriteUpEditForm(RelatedWGForm):
         if self.data.get('modify_tag', False):
             followup = self.cleaned_data.get('followup', False)
             comment = self.cleaned_data.get('comment', False)
-            shepherd = self.doc.shepherd
+            try:
+                shepherd = self.doc.shepherd
+            except PersonOrOrgInfo.DoesNotExist:
+                shepherd = None
             if shepherd:
                 extra_notify = ['%s <%s>' % shepherd.email()]
             else:
diff --git a/static/css/base2.css b/static/css/base2.css
index 96cecb3c7..0e719bbce 100644
--- a/static/css/base2.css
+++ b/static/css/base2.css
@@ -71,6 +71,17 @@ body { margin: 0; }
 .ietf-ballot .square { border:1px solid black; display: block; float:left;width: 10px; height:10px;font-size:1px;margin-right:4px; margin-top:1px;}
 .ietf-ballot .was { padding-left: 10px; font-size:85%; }
 
+.ietf-stream tr { vertical-align: top; }
+.ietf-stream ul.ietf-stream-tag-list { padding: 10px; margin: 0px; list-style-type: none; font-size: 10px; }
+.ietf-stream ul.ietf-stream-tag-list li { margin-bottom: 1em; list-style-type: circle; color: #999999; }
+.ietf-stream ul.ietf-stream-tag-list li.tag_set { font-weight: bold; list-style-type: disc; color: black; }
+.ietf-stream td.right { padding-top: 1em; }
+.ietf-stream .ietf-stream-head h2, .ietf-stream .ietf-stream-head h3 { margin: 0px; }
+.ietf-stream .ietf-stream-head { margin-bottom: 2em; }
+.ietf-stream .entry-title { background: #2647A0; color:white; padding: 2px 4px; font-size: 108%; margin-top: 0;}
+.ietf-stream .entry-title .entry-date { float: right; }
+.ietf-stream .entry-comment { background: #eeeeee; margin: 1em 0px; padding: 1em; }
+
 .search_form_box {width: 99.5%; margin-top:8px; padding:4px; margin-bottom:1em; padding-left:8px;}
 form#search_form { padding-top: 4px; padding-bottom: 4px; }
 #search_form input { padding: 0; padding-left: 2px; border: 1px solid #89d;}
diff --git a/static/js/base.js b/static/js/base.js
index a24ac900b..08cb5b853 100644
--- a/static/js/base.js
+++ b/static/js/base.js
@@ -74,3 +74,53 @@ function showBallot(draftName, editPositionUrl) {
 function editBallot(editPositionUrl) {
     window.open(editPositionUrl);
 }
+function showStream(dialogTitle, infoStreamUrl) {
+    var handleClose = function() {
+        IETF.streamDialog.hide();
+    };
+    var el;
+
+    if (!IETF.streamDialog) {
+        el = document.createElement("div");
+        el.innerHTML = '<div id="stream_dialog" style="visibility:hidden;"><div class="hd"><span id="stream_title">' + dialogTitle + '</span></div><div class="bd">  <div id="stream_dialog_body" style="overflow-y:scroll; height:400px;"></div>   </div></div>';
+        document.getElementById("ietf-extras").appendChild(el);
+
+        var buttons = [{text:"Close", handler:handleClose, isDefault:true}];
+	var kl = [new YAHOO.util.KeyListener(document, {keys:27}, handleClose)]						 
+        IETF.streamDialog = new YAHOO.widget.Dialog("stream_dialog", {
+            visible:false, draggable:false, close:true, modal:true,
+            width:"860px", fixedcenter:true, constraintoviewport:true,
+            buttons: buttons, keylisteners:kl});
+        IETF.streamDialog.render();
+    }
+    document.getElementById("stream_title").innerHTML = dialogTitle;
+    IETF.streamDialog.show();
+
+    el = document.getElementById("stream_dialog_body");
+    el.innerHTML = "Loading...";
+    YAHOO.util.Connect.asyncRequest('GET', 
+          infoStreamUrl,
+          { success: function(o) { el.innerHTML = (o.responseText !== undefined) ? o.responseText : "?"; }, 
+            failure: function(o) { el.innerHTML = "Error: "+o.status+" "+o.statusText; },
+            argument: null
+   	  }, null);
+}
+(function ($) {
+
+    $.fn.StreamInfo = function() {
+        return this.each(function () {
+            var infoStreamUrl = $(this).attr('href');
+            var title = $(this).attr('title');
+
+            $(this).click(function() {
+                showStream(title, infoStreamUrl);
+                return false;
+            });
+        });
+    };
+
+    $(document).ready(function () {
+        $('a.show_stream_info').StreamInfo();
+    });
+
+})(jQuery);

From 7a2302d37782cbfbf26eed4c9c5057bb2722fb43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 4 Feb 2011 11:55:10 +0000
Subject: [PATCH 44/57] Main views for state change and state history. See #582
  - Legacy-Id: 2811

---
 ietf/ietfworkflows/admin.py                   |   4 +-
 ietf/ietfworkflows/forms.py                   |  55 ++++++++++
 ietf/ietfworkflows/models.py                  |  12 ++-
 .../templatetags/ietf_streams.py              |   1 -
 ietf/ietfworkflows/urls.py                    |   3 +
 ietf/ietfworkflows/utils.py                   |  21 +++-
 ietf/ietfworkflows/views.py                   |  57 ++++++----
 ietf/templates/ietfworkflows/state_edit.html  | 101 ++++++++++++++++++
 8 files changed, 225 insertions(+), 29 deletions(-)
 create mode 100644 ietf/ietfworkflows/forms.py
 create mode 100644 ietf/templates/ietfworkflows/state_edit.html

diff --git a/ietf/ietfworkflows/admin.py b/ietf/ietfworkflows/admin.py
index d282d7aa9..0e9d7c6d7 100644
--- a/ietf/ietfworkflows/admin.py
+++ b/ietf/ietfworkflows/admin.py
@@ -4,12 +4,14 @@ from ietf.ietfworkflows.models import (AnnotationTag, WGWorkflow,
                                        Stream)
 from workflows.admin import StateInline
 
+
 class AnnotationTagInline(admin.TabularInline):
     model = AnnotationTag
 
+
 class IETFWorkflowAdmin(admin.ModelAdmin):
     inlines = [StateInline, AnnotationTagInline]
 
-    
+
 admin.site.register(WGWorkflow, IETFWorkflowAdmin)
 admin.site.register(Stream, admin.ModelAdmin)
diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
new file mode 100644
index 000000000..32cf9f399
--- /dev/null
+++ b/ietf/ietfworkflows/forms.py
@@ -0,0 +1,55 @@
+from django import forms
+
+from ietf.ietfworkflows.utils import (get_workflow_for_draft,
+                                      get_state_for_draft)
+
+
+class StreamDraftForm(forms.Form):
+
+    can_cancel = False
+
+    def __init__(self, *args, **kwargs):
+        self.draft = kwargs.pop('draft', None)
+        self.user = kwargs.pop('user', None)
+        self.message = {}
+        super(StreamDraftForm, self).__init__(*args, **kwargs)
+
+    def get_message(self):
+        return self.message
+
+    def set_message(self, msg_type, msg_value):
+        self.message = {'type': msg_type,
+                        'value': msg_value,
+                       }
+
+
+class DraftStateForm(StreamDraftForm):
+
+    comment = forms.CharField(widget=forms.Textarea)
+    new_state = forms.ChoiceField()
+    weeks = forms.IntegerField(required=False)
+
+    def __init__(self, *args, **kwargs):
+        super(DraftStateForm, self).__init__(*args, **kwargs)
+        if self.is_bound:
+            for key, value in self.data.items():
+                if key.startswith('transition_'):
+                    new_state = self.get_new_state(key)
+                    if new_state:
+                        self.data.update({'new_state': new_state.id})
+        self.workflow = get_workflow_for_draft(self.draft)
+        self.state = get_state_for_draft(self.draft)
+        self.fields['new_state'].choices = self.get_states()
+
+    def get_new_state(self, key):
+        transition_id = key.replace('transition_', '')
+        transition = self.get_transitions().filter(id=transition_id)
+        if transition:
+            return transition[0].destination
+        return None
+
+    def get_transitions(self):
+        return self.state.transitions.filter(workflow=self.workflow)
+
+    def get_states(self):
+        return [(i.pk, i.name) for i in self.workflow.get_states()]
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index ede8ac995..62701d979 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -20,9 +20,8 @@ class ObjectHistoryEntry(models.Model):
     class Meta:
         ordering = ('-date', )
 
-
     def get_real_instance(self):
-        if hasattr(self, '_real_instance'): 
+        if hasattr(self, '_real_instance'):
             return self._real_instance
         for i in ('objectworkflowhistoryentry', 'objectannotationtaghistoryentry', 'objectstreamhistoryentry'):
             try:
@@ -118,6 +117,13 @@ class WGWorkflow(Workflow):
         else:
             return self.selected_tags.all()
 
+    def get_states(self):
+        states = self.states.all()
+        if states.count():
+            return states
+        else:
+            return self.selected_states.all()
+
 
 class Stream(models.Model):
     name = models.CharField(_(u"Name"), max_length=100)
@@ -133,7 +139,7 @@ class Stream(models.Model):
 class StreamedID(models.Model):
     draft = models.OneToOneField(InternetDraft)
     stream = models.ForeignKey(Stream, blank=True, null=True)
-    
+
     content_type = models.ForeignKey(ContentType, verbose_name=_(u"Content type"), related_name="streamed_id", blank=True, null=True)
     content_id = models.PositiveIntegerField(_(u"Content id"), blank=True, null=True)
     group = generic.GenericForeignKey(ct_field="content_type", fk_field="content_id")
diff --git a/ietf/ietfworkflows/templatetags/ietf_streams.py b/ietf/ietfworkflows/templatetags/ietf_streams.py
index f18d7709e..b26963b9f 100644
--- a/ietf/ietfworkflows/templatetags/ietf_streams.py
+++ b/ietf/ietfworkflows/templatetags/ietf_streams.py
@@ -12,7 +12,6 @@ register = template.Library()
 @register.inclusion_tag('ietfworkflows/stream_state.html', takes_context=True)
 def stream_state(context, doc):
     request = context.get('request', None)
-    user = request and request.user
     data = {}
     stream = get_stream_from_wrapper(doc)
     data.update({'stream': stream})
diff --git a/ietf/ietfworkflows/urls.py b/ietf/ietfworkflows/urls.py
index 7ea564563..7b93d997d 100644
--- a/ietf/ietfworkflows/urls.py
+++ b/ietf/ietfworkflows/urls.py
@@ -4,4 +4,7 @@ from django.conf.urls.defaults import patterns, url
 
 urlpatterns = patterns('ietf.ietfworkflows.views',
      url(r'^(?P<name>[^/]+)/history/$', 'stream_history', name='stream_history'),
+     url(r'^(?P<name>[^/]+)/edit/state/$', 'edit_state', name='edit_state'),
+     url(r'^(?P<name>[^/]+)/edit/tags/$', 'edit_tags', name='edit_tags'),
+     url(r'^(?P<name>[^/]+)/edit/stream/$', 'edit_stream', name='edit_stream'),
 )
diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 3f40b614b..821c53eda 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -10,7 +10,7 @@ from workflows.models import State
 from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
                              get_state)
 
-from ietf.ietfworkflows.streams import get_streamed_draft
+from ietf.ietfworkflows.streams import get_streamed_draft, get_stream_from_draft
 from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
                                        AnnotationTag, ObjectAnnotationTagHistoryEntry,
                                        ObjectHistoryEntry)
@@ -96,9 +96,13 @@ def get_workflow_for_draft(draft):
     return workflow
 
 
-def get_workflow_history_for_draft(draft):
+def get_workflow_history_for_draft(draft, entry_type=None):
     ctype = ContentType.objects.get_for_model(draft)
-    history = ObjectHistoryEntry.objects.filter(content_type=ctype, content_id=draft.pk).\
+    filter_param = {'content_type': ctype,
+                    'content_id': draft.pk}
+    if entry_type:
+        filter_param.update({'%s__isnull' % entry_type: False})
+    history = ObjectHistoryEntry.objects.filter(**filter_param).\
         select_related('objectworkflowhistoryentry', 'objectannotationtaghistoryentry',
                        'objectstreamhistoryentry')
     return history
@@ -207,3 +211,14 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[
         comment=comment,
         person=person)
     notify_tag_entry(entry, extra_notify)
+
+
+def get_full_info_for_draft(draft):
+    return dict(
+        streamed = get_streamed_draft(draft),
+        stream = get_stream_from_draft(draft),
+        workflow = get_workflow_for_draft(draft),
+        tags = [i.annotation_tag for i in get_annotation_tags_for_draft(draft)],
+        state = get_state_for_draft(draft),
+        shepherd = draft.shepherd,
+    )
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
index a11f3b3d0..a278ba03b 100644
--- a/ietf/ietfworkflows/views.py
+++ b/ietf/ietfworkflows/views.py
@@ -1,37 +1,20 @@
-from ietf.idtracker.models import IETFWG, InternetDraft, IESGLogin
+from ietf.idtracker.models import InternetDraft
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
-from django.http import HttpResponseForbidden, Http404
 
-from ietf.idrfc.views_search import SearchForm, search_query
-from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
-                                 workflow_form_factory, TransitionFormSet,
-                                 WriteUpEditForm)
-from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
-                                    can_manage_shepherds_in_group,
-                                    can_manage_workflow_in_group,
-                                    can_manage_shepherd_of_a_document,
-                                    can_manage_writeup_of_a_document,
-                                    can_manage_writeup_of_a_document_no_state,
-                                    )
+from ietf.ietfworkflows.forms import DraftStateForm
 from ietf.ietfworkflows.streams import (get_stream_from_draft,
                                         get_streamed_draft)
-from ietf.ietfworkflows.utils import (get_workflow_for_wg,
-                                      get_default_workflow_for_wg,
-                                      get_workflow_history_for_draft,
+from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
                                       get_workflow_for_draft,
-                                      get_state_by_name,
                                       get_annotation_tags_for_draft,
-                                      get_state_for_draft, WAITING_WRITEUP,
-                                      FOLLOWUP_TAG)
+                                      get_state_for_draft)
 
 
 REDUCED_HISTORY_LEN = 20
 
 
 def stream_history(request, name):
-    user = request.user
-    person = get_person_for_user(user)
     draft = get_object_or_404(InternetDraft, filename=name)
     streamed = get_streamed_draft(draft)
     stream = get_stream_from_draft(draft)
@@ -58,3 +41,35 @@ def stream_history(request, name):
                                'history': history[:REDUCED_HISTORY_LEN],
                               },
                               context_instance=RequestContext(request))
+
+
+def edit_state(request, name):
+    user = request.user
+    draft = get_object_or_404(InternetDraft, filename=name)
+    state = get_state_for_draft(draft)
+    stream = get_stream_from_draft(draft)
+    workflow = get_workflow_for_draft(draft)
+    history = get_workflow_history_for_draft(draft, 'objectworkflowhistoryentry')
+    tags = get_annotation_tags_for_draft(draft)
+    if request.method == 'POST':
+        form = DraftStateForm(user=user, draft=draft, data=request.POST)
+    else:
+        form = DraftStateForm(user=user, draft=draft)
+    return render_to_response('ietfworkflows/state_edit.html',
+                              {'draft': draft,
+                               'state': state,
+                               'stream': stream,
+                               'workflow': workflow,
+                               'history': history,
+                               'tags': tags,
+                               'form': form,
+                              },
+                              context_instance=RequestContext(request))
+
+
+def edit_tags(request, name):
+    pass
+
+
+def edit_stream(request, name):
+    pass
diff --git a/ietf/templates/ietfworkflows/state_edit.html b/ietf/templates/ietfworkflows/state_edit.html
new file mode 100644
index 000000000..a50f05c9f
--- /dev/null
+++ b/ietf/templates/ietfworkflows/state_edit.html
@@ -0,0 +1,101 @@
+{% extends "base.html" %}
+{% load ietf_streams %}
+
+{% block morecss %}
+table.state-history p { margin: 0px; }
+table.edit-form ul { padding: 0px; list-style-type: none; margin: 0px; margin-bottom: 2em; border-bottom: 1px dashed #cccccc; }
+table.edit-form ul li, table.edit-form div.free-change { padding: 10px 2em; }
+table.edit-form ul li.evenrow { background-color: #edf5ff; }
+table.edit-form textarea { width: 95%; height: 120px; }
+table.edit-form span.required { color: red; }
+table.edit-form ul.errorlist { border-width: 0px; padding: 0px; margin: 0px;}
+table.edit-form ul.errorlist li { color: red; margin: 0px; padding: 0px;}
+table.edit-form div.field { margin-bottom: 1em; }
+table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; padding: 5px 10px; }
+{% endblock morecss %}
+
+{% block title %}Change state for {{ draft }}{% endblock %}
+
+{% block content %}
+<h1>Change state for {{ draft }}</h1>
+
+<table class="ietf-table" style="width: 100%;">
+<tr>
+<th>Current stream</th>
+<th>Current state</th>
+<th>Annotation tags</th>
+</tr>
+<tr><td style="width: 25%;">
+ {{ state.name|default:"None" }}
+</td>
+<td style="width: 25%;">
+ {{ state.name|default:"None" }}
+</td><td>
+<ul style="list-style-type: none; padding: 0px;">
+  {% for tag in tags%}
+    <li>{{ tag.annotation_tag }}</li>
+  {% endfor %}
+</ul>
+</td></tr></table>
+<br />
+
+<form action="" method="post">
+<table class="ietf-table edit-form" style="width: 100%;">
+<tr>
+  <th>1. Input information about change</th>
+  <th>2. Select the new state</th>
+</tr>
+<tr><td style="width: 50%;">
+    <div class="field{% if form.errors.comment %} error{% endif %}">
+    {{ form.errors.comment }}
+    Comment: <span class="required">*</span><br />
+    <textarea name="comment">{{ form.data.comment }}</textarea>
+    </div>
+    <div class="field{% if form.errors.weeks %} error{% endif %}">
+    {{ form.errors.weeks }}
+    Estimated time in next status:<br />
+    <input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
+    </div>
+</td><td style="padding: 0px; vertical-align: top;">
+  {% with form.get_transitions as transitions %}
+  {% if transitions %}
+  <ul>
+    {% for transition in transitions %}
+    <li class="{% cycle oddrow,evenrow %}">
+    <input type="submit" name="transition_{{ transition.pk }}" value="{{ transition.name }}" />
+    Changes state to: <strong>{{ transition.destination.name }}</strong>
+    </li>
+    {% endfor %}
+  {% endif %}
+  </ul>
+  {% endwith %}
+    <div class="free-change field{% if form.errors.new_state %} error{% endif %}">
+      {{ form.errors.new_state }}
+      <select name="new_state">
+        {% for value, name in form.get_states %}
+          <option value="{{ value }}">{{ name }}</option>
+        {% endfor %}
+      </select>
+      <input type="submit" name="change" value="State change" />
+    </div>
+</td></tr>
+</table>
+</form>
+
+<br />
+<strong>State history</strong>
+<table class="ietf-table state-history" style="width: 100%">
+    {% if history %}
+    <tr><th>Date</th><th>Person</th><th>Change</th><th>Comment</th></tr>
+    {% for baseentry in history %}
+    {% with baseentry.get_real_instance as entry %}
+    <tr class="{% cycle oddrow,evenrow %}"><td>{{ entry.date }}</td><td>{{ entry.person }}</td>
+        <td>{{ entry.describe_change|safe }}</td><td>{{ entry.comment }}</td>
+    </tr>
+    {% endwith %}
+    {% endfor %}
+    {% else %}
+    <tr><td>There is no state history for this document.</td></th>
+    {% endif %}
+</table>
+{% endblock %}

From 3f13f31569ca34c21ac40cdc7c01063f6df40080 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 7 Feb 2011 09:56:11 +0000
Subject: [PATCH 45/57] Allow to change the state of a draft and store the
 information about that change. Fixes #542  - Legacy-Id: 2815

---
 ietf/ietfworkflows/utils.py | 51 ++++++++++++++++++++++++++++++-------
 1 file changed, 42 insertions(+), 9 deletions(-)

diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 821c53eda..cd5d3c3a9 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -6,14 +6,15 @@ from django.contrib.contenttypes.models import ContentType
 from django.core.mail import EmailMessage
 from django.template.loader import render_to_string
 
-from workflows.models import State
+from workflows.models import State, StateObjectRelation
 from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
-                             get_state)
+                             get_state, set_state)
 
 from ietf.ietfworkflows.streams import get_streamed_draft, get_stream_from_draft
 from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
                                        AnnotationTag, ObjectAnnotationTagHistoryEntry,
-                                       ObjectHistoryEntry)
+                                       ObjectHistoryEntry, StateObjectRelationMetadata,
+                                       ObjectWorkflowHistoryEntry)
 
 
 WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
@@ -161,6 +162,21 @@ def reset_tag_by_name(obj, tag_name):
         return False
 
 
+def set_state_for_draft(draft, state, estimated_date=None):
+    workflow = get_workflow_for_draft(draft)
+    if state in workflow.get_states():
+        set_state(draft, state)
+        relation = StateObjectRelation.objects.get(
+            content_type=ContentType.objects.get_for_model(draft),
+            content_id=draft.pk)
+        metadata = StateObjectRelationMetadata.objects.get_or_create(relation=relation)[0]
+        metadata.from_date = datetime.date.today()
+        metadata.to_date = estimated_date
+        metadata.save()
+        return state
+    return False
+
+
 def notify_entry(entry, template, extra_notify=[]):
     doc = entry.content
     wg = doc.group.ietfwg
@@ -213,12 +229,29 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[
     notify_tag_entry(entry, extra_notify)
 
 
+def update_state(obj, comment, person, to_state, estimated_date=None, extra_notify=[]):
+    ctype = ContentType.objects.get_for_model(obj)
+    from_state = get_state_for_draft(obj)
+    to_state = set_state_for_draft(obj, to_state, estimated_date)
+    if not to_state:
+        return False
+    entry = ObjectWorkflowHistoryEntry.objects.create(
+        content_type=ctype,
+        content_id=obj.pk,
+        from_state=from_state.name,
+        to_state=to_state.name,
+        date=datetime.datetime.now(),
+        comment=comment,
+        person=person)
+    notify_state_entry(entry, extra_notify)
+
+
 def get_full_info_for_draft(draft):
     return dict(
-        streamed = get_streamed_draft(draft),
-        stream = get_stream_from_draft(draft),
-        workflow = get_workflow_for_draft(draft),
-        tags = [i.annotation_tag for i in get_annotation_tags_for_draft(draft)],
-        state = get_state_for_draft(draft),
-        shepherd = draft.shepherd,
+        streamed=get_streamed_draft(draft),
+        stream=get_stream_from_draft(draft),
+        workflow=get_workflow_for_draft(draft),
+        tags=[i.annotation_tag for i in get_annotation_tags_for_draft(draft)],
+        state=get_state_for_draft(draft),
+        shepherd=draft.shepherd,
     )

From 53ad5be094f7ac0ff653e6ec32594c6b2129630a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 7 Feb 2011 19:44:54 +0000
Subject: [PATCH 46/57] Allow to edit the stream/state/tags of a draft. Fixes
 #582  - Legacy-Id: 2818

---
 ietf/idrfc/views_doc.py                       |  21 +-
 ietf/ietfworkflows/forms.py                   | 105 +++++++++-
 .../0009_allow_null_in_from_date.py           | 197 ++++++++++++++++++
 ietf/ietfworkflows/models.py                  |   2 +-
 ietf/ietfworkflows/streams.py                 |  12 ++
 ietf/ietfworkflows/utils.py                   |  80 +++++--
 ietf/ietfworkflows/views.py                   |  20 +-
 ietf/templates/idrfc/doc_main_id.html         |  21 +-
 ietf/templates/ietfworkflows/state_edit.html  |  50 +----
 ietf/templates/ietfworkflows/state_form.html  |  42 ++++
 ietf/templates/ietfworkflows/stream_form.html |  21 ++
 .../ietfworkflows/stream_updated_mail.txt     |   9 +
 ietf/templates/ietfworkflows/tags_form.html   |  22 ++
 13 files changed, 496 insertions(+), 106 deletions(-)
 create mode 100644 ietf/ietfworkflows/migrations/0009_allow_null_in_from_date.py
 create mode 100644 ietf/templates/ietfworkflows/state_form.html
 create mode 100644 ietf/templates/ietfworkflows/stream_form.html
 create mode 100644 ietf/templates/ietfworkflows/stream_updated_mail.txt
 create mode 100644 ietf/templates/ietfworkflows/tags_form.html

diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index ca29b988a..e837e4f96 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -48,6 +48,7 @@ from ietf.idtracker.templatetags.ietf_filters import format_textarea, fill
 from ietf.idrfc import markup_txt
 from ietf.idrfc.models import RfcIndex, DraftVersions
 from ietf.idrfc.idrfc_wrapper import BallotWrapper, IdWrapper, RfcWrapper
+from ietf.ietfworkflows.utils import get_full_info_for_draft
 
 def document_debug(request, name):
     r = re.compile("^rfc([1-9][0-9]*)$")
@@ -109,25 +110,6 @@ def document_main(request, name):
     doc = IdWrapper(id) 
     
     info = {}
-    stream_id = doc.stream_id()
-    if stream_id == 2:
-        stream = " (IAB document)"
-    elif stream_id == 3:
-        stream = " (IRTF document)"
-    elif stream_id == 4:
-        stream = " (Independent submission via RFC Editor)"
-    elif doc.group_acronym():
-        stream = " ("+doc.group_acronym().upper()+" WG document)"
-    else:
-        stream = " (Individual document)"
-        
-    if id.status.status == "Active":
-        info['is_active_draft'] = True
-        info['type'] = "Active Internet-Draft"+stream
-    else:
-        info['is_active_draft'] = False
-        info['type'] = "Old Internet-Draft"+stream
-
     info['has_pdf'] = (".pdf" in doc.file_types())
     info['is_rfc'] = False
     
@@ -141,6 +123,7 @@ def document_main(request, name):
     return render_to_response('idrfc/doc_main_id.html',
                               {'content1':content1, 'content2':content2,
                                'doc':doc, 'info':info, 
+                               'stream_info': get_full_info_for_draft(id),
                                'versions':versions, 'history':history},
                               context_instance=RequestContext(request));
 
diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
index 32cf9f399..4622fa9ee 100644
--- a/ietf/ietfworkflows/forms.py
+++ b/ietf/ietfworkflows/forms.py
@@ -1,16 +1,31 @@
-from django import forms
+import datetime
 
+
+from django import forms
+from django.template.loader import render_to_string
+from workflows.models import State
+
+from ietf.idtracker.models import PersonOrOrgInfo
+from ietf.wgchairs.accounts import get_person_for_user
+from ietf.ietfworkflows.models import Stream
 from ietf.ietfworkflows.utils import (get_workflow_for_draft,
-                                      get_state_for_draft)
+                                      get_state_for_draft,
+                                      update_state, FOLLOWUP_TAG,
+                                      get_annotation_tags_for_draft,
+                                      update_tags, update_stream)
+from ietf.ietfworkflows.streams import get_stream_from_draft
 
 
 class StreamDraftForm(forms.Form):
 
     can_cancel = False
+    template = None
 
     def __init__(self, *args, **kwargs):
         self.draft = kwargs.pop('draft', None)
         self.user = kwargs.pop('user', None)
+        self.person = get_person_for_user(self.user)
+        self.workflow = get_workflow_for_draft(self.draft)
         self.message = {}
         super(StreamDraftForm, self).__init__(*args, **kwargs)
 
@@ -22,6 +37,9 @@ class StreamDraftForm(forms.Form):
                         'value': msg_value,
                        }
 
+    def __unicode__(self):
+        return render_to_string(self.template, {'form': self})
+
 
 class DraftStateForm(StreamDraftForm):
 
@@ -29,17 +47,19 @@ class DraftStateForm(StreamDraftForm):
     new_state = forms.ChoiceField()
     weeks = forms.IntegerField(required=False)
 
+    template = 'ietfworkflows/state_form.html'
+
     def __init__(self, *args, **kwargs):
         super(DraftStateForm, self).__init__(*args, **kwargs)
+        self.state = get_state_for_draft(self.draft)
+        self.fields['new_state'].choices = self.get_states()
         if self.is_bound:
             for key, value in self.data.items():
                 if key.startswith('transition_'):
                     new_state = self.get_new_state(key)
                     if new_state:
+                        self.data = self.data.copy()
                         self.data.update({'new_state': new_state.id})
-        self.workflow = get_workflow_for_draft(self.draft)
-        self.state = get_state_for_draft(self.draft)
-        self.fields['new_state'].choices = self.get_states()
 
     def get_new_state(self, key):
         transition_id = key.replace('transition_', '')
@@ -53,3 +73,78 @@ class DraftStateForm(StreamDraftForm):
 
     def get_states(self):
         return [(i.pk, i.name) for i in self.workflow.get_states()]
+
+    def save(self):
+        comment = self.cleaned_data.get('comment')
+        state = State.objects.get(pk=self.cleaned_data.get('new_state'))
+        weeks = self.cleaned_data.get('weeks')
+        estimated_date = None
+        if weeks:
+            now = datetime.date.today()
+            estimated_date = now + datetime.timedelta(weeks=weeks)
+        update_state(obj=self.draft,
+                     comment=comment,
+                     person=self.person,
+                     to_state=state,
+                     estimated_date=estimated_date)
+
+
+class DraftTagsForm(StreamDraftForm):
+
+    comment = forms.CharField(widget=forms.Textarea)
+    tags = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, required=False)
+
+    template = 'ietfworkflows/tags_form.html'
+
+    def __init__(self, *args, **kwargs):
+        super(DraftTagsForm, self).__init__(*args, **kwargs)
+        self.available_tags = self.workflow.get_tags()
+        self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
+        self.fields['tags'].choices = [(i.pk, i.name) for i in self.available_tags]
+        self.fields['tags'].initial = [i.pk for i in self.tags]
+
+    def save(self):
+        comment = self.cleaned_data.get('comment')
+        new_tags = self.cleaned_data.get('tags')
+
+        set_tags = [tag for tag in self.available_tags if str(tag.pk) in new_tags and tag not in self.tags]
+        reset_tags = [tag for tag in self.available_tags if str(tag.pk) not in new_tags and tag in self.tags]
+        followup = bool([tag for tag in set_tags if tag.name == FOLLOWUP_TAG])
+        extra_notify = []
+        if followup:
+            try:
+                shepherd = self.draft.shepherd
+                if shepherd:
+                    extra_notify = ['%s <%s>' % shepherd.email()]
+            except PersonOrOrgInfo.DoesNotExist:
+                pass
+        update_tags(self.draft,
+                    comment=comment,
+                    person=self.person,
+                    set_tags=set_tags,
+                    reset_tags=reset_tags,
+                    extra_notify=extra_notify)
+
+
+class DraftStreamForm(StreamDraftForm):
+
+    comment = forms.CharField(widget=forms.Textarea)
+    stream = forms.ModelChoiceField(Stream.objects.all())
+
+    template = 'ietfworkflows/stream_form.html'
+
+    def __init__(self, *args, **kwargs):
+        super(DraftStreamForm, self).__init__(*args, **kwargs)
+        self.stream = get_stream_from_draft(self.draft)
+        self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
+        if self.stream:
+            self.fields['stream'].initial = self.stream.pk
+
+    def save(self):
+        comment = self.cleaned_data.get('comment')
+        to_stream = self.cleaned_data.get('stream')
+
+        update_stream(self.draft,
+                      comment=comment,
+                      person=self.person,
+                      to_stream=to_stream)
diff --git a/ietf/ietfworkflows/migrations/0009_allow_null_in_from_date.py b/ietf/ietfworkflows/migrations/0009_allow_null_in_from_date.py
new file mode 100644
index 000000000..b2c19c982
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0009_allow_null_in_from_date.py
@@ -0,0 +1,197 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Changing field 'StateObjectRelationMetadata.from_date'
+        # (to signature: django.db.models.fields.DateTimeField(null=True, blank=True))
+        db.alter_column('ietfworkflows_stateobjectrelationmetadata', 'from_date', orm['ietfworkflows.stateobjectrelationmetadata:from_date'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Changing field 'StateObjectRelationMetadata.from_date'
+        # (to signature: django.db.models.fields.DateTimeField())
+        db.alter_column('ietfworkflows_stateobjectrelationmetadata', 'from_date', orm['ietfworkflows.stateobjectrelationmetadata:from_date'])
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objecthistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"})
+        },
+        'ietfworkflows.objectstreamhistoryentry': {
+            'from_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'to_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.stream': {
+            'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
+        },
+        'ietfworkflows.streamedid': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'streamed_id'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']", 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index 62701d979..f0ded3a14 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -98,7 +98,7 @@ class AnnotationTagObjectRelation(models.Model):
 
 class StateObjectRelationMetadata(models.Model):
     relation = models.ForeignKey(StateObjectRelation)
-    from_date = models.DateTimeField(_('Initial date'))
+    from_date = models.DateTimeField(_('Initial date'), blank=True, null=True)
     estimated_date = models.DateTimeField(_('Estimated date'), blank=True, null=True)
 
 
diff --git a/ietf/ietfworkflows/streams.py b/ietf/ietfworkflows/streams.py
index 18ce32f36..503eba4f5 100644
--- a/ietf/ietfworkflows/streams.py
+++ b/ietf/ietfworkflows/streams.py
@@ -1,5 +1,7 @@
 from django.db import models
 
+from workflows.utils import set_workflow_for_object
+
 from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
 from ietf.ietfworkflows.models import StreamedID, Stream
 
@@ -82,3 +84,13 @@ def get_stream_from_wrapper(idrfc_wrapper):
     else:
         return stream
     return None
+
+
+def set_stream_for_draft(draft, stream):
+    (streamed, created) = StreamedID.objects.get_or_create(draft=draft)
+    if streamed.stream != stream:
+        streamed.stream = stream
+        streamed.group = None
+        streamed.save()
+        set_workflow_for_object(draft, stream.workflow)
+    return streamed.stream
diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index cd5d3c3a9..940e2a9c8 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -10,11 +10,12 @@ from workflows.models import State, StateObjectRelation
 from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
                              get_state, set_state)
 
-from ietf.ietfworkflows.streams import get_streamed_draft, get_stream_from_draft
+from ietf.ietfworkflows.streams import (get_streamed_draft, get_stream_from_draft,
+                                        set_stream_for_draft)
 from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
                                        AnnotationTag, ObjectAnnotationTagHistoryEntry,
                                        ObjectHistoryEntry, StateObjectRelationMetadata,
-                                       ObjectWorkflowHistoryEntry)
+                                       ObjectWorkflowHistoryEntry, ObjectStreamHistoryEntry)
 
 
 WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
@@ -133,23 +134,25 @@ def get_annotation_tag_by_name(tag_name):
         return None
 
 
-def set_tag_by_name(obj, tag_name):
+def set_tag(obj, tag):
     ctype = ContentType.objects.get_for_model(obj)
-    try:
-        tag = AnnotationTag.objects.get(name=tag_name)
-        (relation, created) = AnnotationTagObjectRelation.objects.get_or_create(
-            content_type=ctype,
-            content_id=obj.pk,
-            annotation_tag=tag)
-    except AnnotationTag.DoesNotExist:
-        return None
+    (relation, created) = AnnotationTagObjectRelation.objects.get_or_create(
+        content_type=ctype,
+        content_id=obj.pk,
+        annotation_tag=tag)
     return relation
 
-
-def reset_tag_by_name(obj, tag_name):
-    ctype = ContentType.objects.get_for_model(obj)
+def set_tag_by_name(obj, tag_name):
     try:
         tag = AnnotationTag.objects.get(name=tag_name)
+        return set_tag(obj, tag)
+    except AnnotationTag.DoesNotExist:
+        return None
+
+
+def reset_tag(obj, tag):
+    ctype = ContentType.objects.get_for_model(obj)
+    try:
         tag_relation = AnnotationTagObjectRelation.objects.get(
             content_type=ctype,
             content_id=obj.pk,
@@ -158,6 +161,12 @@ def reset_tag_by_name(obj, tag_name):
         return True
     except AnnotationTagObjectRelation.DoesNotExist:
         return False
+
+
+def reset_tag_by_name(obj, tag_name):
+    try:
+        tag = AnnotationTag.objects.get(name=tag_name)
+        return reset_tag(obj, tag)
     except AnnotationTag.DoesNotExist:
         return False
 
@@ -208,16 +217,28 @@ def notify_state_entry(entry, extra_notify=[]):
     return notify_entry(entry, 'ietfworkflows/state_updated_mail.txt', extra_notify)
 
 
+def notify_stream_entry(entry, extra_notify=[]):
+    return notify_entry(entry, 'ietfworkflows/stream_updated_mail.txt', extra_notify)
+
+
 def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
     ctype = ContentType.objects.get_for_model(obj)
     setted = []
     resetted = []
-    for name in set_tags:
-        if set_tag_by_name(obj, name):
-            setted.append(name)
-    for name in reset_tags:
-        if reset_tag_by_name(obj, name):
-            resetted.append(name)
+    for tag in set_tags:
+        if isinstance(tag, basestring):
+            if set_tag_by_name(obj, tag):
+                setted.append(tag)
+        else:
+            if set_tag(obj, tag):
+                setted.append(tag.name)
+    for tag in reset_tags:
+        if isinstance(tag, basestring):
+            if reset_tag_by_name(obj, tag):
+                resetted.append(tag)
+        else:
+            if reset_tag(obj, tag):
+                resetted.append(tag.name)
     entry = ObjectAnnotationTagHistoryEntry.objects.create(
         content_type=ctype,
         content_id=obj.pk,
@@ -238,14 +259,29 @@ def update_state(obj, comment, person, to_state, estimated_date=None, extra_noti
     entry = ObjectWorkflowHistoryEntry.objects.create(
         content_type=ctype,
         content_id=obj.pk,
-        from_state=from_state.name,
-        to_state=to_state.name,
+        from_state=from_state and from_state.name or '',
+        to_state=to_state and to_state.name or '',
         date=datetime.datetime.now(),
         comment=comment,
         person=person)
     notify_state_entry(entry, extra_notify)
 
 
+def update_stream(obj, comment, person, to_stream, extra_notify=[]):
+    ctype = ContentType.objects.get_for_model(obj)
+    from_stream = get_stream_from_draft(obj)
+    to_stream = set_stream_for_draft(obj, to_stream)
+    entry = ObjectStreamHistoryEntry.objects.create(
+        content_type=ctype,
+        content_id=obj.pk,
+        from_stream=from_stream and from_stream.name or '',
+        to_stream=to_stream and to_stream.name or '',
+        date=datetime.datetime.now(),
+        comment=comment,
+        person=person)
+    notify_stream_entry(entry, extra_notify)
+
+
 def get_full_info_for_draft(draft):
     return dict(
         streamed=get_streamed_draft(draft),
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
index a278ba03b..14456ae70 100644
--- a/ietf/ietfworkflows/views.py
+++ b/ietf/ietfworkflows/views.py
@@ -2,7 +2,8 @@ from ietf.idtracker.models import InternetDraft
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 
-from ietf.ietfworkflows.forms import DraftStateForm
+from ietf.ietfworkflows.forms import (DraftStateForm, DraftTagsForm,
+                                      DraftStreamForm)
 from ietf.ietfworkflows.streams import (get_stream_from_draft,
                                         get_streamed_draft)
 from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
@@ -43,18 +44,21 @@ def stream_history(request, name):
                               context_instance=RequestContext(request))
 
 
-def edit_state(request, name):
+def edit_state(request, name, form_class=DraftStateForm):
     user = request.user
     draft = get_object_or_404(InternetDraft, filename=name)
+    if request.method == 'POST':
+        form = form_class(user=user, draft=draft, data=request.POST)
+        if form.is_valid():
+            form.save()
+            form = form_class(user=user, draft=draft)
+    else:
+        form = form_class(user=user, draft=draft)
     state = get_state_for_draft(draft)
     stream = get_stream_from_draft(draft)
     workflow = get_workflow_for_draft(draft)
     history = get_workflow_history_for_draft(draft, 'objectworkflowhistoryentry')
     tags = get_annotation_tags_for_draft(draft)
-    if request.method == 'POST':
-        form = DraftStateForm(user=user, draft=draft, data=request.POST)
-    else:
-        form = DraftStateForm(user=user, draft=draft)
     return render_to_response('ietfworkflows/state_edit.html',
                               {'draft': draft,
                                'state': state,
@@ -68,8 +72,8 @@ def edit_state(request, name):
 
 
 def edit_tags(request, name):
-    pass
+    return edit_state(request, name, DraftTagsForm)
 
 
 def edit_stream(request, name):
-    pass
+    return edit_state(request, name, DraftStreamForm)
diff --git a/ietf/templates/idrfc/doc_main_id.html b/ietf/templates/idrfc/doc_main_id.html
index bfdadb966..8f9893b32 100644
--- a/ietf/templates/idrfc/doc_main_id.html
+++ b/ietf/templates/idrfc/doc_main_id.html
@@ -39,19 +39,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 {% block doc_h1 %}{{ doc.title|escape }}<br/>{{ doc.draft_name_and_revision }}{% endblock %}
 
 {% block doc_metatable %}
-<tr><td style="width:18ex;">Document type:</td><td>{{ info.type|escape }}
-{% with doc.replaces as r %}{% if r %}<br />Replaces {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %}
-</td></tr>
-<tr><td>Last updated:</td><td> {{ doc.publication_date|default:"(data missing)" }}</td></tr>
-<tr><td>State:</td><td> 
-{{ doc.friendly_state|safe }}
-{% if doc.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.draft_name}}">{{ doc.rfc_editor_state|escape }}</a>{% endif %}
+<tr><td>Document Stream:</td><td> {{ stream_info.stream.name|default:"No stream defined" }}</td></tr>
+<tr><td>I-D availability status:</td><td> {{ doc.draft_status }}
 {% ifequal doc.draft_status "Expired" %}
 {% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %}
 {% endifequal %}
+{% with doc.replaces as r %}{% if r %}<br />Replaces {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %}
+</td></tr>
+<tr><td>Last updated:</td><td> {{ doc.publication_date|default:"(data missing)" }}</td></tr>
+<tr><td>IETF WG status:</td><td>{{ stream_info.state.name }} ({{ stream_info.streamed.group }})
+{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}
+</td></tr>
+<tr><td>Intended RFC status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-"  }}{% else %}-{%endif%}</td></tr>
+<tr><td>Document shepherd:</td><td>{{ stream_info.shepherd }}</td></tr>
+<tr><td>IESG state:</td><td> 
+{{ doc.friendly_state|safe }}
+{% if doc.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.draft_name}}">{{ doc.rfc_editor_state|escape }}</a>{% endif %}
 {% if doc.in_ietf_process %}{% if doc.ietf_process.telechat_date %}<br/>On agenda of {{ doc.ietf_process.telechat_date }} IESG telechat {% if doc.ietf_process.telechat_returning_item %} (returning item){%endif%}{%endif%}{% if doc.ietf_process.has_active_iesg_ballot %}<br/><i>({{ doc.ietf_process.iesg_ballot_needed }})</i>{%endif%}{%endif%}
 </td></tr>
-<tr><td>Intended status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-"  }}{% else %}-{%endif%}</td></tr>
 <tr><td>Submission:</td><td>{{ doc.submission }}</td></tr>
 <tr><td>Responsible AD:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.ad_name|default:"-"|escape }}{%else%}-{%endif%}</td></tr>
 {% if doc.in_ietf_process and doc.ietf_process.iesg_note %}<tr><td>IESG Note:</td><td>{{ doc.ietf_process.iesg_note|format_textarea|safe }}</td></tr>{% endif %}
diff --git a/ietf/templates/ietfworkflows/state_edit.html b/ietf/templates/ietfworkflows/state_edit.html
index a50f05c9f..5572e9692 100644
--- a/ietf/templates/ietfworkflows/state_edit.html
+++ b/ietf/templates/ietfworkflows/state_edit.html
@@ -11,7 +11,12 @@ table.edit-form span.required { color: red; }
 table.edit-form ul.errorlist { border-width: 0px; padding: 0px; margin: 0px;}
 table.edit-form ul.errorlist li { color: red; margin: 0px; padding: 0px;}
 table.edit-form div.field { margin-bottom: 1em; }
+table.edit-form div.submit-row { margin: 1em 2em; }
 table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; padding: 5px 10px; }
+table.edit-form-tags tr { vertical-align: top; }
+table.edit-form-tags textarea { height: 200px; }
+table.edit-form-tags ul { border-width: 0px; padding: 1em 2em; }
+table.edit-form-tags ul li { padding: 0px; }
 {% endblock morecss %}
 
 {% block title %}Change state for {{ draft }}{% endblock %}
@@ -26,7 +31,7 @@ table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; pa
 <th>Annotation tags</th>
 </tr>
 <tr><td style="width: 25%;">
- {{ state.name|default:"None" }}
+ {{ stream.name|default:"None" }}
 </td>
 <td style="width: 25%;">
  {{ state.name|default:"None" }}
@@ -39,48 +44,7 @@ table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; pa
 </td></tr></table>
 <br />
 
-<form action="" method="post">
-<table class="ietf-table edit-form" style="width: 100%;">
-<tr>
-  <th>1. Input information about change</th>
-  <th>2. Select the new state</th>
-</tr>
-<tr><td style="width: 50%;">
-    <div class="field{% if form.errors.comment %} error{% endif %}">
-    {{ form.errors.comment }}
-    Comment: <span class="required">*</span><br />
-    <textarea name="comment">{{ form.data.comment }}</textarea>
-    </div>
-    <div class="field{% if form.errors.weeks %} error{% endif %}">
-    {{ form.errors.weeks }}
-    Estimated time in next status:<br />
-    <input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
-    </div>
-</td><td style="padding: 0px; vertical-align: top;">
-  {% with form.get_transitions as transitions %}
-  {% if transitions %}
-  <ul>
-    {% for transition in transitions %}
-    <li class="{% cycle oddrow,evenrow %}">
-    <input type="submit" name="transition_{{ transition.pk }}" value="{{ transition.name }}" />
-    Changes state to: <strong>{{ transition.destination.name }}</strong>
-    </li>
-    {% endfor %}
-  {% endif %}
-  </ul>
-  {% endwith %}
-    <div class="free-change field{% if form.errors.new_state %} error{% endif %}">
-      {{ form.errors.new_state }}
-      <select name="new_state">
-        {% for value, name in form.get_states %}
-          <option value="{{ value }}">{{ name }}</option>
-        {% endfor %}
-      </select>
-      <input type="submit" name="change" value="State change" />
-    </div>
-</td></tr>
-</table>
-</form>
+{{ form }}
 
 <br />
 <strong>State history</strong>
diff --git a/ietf/templates/ietfworkflows/state_form.html b/ietf/templates/ietfworkflows/state_form.html
new file mode 100644
index 000000000..4ae92dc42
--- /dev/null
+++ b/ietf/templates/ietfworkflows/state_form.html
@@ -0,0 +1,42 @@
+<form action="" method="post">
+<table class="ietf-table edit-form" style="width: 100%;">
+<tr>
+  <th>1. Input information about change</th>
+  <th>2. Select the new state</th>
+</tr>
+<tr><td style="width: 50%;">
+    <div class="field{% if form.errors.comment %} error{% endif %}">
+    {{ form.errors.comment }}
+    Comment: <span class="required">*</span><br />
+    <textarea name="comment">{{ form.data.comment }}</textarea>
+    </div>
+    <div class="field{% if form.errors.weeks %} error{% endif %}">
+    {{ form.errors.weeks }}
+    Estimated time in next status:<br />
+    <input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
+    </div>
+</td><td style="padding: 0px; vertical-align: top;">
+  {% with form.get_transitions as transitions %}
+  {% if transitions %}
+  <ul>
+    {% for transition in transitions %}
+    <li class="{% cycle oddrow,evenrow %}">
+    <input type="submit" name="transition_{{ transition.pk }}" value="{{ transition.name }}" />
+    Changes state to: <strong>{{ transition.destination.name }}</strong>
+    </li>
+    {% endfor %}
+  {% endif %}
+  </ul>
+  {% endwith %}
+    <div class="free-change field{% if form.errors.new_state %} error{% endif %}">
+      {{ form.errors.new_state }}
+      <select name="new_state">
+        {% for value, name in form.get_states %}
+          <option value="{{ value }}">{{ name }}</option>
+        {% endfor %}
+      </select>
+      <input type="submit" name="change" value="State change" />
+    </div>
+</td></tr>
+</table>
+</form>
diff --git a/ietf/templates/ietfworkflows/stream_form.html b/ietf/templates/ietfworkflows/stream_form.html
new file mode 100644
index 000000000..272fb4152
--- /dev/null
+++ b/ietf/templates/ietfworkflows/stream_form.html
@@ -0,0 +1,21 @@
+<form action="" method="post">
+<table class="ietf-table edit-form" style="width: 100%;">
+<tr>
+  <th>Select the new stream</th>
+</tr>
+<tr><td>
+    <div class="field{% if form.errors.comment %} error{% endif %}">
+    {{ form.errors.comment }}
+    Comment: <span class="required">*</span><br />
+    <textarea name="comment">{{ form.data.comment }}</textarea>
+    </div>
+</td>
+</tr>
+<td>
+    <div class="field{% if form.errors.stream %} error{% endif %}">
+    {{ form.errors.stream }}
+    {{ form.stream }} <input type="submit" value="Change stream" />
+    </div>
+</td></tr>
+</table>
+</form>
diff --git a/ietf/templates/ietfworkflows/stream_updated_mail.txt b/ietf/templates/ietfworkflows/stream_updated_mail.txt
new file mode 100644
index 000000000..23299af5a
--- /dev/null
+++ b/ietf/templates/ietfworkflows/stream_updated_mail.txt
@@ -0,0 +1,9 @@
+The stream of document {{ doc }} has been updated. See more information below.
+
+Previous stream: {{ entry.from_stream }}
+Current stream: {{ entry.to_stream }}
+Transition date: {{ entry.transition_date }}
+Author of the change: {{ entry.person }}
+
+Comment:
+{{ entry.comment }}
diff --git a/ietf/templates/ietfworkflows/tags_form.html b/ietf/templates/ietfworkflows/tags_form.html
new file mode 100644
index 000000000..e3388a868
--- /dev/null
+++ b/ietf/templates/ietfworkflows/tags_form.html
@@ -0,0 +1,22 @@
+<form action="" method="post">
+<table class="ietf-table edit-form edit-form-tags" style="width: 100%;">
+<tr>
+  <th>1. Input information about change</th>
+  <th>2. Select annotation tags</th>
+</tr>
+<tr><td style="width: 50%;">
+    <div class="field comment{% if form.errors.comment %} error{% endif %}">
+    {{ form.errors.comment }}
+    Comment: <span class="required">*</span><br />
+    <textarea name="comment">{{ form.data.comment }}</textarea>
+    </div>
+</td><td style="padding: 0px; vertical-align: top;">
+  <div class="field">
+    {{ form.tags }}
+  </div>
+  <div class="submit-row">
+  <input type="submit" value="Edit tags" />
+  </div>
+</td></tr>
+</table>
+</form>

From 7e8d51e40533863e4d9fd17a7d0d498b99c15659 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 3 Mar 2011 11:35:10 +0000
Subject: [PATCH 47/57] Included MAX_WG_DELEGATES into settings.py Some minor
 textual changes in templates. Fixes #605  - Legacy-Id: 2874

---
 ietf/settings.py                                    |  3 +++
 ietf/templates/wgchairs/manage_delegates.html       | 11 +++++++++--
 ietf/templates/wgchairs/wg_shepherd_documents.html  |  6 +++---
 ietf/templates/wgchairs/wgchairs_admin_options.html |  4 ++--
 ietf/wgchairs/accounts.py                           |  5 ++---
 ietf/wgchairs/forms.py                              |  6 ++----
 ietf/wgchairs/models.py                             |  4 ++--
 ietf/wgchairs/views.py                              | 11 +++++++----
 8 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/ietf/settings.py b/ietf/settings.py
index 717c8328f..0a84060e8 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -155,6 +155,9 @@ SERVER_MODE = 'development'
 # The name of the method to use to invoke the test suite
 TEST_RUNNER = 'ietf.utils.test_runner.run_tests'
 
+# WG Chair configuration
+MAX_WG_DELEGATES = 5
+
 # Override this in settings_local.py if needed
 # *_PATH variables ends with a slash/ .
 INTERNET_DRAFT_PATH = '/a/www/ietf-ftp/internet-drafts/'
diff --git a/ietf/templates/wgchairs/manage_delegates.html b/ietf/templates/wgchairs/manage_delegates.html
index fb58fc0fe..f1f2eaf58 100644
--- a/ietf/templates/wgchairs/manage_delegates.html
+++ b/ietf/templates/wgchairs/manage_delegates.html
@@ -7,7 +7,14 @@
 <div class="wg-chair-management">
 
 <h2>Manage delegates</h2>
-<p><strong>Please,</strong> rembember that you only can assign a maximum of three delegates.</p>
+<p>
+Sometimes, a WG has one (or more) WG Secretaries, in addition to the WG Chairs. 
+This page lets the WG Chairs delegate the authority to do updates to the WG state of WG documents in the datatracker.
+</p>
+
+<p>
+You may at most delegate the datatracker update rights to {{ max_delegates }} persons at any given time.
+</p>
 
 <table style="width: 100%;">
 <tr style="vertical-align: top;"><td>
@@ -43,7 +50,7 @@ No delegates
     </p>
   </form>
 {% else %}
-You can only assign three delegates. Please remove delegates to add a new one.
+You can only assign {{ max_delegates }} delegates. Please remove delegates to add a new one.
 {% endif %}
 </td></tr>
 </table>
diff --git a/ietf/templates/wgchairs/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
index 9cb5fff36..0bbbc1074 100644
--- a/ietf/templates/wgchairs/wg_shepherd_documents.html
+++ b/ietf/templates/wgchairs/wg_shepherd_documents.html
@@ -43,12 +43,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 {% block wg_content %}
            
-<h2>Documets by its shepherd</h2>
+<h2>Documents by its shepherd</h2>
 <div id="mytabs" class="yui-navset">
 <ul class="yui-nav">
   <li class="selected"><a href="#noshepherd"><em>Without shepherd</em></a></li>
-  <li><a href="#mydocs"><em>I Shepherd</em></a></li>
-  <li><a href="#othershepherds"><em>Other shepherds</em></a></li>
+  <li><a href="#mydocs"><em>Shepherded by me</em></a></li>
+  <li><a href="#othershepherds"><em>Shepherded by others</em></a></li>
 </ul>
 
 <div class="yui-content">
diff --git a/ietf/templates/wgchairs/wgchairs_admin_options.html b/ietf/templates/wgchairs/wgchairs_admin_options.html
index 7df492513..b8c45a00f 100644
--- a/ietf/templates/wgchairs/wgchairs_admin_options.html
+++ b/ietf/templates/wgchairs/wgchairs_admin_options.html
@@ -8,9 +8,9 @@
 
 {% if can_manage_delegates %}
   {% ifequal selected "manage_delegates" %}
-    <span class="selected">Manage delegates</span>
+    <span class="selected">Manage delegations</span>
   {% else %}
-    <a href="{% url manage_delegates wg.group_acronym.acronym %}">Manage delegates</a>
+    <a href="{% url manage_delegates wg.group_acronym.acronym %}">Manage delegations</a>
   {% endifequal %} |
 {% endif %}
 
diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
index dd8ec110b..f70060fc6 100644
--- a/ietf/wgchairs/accounts.py
+++ b/ietf/wgchairs/accounts.py
@@ -71,7 +71,7 @@ def can_manage_writeup_of_a_document_no_state(user, document):
         return False
     group = document.group.ietfwg
     return (is_group_chair(person, group) or
-            is_areadirector_for_group(person, group) or
+            is_area_director_for_group(person, group) or
             is_group_delegate(person, group))
 
 
@@ -79,6 +79,5 @@ def can_manage_writeup_of_a_document(user, document):
     person = get_person_for_user(user)
     if not person or not document.group:
         return False
-    group = document.group.ietfwg
     return (can_manage_writeup_of_a_document_no_state(user, document) or
-            is_document_shepherd(person, doc))
+            is_document_shepherd(person, document))
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 9132a9c63..89ec4b081 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -1,5 +1,4 @@
 from django import forms
-from django.db.models import Q
 from django.conf import settings
 from django.core.mail import EmailMessage
 from django.forms.models import BaseModelFormSet
@@ -337,14 +336,13 @@ class WriteUpEditForm(RelatedWGForm):
     followup = forms.BooleanField(required=False)
     comment = forms.CharField(widget=forms.Textarea, required=False)
 
-
     def __init__(self, *args, **kwargs):
         self.doc = kwargs.pop('doc', None)
         self.doc_writeup = self.doc.protowriteup_set.all()
         if self.doc_writeup.count():
-            self.doc_writeup=self.doc_writeup[0]
+            self.doc_writeup = self.doc_writeup[0]
         else:
-            self.doc_writeup=None
+            self.doc_writeup = None
         super(WriteUpEditForm, self).__init__(*args, **kwargs)
         self.person = get_person_for_user(self.user)
 
diff --git a/ietf/wgchairs/models.py b/ietf/wgchairs/models.py
index b40748908..3b57827e3 100644
--- a/ietf/wgchairs/models.py
+++ b/ietf/wgchairs/models.py
@@ -40,6 +40,6 @@ class ProtoWriteUp(models.Model):
         )
 
     writeup = models.TextField(
-        blank = False,
-        null = False,
+        blank=False,
+        null=False,
         )
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index b03c91b29..ac1990b6a 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -1,4 +1,5 @@
-from ietf.idtracker.models import IETFWG, InternetDraft, IESGLogin
+from django.conf import settings
+from ietf.idtracker.models import IETFWG, InternetDraft
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 from django.http import HttpResponseForbidden, Http404
@@ -37,11 +38,13 @@ def manage_delegates(request, acronym):
         elif add_form.is_valid():
             add_form.save()
             add_form = add_form.get_next_form()
+    max_delegates = getattr(settings, 'MAX_WG_DELEGATES', 3)
     return render_to_response('wgchairs/manage_delegates.html',
                               {'wg': wg,
                                'delegates': delegates,
                                'selected': 'manage_delegates',
-                               'can_add': delegates.count() < 3,
+                               'can_add': delegates.count() < max_delegates,
+                               'max_delegates': max_delegates,
                                'add_form': add_form,
                               }, RequestContext(request))
 
@@ -145,16 +148,16 @@ def wg_shepherd_documents(request, acronym):
     }
     return render_to_response('wgchairs/wg_shepherd_documents.html', context, RequestContext(request))
 
+
 def managing_writeup(request, acronym, name):
     wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
     user = request.user
-    person = get_person_for_user(user)
     doc = get_object_or_404(InternetDraft, filename=name)
     if not can_manage_writeup_of_a_document(user, doc):
         raise Http404
     current_state = get_state_for_draft(doc)
     can_edit = True
-    if current_state != get_state_by_name(WAITING_WRITEUP) and not can_manage_writeup_of_a_document_no_state(user,doc):
+    if current_state != get_state_by_name(WAITING_WRITEUP) and not can_manage_writeup_of_a_document_no_state(user, doc):
         can_edit = False
     writeup = doc.protowriteup_set.all()
     if writeup.count():

From 9acbac73519737f44dc8dd03da1b666f7d50f023 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Thu, 3 Mar 2011 11:39:06 +0000
Subject: [PATCH 48/57] The default max number of delegates must be 3. See #605
  - Legacy-Id: 2875

---
 ietf/settings.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/settings.py b/ietf/settings.py
index 0a84060e8..48747d0a8 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -156,7 +156,7 @@ SERVER_MODE = 'development'
 TEST_RUNNER = 'ietf.utils.test_runner.run_tests'
 
 # WG Chair configuration
-MAX_WG_DELEGATES = 5
+MAX_WG_DELEGATES = 3
 
 # Override this in settings_local.py if needed
 # *_PATH variables ends with a slash/ .

From e2e11b82aa50866a977cfb07ab3b2d2f5efcdadc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 21 Mar 2011 09:48:35 +0000
Subject: [PATCH 49/57] Place the edit state/stream action into the draft view.
 Show only actions allowed. Fixes #625  - Legacy-Id: 2897

---
 ietf/idtracker/models.py                      |  2 +
 ietf/ietfworkflows/accounts.py                | 45 +++++++++++++++++++
 ietf/ietfworkflows/streams.py                 | 14 ++++++
 .../templatetags/ietf_streams.py              | 24 ++++++++++
 ietf/templates/idrfc/doc_main_id.html         |  3 +-
 .../templates/ietfworkflows/edit_actions.html |  6 +++
 6 files changed, 93 insertions(+), 1 deletion(-)
 create mode 100644 ietf/ietfworkflows/accounts.py
 create mode 100644 ietf/templates/ietfworkflows/edit_actions.html

diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index aaa1db268..528f552f5 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -1042,6 +1042,8 @@ class IRTF(models.Model):
     meeting_scheduled = models.BooleanField(blank=True)
     def __str__(self):
 	return self.acronym
+    def chairs(self): # return a set of IRTFChair objects for this work group
+        return IRTFChair.objects.filter(irtf=self)
     class Meta:
         db_table = 'irtf'
         verbose_name="IRTF Research Group"
diff --git a/ietf/ietfworkflows/accounts.py b/ietf/ietfworkflows/accounts.py
new file mode 100644
index 000000000..ea4f89cf1
--- /dev/null
+++ b/ietf/ietfworkflows/accounts.py
@@ -0,0 +1,45 @@
+from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
+from ietf.ietfworkflows.streams import get_streamed_draft, get_chair_model
+
+
+def get_person_for_user(user):
+    try:
+        return user.get_profile().person()
+    except:
+        return None
+
+
+def is_secretariat(user):
+    if not user or not user.is_authenticated():
+        return False
+    return bool(user.groups.filter(name='Secretariat'))
+
+
+def is_chair(user, draft):
+    person = get_person_for_user(user)
+    if not person:
+        return False
+    streamed = get_streamed_draft(draft)
+    if not streamed or not streamed.stream:
+        return False
+    group = streamed.group
+    if not group or not hasattr(group, 'chairs'):
+        return False
+    return bool(group.chairs().filter(person=person).count())
+
+
+def can_edit_state(user, draft):
+    streamed = get_streamed_draft(draft)
+    if not streamed or not streamed.stream:
+        return False
+    return (is_secretariat(user) or
+            is_chair(user, draft)
+           )
+
+
+def can_edit_tags(user, draft):
+    return can_edit_state(user, draft)
+
+
+def can_edit_stream(user, draft):
+    return is_secretariat(user)
diff --git a/ietf/ietfworkflows/streams.py b/ietf/ietfworkflows/streams.py
index 503eba4f5..7eebddffa 100644
--- a/ietf/ietfworkflows/streams.py
+++ b/ietf/ietfworkflows/streams.py
@@ -29,6 +29,20 @@ def get_stream_from_id(stream_id):
         return None
 
 
+def get_chair_model(stream):
+    model_str = stream.group_chair_model
+    if not model_str:
+        return None
+    try:
+        app, model = model_str.split('.', 1)
+    except ValueError:
+        return None
+    chair_model = models.get_model(app, model)
+    if not chair_model:
+        return
+    return chair_model
+
+
 def _get_group_from_acronym(group_model_str, acronym):
     try:
         app, model = group_model_str.split('.', 1)
diff --git a/ietf/ietfworkflows/templatetags/ietf_streams.py b/ietf/ietfworkflows/templatetags/ietf_streams.py
index b26963b9f..98b32b76b 100644
--- a/ietf/ietfworkflows/templatetags/ietf_streams.py
+++ b/ietf/ietfworkflows/templatetags/ietf_streams.py
@@ -4,6 +4,8 @@ from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
 from ietf.ietfworkflows.utils import (get_workflow_for_draft,
                                       get_state_for_draft)
 from ietf.ietfworkflows.streams import get_stream_from_wrapper
+from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_tags,
+                                         can_edit_stream)
 
 
 register = template.Library()
@@ -45,3 +47,25 @@ def workflow_history_entry(context, entry):
     real_entry = entry.get_real_instance()
     return {'entry': real_entry,
             'entry_class': real_entry.__class__.__name__.lower()}
+
+
+@register.inclusion_tag('ietfworkflows/edit_actions.html', takes_context=True)
+def edit_actions(context, wrapper):
+    request = context.get('request', None)
+    user = request and request.user
+    if not user:
+        return {}
+    idwrapper = None
+    if isinstance(wrapper, IdRfcWrapper):
+        idwrapper = wrapper.id
+    elif isinstance(wrapper, IdWrapper):
+        idwrapper = wrapper
+    if not idwrapper:
+        return None
+    draft = idwrapper._draft
+    return {
+        'can_edit_state': can_edit_state(user, draft),
+        'can_edit_tags': can_edit_tags(user, draft),
+        'can_edit_stream': can_edit_stream(user, draft),
+        'doc': wrapper,
+    }
diff --git a/ietf/templates/idrfc/doc_main_id.html b/ietf/templates/idrfc/doc_main_id.html
index 8f9893b32..3c4a08f09 100644
--- a/ietf/templates/idrfc/doc_main_id.html
+++ b/ietf/templates/idrfc/doc_main_id.html
@@ -32,7 +32,7 @@ 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 %}
+{% load ietf_filters ietf_streams %}
 
 {% block title %}{{ doc.draft_name_and_revision }}{% endblock %}
 {% block doc_meta_description %}{{ doc.title }} ({{info.type}}; {{doc.publication_date|date:"Y"}}){% endblock %}
@@ -81,6 +81,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 {% endblock doc_metatable %}
 
 {% block doc_metalinks %}
+{% edit_actions doc %}
 <div>
   <a href="mailto:{{doc.draft_name}}@tools.ietf.org?subject=Mail%20regarding%20{{doc.draft_name}}" rel="nofollow">Email Authors</a>
 | <a href="/ipr/search/?option=document_search&amp;id_document_tag={{doc.tracker_id}}" rel="nofollow">IPR Disclosures</a>
diff --git a/ietf/templates/ietfworkflows/edit_actions.html b/ietf/templates/ietfworkflows/edit_actions.html
new file mode 100644
index 000000000..05634197e
--- /dev/null
+++ b/ietf/templates/ietfworkflows/edit_actions.html
@@ -0,0 +1,6 @@
+<div>
+{% if can_edit_state %}  <a href="{% url edit_state doc.draft_name %}">Change state</a> {% endif %}
+{% if can_edit_tags %}{% if can_edit_state %} | {% endif %} <a href="{% url edit_tags doc.draft_name %}">Change annotation tags</a> {% endif %}
+{% if can_edit_stream %}{% if can_edit_state or can_edit_tags %} | {% endif %} <a href="{% url edit_stream doc.draft_name %}">Change draft stream</a>{% endif %}
+</div>
+

From aa7cfebc68d92b8c5b07476fab3d790232803ea8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Fri, 25 Mar 2011 12:34:02 +0000
Subject: [PATCH 50/57] Mix the state and tag edit views into one. Fixes #619 
 - Legacy-Id: 2922

---
 ietf/ietfworkflows/accounts.py                |  4 --
 ietf/ietfworkflows/forms.py                   | 62 +++++++++----------
 .../templatetags/ietf_streams.py              |  4 +-
 ietf/ietfworkflows/urls.py                    |  1 -
 ietf/ietfworkflows/views.py                   |  8 +--
 .../templates/ietfworkflows/edit_actions.html |  3 +-
 ietf/templates/ietfworkflows/state_edit.html  | 15 +++--
 ietf/templates/ietfworkflows/state_form.html  | 24 ++++++-
 8 files changed, 65 insertions(+), 56 deletions(-)

diff --git a/ietf/ietfworkflows/accounts.py b/ietf/ietfworkflows/accounts.py
index ea4f89cf1..c31b70e3e 100644
--- a/ietf/ietfworkflows/accounts.py
+++ b/ietf/ietfworkflows/accounts.py
@@ -37,9 +37,5 @@ def can_edit_state(user, draft):
            )
 
 
-def can_edit_tags(user, draft):
-    return can_edit_state(user, draft)
-
-
 def can_edit_stream(user, draft):
     return is_secretariat(user)
diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
index 4622fa9ee..4f6138dcb 100644
--- a/ietf/ietfworkflows/forms.py
+++ b/ietf/ietfworkflows/forms.py
@@ -41,16 +41,17 @@ class StreamDraftForm(forms.Form):
         return render_to_string(self.template, {'form': self})
 
 
-class DraftStateForm(StreamDraftForm):
+class DraftTagsStateForm(StreamDraftForm):
 
     comment = forms.CharField(widget=forms.Textarea)
     new_state = forms.ChoiceField()
     weeks = forms.IntegerField(required=False)
+    tags = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, required=False)
 
     template = 'ietfworkflows/state_form.html'
 
     def __init__(self, *args, **kwargs):
-        super(DraftStateForm, self).__init__(*args, **kwargs)
+        super(DraftTagsStateForm, self).__init__(*args, **kwargs)
         self.state = get_state_for_draft(self.draft)
         self.fields['new_state'].choices = self.get_states()
         if self.is_bound:
@@ -60,6 +61,10 @@ class DraftStateForm(StreamDraftForm):
                     if new_state:
                         self.data = self.data.copy()
                         self.data.update({'new_state': new_state.id})
+        self.available_tags = self.workflow.get_tags()
+        self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
+        self.fields['tags'].choices = [(i.pk, i.name) for i in self.available_tags]
+        self.fields['tags'].initial = [i.pk for i in self.tags]
 
     def get_new_state(self, key):
         transition_id = key.replace('transition_', '')
@@ -74,36 +79,7 @@ class DraftStateForm(StreamDraftForm):
     def get_states(self):
         return [(i.pk, i.name) for i in self.workflow.get_states()]
 
-    def save(self):
-        comment = self.cleaned_data.get('comment')
-        state = State.objects.get(pk=self.cleaned_data.get('new_state'))
-        weeks = self.cleaned_data.get('weeks')
-        estimated_date = None
-        if weeks:
-            now = datetime.date.today()
-            estimated_date = now + datetime.timedelta(weeks=weeks)
-        update_state(obj=self.draft,
-                     comment=comment,
-                     person=self.person,
-                     to_state=state,
-                     estimated_date=estimated_date)
-
-
-class DraftTagsForm(StreamDraftForm):
-
-    comment = forms.CharField(widget=forms.Textarea)
-    tags = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, required=False)
-
-    template = 'ietfworkflows/tags_form.html'
-
-    def __init__(self, *args, **kwargs):
-        super(DraftTagsForm, self).__init__(*args, **kwargs)
-        self.available_tags = self.workflow.get_tags()
-        self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
-        self.fields['tags'].choices = [(i.pk, i.name) for i in self.available_tags]
-        self.fields['tags'].initial = [i.pk for i in self.tags]
-
-    def save(self):
+    def save_tags(self):
         comment = self.cleaned_data.get('comment')
         new_tags = self.cleaned_data.get('tags')
 
@@ -118,6 +94,8 @@ class DraftTagsForm(StreamDraftForm):
                     extra_notify = ['%s <%s>' % shepherd.email()]
             except PersonOrOrgInfo.DoesNotExist:
                 pass
+        if not set_tags and not reset_tags:
+            return
         update_tags(self.draft,
                     comment=comment,
                     person=self.person,
@@ -125,6 +103,26 @@ class DraftTagsForm(StreamDraftForm):
                     reset_tags=reset_tags,
                     extra_notify=extra_notify)
 
+    def save_state(self):
+        comment = self.cleaned_data.get('comment')
+        state = State.objects.get(pk=self.cleaned_data.get('new_state'))
+        weeks = self.cleaned_data.get('weeks')
+        estimated_date = None
+        if weeks:
+            now = datetime.date.today()
+            estimated_date = now + datetime.timedelta(weeks=weeks)
+        update_state(obj=self.draft,
+                     comment=comment,
+                     person=self.person,
+                     to_state=state,
+                     estimated_date=estimated_date)
+
+    def save(self):
+        self.save_tags()
+        if 'only_tags' in self.data.keys():
+            return
+        self.save_state()
+
 
 class DraftStreamForm(StreamDraftForm):
 
diff --git a/ietf/ietfworkflows/templatetags/ietf_streams.py b/ietf/ietfworkflows/templatetags/ietf_streams.py
index 98b32b76b..0edcbf36d 100644
--- a/ietf/ietfworkflows/templatetags/ietf_streams.py
+++ b/ietf/ietfworkflows/templatetags/ietf_streams.py
@@ -4,8 +4,7 @@ from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
 from ietf.ietfworkflows.utils import (get_workflow_for_draft,
                                       get_state_for_draft)
 from ietf.ietfworkflows.streams import get_stream_from_wrapper
-from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_tags,
-                                         can_edit_stream)
+from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream)
 
 
 register = template.Library()
@@ -65,7 +64,6 @@ def edit_actions(context, wrapper):
     draft = idwrapper._draft
     return {
         'can_edit_state': can_edit_state(user, draft),
-        'can_edit_tags': can_edit_tags(user, draft),
         'can_edit_stream': can_edit_stream(user, draft),
         'doc': wrapper,
     }
diff --git a/ietf/ietfworkflows/urls.py b/ietf/ietfworkflows/urls.py
index 7b93d997d..4583bff7d 100644
--- a/ietf/ietfworkflows/urls.py
+++ b/ietf/ietfworkflows/urls.py
@@ -5,6 +5,5 @@ from django.conf.urls.defaults import patterns, url
 urlpatterns = patterns('ietf.ietfworkflows.views',
      url(r'^(?P<name>[^/]+)/history/$', 'stream_history', name='stream_history'),
      url(r'^(?P<name>[^/]+)/edit/state/$', 'edit_state', name='edit_state'),
-     url(r'^(?P<name>[^/]+)/edit/tags/$', 'edit_tags', name='edit_tags'),
      url(r'^(?P<name>[^/]+)/edit/stream/$', 'edit_stream', name='edit_stream'),
 )
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
index 14456ae70..a4e04f431 100644
--- a/ietf/ietfworkflows/views.py
+++ b/ietf/ietfworkflows/views.py
@@ -2,7 +2,7 @@ from ietf.idtracker.models import InternetDraft
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 
-from ietf.ietfworkflows.forms import (DraftStateForm, DraftTagsForm,
+from ietf.ietfworkflows.forms import (DraftTagsStateForm,
                                       DraftStreamForm)
 from ietf.ietfworkflows.streams import (get_stream_from_draft,
                                         get_streamed_draft)
@@ -44,7 +44,7 @@ def stream_history(request, name):
                               context_instance=RequestContext(request))
 
 
-def edit_state(request, name, form_class=DraftStateForm):
+def edit_state(request, name, form_class=DraftTagsStateForm):
     user = request.user
     draft = get_object_or_404(InternetDraft, filename=name)
     if request.method == 'POST':
@@ -71,9 +71,5 @@ def edit_state(request, name, form_class=DraftStateForm):
                               context_instance=RequestContext(request))
 
 
-def edit_tags(request, name):
-    return edit_state(request, name, DraftTagsForm)
-
-
 def edit_stream(request, name):
     return edit_state(request, name, DraftStreamForm)
diff --git a/ietf/templates/ietfworkflows/edit_actions.html b/ietf/templates/ietfworkflows/edit_actions.html
index 05634197e..9e677f71d 100644
--- a/ietf/templates/ietfworkflows/edit_actions.html
+++ b/ietf/templates/ietfworkflows/edit_actions.html
@@ -1,6 +1,5 @@
 <div>
 {% if can_edit_state %}  <a href="{% url edit_state doc.draft_name %}">Change state</a> {% endif %}
-{% if can_edit_tags %}{% if can_edit_state %} | {% endif %} <a href="{% url edit_tags doc.draft_name %}">Change annotation tags</a> {% endif %}
-{% if can_edit_stream %}{% if can_edit_state or can_edit_tags %} | {% endif %} <a href="{% url edit_stream doc.draft_name %}">Change draft stream</a>{% endif %}
+{% if can_edit_stream %}{% if can_edit_state %} | {% endif %} <a href="{% url edit_stream doc.draft_name %}">Change draft stream</a>{% endif %}
 </div>
 
diff --git a/ietf/templates/ietfworkflows/state_edit.html b/ietf/templates/ietfworkflows/state_edit.html
index 5572e9692..b2c281fa4 100644
--- a/ietf/templates/ietfworkflows/state_edit.html
+++ b/ietf/templates/ietfworkflows/state_edit.html
@@ -3,19 +3,19 @@
 
 {% block morecss %}
 table.state-history p { margin: 0px; }
-table.edit-form ul { padding: 0px; list-style-type: none; margin: 0px; margin-bottom: 2em; border-bottom: 1px dashed #cccccc; }
-table.edit-form ul li, table.edit-form div.free-change { padding: 10px 2em; }
+table.edit-form ul { padding: 0px; list-style-type: none; margin: 0px; margin-bottom: 2em; }
+table.edit-form ul li, table.edit-form div.free-change { padding: 0px 2em; }
 table.edit-form ul li.evenrow { background-color: #edf5ff; }
 table.edit-form textarea { width: 95%; height: 120px; }
 table.edit-form span.required { color: red; }
 table.edit-form ul.errorlist { border-width: 0px; padding: 0px; margin: 0px;}
 table.edit-form ul.errorlist li { color: red; margin: 0px; padding: 0px;}
-table.edit-form div.field { margin-bottom: 1em; }
-table.edit-form div.submit-row { margin: 1em 2em; }
+table.edit-form div.field { margin: 1em 0px; }
+table.edit-form div.submit-row { margin: 0px 2em; }
 table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; padding: 5px 10px; }
 table.edit-form-tags tr { vertical-align: top; }
 table.edit-form-tags textarea { height: 200px; }
-table.edit-form-tags ul { border-width: 0px; padding: 1em 2em; }
+table.edit-form-tags ul { border-width: 0px; padding: 0px 2em; }
 table.edit-form-tags ul li { padding: 0px; }
 {% endblock morecss %}
 
@@ -24,6 +24,11 @@ table.edit-form-tags ul li { padding: 0px; }
 {% block content %}
 <h1>Change state for {{ draft }}</h1>
 
+<div class="return-to-document">
+<p>
+<a href="{% url doc_view draft.filename %}">Return to document view</a>
+</p>
+</div>
 <table class="ietf-table" style="width: 100%;">
 <tr>
 <th>Current stream</th>
diff --git a/ietf/templates/ietfworkflows/state_form.html b/ietf/templates/ietfworkflows/state_form.html
index 4ae92dc42..a201c92b9 100644
--- a/ietf/templates/ietfworkflows/state_form.html
+++ b/ietf/templates/ietfworkflows/state_form.html
@@ -2,9 +2,9 @@
 <table class="ietf-table edit-form" style="width: 100%;">
 <tr>
   <th>1. Input information about change</th>
-  <th>2. Select the new state</th>
+  <th>2. Change annotation tags if needed</th>
 </tr>
-<tr><td style="width: 50%;">
+<tr style="vertical-align: top;"><td style="width: 50%;">
     <div class="field{% if form.errors.comment %} error{% endif %}">
     {{ form.errors.comment }}
     Comment: <span class="required">*</span><br />
@@ -15,7 +15,25 @@
     Estimated time in next status:<br />
     <input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
     </div>
-</td><td style="padding: 0px; vertical-align: top;">
+</td><td>
+  <div class="field">
+    {{ form.tags }}
+  </div>
+</td></tr>
+</table>
+
+<br />
+
+<table class="ietf-table edit-form edit-form-tags" style="width: 100%;">
+<tr>
+  <th>3. Select one action</th>
+</tr>
+<tr><td>
+    <div class="only-tags field">
+      <ul>
+      <li><input type="submit" name="only_tags" value="Update annotation tags" /> State remains unchanged: <strong>{{ form.state.name }}</strong></li>
+      </ul>
+    </div>
   {% with form.get_transitions as transitions %}
   {% if transitions %}
   <ul>

From 280cbbd7074b2cdf9e68ecae595344874b133df0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 30 Mar 2011 15:57:38 +0000
Subject: [PATCH 51/57] Handle draft without stream so WG chairs/delegates
 could raise call for adoptions. Fixes #642  - Legacy-Id: 3007

---
 ietf/ietfworkflows/accounts.py                | 21 +++++--
 ietf/ietfworkflows/constants.py               | 13 +++++
 ietf/ietfworkflows/forms.py                   | 57 +++++++++++++++++--
 ietf/ietfworkflows/streams.py                 | 10 +++-
 ietf/ietfworkflows/utils.py                   |  1 +
 ietf/ietfworkflows/views.py                   | 12 ++--
 .../ietfworkflows/noworkflow_state_form.html  | 26 +++++++++
 7 files changed, 123 insertions(+), 17 deletions(-)
 create mode 100644 ietf/ietfworkflows/constants.py
 create mode 100644 ietf/templates/ietfworkflows/noworkflow_state_form.html

diff --git a/ietf/ietfworkflows/accounts.py b/ietf/ietfworkflows/accounts.py
index c31b70e3e..da44f475b 100644
--- a/ietf/ietfworkflows/accounts.py
+++ b/ietf/ietfworkflows/accounts.py
@@ -1,5 +1,4 @@
-from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
-from ietf.ietfworkflows.streams import get_streamed_draft, get_chair_model
+from ietf.ietfworkflows.streams import get_streamed_draft
 
 
 def get_person_for_user(user):
@@ -15,7 +14,15 @@ def is_secretariat(user):
     return bool(user.groups.filter(name='Secretariat'))
 
 
-def is_chair(user, draft):
+def is_wgchair(person):
+    return bool(person.wgchair_set.all())
+
+
+def is_wgdelegate(person):
+    return bool(person.wgdelegate_set.all())
+
+
+def is_chair_of_draft(user, draft):
     person = get_person_for_user(user)
     if not person:
         return False
@@ -31,10 +38,12 @@ def is_chair(user, draft):
 def can_edit_state(user, draft):
     streamed = get_streamed_draft(draft)
     if not streamed or not streamed.stream:
-        return False
+        person = get_person_for_user(user)
+        return (is_secretariat(user) or
+                is_wgchair(person) or
+                is_wgdelegate(person))
     return (is_secretariat(user) or
-            is_chair(user, draft)
-           )
+            is_chair_of_draft(user, draft))
 
 
 def can_edit_stream(user, draft):
diff --git a/ietf/ietfworkflows/constants.py b/ietf/ietfworkflows/constants.py
new file mode 100644
index 000000000..d29a1164f
--- /dev/null
+++ b/ietf/ietfworkflows/constants.py
@@ -0,0 +1,13 @@
+# Required states
+CALL_FOR_ADOPTION = 'Call For Adoption By WG Issued'
+WG_DOCUMENT = 'WG Document'
+SUBMITTED_TO_IESG = 'Submitted to IESG for Publication'
+
+REQUIRED_STATES = (
+    CALL_FOR_ADOPTION,
+    WG_DOCUMENT,
+    SUBMITTED_TO_IESG,
+    )
+
+# IETF Stream
+IETF_STREAM = 'IETF'
diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
index 4f6138dcb..bc785192d 100644
--- a/ietf/ietfworkflows/forms.py
+++ b/ietf/ietfworkflows/forms.py
@@ -4,16 +4,19 @@ import datetime
 from django import forms
 from django.template.loader import render_to_string
 from workflows.models import State
+from workflows.utils import set_workflow_for_object
 
-from ietf.idtracker.models import PersonOrOrgInfo
+from ietf.idtracker.models import PersonOrOrgInfo, IETFWG
 from ietf.wgchairs.accounts import get_person_for_user
 from ietf.ietfworkflows.models import Stream
-from ietf.ietfworkflows.utils import (get_workflow_for_draft,
-                                      get_state_for_draft,
+from ietf.ietfworkflows.utils import (get_workflow_for_draft, get_workflow_for_wg,
+                                      get_state_for_draft, get_state_by_name,
                                       update_state, FOLLOWUP_TAG,
                                       get_annotation_tags_for_draft,
                                       update_tags, update_stream)
-from ietf.ietfworkflows.streams import get_stream_from_draft
+from ietf.ietfworkflows.streams import (get_stream_from_draft, get_streamed_draft,
+                                        get_stream_by_name, set_stream_for_draft)
+from ietf.ietfworkflows.constants import CALL_FOR_ADOPTION, IETF_STREAM
 
 
 class StreamDraftForm(forms.Form):
@@ -41,6 +44,52 @@ class StreamDraftForm(forms.Form):
         return render_to_string(self.template, {'form': self})
 
 
+class NoWorkflowStateForm(StreamDraftForm):
+    comment = forms.CharField(widget=forms.Textarea)
+    weeks = forms.IntegerField(required=False)
+    wg = forms.ChoiceField(required=False)
+
+    template = 'ietfworkflows/noworkflow_state_form.html'
+
+    def __init__(self, *args, **kwargs):
+        super(NoWorkflowStateForm, self).__init__(*args, **kwargs)
+        self.wgs = None
+        self.onlywg = None
+        wgs = set(self.person.wgchair_set.all()).union(set(self.person.wgdelegate_set.all()))
+        if len(wgs) > 1:
+            self.wgs = list(wgs)
+            self.fields['wg'].choices = [(i.group_acronym.pk, i.group_acronym.group_acronym.name) for i in self.wgs]
+        else:
+            self.onlywg = wgs[0].group_acronym
+
+    def save(self):
+        comment = self.cleaned_data.get('comment')
+        weeks = self.cleaned_data.get('weeks')
+        if self.onlywg:
+            wg = self.onlywg
+        else:
+            wg = IETFWG.objects.get(pk=self.cleaned_data.get('wg'))
+        estimated_date = None
+        if weeks:
+            now = datetime.date.today()
+            estimated_date = now + datetime.timedelta(weeks=weeks)
+        workflow = get_workflow_for_wg(wg)
+        set_workflow_for_object(self.draft, workflow)
+        stream = get_stream_by_name(IETF_STREAM)
+        streamed = get_streamed_draft(self.draft)
+        if not streamed:
+            set_stream_for_draft(self.draft, stream)
+            streamed = get_streamed_draft(self.draft)
+        streamed.stream = stream
+        streamed.group = wg
+        streamed.save()
+        update_state(obj=self.draft,
+                     comment=comment,
+                     person=self.person,
+                     to_state=get_state_by_name(CALL_FOR_ADOPTION),
+                     estimated_date=estimated_date)
+
+
 class DraftTagsStateForm(StreamDraftForm):
 
     comment = forms.CharField(widget=forms.Textarea)
diff --git a/ietf/ietfworkflows/streams.py b/ietf/ietfworkflows/streams.py
index 7eebddffa..7c037b73a 100644
--- a/ietf/ietfworkflows/streams.py
+++ b/ietf/ietfworkflows/streams.py
@@ -1,7 +1,5 @@
 from django.db import models
 
-from workflows.utils import set_workflow_for_object
-
 from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
 from ietf.ietfworkflows.models import StreamedID, Stream
 
@@ -22,6 +20,13 @@ def get_stream_from_draft(draft):
     return False
 
 
+def get_stream_by_name(stream_name):
+    try:
+        return Stream.objects.get(name=stream_name)
+    except Stream.DoesNotExist:
+        return None
+
+
 def get_stream_from_id(stream_id):
     try:
         return Stream.objects.get(id=stream_id)
@@ -106,5 +111,4 @@ def set_stream_for_draft(draft, stream):
         streamed.stream = stream
         streamed.group = None
         streamed.save()
-        set_workflow_for_object(draft, stream.workflow)
     return streamed.stream
diff --git a/ietf/ietfworkflows/utils.py b/ietf/ietfworkflows/utils.py
index 940e2a9c8..d9f27d39f 100644
--- a/ietf/ietfworkflows/utils.py
+++ b/ietf/ietfworkflows/utils.py
@@ -142,6 +142,7 @@ def set_tag(obj, tag):
         annotation_tag=tag)
     return relation
 
+
 def set_tag_by_name(obj, tag_name):
     try:
         tag = AnnotationTag.objects.get(name=tag_name)
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
index a4e04f431..515d4856e 100644
--- a/ietf/ietfworkflows/views.py
+++ b/ietf/ietfworkflows/views.py
@@ -1,9 +1,11 @@
-from ietf.idtracker.models import InternetDraft
+from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 
+from ietf.idtracker.models import InternetDraft
 from ietf.ietfworkflows.forms import (DraftTagsStateForm,
-                                      DraftStreamForm)
+                                      DraftStreamForm,
+                                      NoWorkflowStateForm)
 from ietf.ietfworkflows.streams import (get_stream_from_draft,
                                         get_streamed_draft)
 from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
@@ -47,16 +49,18 @@ def stream_history(request, name):
 def edit_state(request, name, form_class=DraftTagsStateForm):
     user = request.user
     draft = get_object_or_404(InternetDraft, filename=name)
+    workflow = get_workflow_for_draft(draft)
+    if not workflow:
+        form_class = NoWorkflowStateForm
     if request.method == 'POST':
         form = form_class(user=user, draft=draft, data=request.POST)
         if form.is_valid():
             form.save()
-            form = form_class(user=user, draft=draft)
+            return HttpResponseRedirect('.')
     else:
         form = form_class(user=user, draft=draft)
     state = get_state_for_draft(draft)
     stream = get_stream_from_draft(draft)
-    workflow = get_workflow_for_draft(draft)
     history = get_workflow_history_for_draft(draft, 'objectworkflowhistoryentry')
     tags = get_annotation_tags_for_draft(draft)
     return render_to_response('ietfworkflows/state_edit.html',
diff --git a/ietf/templates/ietfworkflows/noworkflow_state_form.html b/ietf/templates/ietfworkflows/noworkflow_state_form.html
new file mode 100644
index 000000000..cd8cbb5bd
--- /dev/null
+++ b/ietf/templates/ietfworkflows/noworkflow_state_form.html
@@ -0,0 +1,26 @@
+<form action="" method="post">
+<table class="ietf-table edit-form" style="width: 100%;">
+<tr>
+  <th>Adopt this draft in your WG</th>
+</tr>
+<tr style="vertical-align: top;"><td style="width: 50%;">
+    <div class="field{% if form.errors.comment %} error{% endif %}">
+    {{ form.errors.comment }}
+    Comment: <span class="required">*</span><br />
+    <textarea name="comment">{{ form.data.comment }}</textarea>
+    </div>
+    <div class="field{% if form.errors.weeks %} error{% endif %}">
+    {{ form.errors.weeks }}
+    Estimated time in 'Call for Adoption by WG Issued': <input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
+    </div>
+    <div class="field{% if form.errors.wg %} error{% endif %}">
+    <p>
+    You can manage different WGs, please select the WG in wich you want to call for adoption this draft
+    </p>
+    {{ form.errors.wg }}
+    Select a WG: {{ form.wg }}
+    </div>
+    <input type="submit" name="change" value="Call for adoption" />
+</td></tr>
+</table>
+</form>

From e681c5e3c94b37d72cba5015e1f08aa8c0713bac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 30 Mar 2011 17:05:10 +0000
Subject: [PATCH 52/57] Insert change shepherd and change writeup links in
 document view.

Insert document view link in shepherd document list.
Allow secretariat to manage shepherds and writeups.
Fixes #643
 - Legacy-Id: 3012
---
 ietf/ietfworkflows/forms.py                   |  2 +-
 .../templatetags/ietf_streams.py              |  5 +++++
 ietf/ietfworkflows/views.py                   | 17 +++++++++++++----
 .../templates/ietfworkflows/edit_actions.html |  6 ++++--
 .../wgchairs/shepherd_document_row.html       |  6 +++---
 .../wgchairs/wg_shepherd_documents.html       |  2 +-
 ietf/wgchairs/accounts.py                     | 19 +++++++++++++------
 7 files changed, 40 insertions(+), 17 deletions(-)

diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
index bc785192d..5ff14578d 100644
--- a/ietf/ietfworkflows/forms.py
+++ b/ietf/ietfworkflows/forms.py
@@ -60,7 +60,7 @@ class NoWorkflowStateForm(StreamDraftForm):
             self.wgs = list(wgs)
             self.fields['wg'].choices = [(i.group_acronym.pk, i.group_acronym.group_acronym.name) for i in self.wgs]
         else:
-            self.onlywg = wgs[0].group_acronym
+            self.onlywg = list(wgs)[0].group_acronym
 
     def save(self):
         comment = self.cleaned_data.get('comment')
diff --git a/ietf/ietfworkflows/templatetags/ietf_streams.py b/ietf/ietfworkflows/templatetags/ietf_streams.py
index 0edcbf36d..7e88cf0f2 100644
--- a/ietf/ietfworkflows/templatetags/ietf_streams.py
+++ b/ietf/ietfworkflows/templatetags/ietf_streams.py
@@ -3,6 +3,8 @@ from django import template
 from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
 from ietf.ietfworkflows.utils import (get_workflow_for_draft,
                                       get_state_for_draft)
+from ietf.wgchairs.accounts import (can_manage_shepherd_of_a_document,
+                                    can_manage_writeup_of_a_document)
 from ietf.ietfworkflows.streams import get_stream_from_wrapper
 from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream)
 
@@ -65,5 +67,8 @@ def edit_actions(context, wrapper):
     return {
         'can_edit_state': can_edit_state(user, draft),
         'can_edit_stream': can_edit_stream(user, draft),
+        'can_writeup': can_manage_writeup_of_a_document(user, draft),
+        'can_shepherd': can_manage_shepherd_of_a_document(user, draft),
+        'draft': draft,
         'doc': wrapper,
     }
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
index 515d4856e..083e2501c 100644
--- a/ietf/ietfworkflows/views.py
+++ b/ietf/ietfworkflows/views.py
@@ -1,4 +1,4 @@
-from django.http import HttpResponseRedirect
+from django.http import HttpResponseRedirect, HttpResponseForbidden
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 
@@ -12,6 +12,7 @@ from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
                                       get_workflow_for_draft,
                                       get_annotation_tags_for_draft,
                                       get_state_for_draft)
+from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream)
 
 
 REDUCED_HISTORY_LEN = 20
@@ -46,9 +47,8 @@ def stream_history(request, name):
                               context_instance=RequestContext(request))
 
 
-def edit_state(request, name, form_class=DraftTagsStateForm):
+def _edit_draft_stream(request, draft, form_class=DraftTagsStateForm):
     user = request.user
-    draft = get_object_or_404(InternetDraft, filename=name)
     workflow = get_workflow_for_draft(draft)
     if not workflow:
         form_class = NoWorkflowStateForm
@@ -75,5 +75,14 @@ def edit_state(request, name, form_class=DraftTagsStateForm):
                               context_instance=RequestContext(request))
 
 
+def edit_state(request, name):
+    draft = get_object_or_404(InternetDraft, filename=name)
+    if not can_edit_state(request.user, draft):
+        return HttpResponseForbidden('You have no permission to access this view')
+    return _edit_draft_stream(request, draft, DraftTagsStateForm)
+
 def edit_stream(request, name):
-    return edit_state(request, name, DraftStreamForm)
+    draft = get_object_or_404(InternetDraft, filename=name)
+    if not can_edit_stream(request.user, draft):
+        return HttpResponseForbidden('You have no permission to access this view')
+    return _edit_draft_stream(request, draft, DraftStreamForm)
diff --git a/ietf/templates/ietfworkflows/edit_actions.html b/ietf/templates/ietfworkflows/edit_actions.html
index 9e677f71d..e5bd3102e 100644
--- a/ietf/templates/ietfworkflows/edit_actions.html
+++ b/ietf/templates/ietfworkflows/edit_actions.html
@@ -1,5 +1,7 @@
-<div>
+<div style="margin-bottom: 1em;">
 {% if can_edit_state %}  <a href="{% url edit_state doc.draft_name %}">Change state</a> {% endif %}
-{% if can_edit_stream %}{% if can_edit_state %} | {% endif %} <a href="{% url edit_stream doc.draft_name %}">Change draft stream</a>{% endif %}
+{% if can_edit_stream %}{% if can_edit_state %} | {% endif %}<a href="{% url edit_stream doc.draft_name %}">Change draft stream</a>{% endif %}
+{% if can_shepherd %}{% if can_edit_state or can_edit_stream %} | {% endif %}<a href="{% url doc_managing_shepherd draft.group.acronym draft.filename %}">Change draft shepherd</a>{% endif %}
+{% if can_writeup %}{% if can_edit_state or can_edit_stream or can_writeup %} | {% endif %}<a href="{% url doc_managing_writeup draft.group.acronym draft.filename %}">Change draft writeup</a>{% endif %}
 </div>
 
diff --git a/ietf/templates/wgchairs/shepherd_document_row.html b/ietf/templates/wgchairs/shepherd_document_row.html
index dec1a12dc..0a7e03ad3 100644
--- a/ietf/templates/wgchairs/shepherd_document_row.html
+++ b/ietf/templates/wgchairs/shepherd_document_row.html
@@ -2,10 +2,10 @@
 
         <tr class="{% cycle oddrow,evenrow %}">
           <td class="title">
-            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">{{ doc.title }}</a>
+            <a href="{% url doc_view doc.filename %}">{{ doc.title }}</a>
           </td>
-          <td class="status">
-              {{ doc.status.status }}
+          <td class="shepherd">
+            <a href="{% url doc_managing_shepherd wg.group_acronym.acronym doc %}">Change shepherd</a>
           </td>        
           <td class="writeup">
              {% writeup doc %}
diff --git a/ietf/templates/wgchairs/wg_shepherd_documents.html b/ietf/templates/wgchairs/wg_shepherd_documents.html
index 0bbbc1074..65435bd94 100644
--- a/ietf/templates/wgchairs/wg_shepherd_documents.html
+++ b/ietf/templates/wgchairs/wg_shepherd_documents.html
@@ -56,7 +56,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 <table class="ietf-table ietf-doctable" style="margin-top:16px; width: 100%;">
     <tr>       
        <th class="title">Document</th>
-       <th class="status">Status</th>
+       <th class="shepherd">Change shepherd</th>
        <th class="writeup">Protocol write-up</th>
        <th class="writeup_date">Protocol write-up last update</th>
     </tr> 
diff --git a/ietf/wgchairs/accounts.py b/ietf/wgchairs/accounts.py
index f70060fc6..490f26e0b 100644
--- a/ietf/wgchairs/accounts.py
+++ b/ietf/wgchairs/accounts.py
@@ -1,3 +1,9 @@
+def is_secretariat(user):
+    if not user or not user.is_authenticated():
+        return False
+    return bool(user.groups.filter(name='Secretariat'))
+
+    
 def is_area_director_for_group(person, group):
     return bool(group.area.area.areadirector_set.filter(person=person).count())
 
@@ -27,35 +33,35 @@ def can_do_wg_workflow_in_group(user, group):
     person = get_person_for_user(user)
     if not person:
         return False
-    return is_group_chair(person, group)
+    return (is_secretariat(user) or is_group_chair(person, group))
 
 
 def can_do_wg_workflow_in_document(user, document):
     person = get_person_for_user(user)
     if not person or not document.group:
         return False
-    return can_do_wg_workflow_in_group(document.group.ietfwg)
+    return (is_secretariat(user) or can_do_wg_workflow_in_group(document.group.ietfwg))
 
 
 def can_manage_workflow_in_group(user, group):
     person = get_person_for_user(user)
     if not person:
         return False
-    return is_group_chair(person, group)
+    return (is_secretariat(user) or is_group_chair(person, group))
 
 
 def can_manage_delegates_in_group(user, group):
     person = get_person_for_user(user)
     if not person:
         return False
-    return is_group_chair(person, group)
+    return (is_secretariat(user) or is_group_chair(person, group))
 
 
 def can_manage_shepherds_in_group(user, group):
     person = get_person_for_user(user)
     if not person:
         return False
-    return is_group_chair(person, group)
+    return (is_secretariat(user) or is_group_chair(person, group))
 
 
 def can_manage_shepherd_of_a_document(user, document):
@@ -70,7 +76,8 @@ def can_manage_writeup_of_a_document_no_state(user, document):
     if not person or not document.group:
         return False
     group = document.group.ietfwg
-    return (is_group_chair(person, group) or
+    return (is_secretariat(user) or
+            is_group_chair(person, group) or
             is_area_director_for_group(person, group) or
             is_group_delegate(person, group))
 

From 8527cdaf6b69414b846b6dc98f206683f163e554 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 30 Mar 2011 18:24:33 +0000
Subject: [PATCH 53/57] Freeze global WG states. See #644  - Legacy-Id: 3013

---
 ietf/templates/wgchairs/manage_workflow.html |  2 +-
 ietf/wgchairs/forms.py                       | 11 ++++++++---
 ietf/wgchairs/views.py                       |  3 +++
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/ietf/templates/wgchairs/manage_workflow.html b/ietf/templates/wgchairs/manage_workflow.html
index 5ac578ffd..8c1b4ed02 100644
--- a/ietf/templates/wgchairs/manage_workflow.html
+++ b/ietf/templates/wgchairs/manage_workflow.html
@@ -78,7 +78,7 @@
   </tr>
   {% for state in default_states %}
   <tr class="{% cycle "oddrow" "evenrow" %}">
-    <td><input type="checkbox" id="id_states_{{ state.pk }}" name="states" value="{{ state.pk }}" {% if state.used %}checked="checked" {% endif %}/></td>
+    <td><input type="checkbox" id="id_states_{{ state.pk }}" name="states" value="{{ state.pk }}" {% if state.used %}checked="checked" {% endif %}{% if state.freeze %} disabled="disabled"{% endif %}/></td>
     <td><label for="id_states_{{ state.pk }}">{{ state.name }}</label></td>
   </tr>
 {% endfor %}
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 89ec4b081..722101212 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -7,8 +7,9 @@ from django.utils.safestring import mark_safe
 
 from ietf.wgchairs.models import WGDelegate, ProtoWriteUp
 from ietf.wgchairs.accounts import get_person_for_user
+from ietf.ietfworkflows.constants import REQUIRED_STATES
 from ietf.ietfworkflows.utils import (get_default_workflow_for_wg, get_workflow_for_wg,
-                                      update_tags, FOLLOWUP_TAG)
+                                      update_tags, FOLLOWUP_TAG, get_state_by_name)
 from ietf.idtracker.models import PersonOrOrgInfo
 
 from workflows.models import Transition
@@ -36,7 +37,7 @@ class RelatedWGForm(forms.Form):
 class TagForm(RelatedWGForm):
 
     tags = forms.ModelMultipleChoiceField(get_default_workflow_for_wg().annotation_tags.all(),
-                                          widget=forms.CheckboxSelectMultiple)
+                                          widget=forms.CheckboxSelectMultiple, required=False)
 
     def save(self):
         workflow = get_workflow_for_wg(self.wg)
@@ -49,7 +50,7 @@ class TagForm(RelatedWGForm):
 class StateForm(RelatedWGForm):
 
     states = forms.ModelMultipleChoiceField(get_default_workflow_for_wg().states.all(),
-                                            widget=forms.CheckboxSelectMultiple)
+                                            widget=forms.CheckboxSelectMultiple, required=False)
 
     def update_transitions(self, workflow):
         for transition in workflow.transitions.all():
@@ -68,6 +69,10 @@ class StateForm(RelatedWGForm):
         workflow.selected_states.clear()
         for state in self.cleaned_data['states']:
             workflow.selected_states.add(state)
+        for name in REQUIRED_STATES:
+            rstate = get_state_by_name(name)
+            if rstate:
+                workflow.selected_states.add(rstate)
         self.update_transitions(workflow)
         return workflow
 
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index ac1990b6a..11ee6ea14 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -15,6 +15,7 @@ from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_fo
                                     can_manage_writeup_of_a_document,
                                     can_manage_writeup_of_a_document_no_state,
                                     )
+from ietf.ietfworkflows.constants import REQUIRED_STATES
 from ietf.ietfworkflows.utils import (get_workflow_for_wg,
                                       get_default_workflow_for_wg,
                                       get_state_by_name,
@@ -70,6 +71,8 @@ def manage_workflow(request, acronym):
     for i in default_states:
         if states.filter(name=i.name).count() == 1:
             i.used = True
+        if i.name in REQUIRED_STATES:
+            i.freeze = True
     for i in default_tags:
         if tags.filter(name=i.name).count() == 1:
             i.used = True

From e4f9bbe1ff09b14a7e576ca7883b461ee6d227f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 30 Mar 2011 18:56:35 +0000
Subject: [PATCH 54/57] Added state definitions in workflow edit form.

Re-order states in workflow edit form.
See #644
 - Legacy-Id: 3014
---
 ietf/ietfworkflows/fixtures/initial_data.xml  | 244 ++++++++++++++++++
 .../migrations/0010_add_state_definitions.py  | 207 +++++++++++++++
 ietf/ietfworkflows/models.py                  |  12 +
 ietf/templates/wgchairs/manage_workflow.html  |  25 +-
 ietf/wgchairs/views.py                        |   4 +-
 5 files changed, 488 insertions(+), 4 deletions(-)
 create mode 100644 ietf/ietfworkflows/migrations/0010_add_state_definitions.py

diff --git a/ietf/ietfworkflows/fixtures/initial_data.xml b/ietf/ietfworkflows/fixtures/initial_data.xml
index f78fdce7f..a8901f482 100644
--- a/ietf/ietfworkflows/fixtures/initial_data.xml
+++ b/ietf/ietfworkflows/fixtures/initial_data.xml
@@ -458,4 +458,248 @@
         <field type="CharField" name="group_chair_model"></field>
         <field to="ietfworkflows.wgworkflow" name="workflow" rel="ManyToOneRel">4</field>
     </object>
+    <object pk="1" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">11</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.1" target="_blank"&gt;4.2.1. Call for Adoption by WG Issued&lt;/a&gt;
+
+
+   The "Call for Adoption by WG Issued" state should be used to indicate
+   when an I-D is being considered for adoption by an IETF WG.  An I-D
+   that is in this state is actively being considered for adoption and
+   has not yet achieved consensus, preference, or selection in the WG.
+
+   This state may be used to describe an I-D that someone has asked a WG
+   to consider for adoption, if the WG Chair has agreed with the
+   request.  This state may also be used to identify an I-D that a WG
+   Chair asked an author to write specifically for consideration as a
+   candidate WG item [WGDTSPEC], and/or an I-D that is listed as a
+   'candidate draft' in the WG's charter.
+
+   Under normal conditions, it should not be possible for an I-D to be
+   in the "Call for Adoption by WG Issued" state in more than one
+   working group at the same time.  This said, it is not uncommon for
+   authors to "shop" their I-Ds to more than one WG at a time, with the
+   hope of getting their documents adopted somewhere.
+
+   After this state is implemented in the Datatracker, an I-D that is in
+   the "Call for Adoption by WG Issued" state will not be able to be
+   "shopped" to any other WG without the consent of the WG Chairs and
+   the responsible ADs impacted by the shopping.
+
+   Note that Figure 1 includes an arc leading from this state to outside
+   of the WG state machine.  This illustrates that some I-Ds that are
+   considered do not get adopted as WG drafts.  An I-D that is not
+   adopted as a WG draft will transition out of the WG state machine and
+   revert back to having no stream-specific state; however, the status
+   change history log of the I-D will record that the I-D was previously
+   in the "Call for Adoption by WG Issued" state.
+</field>
+        <field type="PositiveIntegerField" name="order">1</field>
+    </object>
+    <object pk="2" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">12</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.2" target="_blank"&gt;4.2.2. Adopted by a WG&lt;/a&gt;
+
+
+   The "Adopted by a WG" state describes an individual submission I-D
+   that an IETF WG has agreed to adopt as one of its WG drafts.
+
+   WG Chairs who use this state will be able to clearly indicate when
+   their WGs adopt individual submission I-Ds.  This will facilitate the
+   Datatracker's ability to correctly capture "Replaces" information for
+   WG drafts and correct "Replaced by" information for individual
+   submission I-Ds that have been replaced by WG drafts.
+
+   This state is needed because the Datatracker uses the filename of an
+   I-D as a key to search its database for status information about the
+   I-D, and because the filename of a WG I-D is supposed to be different
+   from the filename of an individual submission I-D.
+   The filename of an individual submission I-D will typically be
+   formatted as 'draft-author-wgname-topic-nn'.
+
+   The filename of a WG document is supposed to be formatted as 'draft-
+   ietf-wgname-topic-nn'.
+
+   An individual I-D that is adopted by a WG may take weeks or months to
+   be resubmitted by the author as a new (version-00) WG draft.  If the
+   "Adopted by a WG" state is not used, the Datatracker has no way to
+   determine that an I-D has been adopted until a new version of the I-D
+   is submitted to the WG by the author and until the I-D is approved
+   for posting by a WG Chair.
+</field>
+        <field type="PositiveIntegerField" name="order">2</field>
+    </object>
+    <object pk="3" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">13</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.3" target="_blank"&gt;4.2.3. Adopted for WG Info Only&lt;/a&gt;
+
+
+   The "Adopted for WG Info Only" state describes a document that
+   contains useful information for the WG that adopted it, but the
+   document is not intended to be published as an RFC.  The WG will not
+   actively develop the contents of the I-D or progress it for
+   publication as an RFC.  The only purpose of the I-D is to provide
+   information for internal use by the WG.
+</field>
+        <field type="PositiveIntegerField" name="order">3</field>
+    </object>
+    <object pk="4" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">14</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.4" target="_blank"&gt;4.2.4. WG Document&lt;/a&gt;
+
+
+   The "WG Document" state describes an I-D that has been adopted by an
+   IETF WG and is being actively developed.
+
+   A WG Chair may transition an I-D into the "WG Document" state at any
+   time as long as the I-D is not being considered or developed in any
+   other WG.
+
+   Alternatively, WG Chairs may rely upon new functionality to be added
+   to the Datatracker to automatically move version-00 drafts into the
+   "WG Document" state as described in Section 4.1.
+
+   Under normal conditions, it should not be possible for an I-D to be
+   in the "WG Document" state in more than one WG at a time.  This said,
+   I-Ds may be transferred from one WG to another with the consent of
+   the WG Chairs and the responsible ADs.
+
+</field>
+        <field type="PositiveIntegerField" name="order">4</field>
+    </object>
+    <object pk="5" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">15</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.5"  target="_blank"&gt;4.2.5. Parked WG Document&lt;/a&gt;
+
+
+   A "Parked WG Document" is an I-D that has lost its author or editor,
+   is waiting for another document to be written or for a review to be
+   completed, or cannot be progressed by the working group for some
+   other reason.
+
+   Some of the annotation tags described in Section 4.3 may be used in
+   conjunction with this state to indicate why an I-D has been parked,
+   and/or what may need to happen for the I-D to be un-parked.
+
+   Parking a WG draft will not prevent it from expiring; however, this
+   state can be used to indicate why the I-D has stopped progressing in
+   the WG.
+
+   A "Parked WG Document" that is not expired may be transferred from
+   one WG to another with the consent of the WG Chairs and the
+   responsible ADs.
+
+</field>
+        <field type="PositiveIntegerField" name="order">5</field>
+    </object>
+    <object pk="6" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">16</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.6"  target="_blank"&gt;4.2.6. Dead WG Document&lt;/a&gt;
+
+
+   A "Dead WG Document" is an I-D that has been abandoned.  Note that
+   'Dead' is not always a final state for a WG I-D.  If consensus is
+   subsequently achieved, a "Dead WG Document" may be resurrected.  A
+   "Dead WG Document" that is not resurrected will eventually expire.
+
+   Note that an I-D that is declared to be "Dead" in one WG and that is
+   not expired may be transferred to a non-dead state in another WG with
+   the consent of the WG Chairs and the responsible ADs.
+
+</field>
+        <field type="PositiveIntegerField" name="order">6</field>
+    </object>
+    <object pk="7" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">17</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.7"  target="_blank"&gt;4.2.7. In WG Last Call&lt;/a&gt;
+
+
+   A document "In WG Last Call" is an I-D for which a WG Last Call
+   (WGLC) has been issued and is in progress.
+
+   Note that conducting a WGLC is an optional part of the IETF WG
+   process, per Section 7.4 of RFC 2418 [RFC2418].
+
+   If a WG Chair decides to conduct a WGLC on an I-D, the "In WG Last
+   Call" state can be used to track the progress of the WGLC.  The Chair
+   may configure the Datatracker to send a WGLC message to one or more
+   mailing lists when the Chair moves the I-D into this state.  The WG
+   Chair may also be able to select a different set of mailing lists for
+   a different document undergoing a WGLC; some documents may deserve
+   coordination with other WGs.
+
+   A WG I-D in this state should remain "In WG Last Call" until the WG
+   Chair moves it to another state.  The WG Chair may configure the
+   Datatracker to send an e-mail after a specified period of time to
+   remind or 'nudge' the Chair to conclude the WGLC and to determine the
+   next state for the document.
+
+   It is possible for one WGLC to lead into another WGLC for the same
+   document.  For example, an I-D that completed a WGLC as an
+   "Informational" document may need another WGLC if a decision is taken
+   to convert the I-D into a Standards Track document.
+
+</field>
+        <field type="PositiveIntegerField" name="order">7</field>
+    </object>
+    <object pk="8" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">18</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.8" target="_blank"&gt;4.2.8. Waiting for WG Chair Go-Ahead&lt;/a&gt;
+
+
+   A WG Chair may wish to place an I-D that receives a lot of comments
+   during a WGLC into the "Waiting for WG Chair Go-Ahead" state.  This
+   state describes an I-D that has undergone a WGLC; however, the Chair
+   is not yet ready to call consensus on the document.
+
+   If comments from the WGLC need to be responded to, or a revision to
+   the I-D is needed, the Chair may place an I-D into this state until
+   all of the WGLC comments are adequately addressed and the (possibly
+   revised) document is in the I-D repository.
+
+</field>
+        <field type="PositiveIntegerField" name="order">8</field>
+    </object>
+    <object pk="9" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">19</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.9"  target="_blank"&gt;4.2.9. WG Consensus: Waiting for Writeup&lt;/a&gt;
+
+
+   A document in the "WG Consensus: Waiting for Writeup" state has
+   essentially completed its development within the working group, and
+   is nearly ready to be sent to the IESG for publication.  The last
+   thing to be done is the preparation of a protocol writeup by a
+   Document Shepherd.  The IESG requires that a document shepherd
+   writeup be completed before publication of the I-D is requested.  The
+   IETF document shepherding process and the role of a WG Document
+   Shepherd is described in RFC 4858 [RFC4858]
+
+   A WG Chair may call consensus on an I-D without a formal WGLC and
+   transition an I-D that was in the "WG Document" state directly into
+   this state.
+
+   The name of this state includes the words "Waiting for Writeup"
+   because a good document shepherd writeup takes time to prepare.
+
+</field>
+        <field type="PositiveIntegerField" name="order">9</field>
+    </object>
+    <object pk="10" model="ietfworkflows.statedescription">
+        <field to="workflows.state" name="state" rel="ManyToOneRel">20</field>
+        <field type="TextField" name="definition">&lt;a href="http://tools.ietf.org/html/rfc6174#section-4.2.10"  target="_blank"&gt;4.2.10. Submitted to IESG for Publication&lt;/a&gt;
+
+
+   This state describes a WG document that has been submitted to the
+   IESG for publication and that has not been sent back to the working
+   group for revision.
+
+   An I-D in this state may be under review by the IESG, it may have
+   been approved and be in the RFC Editor's queue, or it may have been
+   published as an RFC.  Other possibilities exist too.  The document
+   may be "Dead" (in the IESG state machine) or in a "Do Not Publish"
+   state.
+
+</field>
+        <field type="PositiveIntegerField" name="order">10</field>
+    </object>
 </django-objects>
diff --git a/ietf/ietfworkflows/migrations/0010_add_state_definitions.py b/ietf/ietfworkflows/migrations/0010_add_state_definitions.py
new file mode 100644
index 000000000..503943c56
--- /dev/null
+++ b/ietf/ietfworkflows/migrations/0010_add_state_definitions.py
@@ -0,0 +1,207 @@
+
+from south.db import db
+from django.db import models
+from ietf.ietfworkflows.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'StateDescription'
+        db.create_table('ietfworkflows_statedescription', (
+            ('id', orm['ietfworkflows.statedescription:id']),
+            ('state', orm['ietfworkflows.statedescription:state']),
+            ('definition', orm['ietfworkflows.statedescription:definition']),
+            ('order', orm['ietfworkflows.statedescription:order']),
+        ))
+        db.send_create_signal('ietfworkflows', ['StateDescription'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'StateDescription'
+        db.delete_table('ietfworkflows_statedescription')
+        
+    
+    
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", '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'})
+        },
+        'idtracker.acronym': {
+            'Meta': {'db_table': "'acronym'"},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+            'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'idtracker.idintendedstatus': {
+            'Meta': {'db_table': "'id_intended_status'"},
+            'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.idstatus': {
+            'Meta': {'db_table': "'id_status'"},
+            'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
+            'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'idtracker.internetdraft': {
+            'Meta': {'db_table': "'internet_drafts'"},
+            'abstract': ('django.db.models.fields.TextField', [], {}),
+            'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
+            'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
+            'last_modified_date': ('django.db.models.fields.DateField', [], {}),
+            'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
+            'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
+            'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'revision_date': ('django.db.models.fields.DateField', [], {}),
+            'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
+            'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
+            'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'idtracker.personororginfo': {
+            'Meta': {'db_table': "'person_or_org_info'"},
+            'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
+            'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
+            'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.annotationtag': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
+        },
+        'ietfworkflows.annotationtagobjectrelation': {
+            'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'ietfworkflows.objectannotationtaghistoryentry': {
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objecthistoryentry': {
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"})
+        },
+        'ietfworkflows.objectstreamhistoryentry': {
+            'from_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'to_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.objectworkflowhistoryentry': {
+            'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
+            'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'ietfworkflows.statedescription': {
+            'definition': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'order': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'ietfworkflows.stateobjectrelationmetadata': {
+            'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'from_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
+        },
+        'ietfworkflows.stream': {
+            'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
+        },
+        'ietfworkflows.streamedid': {
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'streamed_id'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']", 'null': 'True', 'blank': 'True'})
+        },
+        'ietfworkflows.wgworkflow': {
+            'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
+            'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
+            'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'permissions.permission': {
+            'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'workflows.state': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.stateobjectrelation': {
+            'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
+        },
+        'workflows.transition': {
+            'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
+        },
+        'workflows.workflow': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
+        }
+    }
+    
+    complete_apps = ['ietfworkflows']
diff --git a/ietf/ietfworkflows/models.py b/ietf/ietfworkflows/models.py
index f0ded3a14..b73261975 100644
--- a/ietf/ietfworkflows/models.py
+++ b/ietf/ietfworkflows/models.py
@@ -76,6 +76,18 @@ class ObjectStreamHistoryEntry(ObjectHistoryEntry):
         return html
 
 
+class StateDescription(models.Model):
+    state = models.ForeignKey(State)
+    definition = models.TextField()
+    order = models.PositiveIntegerField()
+
+    class Meta:
+        ordering = ('order', )
+
+    def __unicode__(self):
+        return unicode(self.state)
+
+
 class AnnotationTag(models.Model):
     name = models.CharField(_(u"Name"), max_length=100)
     workflow = models.ForeignKey(Workflow, verbose_name=_(u"Workflow"), related_name="annotation_tags")
diff --git a/ietf/templates/wgchairs/manage_workflow.html b/ietf/templates/wgchairs/manage_workflow.html
index 8c1b4ed02..fe89106a0 100644
--- a/ietf/templates/wgchairs/manage_workflow.html
+++ b/ietf/templates/wgchairs/manage_workflow.html
@@ -71,15 +71,23 @@
 </div>
 
 <div id="states">
+<p>
+Please note that the states you can not uncheck are needed in all IETF WG.
+</p>
 <form action="#info" method="POST">
 <table class="ietf-table">
   <tr> 
-    <th>Used in {{ wg }}</th><th>Available states</th>
+    <th>Used in {{ wg }}</th><th>Available states</th><th>Definition</th>
   </tr>
   {% for state in default_states %}
-  <tr class="{% cycle "oddrow" "evenrow" %}">
+  <tr class="{% cycle "oddrow" "evenrow" %}" style="vertical-align: top;">
     <td><input type="checkbox" id="id_states_{{ state.pk }}" name="states" value="{{ state.pk }}" {% if state.used %}checked="checked" {% endif %}{% if state.freeze %} disabled="disabled"{% endif %}/></td>
     <td><label for="id_states_{{ state.pk }}">{{ state.name }}</label></td>
+    <td>
+    <div class="statedefinition" style="height: 1em; overflow: hidden;">
+       <pre style="margin-top: 0px;"><a class="showDefinition" href="#">[+]</a><a class="hideDefinition" style="display: none;" href="#">[-]</a> {{ state.statedescription_set.all.0.definition|safe }}</pre>
+    </div>
+    </td>
   </tr>
 {% endfor %}
 </table>
@@ -132,6 +140,19 @@ if (url[1]) {
      } 
   } 
 } 
+
+jQuery('.showDefinition').click(function() {
+    jQuery(this).parent().parent().css('height', 'auto');
+    jQuery(this).hide();
+    jQuery(this).next().show();
+    return false;
+});
+jQuery('.hideDefinition').click(function() {
+    jQuery(this).parent().parent().css('height', '1em');
+    jQuery(this).hide();
+    jQuery(this).prev().show();
+    return false;
+});
 //]]>
 </script>
 
diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py
index 11ee6ea14..2e33543ff 100644
--- a/ietf/wgchairs/views.py
+++ b/ietf/wgchairs/views.py
@@ -66,8 +66,8 @@ def manage_workflow(request, acronym):
             formset = form
     tags = workflow.selected_tags.all()
     default_tags = default_workflow.annotation_tags.all()
-    states = workflow.selected_states.all()
-    default_states = default_workflow.states.all()
+    states = workflow.selected_states.all().order_by('statedescription__order')
+    default_states = default_workflow.states.all().order_by('statedescription__order')
     for i in default_states:
         if states.filter(name=i.name).count() == 1:
             i.used = True

From e80062394c4c81d6a116a0b9f4cf5fa65537e97a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 30 Mar 2011 19:00:40 +0000
Subject: [PATCH 55/57] Add a link to the default Working Group I-D State
 Diagram. Fixes #644  - Legacy-Id: 3015

---
 ietf/templates/wgchairs/manage_workflow.html | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/ietf/templates/wgchairs/manage_workflow.html b/ietf/templates/wgchairs/manage_workflow.html
index fe89106a0..bc5a4fbd7 100644
--- a/ietf/templates/wgchairs/manage_workflow.html
+++ b/ietf/templates/wgchairs/manage_workflow.html
@@ -72,7 +72,10 @@
 
 <div id="states">
 <p>
-Please note that the states you can not uncheck are needed in all IETF WG.
+Please note that the states you can not uncheck are needed in all IETF WGs.
+</p>
+<p>
+You can see the default Working Group I-D State Diagram in <a href="http://tools.ietf.org/html/rfc6174#section-4.1">Section 4.1 of RFC6174</a>
 </p>
 <form action="#info" method="POST">
 <table class="ietf-table">
@@ -96,6 +99,9 @@ Please note that the states you can not uncheck are needed in all IETF WG.
 </div>
 
 <div id="transitions">
+<p>
+You can see the default Working Group I-D State Diagram in <a href="http://tools.ietf.org/html/rfc6174#section-4.1">Section 4.1 of RFC6174</a>
+</p>
 <form action="#transitions" method="POST">
 <table class="ietf-table">
   <tr>

From c9241edf7bfff7818dee82f8ec900cf7273c93fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Wed, 30 Mar 2011 19:07:45 +0000
Subject: [PATCH 56/57] Changed the "no user with this email" warning when
 adding delegates/shepherds.  - Legacy-Id: 3016

---
 ietf/templates/wgchairs/notexistdelegate.html | 20 +++++++++++++++++--
 ietf/wgchairs/forms.py                        |  2 +-
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/ietf/templates/wgchairs/notexistdelegate.html b/ietf/templates/wgchairs/notexistdelegate.html
index 3a7fa6482..706907022 100644
--- a/ietf/templates/wgchairs/notexistdelegate.html
+++ b/ietf/templates/wgchairs/notexistdelegate.html
@@ -1,10 +1,26 @@
+{% if shepherd %}
+<p>
+The shepherd you are trying to designate does not have a personal user-id and password to log-on to the Datatracker.
+</p>
+<p>
+An email will be sent to the following addresses to inform that
+the person you have designated to be one of your document shepherds
+currently does not have login credentials for the Datatracker
+and should contact the Secretariat to obtain their own user-id and
+password for the Datatracker.
+</p>
+{% else %}
 <p>
 The delegate you are trying to designate does not have a personal user-id and password to log-on to the Datatracker.
 </p>
 <p>
-An email will be sent to the following address to inform that the person designated sould contact with the Secretariat
-to obtain their own user-id and password to the Datatracker.
+An email will be sent to the following addresses to inform that
+the person you have designated to be one of your delegates
+currently does not have login credentials for the Datatracker
+and should contact the Secretariat to obtain their own user-id and
+password for the Datatracker.
 </p>
+{% endif %}
 <ul>
 {% for email in email_list %}
 <li>{{ email }}</li>
diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py
index 722101212..7aca1291c 100644
--- a/ietf/wgchairs/forms.py
+++ b/ietf/wgchairs/forms.py
@@ -278,7 +278,7 @@ class NotExistDelegateForm(MultipleDelegateForm):
 
     def as_p(self):
         email_list = self.get_email_list()
-        info = render_to_string('wgchairs/notexistdelegate.html', {'email_list': email_list})
+        info = render_to_string('wgchairs/notexistdelegate.html', {'email_list': email_list, 'shepherd': self.shepherd})
         return info + super(NotExistDelegateForm, self).as_p()
 
     def send_email(self, email, template):

From 39c3f6d75f7bd1d4dd978fc920c69e2a9a53810e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?=
 <esanchez@yaco.es>
Date: Mon, 4 Apr 2011 11:06:21 +0000
Subject: [PATCH 57/57] Fixes for state management by secretariat. Fixes #646 
 - Legacy-Id: 3019

---
 ietf/ietfworkflows/accounts.py | 2 ++
 ietf/ietfworkflows/forms.py    | 9 +++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/ietf/ietfworkflows/accounts.py b/ietf/ietfworkflows/accounts.py
index da44f475b..13f82f713 100644
--- a/ietf/ietfworkflows/accounts.py
+++ b/ietf/ietfworkflows/accounts.py
@@ -39,6 +39,8 @@ def can_edit_state(user, draft):
     streamed = get_streamed_draft(draft)
     if not streamed or not streamed.stream:
         person = get_person_for_user(user)
+        if not person:
+            return False
         return (is_secretariat(user) or
                 is_wgchair(person) or
                 is_wgdelegate(person))
diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
index 5ff14578d..379e09343 100644
--- a/ietf/ietfworkflows/forms.py
+++ b/ietf/ietfworkflows/forms.py
@@ -14,6 +14,7 @@ from ietf.ietfworkflows.utils import (get_workflow_for_draft, get_workflow_for_w
                                       update_state, FOLLOWUP_TAG,
                                       get_annotation_tags_for_draft,
                                       update_tags, update_stream)
+from ietf.ietfworkflows.accounts import is_secretariat
 from ietf.ietfworkflows.streams import (get_stream_from_draft, get_streamed_draft,
                                         get_stream_by_name, set_stream_for_draft)
 from ietf.ietfworkflows.constants import CALL_FOR_ADOPTION, IETF_STREAM
@@ -55,10 +56,14 @@ class NoWorkflowStateForm(StreamDraftForm):
         super(NoWorkflowStateForm, self).__init__(*args, **kwargs)
         self.wgs = None
         self.onlywg = None
-        wgs = set(self.person.wgchair_set.all()).union(set(self.person.wgdelegate_set.all()))
+        if is_secretariat(self.user):
+            wgs = IETFWG.objects.all()
+        else:
+            wgs = set([i.group_acronym for i in self.person.wgchair_set.all()]).union(set([i.wg for i in self.person.wgdelegate_set.all()]))
         if len(wgs) > 1:
             self.wgs = list(wgs)
-            self.fields['wg'].choices = [(i.group_acronym.pk, i.group_acronym.group_acronym.name) for i in self.wgs]
+            self.wgs.sort(lambda x,y: cmp(x.group_acronym.acronym, y.group_acronym.acronym))
+            self.fields['wg'].choices = [(i.pk, '%s - %s' % (i.group_acronym.acronym, i.group_acronym.name)) for i in self.wgs]
         else:
             self.onlywg = list(wgs)[0].group_acronym