Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Capture that IPR disclosures are removed under the Objectively False IPR Disclosure Policy #6231

Merged
merged 7 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions ietf/doc/migrations/0007_alter_docevent_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Generated by Django 4.2.4 on 2023-08-23 21:38

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("doc", "0006_statements"),
]

operations = [
migrations.AlterField(
model_name="docevent",
name="type",
field=models.CharField(
choices=[
("new_revision", "Added new revision"),
("new_submission", "Uploaded new revision"),
("changed_document", "Changed document metadata"),
("added_comment", "Added comment"),
("added_message", "Added message"),
("edited_authors", "Edited the documents author list"),
("deleted", "Deleted document"),
("changed_state", "Changed state"),
("changed_stream", "Changed document stream"),
("expired_document", "Expired document"),
("extended_expiry", "Extended expiry of document"),
("requested_resurrect", "Requested resurrect"),
("completed_resurrect", "Completed resurrect"),
("changed_consensus", "Changed consensus"),
("published_rfc", "Published RFC"),
(
"added_suggested_replaces",
"Added suggested replacement relationships",
),
(
"reviewed_suggested_replaces",
"Reviewed suggested replacement relationships",
),
("changed_action_holders", "Changed action holders for document"),
("changed_group", "Changed group"),
("changed_protocol_writeup", "Changed protocol writeup"),
("changed_charter_milestone", "Changed charter milestone"),
("initial_review", "Set initial review time"),
("changed_review_announcement", "Changed WG Review text"),
("changed_action_announcement", "Changed WG Action text"),
("started_iesg_process", "Started IESG process on document"),
("created_ballot", "Created ballot"),
("closed_ballot", "Closed ballot"),
("sent_ballot_announcement", "Sent ballot announcement"),
("changed_ballot_position", "Changed ballot position"),
("changed_ballot_approval_text", "Changed ballot approval text"),
("changed_ballot_writeup_text", "Changed ballot writeup text"),
("changed_rfc_editor_note_text", "Changed RFC Editor Note text"),
("changed_last_call_text", "Changed last call text"),
("requested_last_call", "Requested last call"),
("sent_last_call", "Sent last call"),
("scheduled_for_telechat", "Scheduled for telechat"),
("iesg_approved", "IESG approved document (no problem)"),
("iesg_disapproved", "IESG disapproved document (do not publish)"),
("approved_in_minute", "Approved in minute"),
("iana_review", "IANA review comment"),
("rfc_in_iana_registry", "RFC is in IANA registry"),
(
"rfc_editor_received_announcement",
"Announcement was received by RFC Editor",
),
("requested_publication", "Publication at RFC Editor requested"),
(
"sync_from_rfc_editor",
"Received updated information from RFC Editor",
),
("requested_review", "Requested review"),
("assigned_review_request", "Assigned review request"),
("closed_review_request", "Closed review request"),
("closed_review_assignment", "Closed review assignment"),
("downref_approved", "Downref approved"),
("posted_related_ipr", "Posted related IPR"),
("removed_related_ipr", "Removed related IPR"),
(
"removed_objfalse_related_ipr",
"Removed Objectively False related IPR",
),
("changed_editors", "Changed BOF Request editors"),
("published_statement", "Published statement"),
],
max_length=50,
),
),
]
5 changes: 3 additions & 2 deletions ietf/doc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ def most_recent_ietflc(self):
def displayname_with_link(self):
return mark_safe('<a href="%s">%s-%s</a>' % (self.get_absolute_url(), self.name , self.rev))

def ipr(self,states=('posted','removed')):
def ipr(self,states=settings.PUBLISH_IPR_STATES):
"""Returns the IPR disclosures against this document (as a queryset over IprDocRel)."""
from ietf.ipr.models import IprDocRel
return IprDocRel.objects.filter(document__docs=self, disclosure__state__in=states)
Expand All @@ -973,7 +973,7 @@ def related_ipr(self):
document directly or indirectly obsoletes or replaces
"""
from ietf.ipr.models import IprDocRel
iprs = IprDocRel.objects.filter(document__in=list(self.docalias.all())+self.all_related_that_doc(('obs','replaces'))).filter(disclosure__state__in=('posted','removed')).values_list('disclosure', flat=True).distinct()
iprs = IprDocRel.objects.filter(document__in=list(self.docalias.all())+self.all_related_that_doc(('obs','replaces'))).filter(disclosure__state__in=settings.PUBLISH_IPR_STATES).values_list('disclosure', flat=True).distinct()
return iprs

def future_presentations(self):
Expand Down Expand Up @@ -1288,6 +1288,7 @@ class DocReminder(models.Model):
# IPR events
("posted_related_ipr", "Posted related IPR"),
("removed_related_ipr", "Removed related IPR"),
("removed_objfalse_related_ipr", "Removed Objectively False related IPR"),

# Bofreq Editor events
("changed_editors", "Changed BOF Request editors"),
Expand Down
5 changes: 3 additions & 2 deletions ietf/ipr/feeds.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright The IETF Trust 2007-2020, All Rights Reserved
# Copyright The IETF Trust 2007-2023, All Rights Reserved
# -*- coding: utf-8 -*-


from django.conf import settings
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Atom1Feed
from django.urls import reverse_lazy
Expand All @@ -19,7 +20,7 @@ class LatestIprDisclosuresFeed(Feed):
feed_url = "/feed/ipr/"

def items(self):
return IprDisclosureBase.objects.filter(state__in=('posted','removed')).order_by('-time')[:30]
return IprDisclosureBase.objects.filter(state__in=settings.PUBLISH_IPR_STATES).order_by('-time')[:30]

def item_title(self, item):
return mark_safe(item.title)
Expand Down
3 changes: 2 additions & 1 deletion ietf/ipr/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2007-2020, All Rights Reserved
# Copyright The IETF Trust 2007-2023, All Rights Reserved
# -*- coding: utf-8 -*-


Expand Down Expand Up @@ -231,6 +231,7 @@ def create_doc_events(self):
event_type_map = {
'posted': 'posted_related_ipr',
'removed': 'removed_related_ipr',
'removed_objfalse': 'removed_objfalse_related_ipr',
}
if self.type_id in event_type_map:
related_docs = set() # related docs, no duplicates
Expand Down
5 changes: 3 additions & 2 deletions ietf/ipr/sitemaps.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Copyright The IETF Trust 2007-2019, All Rights Reserved
# Copyright The IETF Trust 2007-2023, All Rights Reserved
#
from django.conf import settings
from django.contrib.sitemaps import GenericSitemap
from ietf.ipr.models import IprDisclosureBase

# changefreq is "never except when it gets updated or withdrawn"
# so skip giving one

queryset = IprDisclosureBase.objects.filter(state__in=('posted','removed'))
queryset = IprDisclosureBase.objects.filter(state__in=settings.PUBLISH_IPR_STATES)
archive = {'queryset':queryset, 'date_field': 'time', 'allow_empty':True }
IPRMap = GenericSitemap(archive) # type: ignore
20 changes: 19 additions & 1 deletion ietf/ipr/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ def test_show_removed(self):
r = self.client.get(urlreverse("ietf.ipr.views.show", kwargs=dict(id=ipr.pk)))
self.assertContains(r, 'This IPR disclosure was removed')

def test_show_removed_objfalse(self):
ipr = HolderIprDisclosureFactory(state_id='removed_objfalse')
r = self.client.get(urlreverse("ietf.ipr.views.show", kwargs=dict(id=ipr.pk)))
self.assertContains(r, 'This IPR disclosure was removed as objectively false')

def test_ipr_history(self):
ipr = HolderIprDisclosureFactory()
r = self.client.get(urlreverse("ietf.ipr.views.history", kwargs=dict(id=ipr.pk)))
Expand Down Expand Up @@ -576,7 +581,7 @@ def test_admin_removed(self):
self.client.login(username="secretary", password="secretary+password")

# test for presence of pending ipr
num = IprDisclosureBase.objects.filter(state__in=('removed','rejected')).count()
num = IprDisclosureBase.objects.filter(state__in=('removed','removed_objfalse','rejected')).count()

r = self.client.get(url)
self.assertEqual(r.status_code,200)
Expand Down Expand Up @@ -785,25 +790,38 @@ def test_docevent_creation(self):
'New Document already has a "posted_related_ipr" DocEvent')
self.assertEqual(0, doc.docevent_set.filter(type='removed_related_ipr').count(),
'New Document already has a "removed_related_ipr" DocEvent')
self.assertEqual(0, doc.docevent_set.filter(type='removed_objfalse_related_ipr').count(),
'New Document already has a "removed_objfalse_related_ipr" DocEvent')
# A 'posted' IprEvent must create a corresponding DocEvent
IprEventFactory(type_id='posted', disclosure=ipr)
self.assertEqual(1, doc.docevent_set.filter(type='posted_related_ipr').count(),
'Creating "posted" IprEvent did not create a "posted_related_ipr" DocEvent')
self.assertEqual(0, doc.docevent_set.filter(type='removed_related_ipr').count(),
'Creating "posted" IprEvent created a "removed_related_ipr" DocEvent')
self.assertEqual(0, doc.docevent_set.filter(type='removed_objfalse_related_ipr').count(),
'Creating "posted" IprEvent created a "removed_objfalse_related_ipr" DocEvent')
# A 'removed' IprEvent must create a corresponding DocEvent
IprEventFactory(type_id='removed', disclosure=ipr)
self.assertEqual(1, doc.docevent_set.filter(type='posted_related_ipr').count(),
'Creating "removed" IprEvent created a "posted_related_ipr" DocEvent')
self.assertEqual(1, doc.docevent_set.filter(type='removed_related_ipr').count(),
'Creating "removed" IprEvent did not create a "removed_related_ipr" DocEvent')
# A 'removed_objfalse' IprEvent must create a corresponding DocEvent
IprEventFactory(type_id='removed_objfalse', disclosure=ipr)
self.assertEqual(1, doc.docevent_set.filter(type='posted_related_ipr').count(),
'Creating "removed_objfalse" IprEvent created a "posted_related_ipr" DocEvent')
self.assertEqual(1, doc.docevent_set.filter(type='removed_objfalse_related_ipr').count(),
'Creating "removed_objfalse" IprEvent did not create a "removed_objfalse_related_ipr" DocEvent')
# The DocEvent descriptions must refer to the IprEvents
posted_docevent = doc.docevent_set.filter(type='posted_related_ipr').first()
self.assertIn(ipr.title, posted_docevent.desc,
'IprDisclosure title does not appear in DocEvent desc when posted')
removed_docevent = doc.docevent_set.filter(type='removed_related_ipr').first()
self.assertIn(ipr.title, removed_docevent.desc,
'IprDisclosure title does not appear in DocEvent desc when removed')
removed_objfalse_docevent = doc.docevent_set.filter(type='removed_objfalse_related_ipr').first()
self.assertIn(ipr.title, removed_objfalse_docevent.desc,
'IprDisclosure title does not appear in DocEvent desc when removed as objectively false')

def test_no_revisions_message(self):
draft = WgDraftFactory(rev="02")
Expand Down
16 changes: 8 additions & 8 deletions ietf/ipr/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2007-2022, All Rights Reserved
# Copyright The IETF Trust 2007-2023, All Rights Reserved
# -*- coding: utf-8 -*-


Expand Down Expand Up @@ -269,7 +269,7 @@ def add_email(request, id):
@role_required('Secretariat',)
def admin(request, state):
"""Administrative disclosure listing. For non-posted disclosures"""
states = IprDisclosureStateName.objects.filter(slug__in=[state, "rejected"] if state == "removed" else [state])
states = IprDisclosureStateName.objects.filter(slug__in=[state, "rejected", "removed_objfalse"] if state == "removed" else [state])
if not states:
raise Http404

Expand Down Expand Up @@ -648,7 +648,7 @@ def search(request):
related_iprs = []

# set states
states = request.GET.getlist('state',('posted','removed'))
states = request.GET.getlist('state',settings.PUBLISH_IPR_STATES)
if states == ['all']:
states = IprDisclosureStateName.objects.values_list('slug',flat=True)

Expand Down Expand Up @@ -778,7 +778,7 @@ def show(request, id):
"""View of individual declaration"""
ipr = get_object_or_404(IprDisclosureBase, id=id).get_child()
if not has_role(request.user, 'Secretariat'):
if ipr.state.slug == 'removed':
if ipr.state.slug in ['removed', 'removed_objfalse']:
return render(request, "ipr/removed.html", {
'ipr': ipr
})
Expand All @@ -801,10 +801,10 @@ def show(request, id):

def showlist(request):
"""List all disclosures by type, posted only"""
generic = GenericIprDisclosure.objects.filter(state__in=('posted','removed')).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
specific = HolderIprDisclosure.objects.filter(state__in=('posted','removed')).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
thirdpty = ThirdPartyIprDisclosure.objects.filter(state__in=('posted','removed')).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
nondocspecific = NonDocSpecificIprDisclosure.objects.filter(state__in=('posted','removed')).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
generic = GenericIprDisclosure.objects.filter(state__in=settings.PUBLISH_IPR_STATES).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
specific = HolderIprDisclosure.objects.filter(state__in=settings.PUBLISH_IPR_STATES).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
thirdpty = ThirdPartyIprDisclosure.objects.filter(state__in=settings.PUBLISH_IPR_STATES).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')
nondocspecific = NonDocSpecificIprDisclosure.objects.filter(state__in=settings.PUBLISH_IPR_STATES).prefetch_related('relatedipr_source_set__target','relatedipr_target_set__source').order_by('-time')

# combine nondocspecific with generic and re-sort
generic = itertools.chain(generic,nondocspecific)
Expand Down
20 changes: 20 additions & 0 deletions ietf/name/fixtures/names.json
Original file line number Diff line number Diff line change
Expand Up @@ -12097,6 +12097,16 @@
"model": "name.iprdisclosurestatename",
"pk": "removed"
},
{
"fields": {
"desc": "",
"name": "Removed Objectively False",
"order": 5,
"used": true
},
"model": "name.iprdisclosurestatename",
"pk": "removed_objfalse"
},
{
"fields": {
"desc": "",
Expand Down Expand Up @@ -12207,6 +12217,16 @@
"model": "name.ipreventtypename",
"pk": "removed"
},
{
"fields": {
"desc": "",
"name": "Removed Objectively False",
"order": 0,
"used": true
},
"model": "name.ipreventtypename",
"pk": "removed_objfalse"
},
{
"fields": {
"desc": "",
Expand Down
24 changes: 24 additions & 0 deletions ietf/name/migrations/0008_removed_objfalse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright The IETF Trust 2023, All Rights Reserved

from django.db import migrations

def forward(apps, schema_editor):
IprDisclosureStateName = apps.get_model("name", "IprDisclosureStateName")
IprDisclosureStateName.objects.create(slug="removed_objfalse", name="Removed Objectively False", order=5)
IprEventTypeName = apps.get_model("name", "IprEventTypeName")
IprEventTypeName.objects.create(slug="removed_objfalse", name="Removed Objectively False")

def reverse(apps, schema_editor):
IprDisclosureStateName = apps.get_model("name", "IprDisclosureStateName")
IprDisclosureStateName.objects.filter(slug="removed_objfalse").delete()
IprEventTypeName = apps.get_model("name", "IprEventTypeName")
IprEventTypeName.objects.filter(slug="removed_objfalse").delete()

class Migration(migrations.Migration):
dependencies = [
("name", "0007_appeal_artifact_typename"),
]

operations = [
migrations.RunPython(forward, reverse),
]
2 changes: 2 additions & 0 deletions ietf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,8 @@ def skip_unreadable_post(record):
},
}

PUBLISH_IPR_STATES = ['posted', 'removed', 'removed_objfalse']

# We provide a secret key only for test and development modes. It's
# absolutely vital that django fails to start in production mode unless a
# secret key has been provided elsewhere, not in this file which is
Expand Down
4 changes: 4 additions & 0 deletions ietf/templates/ipr/details_view.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ <h2 class="mt-5">Updates</h2>
IPR Disclosure ID #{{ item.source.id }},
{% if item.source.state.slug == "removed" %}
"{{ item.source.title }}" (which was removed at the request of the submitter)
{% elif item.source.state.slug == "removed_objfalse" %}
"{{ item.source.title }}" (which was removed as objectively false)
{% else %}
"<a href="{% url "ietf.ipr.views.show" id=item.source.id %}">{{ item.source.title }}</a>"
{% endif %}
Expand All @@ -122,6 +124,8 @@ <h2 class="mt-5">Updates</h2>
IPR Disclosure ID #{{ item.target.id }},
{% if item.target.state.slug == "removed" %}
"{{ item.target.title }}" (which was removed at the request of the submitter)
{% elif item.source.state.slug == "removed_objfalse" %}
"{{ item.source.title }}" (which was removed as objectively false)
{% elif item.target.state.slug == "rejected" %}
"{{ item.target.title }}" (which was rejected)
{% elif item.target.state.slug == "parked" %}
Expand Down
8 changes: 6 additions & 2 deletions ietf/templates/ipr/removed.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
{% load origin %}
{% block title %}{{ ipr.title }}{% endblock %}
{% block content %}
{% origin %}
<h1>{{ ipr.title }}</h1>
<p class="alert alert-info my-3">
This IPR disclosure was removed at the submitter's request.
{% if ipr.state.slug == "removed" %}
This IPR disclosure was removed at the submitter's request.
{% elif ipr.state.slug == "removed_objfalse" %}
This IPR disclosure was removed as objectively false.
{% endif %}
</p>
{% endblock %}
Loading
Loading