feat: allow blanket IPR disclosures (#7934)
* refactor: avoid shadowing type() * style: Black * feat: is_blanket_disclosure field * feat: add field to form * feat: js to mark field required/not required * feat: blanket disclosure = royalty-free license * feat: manage licensing radio buttons * fix: adjust wording/format of disclosure page * fix: point at RFC 8179 in checkbox label * test: test blanket disclosure licensing restrictions * fix: conditionally render is_blanket_disclosure * test: refactor test case * test: patent details optional for blanket ipr
This commit is contained in:
parent
35074660dc
commit
32057f335a
|
@ -338,7 +338,19 @@ class IprDisclosureFormBase(forms.ModelForm):
|
|||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class HolderIprDisclosureForm(IprDisclosureFormBase):
|
||||
is_blanket_disclosure = forms.BooleanField(
|
||||
label=mark_safe(
|
||||
'This is a blanket IPR disclosure '
|
||||
'(see Section 5.4.3 of <a href="https://www.ietf.org/rfc/rfc8179.txt">RFC 8179</a>)'
|
||||
),
|
||||
help_text="In satisfaction of its disclosure obligations, Patent Holder commits to license all of "
|
||||
"IPR (as defined in RFC 8179) that would have required disclosure under RFC 8179 on a "
|
||||
"royalty-free (and otherwise reasonable and non-discriminatory) basis. Patent Holder "
|
||||
"confirms that all other terms and conditions are described in this IPR disclosure.",
|
||||
required=False,
|
||||
)
|
||||
licensing = CustomModelChoiceField(IprLicenseTypeName.objects.all(),
|
||||
widget=forms.RadioSelect,empty_label=None)
|
||||
|
||||
|
@ -357,6 +369,15 @@ class HolderIprDisclosureForm(IprDisclosureFormBase):
|
|||
# entering new disclosure
|
||||
self.fields['licensing'].queryset = IprLicenseTypeName.objects.exclude(slug='none-selected')
|
||||
|
||||
if self.data.get("is_blanket_disclosure", False):
|
||||
# for a blanket disclosure, patent details are not required
|
||||
self.fields["patent_number"].required = False
|
||||
self.fields["patent_inventor"].required = False
|
||||
self.fields["patent_title"].required = False
|
||||
self.fields["patent_date"].required = False
|
||||
# n.b., self.fields["patent_notes"] is never required
|
||||
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(HolderIprDisclosureForm, self).clean()
|
||||
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright The IETF Trust 2024, All Rights Reserved
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("ipr", "0003_alter_iprdisclosurebase_docs"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="holderiprdisclosure",
|
||||
name="is_blanket_disclosure",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
@ -124,17 +125,30 @@ class IprDisclosureBase(models.Model):
|
|||
|
||||
|
||||
class HolderIprDisclosure(IprDisclosureBase):
|
||||
ietfer_name = models.CharField(max_length=255, blank=True) # "Whose Personal Belief Triggered..."
|
||||
ietfer_contact_email = models.EmailField(blank=True)
|
||||
ietfer_contact_info = models.TextField(blank=True)
|
||||
patent_info = models.TextField()
|
||||
has_patent_pending = models.BooleanField(default=False)
|
||||
holder_contact_email = models.EmailField()
|
||||
holder_contact_name = models.CharField(max_length=255)
|
||||
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
||||
licensing = ForeignKey(IprLicenseTypeName)
|
||||
licensing_comments = models.TextField(blank=True)
|
||||
ietfer_name = models.CharField(
|
||||
max_length=255, blank=True
|
||||
) # "Whose Personal Belief Triggered..."
|
||||
ietfer_contact_email = models.EmailField(blank=True)
|
||||
ietfer_contact_info = models.TextField(blank=True)
|
||||
patent_info = models.TextField()
|
||||
has_patent_pending = models.BooleanField(default=False)
|
||||
holder_contact_email = models.EmailField()
|
||||
holder_contact_name = models.CharField(max_length=255)
|
||||
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
||||
licensing = ForeignKey(IprLicenseTypeName)
|
||||
licensing_comments = models.TextField(blank=True)
|
||||
submitter_claims_all_terms_disclosed = models.BooleanField(default=False)
|
||||
is_blanket_disclosure = models.BooleanField(default=False)
|
||||
|
||||
def clean(self):
|
||||
if self.is_blanket_disclosure:
|
||||
# If the IprLicenseTypeName does not exist, we have a serious problem and a 500 response is ok,
|
||||
# so not handling failure of the `get()`
|
||||
royalty_free_licensing = IprLicenseTypeName.objects.get(slug="royalty-free")
|
||||
if self.licensing_id != royalty_free_licensing.pk:
|
||||
raise ValidationError(
|
||||
f'Must select "{royalty_free_licensing.desc}" for a blanket IPR disclosure.')
|
||||
|
||||
|
||||
class ThirdPartyIprDisclosure(IprDisclosureBase):
|
||||
ietfer_name = models.CharField(max_length=255) # "Whose Personal Belief Triggered..."
|
||||
|
|
|
@ -33,7 +33,7 @@ from ietf.ipr.factories import (
|
|||
IprDocRelFactory,
|
||||
IprEventFactory
|
||||
)
|
||||
from ietf.ipr.forms import DraftForm
|
||||
from ietf.ipr.forms import DraftForm, HolderIprDisclosureForm
|
||||
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,
|
||||
|
@ -272,16 +272,16 @@ class IprTests(TestCase):
|
|||
|
||||
def test_new_generic(self):
|
||||
"""Ensure new-generic redirects to new-general"""
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "generic" })
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "generic" })
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.assertEqual(urlparse(r["Location"]).path, urlreverse("ietf.ipr.views.new", kwargs={ "type": "general"}))
|
||||
self.assertEqual(urlparse(r["Location"]).path, urlreverse("ietf.ipr.views.new", kwargs={ "_type": "general"}))
|
||||
|
||||
|
||||
def test_new_general(self):
|
||||
"""Add a new general disclosure. Note: submitter does not need to be logged in.
|
||||
"""
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "general" })
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "general" })
|
||||
|
||||
# invalid post
|
||||
r = self.client.post(url, {
|
||||
|
@ -319,7 +319,7 @@ class IprTests(TestCase):
|
|||
"""
|
||||
draft = WgDraftFactory()
|
||||
rfc = WgRfcFactory()
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
|
@ -375,7 +375,7 @@ class IprTests(TestCase):
|
|||
def test_new_specific_no_revision(self):
|
||||
draft = WgDraftFactory()
|
||||
rfc = WgRfcFactory()
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
|
@ -409,7 +409,7 @@ class IprTests(TestCase):
|
|||
"""
|
||||
draft = WgDraftFactory()
|
||||
rfc = WgRfcFactory()
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "third-party" })
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "third-party" })
|
||||
|
||||
# successful post
|
||||
empty_outbox()
|
||||
|
@ -456,7 +456,7 @@ class IprTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertContains(r, original_ipr.holder_legal_name)
|
||||
|
||||
#url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
#url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||
# successful post
|
||||
empty_outbox()
|
||||
post_data = {
|
||||
|
@ -503,7 +503,7 @@ class IprTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertContains(r, original_ipr.title)
|
||||
|
||||
#url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
#url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||
# successful post
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
|
@ -543,7 +543,7 @@ class IprTests(TestCase):
|
|||
|
||||
def test_update_bad_post(self):
|
||||
draft = WgDraftFactory()
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||
|
||||
empty_outbox()
|
||||
r = self.client.post(url, {
|
||||
|
@ -1022,3 +1022,61 @@ class DraftFormTests(TestCase):
|
|||
"revisions",
|
||||
null_char_error_msg,
|
||||
)
|
||||
|
||||
|
||||
class HolderIprDisclosureFormTests(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Checkboxes that are False are left out of the Form data, not sent back at all. These are
|
||||
# commented out - if they were checked, their value would be "on".
|
||||
self.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": "1234", # fake id - validates but won't save()
|
||||
"iprdocrel_set-0-revisions": '00',
|
||||
"iprdocrel_set-1-document": "4567", # fake id - validates but won't save()
|
||||
# "is_blanket_disclosure": "on",
|
||||
"patent_number": "SE12345678901",
|
||||
"patent_inventor": "A. Nonymous",
|
||||
"patent_title": "A method of transferring bits",
|
||||
"patent_date": "2000-01-01",
|
||||
# "has_patent_pending": "on",
|
||||
"licensing": "reasonable",
|
||||
"submitter_name": "Test Holder",
|
||||
"submitter_email": "test@holder.com",
|
||||
}
|
||||
|
||||
def test_blanket_disclosure_licensing_restrictions(self):
|
||||
"""when is_blanket_disclosure is True only royalty-free licensing is valid
|
||||
|
||||
Most of the form functionality is tested via the views in IprTests above. More thorough testing
|
||||
of validation ought to move here so we don't have to exercise the whole Django plumbing repeatedly.
|
||||
"""
|
||||
self.assertTrue(HolderIprDisclosureForm(data=self.data).is_valid())
|
||||
self.data["is_blanket_disclosure"] = "on"
|
||||
self.assertFalse(HolderIprDisclosureForm(data=self.data).is_valid())
|
||||
self.data["licensing"] = "royalty-free"
|
||||
self.assertTrue(HolderIprDisclosureForm(data=self.data).is_valid())
|
||||
|
||||
def test_patent_details_required_unless_blanket(self):
|
||||
self.assertTrue(HolderIprDisclosureForm(data=self.data).is_valid())
|
||||
patent_fields = ["patent_number", "patent_inventor", "patent_title", "patent_date"]
|
||||
# any of the fields being missing should invalidate the form
|
||||
for pf in patent_fields:
|
||||
val = self.data.pop(pf)
|
||||
self.assertFalse(HolderIprDisclosureForm(data=self.data).is_valid())
|
||||
self.data[pf] = val
|
||||
|
||||
# should be optional if is_blanket_disclosure is True
|
||||
self.data["is_blanket_disclosure"] = "on"
|
||||
self.data["licensing"] = "royalty-free" # also needed for a blanket disclosure
|
||||
for pf in patent_fields:
|
||||
val = self.data.pop(pf)
|
||||
self.assertTrue(HolderIprDisclosureForm(data=self.data).is_valid())
|
||||
self.data[pf] = val
|
||||
|
|
|
@ -25,6 +25,6 @@ urlpatterns = [
|
|||
url(r'^(?P<id>\d+)/state/$', views.state),
|
||||
url(r'^update/$', RedirectView.as_view(url=reverse_lazy('ietf.ipr.views.showlist'), permanent=True)),
|
||||
url(r'^update/(?P<id>\d+)/$', views.update),
|
||||
url(r'^new-(?P<type>(specific|generic|general|third-party))/$', views.new),
|
||||
url(r'^new-(?P<_type>(specific|generic|general|third-party))/$', views.new),
|
||||
url(r'^search/$', views.search),
|
||||
]
|
||||
|
|
|
@ -475,28 +475,34 @@ def by_draft_recursive_txt(request):
|
|||
return HttpResponse(content, content_type="text/plain; charset=%s"%settings.DEFAULT_CHARSET)
|
||||
|
||||
|
||||
def new(request, type, updates=None):
|
||||
def new(request, _type, updates=None):
|
||||
"""Submit a new IPR Disclosure. If the updates field != None, this disclosure
|
||||
updates one or more other disclosures."""
|
||||
# Note that URL patterns won't ever send updates - updates is only non-null when called from code
|
||||
|
||||
# This odd construct flipping generic and general allows the URLs to say 'general' while having a minimal impact on the code.
|
||||
# A cleanup to change the code to switch on type 'general' should follow.
|
||||
if type == 'generic' and updates: # Only happens when called directly from the updates view
|
||||
if (
|
||||
_type == "generic" and updates
|
||||
): # Only happens when called directly from the updates view
|
||||
pass
|
||||
elif type == 'generic':
|
||||
return HttpResponseRedirect(urlreverse('ietf.ipr.views.new',kwargs=dict(type='general')))
|
||||
elif type == 'general':
|
||||
type = 'generic'
|
||||
elif _type == "generic":
|
||||
return HttpResponseRedirect(
|
||||
urlreverse("ietf.ipr.views.new", kwargs=dict(_type="general"))
|
||||
)
|
||||
elif _type == "general":
|
||||
_type = "generic"
|
||||
else:
|
||||
pass
|
||||
|
||||
# 1 to show initially + the template
|
||||
DraftFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=DraftForm, can_delete=False, extra=1 + 1)
|
||||
DraftFormset = inlineformset_factory(
|
||||
IprDisclosureBase, IprDocRel, form=DraftForm, can_delete=False, extra=1 + 1
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ipr_form_mapping[type](request.POST)
|
||||
if type != 'generic':
|
||||
if request.method == "POST":
|
||||
form = ipr_form_mapping[_type](request.POST)
|
||||
if _type != "generic":
|
||||
draft_formset = DraftFormset(request.POST, instance=IprDisclosureBase())
|
||||
else:
|
||||
draft_formset = None
|
||||
|
@ -507,21 +513,21 @@ def new(request, type, updates=None):
|
|||
person = request.user.person
|
||||
|
||||
# check formset validity
|
||||
if type != 'generic':
|
||||
if _type != "generic":
|
||||
valid_formsets = draft_formset.is_valid()
|
||||
else:
|
||||
valid_formsets = True
|
||||
|
||||
if form.is_valid() and valid_formsets:
|
||||
if 'updates' in form.cleaned_data:
|
||||
updates = form.cleaned_data['updates']
|
||||
del form.cleaned_data['updates']
|
||||
if "updates" in form.cleaned_data:
|
||||
updates = form.cleaned_data["updates"]
|
||||
del form.cleaned_data["updates"]
|
||||
disclosure = form.save(commit=False)
|
||||
disclosure.by = person
|
||||
disclosure.state = IprDisclosureStateName.objects.get(slug='pending')
|
||||
disclosure.state = IprDisclosureStateName.objects.get(slug="pending")
|
||||
disclosure.save()
|
||||
|
||||
if type != 'generic':
|
||||
if _type != "generic":
|
||||
draft_formset = DraftFormset(request.POST, instance=disclosure)
|
||||
draft_formset.save()
|
||||
|
||||
|
@ -530,22 +536,31 @@ def new(request, type, updates=None):
|
|||
|
||||
if updates:
|
||||
for ipr in updates:
|
||||
RelatedIpr.objects.create(source=disclosure,target=ipr,relationship_id='updates')
|
||||
RelatedIpr.objects.create(
|
||||
source=disclosure, target=ipr, relationship_id="updates"
|
||||
)
|
||||
|
||||
# create IprEvent
|
||||
IprEvent.objects.create(
|
||||
type_id='submitted',
|
||||
type_id="submitted",
|
||||
by=person,
|
||||
disclosure=disclosure,
|
||||
desc="Disclosure Submitted")
|
||||
desc="Disclosure Submitted",
|
||||
)
|
||||
|
||||
# send email notification
|
||||
(to, cc) = gather_address_lists('ipr_disclosure_submitted')
|
||||
send_mail(request, to, ('IPR Submitter App', 'ietf-ipr@ietf.org'),
|
||||
'New IPR Submission Notification',
|
||||
(to, cc) = gather_address_lists("ipr_disclosure_submitted")
|
||||
send_mail(
|
||||
request,
|
||||
to,
|
||||
("IPR Submitter App", "ietf-ipr@ietf.org"),
|
||||
"New IPR Submission Notification",
|
||||
"ipr/new_update_email.txt",
|
||||
{"ipr": disclosure,},
|
||||
cc=cc)
|
||||
{
|
||||
"ipr": disclosure,
|
||||
},
|
||||
cc=cc,
|
||||
)
|
||||
|
||||
return render(request, "ipr/submitted.html")
|
||||
|
||||
|
@ -553,24 +568,35 @@ def new(request, type, updates=None):
|
|||
if updates:
|
||||
original = IprDisclosureBase(id=updates).get_child()
|
||||
initial = model_to_dict(original)
|
||||
initial.update({'updates':str(updates), })
|
||||
patent_info = text_to_dict(initial.get('patent_info', ''))
|
||||
initial.update(
|
||||
{
|
||||
"updates": str(updates),
|
||||
}
|
||||
)
|
||||
patent_info = text_to_dict(initial.get("patent_info", ""))
|
||||
if list(patent_info.keys()):
|
||||
patent_dict = dict([ ('patent_'+k.lower(), v) for k,v in list(patent_info.items()) ])
|
||||
patent_dict = dict(
|
||||
[("patent_" + k.lower(), v) for k, v in list(patent_info.items())]
|
||||
)
|
||||
else:
|
||||
patent_dict = {'patent_notes': initial.get('patent_info', '')}
|
||||
patent_dict = {"patent_notes": initial.get("patent_info", "")}
|
||||
initial.update(patent_dict)
|
||||
form = ipr_form_mapping[type](initial=initial)
|
||||
form = ipr_form_mapping[_type](initial=initial)
|
||||
else:
|
||||
form = ipr_form_mapping[type]()
|
||||
disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
|
||||
form = ipr_form_mapping[_type]()
|
||||
disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
|
||||
draft_formset = DraftFormset(instance=disclosure)
|
||||
|
||||
return render(request, "ipr/details_edit.html", {
|
||||
'form': form,
|
||||
'draft_formset':draft_formset,
|
||||
'type':type,
|
||||
})
|
||||
return render(
|
||||
request,
|
||||
"ipr/details_edit.html",
|
||||
{
|
||||
"form": form,
|
||||
"draft_formset": draft_formset,
|
||||
"type": _type,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def notify(request, id, type):
|
||||
|
|
|
@ -69,4 +69,69 @@ $(document)
|
|||
form.find(".draft-row")
|
||||
.each(updateRevisions);
|
||||
}, 10);
|
||||
|
||||
// Manage fields that depend on the Blanket IPR Disclosure choice
|
||||
const blanketCheckbox = document.getElementById('id_is_blanket_disclosure')
|
||||
if (blanketCheckbox) {
|
||||
const patentDetailInputs = [
|
||||
// The ids are from the HolderIprDisclosureForm and its base form class,
|
||||
// intentionally excluding patent_notes because it's never required
|
||||
'id_patent_number',
|
||||
'id_patent_inventor',
|
||||
'id_patent_title',
|
||||
'id_patent_date'
|
||||
].map((id) => document.getElementById(id))
|
||||
const patentDetailRowDivs = patentDetailInputs.map(
|
||||
(elt) => elt.closest('div.row')
|
||||
)
|
||||
const royaltyFreeLicensingRadio = document.querySelector(
|
||||
'#id_licensing input[value="royalty-free"]'
|
||||
)
|
||||
let lastSelectedLicensingRadio
|
||||
const otherLicensingRadios = document.querySelectorAll(
|
||||
'#id_licensing input:not([value="royalty-free"])'
|
||||
)
|
||||
|
||||
const handleBlanketCheckboxChange = () => {
|
||||
const isBlanket = blanketCheckbox.checked
|
||||
// Update required fields
|
||||
for (elt of patentDetailInputs) {
|
||||
// disable the input element
|
||||
elt.required = !isBlanket
|
||||
}
|
||||
for (elt of patentDetailRowDivs) {
|
||||
// update the styling on the row that indicates required field
|
||||
if (isBlanket) {
|
||||
elt.classList.remove('required')
|
||||
} else {
|
||||
elt.classList.add('required')
|
||||
}
|
||||
}
|
||||
// Update licensing selection
|
||||
if (isBlanket) {
|
||||
lastSelectedLicensingRadio = document.querySelector(
|
||||
'#id_licensing input:checked'
|
||||
)
|
||||
royaltyFreeLicensingRadio.checked = true
|
||||
otherLicensingRadios
|
||||
.forEach(
|
||||
(elt) => elt.setAttribute('disabled', '')
|
||||
)
|
||||
} else {
|
||||
royaltyFreeLicensingRadio.checked = false
|
||||
if (lastSelectedLicensingRadio) {
|
||||
lastSelectedLicensingRadio.checked = true
|
||||
}
|
||||
otherLicensingRadios
|
||||
.forEach(
|
||||
(elt) => elt.removeAttribute('disabled')
|
||||
)
|
||||
}
|
||||
}
|
||||
handleBlanketCheckboxChange()
|
||||
blanketCheckbox.addEventListener(
|
||||
'change',
|
||||
(evt) => handleBlanketCheckboxChange()
|
||||
)
|
||||
}
|
||||
});
|
|
@ -32,7 +32,7 @@
|
|||
regarding an IETF document or contribution when the person letting the
|
||||
IETF know about the patent has no relationship with the patent owners.
|
||||
Click
|
||||
<a href="{% url 'ietf.ipr.views.new' type='specific' %}">here</a>
|
||||
<a href="{% url 'ietf.ipr.views.new' 'specific' %}">here</a>
|
||||
if you want to disclose information about patents or patent
|
||||
applications where you do have a relationship to the patent owners or
|
||||
patent applicants.
|
||||
|
@ -121,12 +121,11 @@
|
|||
{% endif %}
|
||||
{% if type != "generic" %}
|
||||
<h2 class="mt-4">{% cycle section %}. IETF document or other contribution to which this IPR disclosure relates</h2>
|
||||
<p class="form-text">
|
||||
<p>
|
||||
If an Internet-Draft or RFC includes multiple parts and it is not
|
||||
reasonably apparent which part of such Internet-Draft or RFC is alleged
|
||||
to be covered by the patent information disclosed in Section
|
||||
V(A) or V(B), please identify the sections of
|
||||
the Internet-Draft or RFC that are alleged to be so
|
||||
to be covered by the patent information disclosed in Section V,
|
||||
please identify the sections of the Internet-Draft or RFC that are alleged to be so
|
||||
covered.
|
||||
</p>
|
||||
{{ draft_formset.management_form }}
|
||||
|
@ -154,6 +153,13 @@
|
|||
<small>i.e., patents or patent applications required to be disclosed by Section 5 of RFC8179</small>
|
||||
</h2>
|
||||
{% if form.patent_number %}
|
||||
{% if form.is_blanket_disclosure %}
|
||||
<p>
|
||||
This IPR disclosure must either identify a specific patent or patents in sections V(A) and V(B)
|
||||
below, or be made as a blanket IPR disclosure.
|
||||
</p>
|
||||
{% bootstrap_field form.is_blanket_disclosure layout='horizontal' %}
|
||||
{% endif %}
|
||||
<p>
|
||||
A. For granted patents or published pending patent applications,
|
||||
please provide the following information:
|
||||
|
|
Loading…
Reference in a new issue