Skip to content

Commit 9d2fa7a

Browse files
authored
feat: track deleted ipr disclosures (#9691)
* feat: track deleted ipr disclosures * fix: unique constraint on removed_id
1 parent 24101bb commit 9d2fa7a

File tree

8 files changed

+139
-10
lines changed

8 files changed

+139
-10
lines changed

ietf/ipr/admin.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
# Copyright The IETF Trust 2010-2020, All Rights Reserved
1+
# Copyright The IETF Trust 2010-2025, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

55
from django import forms
66
from django.contrib import admin
77
from ietf.name.models import DocRelationshipName
8-
from ietf.ipr.models import (IprDisclosureBase, IprDocRel, IprEvent,
9-
RelatedIpr, HolderIprDisclosure, ThirdPartyIprDisclosure, GenericIprDisclosure,
10-
NonDocSpecificIprDisclosure, LegacyMigrationIprEvent)
8+
from ietf.ipr.models import (
9+
IprDisclosureBase,
10+
IprDocRel,
11+
IprEvent,
12+
RelatedIpr,
13+
HolderIprDisclosure,
14+
RemovedIprDisclosure,
15+
ThirdPartyIprDisclosure,
16+
GenericIprDisclosure,
17+
NonDocSpecificIprDisclosure,
18+
LegacyMigrationIprEvent,
19+
)
1120

1221
# ------------------------------------------------------
1322
# ModelAdmins
@@ -110,3 +119,9 @@ class LegacyMigrationIprEventAdmin(admin.ModelAdmin):
110119
list_filter = ['time', 'type', 'response_due']
111120
raw_id_fields = ['by', 'disclosure', 'message', 'in_reply_to']
112121
admin.site.register(LegacyMigrationIprEvent, LegacyMigrationIprEventAdmin)
122+
123+
class RemovedIprDisclosureAdmin(admin.ModelAdmin):
124+
pass
125+
126+
127+
admin.site.register(RemovedIprDisclosure, RemovedIprDisclosureAdmin)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright The IETF Trust 2025, All Rights Reserved
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("ipr", "0004_holderiprdisclosure_is_blanket_disclosure"),
9+
]
10+
11+
operations = [
12+
migrations.CreateModel(
13+
name="RemovedIprDisclosure",
14+
fields=[
15+
(
16+
"id",
17+
models.AutoField(
18+
auto_created=True,
19+
primary_key=True,
20+
serialize=False,
21+
verbose_name="ID",
22+
),
23+
),
24+
("removed_id", models.PositiveBigIntegerField(unique=True)),
25+
("reason", models.TextField()),
26+
],
27+
),
28+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright The IETF Trust 2025, All Rights Reserved
2+
from django.db import migrations
3+
4+
5+
def forward(apps, schema_editor):
6+
RemovedIprDisclosure = apps.get_model("ipr", "RemovedIprDisclosure")
7+
for id in (6544, 6068):
8+
RemovedIprDisclosure.objects.create(
9+
removed_id=id,
10+
reason="This IPR disclosure was removed as objectively false.",
11+
)
12+
13+
14+
def reverse(apps, schema_editor):
15+
RemovedIprDisclosure = apps.get_model("ipr", "RemovedIprDisclosure")
16+
RemovedIprDisclosure.objects.all().delete()
17+
18+
19+
class Migration(migrations.Migration):
20+
dependencies = [
21+
("ipr", "0005_removediprdisclosure"),
22+
]
23+
24+
operations = [migrations.RunPython(forward, reverse)]

ietf/ipr/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2007-2023, All Rights Reserved
1+
# Copyright The IETF Trust 2007-2025, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

@@ -270,3 +270,7 @@ class LegacyMigrationIprEvent(IprEvent):
270270
"""A subclass of IprEvent specifically for capturing contents of legacy_url_0,
271271
the text of a disclosure submitted by email"""
272272
pass
273+
274+
class RemovedIprDisclosure(models.Model):
275+
removed_id = models.PositiveBigIntegerField(unique=True)
276+
reason = models.TextField()

ietf/ipr/resources.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2015-2019, All Rights Reserved
1+
# Copyright The IETF Trust 2015-2025, All Rights Reserved
22
# -*- coding: utf-8 -*-
33
# Autogenerated by the mkresources management command 2015-03-21 14:05 PDT
44

@@ -11,7 +11,7 @@
1111

1212
from ietf import api
1313

14-
from ietf.ipr.models import ( IprDisclosureBase, IprDocRel, HolderIprDisclosure, ThirdPartyIprDisclosure,
14+
from ietf.ipr.models import ( IprDisclosureBase, IprDocRel, HolderIprDisclosure, RemovedIprDisclosure, ThirdPartyIprDisclosure,
1515
RelatedIpr, NonDocSpecificIprDisclosure, GenericIprDisclosure, IprEvent, LegacyMigrationIprEvent )
1616

1717
from ietf.person.resources import PersonResource
@@ -295,3 +295,18 @@ class Meta:
295295
}
296296
api.ipr.register(LegacyMigrationIprEventResource())
297297

298+
299+
300+
class RemovedIprDisclosureResource(ModelResource):
301+
class Meta:
302+
queryset = RemovedIprDisclosure.objects.all()
303+
serializer = api.Serializer()
304+
cache = SimpleCache()
305+
#resource_name = 'removediprdisclosure'
306+
ordering = ['id', ]
307+
filtering = {
308+
"id": ALL,
309+
"removed_id": ALL,
310+
"reason": ALL,
311+
}
312+
api.ipr.register(RemovedIprDisclosureResource())

ietf/ipr/tests.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from ietf.ipr.forms import DraftForm, HolderIprDisclosureForm
4242
from ietf.ipr.mail import (process_response_email, get_reply_to, get_update_submitter_emails,
4343
get_pseudo_submitter, get_holders, get_update_cc_addrs, UndeliverableIprResponseError)
44-
from ietf.ipr.models import (IprDisclosureBase, GenericIprDisclosure, HolderIprDisclosure,
44+
from ietf.ipr.models import (IprDisclosureBase, GenericIprDisclosure, HolderIprDisclosure, RemovedIprDisclosure,
4545
ThirdPartyIprDisclosure, IprEvent)
4646
from ietf.ipr.templatetags.ipr_filters import no_revisions_message
4747
from ietf.ipr.utils import get_genitive, get_ipr_summary, ingest_response_email
@@ -129,6 +129,26 @@ def test_showlist(self):
129129
self.assertContains(r, "removed as objectively false")
130130
ipr.delete()
131131

132+
def test_show_delete(self):
133+
ipr = HolderIprDisclosureFactory()
134+
removed = RemovedIprDisclosure.objects.create(
135+
removed_id=ipr.pk, reason="Removed for reasons"
136+
)
137+
url = urlreverse("ietf.ipr.views.show", kwargs=dict(id=removed.removed_id))
138+
r = self.client.get(url)
139+
self.assertContains(r, "Removed for reasons")
140+
q = PyQuery(r.content)
141+
self.assertEqual(len(q("#deletion_warning")), 0)
142+
self.client.login(username="secretary", password="secretary+password")
143+
r = self.client.get(url)
144+
self.assertContains(r, "Removed for reasons")
145+
q = PyQuery(r.content)
146+
self.assertEqual(len(q("#deletion_warning")), 1)
147+
ipr.delete()
148+
r = self.client.get(url)
149+
self.assertContains(r, "Removed for reasons")
150+
q = PyQuery(r.content)
151+
self.assertEqual(len(q("#deletion_warning")), 0)
132152

133153
def test_show_posted(self):
134154
ipr = HolderIprDisclosureFactory()

ietf/ipr/views.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
AddCommentForm, AddEmailForm, NotifyForm, StateForm, NonDocSpecificIprDisclosureForm,
2929
GenericIprDisclosureForm)
3030
from ietf.ipr.models import (IprDisclosureStateName, IprDisclosureBase,
31-
HolderIprDisclosure, GenericIprDisclosure, ThirdPartyIprDisclosure,
31+
HolderIprDisclosure, GenericIprDisclosure, RemovedIprDisclosure, ThirdPartyIprDisclosure,
3232
NonDocSpecificIprDisclosure, IprDocRel,
3333
RelatedIpr,IprEvent)
3434
from ietf.ipr.utils import (get_genitive, get_ipr_summary,
@@ -817,7 +817,14 @@ def get_details_tabs(ipr, selected):
817817

818818
def show(request, id):
819819
"""View of individual declaration"""
820-
ipr = get_object_or_404(IprDisclosureBase, id=id).get_child()
820+
ipr = IprDisclosureBase.objects.filter(id=id)
821+
removed = RemovedIprDisclosure.objects.filter(removed_id=id)
822+
if removed.exists():
823+
return render(request, "ipr/deleted.html", {"removed": removed.get(), "ipr": ipr})
824+
if not ipr.exists():
825+
raise Http404
826+
else:
827+
ipr = ipr.get().get_child()
821828
if not has_role(request.user, 'Secretariat'):
822829
if ipr.state.slug in ['removed', 'removed_objfalse']:
823830
return render(request, "ipr/removed.html", {

ietf/templates/ipr/deleted.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% extends "base.html" %}
2+
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
3+
{% load ietf_filters origin %}
4+
{% block title %}Removed IPR Disclosure{% endblock %}
5+
{% block content %}
6+
{% origin %}
7+
<h1>Removed IPR disclosure</h1>
8+
<p class="alert alert-info my-3">
9+
{{ removed.reason }}
10+
</p>
11+
{% if user|has_role:"Secretariat" and ipr.exists %}
12+
<p class="alert alert-warn my-3" id="deletion_warning">
13+
This disclosure has not yet been deleted and parts of its content is available through, e.g, the history view and the /api/v1 views.
14+
</p>
15+
{% endif %}
16+
{% endblock %}

0 commit comments

Comments
 (0)