Skip to content

Commit

Permalink
Merge pull request #6807 from ietf-tools/main
Browse files Browse the repository at this point in the history
ci: merge main to release
  • Loading branch information
rjsparks authored Dec 18, 2023
2 parents adf2a99 + 661e11f commit f3a574c
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 71 deletions.
47 changes: 47 additions & 0 deletions ietf/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
from ietf.group.factories import RoleFactory
from ietf.meeting.factories import MeetingFactory, SessionFactory
from ietf.meeting.models import Session
from ietf.nomcom.models import Volunteer, NomCom
from ietf.nomcom.factories import NomComFactory, nomcom_kwargs_for_year
from ietf.person.factories import PersonFactory, random_faker
from ietf.person.models import User
from ietf.person.models import PersonalApiKey
Expand Down Expand Up @@ -630,6 +632,7 @@ def test_api_new_meeting_registration(self):
'reg_type': 'hackathon',
'ticket_type': '',
'checkedin': 'False',
'is_nomcom_volunteer': 'False',
}
url = urlreverse('ietf.api.views.api_new_meeting_registration')
r = self.client.post(url, reg)
Expand Down Expand Up @@ -691,6 +694,50 @@ def test_api_new_meeting_registration(self):
missing_fields = [f.strip() for f in fields.split(',')]
self.assertEqual(set(missing_fields), set(drop_fields))

def test_api_new_meeting_registration_nomcom_volunteer(self):
'''Test that Volunteer is created if is_nomcom_volunteer=True
is submitted to API
'''
meeting = MeetingFactory(type_id='ietf')
reg = {
'apikey': 'invalid',
'affiliation': "Alguma Corporação",
'country_code': 'PT',
'meeting': meeting.number,
'reg_type': 'onsite',
'ticket_type': '',
'checkedin': 'False',
'is_nomcom_volunteer': 'True',
}
person = PersonFactory()
reg['email'] = person.email().address
reg['first_name'] = person.first_name()
reg['last_name'] = person.last_name()
now = datetime.datetime.now()
if now.month > 10:
year = now.year + 1
else:
year = now.year
# create appropriate group and nomcom objects
nomcom = NomComFactory.create(is_accepting_volunteers=True, **nomcom_kwargs_for_year(year))
url = urlreverse('ietf.api.views.api_new_meeting_registration')
r = self.client.post(url, reg)
self.assertContains(r, 'Invalid apikey', status_code=403)
oidcp = PersonFactory(user__is_staff=True)
# Make sure 'oidcp' has an acceptable role
RoleFactory(name_id='robot', person=oidcp, email=oidcp.email(), group__acronym='secretariat')
key = PersonalApiKey.objects.create(person=oidcp, endpoint=url)
reg['apikey'] = key.hash()
r = self.client.post(url, reg)
nomcom = NomCom.objects.last()
self.assertContains(r, "Accepted, New registration", status_code=202)
# assert Volunteer exists
self.assertEqual(Volunteer.objects.count(), 1)
volunteer = Volunteer.objects.last()
self.assertEqual(volunteer.person, person)
self.assertEqual(volunteer.nomcom, nomcom)
self.assertEqual(volunteer.origin, 'registration')

def test_api_version(self):
DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=datetime.timezone.utc), host='testapi.example.com',tz='UTC')
url = urlreverse('ietf.api.views.version')
Expand Down
17 changes: 15 additions & 2 deletions ietf/api/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright The IETF Trust 2017-2020, All Rights Reserved
# -*- coding: utf-8 -*-


import json
import pytz
import re
Expand Down Expand Up @@ -38,6 +37,7 @@
from ietf.ietfauth.views import send_account_creation_email
from ietf.ietfauth.utils import role_required
from ietf.meeting.models import Meeting
from ietf.nomcom.models import Volunteer, NomCom
from ietf.stats.models import MeetingRegistration
from ietf.utils import log
from ietf.utils.decorators import require_api_key
Expand Down Expand Up @@ -140,7 +140,7 @@ def api_new_meeting_registration(request):
def err(code, text):
return HttpResponse(text, status=code, content_type='text/plain')
required_fields = [ 'meeting', 'first_name', 'last_name', 'affiliation', 'country_code',
'email', 'reg_type', 'ticket_type', 'checkedin']
'email', 'reg_type', 'ticket_type', 'checkedin', 'is_nomcom_volunteer']
fields = required_fields + []
if request.method == 'POST':
# parameters:
Expand Down Expand Up @@ -202,6 +202,19 @@ def err(code, text):
else:
send_account_creation_email(request, email)
response += ", Email sent"

# handle nomcom volunteer
if data['is_nomcom_volunteer'] and object.person:
try:
nomcom = NomCom.objects.get(is_accepting_volunteers=True)
except (NomCom.DoesNotExist, NomCom.MultipleObjectsReturned):
nomcom = None
if nomcom:
Volunteer.objects.create(
nomcom=nomcom,
person=object.person,
affiliation=data['affiliation'],
origin='registration')
return HttpResponse(response, status=202, content_type='text/plain')
else:
return HttpResponse(status=405)
Expand Down
9 changes: 8 additions & 1 deletion ietf/doc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,8 @@ def referenced_by(self):
| models.Q(source__type__slug="rfc")
)


def referenced_by_rfcs(self):
"""Get refs to this doc from RFCs"""
return self.relations_that(("refnorm", "refinfo", "refunk", "refold")).filter(
source__type__slug="rfc"
)
Expand All @@ -675,6 +675,13 @@ def contains(self):
def part_of(self):
return self.related_that("contains")

def referenced_by_rfcs_as_rfc_or_draft(self):
"""Get refs to this doc, or a draft/rfc it came from, from an RFC"""
refs_to = self.referenced_by_rfcs()
if self.type_id == "rfc" and self.came_from_draft():
refs_to |= self.came_from_draft().referenced_by_rfcs()
return refs_to

class Meta:
abstract = True

Expand Down
47 changes: 47 additions & 0 deletions ietf/doc/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2951,4 +2951,51 @@ def test_revisions(self):
self.assertEqual(draft.revisions_by_dochistory(),[f"{i:02d}" for i in range(8,10)])
self.assertEqual(draft.revisions_by_newrevisionevent(),[f"{i:02d}" for i in [*range(0,5), *range(6,10)]])

def test_referenced_by_rfcs(self):
# n.b., no significance to the ref* values in this test
referring_draft = WgDraftFactory()
(rfc, referring_rfc) = WgRfcFactory.create_batch(2)
rfc.targets_related.create(relationship_id="refnorm", source=referring_draft)
rfc.targets_related.create(relationship_id="refnorm", source=referring_rfc)
self.assertCountEqual(
rfc.referenced_by_rfcs(),
rfc.targets_related.filter(source=referring_rfc),
)

def test_referenced_by_rfcs_as_rfc_or_draft(self):
# n.b., no significance to the ref* values in this test
draft = WgDraftFactory()
rfc = WgRfcFactory()
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)

# Draft referring to the rfc and the draft - should not be reported at all
draft_referring_to_both = WgDraftFactory()
draft_referring_to_both.relateddocument_set.create(relationship_id="refnorm", target=draft)
draft_referring_to_both.relateddocument_set.create(relationship_id="refnorm", target=rfc)

# RFC referring only to the draft - should be reported for either the draft or the rfc
rfc_referring_to_draft = WgRfcFactory()
rfc_referring_to_draft.relateddocument_set.create(relationship_id="refinfo", target=draft)

# RFC referring only to the rfc - should be reported only for the rfc
rfc_referring_to_rfc = WgRfcFactory()
rfc_referring_to_rfc.relateddocument_set.create(relationship_id="refinfo", target=rfc)

# RFC referring only to the rfc - should be reported only for the rfc
rfc_referring_to_rfc = WgRfcFactory()
rfc_referring_to_rfc.relateddocument_set.create(relationship_id="refinfo", target=rfc)

# RFC referring to the rfc and the draft - should be reported for both
rfc_referring_to_both = WgRfcFactory()
rfc_referring_to_both.relateddocument_set.create(relationship_id="refnorm", target=draft)
rfc_referring_to_both.relateddocument_set.create(relationship_id="refnorm", target=rfc)

self.assertCountEqual(
draft.referenced_by_rfcs_as_rfc_or_draft(),
draft.targets_related.filter(source__type="rfc"),
)

self.assertCountEqual(
rfc.referenced_by_rfcs_as_rfc_or_draft(),
draft.targets_related.filter(source__type="rfc") | rfc.targets_related.filter(source__type="rfc"),
)
18 changes: 18 additions & 0 deletions ietf/doc/views_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1437,8 +1437,26 @@ def document_references(request, name):
return render(request, "doc/document_references.html",dict(doc=doc,refs=sorted(refs,key=lambda x:x.target.name),))

def document_referenced_by(request, name):
"""View documents that reference the named document
The view lists both direct references to a the named document, plus references to
related other documents. For a draft that became an RFC, this will include references
to the RFC. For an RFC, this will include references to the draft it came from, if any.
For a subseries document, this will include references to any of the RFC documents it
contains.
In the rendered output, a badge is applied to indicate the name of the document the
reference actually targeted. E.g., on the display for a draft that became RFC NNN,
references included because they point to that RFC would be shown with a tag "As RFC NNN".
The intention is to make the "Referenced By" page useful for finding related work while
accurately reflecting the actual reference relationships.
"""
doc = get_object_or_404(Document,name=name)
refs = doc.referenced_by()
if doc.came_from_draft():
refs |= doc.came_from_draft().referenced_by()
if doc.became_rfc():
refs |= doc.became_rfc().referenced_by()
if doc.type_id in ["bcp","std","fyi"]:
for rfc in doc.contains():
refs |= rfc.referenced_by()
Expand Down
53 changes: 29 additions & 24 deletions ietf/doc/views_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,13 +418,19 @@ def state_name(doc_type, state, shorten=True):
for dt in AD_WORKLOAD
}


def state_to_doc_type(state):
for dt in STATE_SLUGS:
if state in STATE_SLUGS[dt]:
return dt
return None


IESG_STATES = State.objects.filter(type="draft-iesg").values_list("name", flat=True)


def date_to_bucket(date, now, num_buckets):
return num_buckets - min(
num_buckets, int((now.date() - date.date()).total_seconds() / 60 / 60 / 24)
)
return num_buckets - int((now.date() - date.date()).total_seconds() / 60 / 60 / 24)


def ad_workload(request):
Expand Down Expand Up @@ -477,6 +483,7 @@ def ad_workload(request):
to_state = state_name(dt, state, shorten=False)
elif e.desc.endswith("has been replaced"):
# stop tracking
last = e.time
break

if not to_state:
Expand All @@ -501,26 +508,30 @@ def ad_workload(request):
elif to_state == "RFC Published":
to_state = "RFC"

if dt == "rfc":
new_dt = state_to_doc_type(to_state)
if new_dt is not None and new_dt != dt:
dt = new_dt

if to_state not in STATE_SLUGS[dt].keys() or to_state == "Replaced":
# change into a state the AD dashboard doesn't display
if to_state in IESG_STATES or to_state == "Replaced":
# if it's an IESG state we don't display, we're done with this doc
# if it's an IESG state we don't display, record it's time
last = e.time
break
# if it's not an IESG state, keep going with next event
# keep going with next event
continue

sn = STATE_SLUGS[dt][to_state]
buckets_start = date_to_bucket(e.time, now, days)
buckets_end = date_to_bucket(last, now, days)

if buckets_end >= days:
# this event is older than we record in the history
if last == now:
# but since we didn't record any state yet,
# this is the state the doc was in for the
# entire history
for b in range(buckets_start, days):
if dt == "charter" and to_state == "Approved" and buckets_start < 0:
# don't count old charter approvals
break

if buckets_start <= 0:
if buckets_end >= 0:
for b in range(0, buckets_end):
ad.buckets[dt][sn][b].append(doc.name)
sums[dt][sn][b].append(doc.name)
last = e.time
Expand All @@ -532,15 +543,6 @@ def ad_workload(request):
sums[dt][sn][b].append(doc.name)
last = e.time

if last == now:
s = state_name(dt, state, shorten=False)
if s in STATE_SLUGS[dt].keys():
# we didn't have a single event for this doc, assume
# the current state applied throughput the history
for b in range(days):
ad.buckets[dt][state][b].append(doc.name)
sums[dt][state][b].append(doc.name)

metadata = [
{
"type": (dt, doc_type_name(dt)),
Expand All @@ -564,8 +566,11 @@ def ad_workload(request):

def docs_for_ad(request, name):
def sort_key(doc):
key = list(AD_WORKLOAD.keys()).index(doc_type(doc))
return key
dt = doc_type(doc)
dt_key = list(AD_WORKLOAD.keys()).index(dt)
ds = doc_state(doc)
ds_key = AD_WORKLOAD[dt].index(ds) if ds in AD_WORKLOAD[dt] else 99
return dt_key * 100 + ds_key

ad = None
responsible = Document.objects.values_list("ad", flat=True).distinct()
Expand Down
2 changes: 1 addition & 1 deletion ietf/group/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,7 +1296,7 @@ def stream_documents(request, acronym):
qs = Document.objects.filter(stream=acronym).filter(
Q(type_id="draft", states__type="draft", states__slug="active")
| Q(type_id="rfc")
)
).distinct()
docs, meta = prepare_document_table(request, qs, max_results=1000)
return render(request, 'group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta, 'editable':editable } )

Expand Down
10 changes: 8 additions & 2 deletions ietf/meeting/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2016-2020, All Rights Reserved
# Copyright The IETF Trust 2016-2023, All Rights Reserved
# -*- coding: utf-8 -*-


Expand Down Expand Up @@ -360,7 +360,13 @@ def save_agenda(self):
class InterimAnnounceForm(forms.ModelForm):
class Meta:
model = Message
fields = ('to', 'frm', 'cc', 'bcc', 'reply_to', 'subject', 'body')
fields = ('to', 'cc', 'frm', 'subject', 'body')

def __init__(self, *args, **kwargs):
super(InterimAnnounceForm, self).__init__(*args, **kwargs)
self.fields['frm'].label='From'
self.fields['frm'].widget.attrs['readonly'] = True
self.fields['to'].widget.attrs['readonly'] = True

def save(self, *args, **kwargs):
user = kwargs.pop('user')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.2.7 on 2023-11-05 09:45

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("nomcom", "0003_alter_nomination_share_nominator"),
]

operations = [
migrations.AddField(
model_name="volunteer",
name="origin",
field=models.CharField(default="datatracker", max_length=32),
),
migrations.AddField(
model_name="volunteer",
name="time",
field=models.DateTimeField(auto_now_add=True, null=True, blank=True),
),
migrations.AddField(
model_name="volunteer",
name="withdrawn",
field=models.DateTimeField(blank=True, null=True),
),
]
5 changes: 4 additions & 1 deletion ietf/nomcom/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,10 @@ class Volunteer(models.Model):
nomcom = ForeignKey('NomCom')
person = ForeignKey(Person)
affiliation = models.CharField(blank=True, max_length=255)

time = models.DateTimeField(auto_now_add=True, null=True, blank=True)
origin = models.CharField(max_length=32, default='datatracker')
withdrawn = models.DateTimeField(blank=True, null=True)

def __str__(self):
return f'{self.person} for {self.nomcom}'

Loading

0 comments on commit f3a574c

Please sign in to comment.