Merged changes from current trunk to Py3 branch.

- Legacy-Id: 16468
This commit is contained in:
Henrik Levkowetz 2019-07-16 15:36:16 +00:00
commit e03784132d
32 changed files with 13719 additions and 11077 deletions

8
PLAN
View file

@ -15,6 +15,14 @@ Planned work in rough order
* Transition to Python 3. This will make it easier to add add support for
internationalsed email, and also other i18n enhancements.
* Change the draft submission form so that an email address is required for
each author in order to complete self-service draft submission. Missing
email address(es) will lead to failure, and require submission via the
secretariat (where email addresses for all will also be required).
* Simplify submission if submitter is logged into the datatracker, skipping the
email verification step.
* Polish the htmlization pages, making the style identical with tools.ietf.org.
* Revisit the review tool, work through the accumulated tickets.

View file

@ -1,3 +1,71 @@
ietfdb (6.98.4) ietf; urgency=medium
This is a small bugfix release, to clear the slate before merging
in the Python 2/3 conversion. From the commit log:
* Added a validation step for SearchablePersonField, to avoid later
server 500 errors on bad input.
* Merged in [16404] from rjsparks@nostrum.com:
Only look for ietf/datatracker-env images when extracting the most
recent build to tag as latest.
* Merged in [16359] from rcross@amsl.com:
Fix registration import, use user.person if it exists.
* Merged in [16096] from rcross@amsl.com:
Update admin permissions. Grant secretariat change permissions on
dbtemplate so they can update proceedings pages.
* Turned off html autoescape in IPR email templates. This fixes
inappropriate html escapes that occurred in various IPR-related
emails.
* Added missing code to skip coverage measurement for skippable tests.
-- Henrik Levkowetz <henrik@levkowetz.com> 16 Jul 2019 14:14:12 +0000
ietfdb (6.98.3) ietf; urgency=medium
* Increased the length of the list Subscribed email field from 64 to 128,
updated the import_mailman_listinfo management command, and added a
migration for the model change.
-- Henrik Levkowetz <henrik@levkowetz.com> 04 Jul 2019 16:07:14 +0000
ietfdb (6.98.2) ietf; urgency=medium
This is a minor release that fixes some bugs and tweaks some settings.
From the commit log:
* Changed some tests to match production group type changes for some special
groups.
* Made the bin/daily cron script quiteter.
* Various changes to make yang-related management commands quieter
* Added a fix for an issue in get_meeting_registration_data() which could
happen if we tried to create a Person record for with a user of an
existing Person.
* Changed the permitted length of patent_title from 127 to 255.
* Added additional guards against duplicate m2m entries.
* Added a utility to check copyright statements in specified files.
* Updated some functions and views in secr/srec/ to use GroupFeatures
instead of hardcoded lists of group types.
* Made session requests also work for ad-hoc groups, which may not have a
parent area.
-- Henrik Levkowetz <henrik@levkowetz.com> 03 Jul 2019 20:27:54 +0000
ietfdb (6.98.1) ietf; urgency=medium
This is a bugfix release that cleans up some remaining issues from the

View file

@ -123,6 +123,6 @@ fi
docker rmi -f ietf/datatracker-environment:trunk || true
docker build -t ietf/datatracker-environment:$TAG docker/
docker tag $(docker images -q | head -n 1) ietf/datatracker-environment:latest
docker tag $(docker images -q ietf/datatracker-environment | head -n 1) ietf/datatracker-environment:latest
docker push ietf/datatracker-environment:latest
docker push ietf/datatracker-environment:$TAG
docker push ietf/datatracker-environment:$TAG

View file

@ -7,13 +7,13 @@ from __future__ import absolute_import, print_function, unicode_literals
from . import checks # pyflakes:ignore
# Don't add patch number here:
__version__ = "6.98.2.dev0"
__version__ = "6.98.5.dev0"
# set this to ".p1", ".p2", etc. after patching
__patch__ = ""
__date__ = "$Date$"
__rev__ = "$Rev$ (dev) Latest release: Rev. 16294 "
__rev__ = "$Rev$ (dev) Latest release: Rev. 16464 "
__id__ = "$Id$"

View file

@ -55,6 +55,7 @@ def main():
# Set Auth Group Admin Permissions
names = ['auth.add_user','auth.change_user','auth.delete_user',
'dbtemplate.change_dbtemplate',
'group.add_group','group.change_group','group.delete_group',
'group.add_role','group.change_role','group.delete_role',
'group.add_groupevent','group.change_groupevent','group.delete_groupevent',

View file

@ -88,7 +88,8 @@ class CommunityListTests(TestCase):
# with list
clist = CommunityList.objects.create(user=User.objects.get(username="plain"))
clist.added_docs.add(draft)
if not draft in clist.added_docs.all():
clist.added_docs.add(draft)
SearchRule.objects.create(
community_list=clist,
rule_type="name_contains",
@ -250,7 +251,8 @@ class CommunityListTests(TestCase):
# with list
clist = CommunityList.objects.create(user=User.objects.get(username="plain"))
clist.added_docs.add(draft)
if not draft in clist.added_docs.all():
clist.added_docs.add(draft)
SearchRule.objects.create(
community_list=clist,
rule_type="name_contains",
@ -285,7 +287,8 @@ class CommunityListTests(TestCase):
# with list
clist = CommunityList.objects.create(user=User.objects.get(username="plain"))
clist.added_docs.add(draft)
if not draft in clist.added_docs.all():
clist.added_docs.add(draft)
SearchRule.objects.create(
community_list=clist,
rule_type="name_contains",
@ -326,7 +329,8 @@ class CommunityListTests(TestCase):
# subscription with list
clist = CommunityList.objects.create(user=User.objects.get(username="plain"))
clist.added_docs.add(draft)
if not draft in clist.added_docs.all():
clist.added_docs.add(draft)
SearchRule.objects.create(
community_list=clist,
rule_type="name_contains",
@ -369,7 +373,8 @@ class CommunityListTests(TestCase):
draft = WgDraftFactory()
clist = CommunityList.objects.create(user=User.objects.get(username="plain"))
clist.added_docs.add(draft)
if not draft in clist.added_docs.all():
clist.added_docs.add(draft)
EmailSubscription.objects.create(community_list=clist, email=Email.objects.filter(person__user__username="plain").first(), notify_on="significant")

View file

@ -59,7 +59,8 @@ def manage_list(request, username=None, acronym=None, group_type=None):
clist.save()
for d in add_doc_form.cleaned_data['documents']:
clist.added_docs.add(d)
if not d in clist.added_docs.all():
clist.added_docs.add(d)
return HttpResponseRedirect("")
else:
@ -136,7 +137,8 @@ def track_document(request, name, username=None, acronym=None):
if clist.pk is None:
clist.save()
clist.added_docs.add(doc)
if not doc in clist.added_docs.all():
clist.added_docs.add(doc)
if request.is_ajax():
return HttpResponse(json.dumps({ 'success': True }), content_type='application/json')

View file

@ -101,8 +101,6 @@ class ReviewTests(TestCase):
self.assertEqual(len(outbox),2)
self.assertTrue('reviewteam Early' in outbox[0]['Subject'])
if not 'reviewsecretary@' in outbox[0]['To']:
print(outbox[0].as_string())
self.assertTrue('reviewsecretary@' in outbox[0]['To'])
self.assertTrue('reviewteam3 Early' in outbox[1]['Subject'])
if not 'reviewsecretary3@' in outbox[1]['To']:

View file

@ -11,6 +11,8 @@ from django.utils.html import escape
from django import forms
from django.urls import reverse as urlreverse
import debug # pyflakes:ignore
from ietf.ipr.models import IprDisclosureBase
def select2_id_ipr_title_json(value):

View file

@ -160,7 +160,7 @@ class GenericDisclosureForm(forms.Form):
patent_number = forms.CharField(max_length=127, required=False, validators=[ validate_patent_number ],
help_text = "Patent publication or application number (2-letter country code followed by serial number)")
patent_inventor = forms.CharField(max_length=63, required=False, validators=[ validate_name ], help_text="Inventor name")
patent_title = forms.CharField(max_length=127, required=False, validators=[ validate_title ], help_text="Title of invention")
patent_title = forms.CharField(max_length=255, required=False, validators=[ validate_title ], help_text="Title of invention")
patent_date = forms.DateField(required=False, help_text="Date granted or applied for")
patent_notes = forms.CharField(max_length=1024, required=False, widget=forms.Textarea)
@ -229,7 +229,7 @@ class IprDisclosureFormBase(forms.ModelForm):
patent_number = forms.CharField(max_length=127, required=True, validators=[ validate_patent_number ],
help_text = "Patent publication or application number (2-letter country code followed by serial number)")
patent_inventor = forms.CharField(max_length=63, required=True, validators=[ validate_name ], help_text="Inventor name")
patent_title = forms.CharField(max_length=127, required=True, validators=[ validate_title ], help_text="Title of invention")
patent_title = forms.CharField(max_length=255, required=True, validators=[ validate_title ], help_text="Title of invention")
patent_date = forms.DateField(required=True, help_text="Date granted or applied for")
patent_notes = forms.CharField(max_length=1024, required=False, widget=forms.Textarea)

View file

@ -1,4 +1,4 @@
# Copyright The IETF Trust 2016, All Rights Reserved
# Copyright The IETF Trust 2016-2019, All Rights Reserved
import sys
from textwrap import dedent
@ -35,6 +35,7 @@ def import_mailman_listinfo(verbosity=0):
names = list(Utils.list_names())
names.sort()
addr_max_length = Subscribed._meta.get_field('email').max_length
for name in names:
mlist = MailList.MailList(name, lock=False)
note("List: %s" % mlist.internal_name())
@ -62,7 +63,7 @@ def import_mailman_listinfo(verbosity=0):
note(" Removing address with no subscriptions: %s" % (addr))
old.delete()
for addr in members:
if len(addr) > 64:
if len(addr) > addr_max_length:
sys.stderr.write(" ** Email address subscribed to '%s' too long for table: <%s>\n" % (name, addr))
continue
if not addr in known:

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright The IETF Trust 2019, All Rights Reserved
# Generated by Django 1.11.22 on 2019-07-03 13:44
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mailinglists', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='subscribed',
name='email',
field=models.CharField(max_length=128, validators=[django.core.validators.EmailValidator()]),
),
]

View file

@ -26,7 +26,7 @@ class List(models.Model):
@python_2_unicode_compatible
class Subscribed(models.Model):
time = models.DateTimeField(auto_now_add=True)
email = models.CharField(max_length=64, validators=[validate_email])
email = models.CharField(max_length=128, validators=[validate_email])
lists = models.ManyToManyField(List)
def __str__(self):
return "<Subscribed: %s at %s>" % (self.email, self.time)

View file

@ -34,8 +34,9 @@ from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignm
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.utils import finalize
from ietf.name.models import SessionStatusName, ImportantDateName
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
from ietf.utils.text import xslugify
from ietf.person.factories import PersonFactory
@ -490,6 +491,7 @@ class MeetingTests(TestCase):
os.unlink(filename)
@skipIf(skip_pdf_tests, skip_message)
@skip_coverage
def test_session_draft_pdf(self):
session = SessionFactory(group__type_id='wg',meeting__type_id='ietf')
doc = DocumentFactory(type_id='draft')

File diff suppressed because it is too large Load diff

View file

@ -10,9 +10,10 @@ import six
from collections import Counter
from six.moves.urllib.parse import urlencode
from django.utils.html import escape
from django import forms
from django.core.validators import validate_email
from django.urls import reverse as urlreverse
from django.utils.html import escape
import debug # pyflakes:ignore
@ -76,6 +77,16 @@ class SearchablePersonsField(forms.CharField):
def parse_select2_value(self, value):
return [x.strip() for x in value.split(",") if x.strip()]
def check_pks(self, pks):
if self.model == Person:
for pk in pks:
if not pk.isdigit():
raise forms.ValidationError("Unexpected value: %s" % pk)
elif self.model == Email:
for pk in pks:
validate_email(pk)
return pks
def prepare_value(self, value):
if not value:
value = ""
@ -105,7 +116,7 @@ class SearchablePersonsField(forms.CharField):
def clean(self, value):
value = super(SearchablePersonsField, self).clean(value)
pks = self.parse_select2_value(value)
pks = self.check_pks(self.parse_select2_value(value))
objs = self.model.objects.filter(pk__in=pks)
if self.model == Email:

View file

@ -50,7 +50,7 @@ class SessionRequestTestCase(TestCase):
self.assertEqual(r.status_code, 200)
sched = r.context['scheduled_groups']
unsched = r.context['unscheduled_groups']
self.assertEqual(len(unsched),3)
self.assertEqual(len(unsched),8)
self.assertEqual(len(sched),2)
def test_approve(self):

View file

@ -91,7 +91,7 @@ def get_requester_text(person,group):
roles = group.role_set.filter(name__in=('chair','secr'),person=person)
if roles:
return '%s, a %s of the %s working group' % (person.ascii, roles[0].name, group.acronym)
if group.parent.role_set.filter(name='ad',person=person):
if group.parent and group.parent.role_set.filter(name='ad',person=person):
return '%s, a %s Area Director' % (person.ascii, group.parent.acronym.upper())
if person.role_set.filter(name='secr',group__acronym='secretariat'):
return '%s, on behalf of the %s working group' % (person.ascii, group.acronym)
@ -494,7 +494,8 @@ def main(request):
)
meeting = get_meeting()
scheduled_groups,unscheduled_groups = groups_by_session(request.user, meeting, types=['wg','rg','ag'])
scheduled_groups, unscheduled_groups = groups_by_session(request.user, meeting)
# warn if there are no associated groups
if not scheduled_groups and not unscheduled_groups:

View file

@ -13,7 +13,7 @@ from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
# Datatracker imports
from ietf.group.models import Group
from ietf.group.models import Group, GroupFeatures
from ietf.meeting.models import Session
from ietf.ietfauth.utils import has_role
@ -56,9 +56,8 @@ def get_my_groups(user,conclude=False):
states = ['bof','proposed','active']
if conclude:
states.extend(['conclude','bof-conc'])
types = ['wg','rg','ag','team','iab']
all_groups = Group.objects.filter(type__in=types,state__in=states).order_by('acronym')
all_groups = Group.objects.filter(type__features__has_meetings=True, state__in=states).order_by('acronym')
if user == None or has_role(user,'Secretariat'):
return all_groups
@ -101,8 +100,10 @@ def groups_by_session(user, meeting, types=None):
if group.state_id not in ('conclude','bof-conc'):
groups_no_session.append(group)
if types:
groups_session = [x for x in groups_session if x.type_id in types]
groups_no_session = [x for x in groups_no_session if x.type_id in types]
if not types:
types = GroupFeatures.objects.filter(has_meetings=True).values_list('type', flat=True)
groups_session = [x for x in groups_session if x.type_id in types]
groups_no_session = [x for x in groups_no_session if x.type_id in types]
return groups_session, groups_no_session

View file

@ -63,7 +63,6 @@ ADMINS = (
# ('Ole Laursen', 'olau@iola.dk'),
('Ryan Cross', 'rcross@amsl.com'),
('Glen Barney', 'glen@amsl.com'),
('Matt Larson', 'mlarson@amsl.com'),
)
BUG_REPORT_EMAIL = "datatracker-project@ietf.org"

View file

@ -294,16 +294,19 @@ def get_meeting_registration_data(meeting):
email=address,
)
aliases = Alias.objects.filter(name=regname)
if aliases.exists():
person = aliases.first().person
else:
# Create the new Person object.
person = Person.objects.create(
name=regname,
ascii=ascii_name,
user=user,
)
try:
person = user.person
except Person.DoesNotExist:
aliases = Alias.objects.filter(name=regname)
if aliases.exists():
person = aliases.first().person
else:
# Create the new Person object.
person = Person.objects.create(
name=regname,
ascii=ascii_name,
user=user,
)
# Create an associated Email address for this Person
try:

View file

@ -84,7 +84,9 @@ def has_been_replaced_by(name):
return None
def validate_submission_name(name):
if not re.search(r'^draft-[a-z][-a-z0-9]{0,43}$', name):
if not re.search(r'^draft-[a-z][-a-z0-9]{0,43}(-\d\d)?$', name):
if re.search(r'-\d\d$', name):
name = name[:-3]
if len(name) > 50:
return "Expected the draft name to be at most 50 ascii characters long; found %d." % len(name)
else:

View file

@ -1,4 +1,4 @@
{% load ietf_filters %}
{% autoescape off %}{% load ietf_filters %}
The Patent Holder states that its position with respect to licensing any patent claims
contained in the patent(s) or patent application(s) disclosed above that would necessarily
be infringed by implementation of the technology required by the relevant IETF
@ -29,3 +29,4 @@ specification, is as follows(select one licensing declaration option only):
{% endif %}Licensing information, comments, notes or URL for further information:
{{ info|safe|wordwrap:76|indent }}
{% endautoescape %}

View file

@ -1,4 +1,4 @@
{% load ietf_filters %}To: {{ to_email }}
{% autoescape off %}{% load ietf_filters %}To: {{ to_email }}
From: IETF Secretariat <ietf-ipr@ietf.org>
Subject: IPR Disclosure {{ ipr.title }}
Cc: {{ cc_email }}
@ -12,3 +12,4 @@ An IPR disclosure that pertains to your {{ doc_info }} was submitted to the IETF
Thank you
IETF Secretariat
{% endautoescape %}

View file

@ -1,4 +1,4 @@
To: {{ to_email }}
{% autoescape off %}To: {{ to_email }}
From: IETF Secretariat <ietf-ipr@ietf.org>
Subject: Posting of IPR Disclosure
Cc:
@ -12,4 +12,5 @@ and has been posted on the "IETF Page of Intellectual Property Rights Disclosure
Thank you
IETF Secretariat
IETF Secretariat
{% endautoescape %}

View file

@ -1,4 +1,4 @@
To: {{ to_email }}
{% autoescape off %}To: {{ to_email }}
From: IETF Secretariat <ietf-ipr@ietf.org>
Subject: Posting of IPR {% if ipr.updates %}Updated {% endif %}Disclosure
Cc: {{ cc_email }}
@ -17,4 +17,5 @@ IPR disclosure ID #{{ rel.target.pk }}, "{{ rel.target.title }}", which was post
Thank you
IETF Secretariat
IETF Secretariat
{% endautoescape %}

View file

@ -1,4 +1,4 @@
To: {{ to_email }}
{% autoescape off %}To: {{ to_email }}
From: IETF Secretariat <ietf-ipr@ietf.org>
Subject: IPR update notification
Reply-To: {{ reply_to }}
@ -24,4 +24,5 @@ the update until we can be assured it is authorized.
Thank you
IETF Secretariat
IETF Secretariat
{% endautoescape %}

View file

@ -281,7 +281,8 @@ def condition_message(to, frm, subject, msg, cc, extra):
if name:
to_hdr.append('"%s"' % name)
to_hdr.append("<%s>," % addr)
to_str = to_hdr.encode('utf-8')
# Please note: The following .encode() does _not_ take a charset argument
to_str = to_hdr.encode()
if to_str and to_str[-1] == ',':
to_str=to_str[:-1]
# It's important to use this string, and not assign the Header object.

View file

@ -82,7 +82,7 @@ class Command(BaseCommand):
modfile.rename(str(moddir/name))
model_list = [ n.replace('"','') for n in model_list ]
except Exception as e:
print("** Error when extracting from %s: %s" % (file, str(e)))
self.stderr.write("** Error when extracting from %s: %s" % (file, str(e)))
sys.stdout = saved_stdout
sys.stderr = saved_stderr
#
@ -107,7 +107,8 @@ class Command(BaseCommand):
if item.stat().st_mtime > latest:
latest = item.stat().st_mtime
print("Extracting to %s ..." % moddir)
if verbosity > 0:
self.stdout.write("Extracting to %s ..." % moddir)
for item in rfcdir.iterdir():
if item.is_file() and item.name.startswith('rfc') and item.name.endswith('.txt') and item.name[3:-4].isdigit():
if item.stat().st_mtime > latest:
@ -115,16 +116,17 @@ class Command(BaseCommand):
for name in model_list:
if name.startswith('ietf') or name.startswith('iana'):
if verbosity > 1:
print(" Extracted from %s: %s" % (item, name))
else:
sys.stdout.write('.')
sys.stdout.flush()
self.stdout.write(" Extracted from %s: %s" % (item, name))
elif verbosity > 0:
self.stdout.write('.', ending='')
self.stdout.flush()
else:
modfile = moddir / name
modfile.unlink()
if verbosity > 1:
print(" Skipped module from %s: %s" % (item, name))
print("")
self.stdout.write(" Skipped module from %s: %s" % (item, name))
if verbosity > 0:
self.stdout.write("")
# Extract valid modules from drafts
@ -137,11 +139,13 @@ class Command(BaseCommand):
moddir = Path(settings.SUBMIT_YANG_DRAFT_MODEL_DIR)
if not moddir.exists():
moddir.mkdir(parents=True)
print("Emptying %s ..." % moddir)
if verbosity > 0:
self.stdout.write("Emptying %s ..." % moddir)
for item in moddir.iterdir():
item.unlink()
print("Extracting to %s ..." % moddir)
if verbosity > 0:
self.stdout.write("Extracting to %s ..." % moddir)
for item in draftdir.iterdir():
try:
if item.is_file() and item.name.startswith('draft') and item.name.endswith('.txt') and active(item):
@ -149,18 +153,19 @@ class Command(BaseCommand):
for name in model_list:
if not name.startswith('example'):
if verbosity > 1:
print(" Extracted module from %s: %s" % (item, name))
else:
sys.stdout.write('.')
sys.stdout.flush()
self.stdout.write(" Extracted module from %s: %s" % (item, name))
elif verbosity > 0:
self.stdout.write('.', ending='')
self.stdout.flush()
else:
modfile = moddir / name
modfile.unlink()
if verbosity > 1:
print(" Skipped module from %s: %s" % (item, name))
self.stdout.write(" Skipped module from %s: %s" % (item, name))
except UnicodeDecodeError as e:
sys.stderr.write('\nError: %s\n' % (e, ))
sys.stderr.write(item.name)
sys.stderr.write('\n')
print("")
self.stderr.write('\nError: %s' % (e, ))
self.stderr.write(item.name)
self.stderr.write('')
if verbosity > 0:
self.stdout.write('')

View file

@ -4,7 +4,6 @@
from __future__ import absolute_import, print_function, unicode_literals
import sys
import json
from textwrap import dedent
@ -40,8 +39,8 @@ class Command(BaseCommand):
def check_yang(self, checker, draft, force=False):
if self.verbosity > 1:
self.stdout.write("Checking %s-%s" % (draft.name, draft.rev))
else:
sys.stderr.write('.')
elif self.verbosity > 0:
self.stderr.write('.', ending='')
submission = Submission.objects.filter(name=draft.name, rev=draft.rev).order_by('-id').first()
if submission or force:
check = submission.checks.filter(checker=checker.name).order_by('-id').first()
@ -62,7 +61,7 @@ class Command(BaseCommand):
message=message, errors=errors, warnings=warnings, items=items,
symbol=checker.symbol)
else:
self.stderr.write("Error: did not find any submission object for %s-%s\n" % (draft.name, draft.rev))
self.stderr.write("Error: did not find any submission object for %s-%s" % (draft.name, draft.rev))
def handle(self, *filenames, **options):
"""

View file

@ -79,16 +79,16 @@ def make_immutable_base_data():
iab = create_group(name="Internet Architecture Board", acronym="iab", type_id="ietf", parent=ietf)
create_person(iab, "chair")
ise = create_group(name="Independent Submission Editor", acronym="ise", type_id="ietf")
ise = create_group(name="Independent Submission Editor", acronym="ise", type_id="rfcedtyp")
create_person(ise, "chair")
rsoc = create_group(name="RFC Series Oversight Committee", acronym="rsoc", type_id="ietf")
rsoc = create_group(name="RFC Series Oversight Committee", acronym="rsoc", type_id="rfcedtyp")
create_person(rsoc, "chair")
iepg = create_group(name="IEPG", acronym="iepg", type_id="ietf")
iepg = create_group(name="IEPG", acronym="iepg", type_id="adhoc")
create_person(iepg, "chair")
iana = create_group(name="IANA", acronym="iana", type_id="ietf")
iana = create_group(name="IANA", acronym="iana", type_id="iana")
create_person(iana, "auth", name="Iña Iana", username="iana", email_address="iana@ia.na")
rfc_editor = create_group(name="RFC Editor", acronym="rfceditor", type_id="rfcedtyp")

Binary file not shown.