Skip to content

Commit

Permalink
feat: Capture that IPR disclosures are removed under the Objectively …
Browse files Browse the repository at this point in the history
…False IPR Disclosure Policy (ietf-tools#6231)

* feat: Capture that IPR disclosures are removed under the Objectively False IPR Disclosure Policy (ietf-tools#6088)

* chore: Move PUBLISH_IPR_STATES from settings_local to settings

* fix: Add migration for removed_objfalse in IprEventTypeName

* fix: De-conflict migration

* fix: De-conflict migration

* style: Move PUBLISH_IPR_STATES ahead of not-production block
  • Loading branch information
pselkirk authored Sep 11, 2023
1 parent 6b6c881 commit febdeff
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 20 deletions.
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

0 comments on commit febdeff

Please sign in to comment.