feat: require draft revisions in ipr disclosures. Explain missing ones. (#6160)
* feat: require draft revisions in ipr disclosures. Explain missing ones. * chore: update copyrights * chore: address review comments * fix: draft should be Internet-Draft * test: cover single revision case
This commit is contained in:
parent
5522de976c
commit
53be2c3793
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2018-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2018-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -95,3 +95,11 @@ class IprEventFactory(factory.django.DjangoModelFactory):
|
|||
disclosure = factory.SubFactory(IprDisclosureBaseFactory)
|
||||
desc = factory.Faker('sentence')
|
||||
|
||||
class IprDocRelFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = IprDocRel
|
||||
|
||||
disclosure = factory.SubFactory(HolderIprDisclosureFactory)
|
||||
document = factory.SubFactory("ietf.doc.factories.IndividualDraftFactory")
|
||||
revisions = "00"
|
||||
sections = ""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2014-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2014-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -105,6 +105,15 @@ class DraftForm(forms.ModelForm):
|
|||
}
|
||||
help_texts = { 'sections': 'Sections' }
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
revisions = cleaned_data.get("revisions")
|
||||
document = cleaned_data.get("document")
|
||||
if not document.name.startswith("rfc"):
|
||||
if revisions.strip() == "":
|
||||
self.add_error("revisions", "Revisions of this Internet-Draft for which this disclosure is relevant must be specified.")
|
||||
return cleaned_data
|
||||
|
||||
patent_number_help_text = "Enter one or more comma-separated patent publication or application numbers as two-letter country code and serial number, e.g.: US62/123456 or WO2017123456. Do not include thousands-separator commas in serial numbers. It is preferable to use individual disclosures for each patent, even if this field permits multiple patents to be listed, in order to get inventor, title, and date information below correct."
|
||||
validate_patent_number = RegexValidator(
|
||||
regex=(r"^("
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
# Copyright The IETF Trust 2014-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2014-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import debug # pyflakes: ignore
|
||||
|
||||
from django import template
|
||||
from django.utils.html import format_html
|
||||
|
||||
from ietf.doc.models import NewRevisionDocEvent
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -26,3 +30,37 @@ def render_message_for_history(msg):
|
|||
@register.filter
|
||||
def to_class_name(value):
|
||||
return value.__class__.__name__
|
||||
|
||||
def draft_rev_at_time(iprdocrel):
|
||||
draft = iprdocrel.document.document
|
||||
event = iprdocrel.disclosure.get_latest_event_posted()
|
||||
if event is None:
|
||||
return ("","The Internet-Draft's revision at the time this disclosure was posted could not be determined.")
|
||||
time = event.time
|
||||
if not NewRevisionDocEvent.objects.filter(doc=draft).exists():
|
||||
return ("","The Internet-Draft's revision at the time this disclosure was posted could not be determined.")
|
||||
rev_event_before = NewRevisionDocEvent.objects.filter(doc=draft, time__lte=time).order_by('-time').first()
|
||||
if rev_event_before is None:
|
||||
return ("","The Internet-Draft's initial submission was after this disclosure was posted.")
|
||||
else:
|
||||
return(rev_event_before.rev, "")
|
||||
|
||||
@register.filter
|
||||
def no_revisions_message(iprdocrel):
|
||||
draft = iprdocrel.document.document
|
||||
if draft.type_id != "draft" or iprdocrel.revisions.strip() != "":
|
||||
return ""
|
||||
rev_at_time, exception = draft_rev_at_time(iprdocrel)
|
||||
current_rev = draft.rev
|
||||
|
||||
first_line = "No revisions for this Internet-Draft were specified in this disclosure."
|
||||
contact_line = "Contact the discloser or patent holder if there are questions about which revisions this disclosure pertains to."
|
||||
|
||||
if current_rev == "00":
|
||||
return f"{first_line} However, there is only one revision of this Internet-Draft."
|
||||
|
||||
if rev_at_time:
|
||||
return f"{first_line} The Internet-Draft's revision was {rev_at_time} at the time this disclosure was posted. {contact_line}"
|
||||
else:
|
||||
return f"{first_line} {exception} {contact_line}"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2009-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2009-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -16,13 +16,24 @@ from django.utils import timezone
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import DocAlias
|
||||
from ietf.doc.factories import DocumentFactory, WgDraftFactory, WgRfcFactory
|
||||
from ietf.doc.factories import (
|
||||
DocumentFactory,
|
||||
WgDraftFactory,
|
||||
WgRfcFactory,
|
||||
NewRevisionDocEventFactory
|
||||
)
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.ipr.factories import HolderIprDisclosureFactory, GenericIprDisclosureFactory, IprEventFactory
|
||||
from ietf.ipr.factories import (
|
||||
HolderIprDisclosureFactory,
|
||||
GenericIprDisclosureFactory,
|
||||
IprDocRelFactory,
|
||||
IprEventFactory
|
||||
)
|
||||
from ietf.ipr.mail import (process_response_email, get_reply_to, get_update_submitter_emails,
|
||||
get_pseudo_submitter, get_holders, get_update_cc_addrs)
|
||||
from ietf.ipr.models import (IprDisclosureBase,GenericIprDisclosure,HolderIprDisclosure,
|
||||
ThirdPartyIprDisclosure)
|
||||
from ietf.ipr.templatetags.ipr_filters import no_revisions_message
|
||||
from ietf.ipr.utils import get_genitive, get_ipr_summary
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.message.models import Message
|
||||
|
@ -305,6 +316,38 @@ class IprTests(TestCase):
|
|||
r = self.client.post(url, data)
|
||||
self.assertContains(r, "Your IPR disclosure has been submitted", msg_prefix="Checked patent number: %s" % patent_number)
|
||||
|
||||
def test_new_specific_no_revision(self):
|
||||
draft = WgDraftFactory()
|
||||
WgRfcFactory()
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
data = {
|
||||
"holder_legal_name": "Test Legal",
|
||||
"holder_contact_name": "Test Holder",
|
||||
"holder_contact_email": "test@holder.com",
|
||||
"holder_contact_info": "555-555-0100",
|
||||
"ietfer_name": "Test Participant",
|
||||
"ietfer_contact_info": "555-555-0101",
|
||||
"iprdocrel_set-TOTAL_FORMS": 2,
|
||||
"iprdocrel_set-INITIAL_FORMS": 0,
|
||||
"iprdocrel_set-0-document": draft.docalias.first().pk,
|
||||
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"patent_number": "SE12345678901",
|
||||
"patent_inventor": "A. Nonymous",
|
||||
"patent_title": "A method of transferring bits",
|
||||
"patent_date": "2000-01-01",
|
||||
"has_patent_pending": False,
|
||||
"licensing": "royalty-free",
|
||||
"submitter_name": "Test Holder",
|
||||
"submitter_email": "test@holder.com",
|
||||
}
|
||||
r = self.client.post(url, data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(q("#id_iprdocrel_set-0-revisions").hasClass("is-invalid"))
|
||||
|
||||
def test_new_thirdparty(self):
|
||||
"""Add a new third-party disclosure. Note: submitter does not need to be logged in.
|
||||
"""
|
||||
|
@ -761,4 +804,64 @@ Subject: test
|
|||
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')
|
||||
|
||||
def test_no_revisions_message(self):
|
||||
draft = WgDraftFactory(rev="02")
|
||||
now = timezone.now()
|
||||
for rev in range(0,3):
|
||||
NewRevisionDocEventFactory(doc=draft, rev=f"{rev:02d}", time=now-datetime.timedelta(days=30*(2-rev)))
|
||||
|
||||
# Disclosure has non-empty revisions field on its related draft
|
||||
iprdocrel = IprDocRelFactory(document=draft.docalias.first())
|
||||
IprEventFactory(type_id="posted",time=now,disclosure=iprdocrel.disclosure)
|
||||
self.assertEqual(
|
||||
no_revisions_message(iprdocrel),
|
||||
""
|
||||
)
|
||||
|
||||
# Disclosure has more than one revision, none called out, disclosure after submissions
|
||||
iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
|
||||
IprEventFactory(type_id="posted",time=now,disclosure=iprdocrel.disclosure)
|
||||
self.assertEqual(
|
||||
no_revisions_message(iprdocrel),
|
||||
"No revisions for this Internet-Draft were specified in this disclosure. The Internet-Draft's revision was 02 at the time this disclosure was posted. Contact the discloser or patent holder if there are questions about which revisions this disclosure pertains to."
|
||||
)
|
||||
|
||||
# Disclosure has more than one revision, none called out, disclosure after 01
|
||||
iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
|
||||
e = IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
|
||||
e.time = now-datetime.timedelta(days=15)
|
||||
e.save()
|
||||
self.assertEqual(
|
||||
no_revisions_message(iprdocrel),
|
||||
"No revisions for this Internet-Draft were specified in this disclosure. The Internet-Draft's revision was 01 at the time this disclosure was posted. Contact the discloser or patent holder if there are questions about which revisions this disclosure pertains to."
|
||||
)
|
||||
|
||||
# Disclosure has more than one revision, none called out, disclosure was before the 00
|
||||
iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
|
||||
e = IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
|
||||
e.time = now-datetime.timedelta(days=180)
|
||||
e.save()
|
||||
self.assertEqual(
|
||||
no_revisions_message(iprdocrel),
|
||||
"No revisions for this Internet-Draft were specified in this disclosure. The Internet-Draft's initial submission was after this disclosure was posted. Contact the discloser or patent holder if there are questions about which revisions this disclosure pertains to."
|
||||
)
|
||||
|
||||
# disclosed draft has no NewRevisionDocEvents
|
||||
draft = WgDraftFactory(rev="20")
|
||||
draft.docevent_set.all().delete()
|
||||
iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
|
||||
IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
|
||||
self.assertEqual(
|
||||
no_revisions_message(iprdocrel),
|
||||
"No revisions for this Internet-Draft were specified in this disclosure. The Internet-Draft's revision at the time this disclosure was posted could not be determined. Contact the discloser or patent holder if there are questions about which revisions this disclosure pertains to."
|
||||
)
|
||||
|
||||
# disclosed draft has only one revision
|
||||
draft = WgDraftFactory(rev="00")
|
||||
iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
|
||||
IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
|
||||
self.assertEqual(
|
||||
no_revisions_message(iprdocrel),
|
||||
"No revisions for this Internet-Draft were specified in this disclosure. However, there is only one revision of this Internet-Draft."
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, 2017. All Rights Reserved. #}
|
||||
{# Copyright The IETF Trust 2015-2023. All Rights Reserved. #}
|
||||
{% load origin %}
|
||||
{% load ietf_filters ipr_filters textfilters %}
|
||||
{% block title %}IPR Details - {{ ipr.title }}{% endblock %}
|
||||
|
@ -388,6 +388,13 @@
|
|||
<dd class="col-sm-8 my-0">
|
||||
{{ iprdocrel.revisions }}
|
||||
</dd>
|
||||
{% elif iprdocrel.doc_type == "Internet-Draft" %}
|
||||
<dt class="{% if prev %}col-sm-4{% else %}col-sm-3{% endif %} my-0 fw-bolder fst-italic">
|
||||
Notice:
|
||||
</dt>
|
||||
<dd class="{% if prev %}col-sm-8{% else %}col-sm-9{% endif %} my-0 fst-italic">
|
||||
{{ iprdocrel|no_revisions_message }}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if iprdocrel.sections %}
|
||||
<dt class="col-sm-4 my-0">
|
||||
|
@ -427,6 +434,13 @@
|
|||
<dd class="{% if prev %}col-sm-8{% else %}col-sm-9{% endif %} my-0">
|
||||
{{ iprdocrel.revisions }}
|
||||
</dd>
|
||||
{% elif iprdocrel.doc_type == "Internet-Draft" %}
|
||||
<dt class="{% if prev %}col-sm-4{% else %}col-sm-3{% endif %} my-0 fw-bolder fst-italic">
|
||||
Notice:
|
||||
</dt>
|
||||
<dd class="{% if prev %}col-sm-8{% else %}col-sm-9{% endif %} my-0 fst-italic">
|
||||
{{ iprdocrel|no_revisions_message }}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if iprdocrel.sections %}
|
||||
<dt class="{% if prev %}col-sm-4{% else %}col-sm-3{% endif %} my-0">
|
||||
|
|
Loading…
Reference in a new issue