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
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class HolderIprDisclosureForm(IprDisclosureFormBase):
|
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(),
|
licensing = CustomModelChoiceField(IprLicenseTypeName.objects.all(),
|
||||||
widget=forms.RadioSelect,empty_label=None)
|
widget=forms.RadioSelect,empty_label=None)
|
||||||
|
|
||||||
|
@ -356,6 +368,15 @@ class HolderIprDisclosureForm(IprDisclosureFormBase):
|
||||||
else:
|
else:
|
||||||
# entering new disclosure
|
# entering new disclosure
|
||||||
self.fields['licensing'].queryset = IprLicenseTypeName.objects.exclude(slug='none-selected')
|
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):
|
def clean(self):
|
||||||
cleaned_data = super(HolderIprDisclosureForm, self).clean()
|
cleaned_data = super(HolderIprDisclosureForm, self).clean()
|
||||||
|
|
|
@ -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.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -124,17 +125,30 @@ class IprDisclosureBase(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class HolderIprDisclosure(IprDisclosureBase):
|
class HolderIprDisclosure(IprDisclosureBase):
|
||||||
ietfer_name = models.CharField(max_length=255, blank=True) # "Whose Personal Belief Triggered..."
|
ietfer_name = models.CharField(
|
||||||
ietfer_contact_email = models.EmailField(blank=True)
|
max_length=255, blank=True
|
||||||
ietfer_contact_info = models.TextField(blank=True)
|
) # "Whose Personal Belief Triggered..."
|
||||||
patent_info = models.TextField()
|
ietfer_contact_email = models.EmailField(blank=True)
|
||||||
has_patent_pending = models.BooleanField(default=False)
|
ietfer_contact_info = models.TextField(blank=True)
|
||||||
holder_contact_email = models.EmailField()
|
patent_info = models.TextField()
|
||||||
holder_contact_name = models.CharField(max_length=255)
|
has_patent_pending = models.BooleanField(default=False)
|
||||||
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
holder_contact_email = models.EmailField()
|
||||||
licensing = ForeignKey(IprLicenseTypeName)
|
holder_contact_name = models.CharField(max_length=255)
|
||||||
licensing_comments = models.TextField(blank=True)
|
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)
|
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):
|
class ThirdPartyIprDisclosure(IprDisclosureBase):
|
||||||
ietfer_name = models.CharField(max_length=255) # "Whose Personal Belief Triggered..."
|
ietfer_name = models.CharField(max_length=255) # "Whose Personal Belief Triggered..."
|
||||||
|
|
|
@ -33,7 +33,7 @@ from ietf.ipr.factories import (
|
||||||
IprDocRelFactory,
|
IprDocRelFactory,
|
||||||
IprEventFactory
|
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,
|
from ietf.ipr.mail import (process_response_email, get_reply_to, get_update_submitter_emails,
|
||||||
get_pseudo_submitter, get_holders, get_update_cc_addrs)
|
get_pseudo_submitter, get_holders, get_update_cc_addrs)
|
||||||
from ietf.ipr.models import (IprDisclosureBase,GenericIprDisclosure,HolderIprDisclosure,
|
from ietf.ipr.models import (IprDisclosureBase,GenericIprDisclosure,HolderIprDisclosure,
|
||||||
|
@ -272,16 +272,16 @@ class IprTests(TestCase):
|
||||||
|
|
||||||
def test_new_generic(self):
|
def test_new_generic(self):
|
||||||
"""Ensure new-generic redirects to new-general"""
|
"""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)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code,302)
|
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):
|
def test_new_general(self):
|
||||||
"""Add a new general disclosure. Note: submitter does not need to be logged in.
|
"""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
|
# invalid post
|
||||||
r = self.client.post(url, {
|
r = self.client.post(url, {
|
||||||
|
@ -319,7 +319,7 @@ class IprTests(TestCase):
|
||||||
"""
|
"""
|
||||||
draft = WgDraftFactory()
|
draft = WgDraftFactory()
|
||||||
rfc = WgRfcFactory()
|
rfc = WgRfcFactory()
|
||||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||||
|
|
||||||
# successful post
|
# successful post
|
||||||
empty_outbox()
|
empty_outbox()
|
||||||
|
@ -375,7 +375,7 @@ class IprTests(TestCase):
|
||||||
def test_new_specific_no_revision(self):
|
def test_new_specific_no_revision(self):
|
||||||
draft = WgDraftFactory()
|
draft = WgDraftFactory()
|
||||||
rfc = WgRfcFactory()
|
rfc = WgRfcFactory()
|
||||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||||
|
|
||||||
# successful post
|
# successful post
|
||||||
empty_outbox()
|
empty_outbox()
|
||||||
|
@ -409,7 +409,7 @@ class IprTests(TestCase):
|
||||||
"""
|
"""
|
||||||
draft = WgDraftFactory()
|
draft = WgDraftFactory()
|
||||||
rfc = WgRfcFactory()
|
rfc = WgRfcFactory()
|
||||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "third-party" })
|
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "third-party" })
|
||||||
|
|
||||||
# successful post
|
# successful post
|
||||||
empty_outbox()
|
empty_outbox()
|
||||||
|
@ -456,7 +456,7 @@ class IprTests(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertContains(r, original_ipr.holder_legal_name)
|
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
|
# successful post
|
||||||
empty_outbox()
|
empty_outbox()
|
||||||
post_data = {
|
post_data = {
|
||||||
|
@ -503,7 +503,7 @@ class IprTests(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertContains(r, original_ipr.title)
|
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
|
# successful post
|
||||||
empty_outbox()
|
empty_outbox()
|
||||||
r = self.client.post(url, {
|
r = self.client.post(url, {
|
||||||
|
@ -543,7 +543,7 @@ class IprTests(TestCase):
|
||||||
|
|
||||||
def test_update_bad_post(self):
|
def test_update_bad_post(self):
|
||||||
draft = WgDraftFactory()
|
draft = WgDraftFactory()
|
||||||
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
|
url = urlreverse("ietf.ipr.views.new", kwargs={ "_type": "specific" })
|
||||||
|
|
||||||
empty_outbox()
|
empty_outbox()
|
||||||
r = self.client.post(url, {
|
r = self.client.post(url, {
|
||||||
|
@ -1022,3 +1022,61 @@ class DraftFormTests(TestCase):
|
||||||
"revisions",
|
"revisions",
|
||||||
null_char_error_msg,
|
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'^(?P<id>\d+)/state/$', views.state),
|
||||||
url(r'^update/$', RedirectView.as_view(url=reverse_lazy('ietf.ipr.views.showlist'), permanent=True)),
|
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'^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),
|
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)
|
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
|
"""Submit a new IPR Disclosure. If the updates field != None, this disclosure
|
||||||
updates one or more other disclosures."""
|
updates one or more other disclosures."""
|
||||||
# Note that URL patterns won't ever send updates - updates is only non-null when called from code
|
# 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.
|
# 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.
|
# 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
|
pass
|
||||||
elif type == 'generic':
|
elif _type == "generic":
|
||||||
return HttpResponseRedirect(urlreverse('ietf.ipr.views.new',kwargs=dict(type='general')))
|
return HttpResponseRedirect(
|
||||||
elif type == 'general':
|
urlreverse("ietf.ipr.views.new", kwargs=dict(_type="general"))
|
||||||
type = 'generic'
|
)
|
||||||
|
elif _type == "general":
|
||||||
|
_type = "generic"
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 1 to show initially + the template
|
# 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':
|
if request.method == "POST":
|
||||||
form = ipr_form_mapping[type](request.POST)
|
form = ipr_form_mapping[_type](request.POST)
|
||||||
if type != 'generic':
|
if _type != "generic":
|
||||||
draft_formset = DraftFormset(request.POST, instance=IprDisclosureBase())
|
draft_formset = DraftFormset(request.POST, instance=IprDisclosureBase())
|
||||||
else:
|
else:
|
||||||
draft_formset = None
|
draft_formset = None
|
||||||
|
@ -505,72 +511,92 @@ def new(request, type, updates=None):
|
||||||
person = Person.objects.get(name="(System)")
|
person = Person.objects.get(name="(System)")
|
||||||
else:
|
else:
|
||||||
person = request.user.person
|
person = request.user.person
|
||||||
|
|
||||||
# check formset validity
|
# check formset validity
|
||||||
if type != 'generic':
|
if _type != "generic":
|
||||||
valid_formsets = draft_formset.is_valid()
|
valid_formsets = draft_formset.is_valid()
|
||||||
else:
|
else:
|
||||||
valid_formsets = True
|
valid_formsets = True
|
||||||
|
|
||||||
if form.is_valid() and valid_formsets:
|
if form.is_valid() and valid_formsets:
|
||||||
if 'updates' in form.cleaned_data:
|
if "updates" in form.cleaned_data:
|
||||||
updates = form.cleaned_data['updates']
|
updates = form.cleaned_data["updates"]
|
||||||
del form.cleaned_data['updates']
|
del form.cleaned_data["updates"]
|
||||||
disclosure = form.save(commit=False)
|
disclosure = form.save(commit=False)
|
||||||
disclosure.by = person
|
disclosure.by = person
|
||||||
disclosure.state = IprDisclosureStateName.objects.get(slug='pending')
|
disclosure.state = IprDisclosureStateName.objects.get(slug="pending")
|
||||||
disclosure.save()
|
disclosure.save()
|
||||||
|
|
||||||
if type != 'generic':
|
if _type != "generic":
|
||||||
draft_formset = DraftFormset(request.POST, instance=disclosure)
|
draft_formset = DraftFormset(request.POST, instance=disclosure)
|
||||||
draft_formset.save()
|
draft_formset.save()
|
||||||
|
|
||||||
set_disclosure_title(disclosure)
|
set_disclosure_title(disclosure)
|
||||||
disclosure.save()
|
disclosure.save()
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
for ipr in 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
|
# create IprEvent
|
||||||
IprEvent.objects.create(
|
IprEvent.objects.create(
|
||||||
type_id='submitted',
|
type_id="submitted",
|
||||||
by=person,
|
by=person,
|
||||||
disclosure=disclosure,
|
disclosure=disclosure,
|
||||||
desc="Disclosure Submitted")
|
desc="Disclosure Submitted",
|
||||||
|
)
|
||||||
|
|
||||||
# send email notification
|
# send email notification
|
||||||
(to, cc) = gather_address_lists('ipr_disclosure_submitted')
|
(to, cc) = gather_address_lists("ipr_disclosure_submitted")
|
||||||
send_mail(request, to, ('IPR Submitter App', 'ietf-ipr@ietf.org'),
|
send_mail(
|
||||||
'New IPR Submission Notification',
|
request,
|
||||||
|
to,
|
||||||
|
("IPR Submitter App", "ietf-ipr@ietf.org"),
|
||||||
|
"New IPR Submission Notification",
|
||||||
"ipr/new_update_email.txt",
|
"ipr/new_update_email.txt",
|
||||||
{"ipr": disclosure,},
|
{
|
||||||
cc=cc)
|
"ipr": disclosure,
|
||||||
|
},
|
||||||
|
cc=cc,
|
||||||
|
)
|
||||||
|
|
||||||
return render(request, "ipr/submitted.html")
|
return render(request, "ipr/submitted.html")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if updates:
|
if updates:
|
||||||
original = IprDisclosureBase(id=updates).get_child()
|
original = IprDisclosureBase(id=updates).get_child()
|
||||||
initial = model_to_dict(original)
|
initial = model_to_dict(original)
|
||||||
initial.update({'updates':str(updates), })
|
initial.update(
|
||||||
patent_info = text_to_dict(initial.get('patent_info', ''))
|
{
|
||||||
|
"updates": str(updates),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
patent_info = text_to_dict(initial.get("patent_info", ""))
|
||||||
if list(patent_info.keys()):
|
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:
|
else:
|
||||||
patent_dict = {'patent_notes': initial.get('patent_info', '')}
|
patent_dict = {"patent_notes": initial.get("patent_info", "")}
|
||||||
initial.update(patent_dict)
|
initial.update(patent_dict)
|
||||||
form = ipr_form_mapping[type](initial=initial)
|
form = ipr_form_mapping[_type](initial=initial)
|
||||||
else:
|
else:
|
||||||
form = ipr_form_mapping[type]()
|
form = ipr_form_mapping[_type]()
|
||||||
disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
|
disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
|
||||||
draft_formset = DraftFormset(instance=disclosure)
|
draft_formset = DraftFormset(instance=disclosure)
|
||||||
|
|
||||||
return render(request, "ipr/details_edit.html", {
|
return render(
|
||||||
'form': form,
|
request,
|
||||||
'draft_formset':draft_formset,
|
"ipr/details_edit.html",
|
||||||
'type':type,
|
{
|
||||||
})
|
"form": form,
|
||||||
|
"draft_formset": draft_formset,
|
||||||
|
"type": _type,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@role_required('Secretariat',)
|
@role_required('Secretariat',)
|
||||||
def notify(request, id, type):
|
def notify(request, id, type):
|
||||||
|
|
|
@ -69,4 +69,69 @@ $(document)
|
||||||
form.find(".draft-row")
|
form.find(".draft-row")
|
||||||
.each(updateRevisions);
|
.each(updateRevisions);
|
||||||
}, 10);
|
}, 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
|
regarding an IETF document or contribution when the person letting the
|
||||||
IETF know about the patent has no relationship with the patent owners.
|
IETF know about the patent has no relationship with the patent owners.
|
||||||
Click
|
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
|
if you want to disclose information about patents or patent
|
||||||
applications where you do have a relationship to the patent owners or
|
applications where you do have a relationship to the patent owners or
|
||||||
patent applicants.
|
patent applicants.
|
||||||
|
@ -121,12 +121,11 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if type != "generic" %}
|
{% if type != "generic" %}
|
||||||
<h2 class="mt-4">{% cycle section %}. IETF document or other contribution to which this IPR disclosure relates</h2>
|
<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
|
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
|
reasonably apparent which part of such Internet-Draft or RFC is alleged
|
||||||
to be covered by the patent information disclosed in Section
|
to be covered by the patent information disclosed in Section V,
|
||||||
V(A) or V(B), please identify the sections of
|
please identify the sections of the Internet-Draft or RFC that are alleged to be so
|
||||||
the Internet-Draft or RFC that are alleged to be so
|
|
||||||
covered.
|
covered.
|
||||||
</p>
|
</p>
|
||||||
{{ draft_formset.management_form }}
|
{{ 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>
|
<small>i.e., patents or patent applications required to be disclosed by Section 5 of RFC8179</small>
|
||||||
</h2>
|
</h2>
|
||||||
{% if form.patent_number %}
|
{% 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>
|
<p>
|
||||||
A. For granted patents or published pending patent applications,
|
A. For granted patents or published pending patent applications,
|
||||||
please provide the following information:
|
please provide the following information:
|
||||||
|
|
Loading…
Reference in a new issue