From 5a1f3eaf36178f9fdb52b3a64d125b9acb7fc965 Mon Sep 17 00:00:00 2001
From: Henrik Levkowetz
Date: Wed, 10 Jan 2018 14:22:10 +0000
Subject: [PATCH 1/4] Changed the patent information text fields to individual
fields for patent number, inventor, title, date and notes, with validation.
Fixes issue #2411. - Legacy-Id: 14499
---
ietf/ipr/forms.py | 84 +++++++++++++++++++++++++---
ietf/ipr/tests.py | 28 +++++++---
ietf/templates/ipr/details_edit.html | 16 +++++-
3 files changed, 111 insertions(+), 17 deletions(-)
diff --git a/ietf/ipr/forms.py b/ietf/ipr/forms.py
index cff81c7e5..8f2f69c1c 100644
--- a/ietf/ipr/forms.py
+++ b/ietf/ipr/forms.py
@@ -1,8 +1,12 @@
import datetime
import email
-from django.utils.safestring import mark_safe
+
from django import forms
+from django.core.validators import RegexValidator
+from django.utils.safestring import mark_safe
+
+import debug # pyflakes:ignore
from ietf.group.models import Group
from ietf.doc.fields import SearchableDocAliasField
@@ -101,6 +105,31 @@ class DraftForm(forms.ModelForm):
}
help_texts = { 'sections': 'Sections' }
+validate_patent_number = RegexValidator(
+ regex="^(([A-Z][A-Z]\d{6,12}|[A-Z][A-Z]\d{4}(\w{1,2}\d{5,7})?)[, ]*)+$",
+ message="Please enter one or more patent publication or application numbers as country code and serial number, e.g.: WO2017123456." )
+
+def validate_string(s, letter_min, digit_min, space_min, message):
+ letter_count = 0
+ space_count = 0
+ digit_count = 0
+ s = s.strip()
+ for c in s:
+ if c.isalpha():
+ letter_count += 1
+ if c.isspace():
+ space_count += 1
+ if not (letter_count >= letter_min and digit_count >= digit_min and space_count >= space_min):
+ raise forms.ValidationError(message)
+
+def validate_name(name):
+ return validate_string(name, letter_min=3, space_min=1, digit_min=0,
+ message="This doesn't look like a name. Please enter the actual inventor name.")
+
+def validate_title(title):
+ return validate_string(title, letter_min=15, space_min=2, digit_min=0,
+ message="This doesn't look like a patent title. Please enter the actual patent title.")
+
class GenericDisclosureForm(forms.Form):
"""Custom ModelForm-like form to use for new Generic or NonDocSpecific Iprs.
If patent_info is submitted create a NonDocSpecificIprDisclosure object
@@ -114,7 +143,14 @@ class GenericDisclosureForm(forms.Form):
holder_contact_info = forms.CharField(label="Other Info (address, phone, etc.)", max_length=255,widget=forms.Textarea,required=False, strip=False)
submitter_name = forms.CharField(max_length=255,required=False)
submitter_email = forms.EmailField(required=False)
- patent_info = forms.CharField(max_length=255,widget=forms.Textarea, required=False, help_text="Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes.", strip=False)
+ #patent_info = forms.CharField(max_length=255,widget=forms.Textarea, required=False, help_text="Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes.", strip=False)
+ patent_number = forms.CharField(max_length=127, required=False, validators=[ validate_patent_number ],
+ help_text = "Patent publication or application number (2-letter country code followed by serial number)")
+ patent_inventor = forms.CharField(max_length=63, required=False, validators=[ validate_name ], help_text="Inventor name")
+ patent_title = forms.CharField(max_length=63, required=False, validators=[ validate_title ], help_text="Title of invention")
+ patent_date = forms.DateField(required=False, help_text="Date granted or applied for")
+ patent_notes = forms.CharField(max_length=127, required=False, widget=forms.Textarea)
+
has_patent_pending = forms.BooleanField(required=False)
statement = forms.CharField(max_length=255,widget=forms.Textarea,required=False, strip=False)
updates = SearchableIprDisclosuresField(required=False, help_text="If this disclosure updates other disclosures identify here which ones. Leave this field blank if this disclosure does not update any prior disclosures. Note: Updates to IPR disclosures must only be made by authorized representatives of the original submitters. Updates will automatically be forwarded to the current Patent Holder's Contact and to the Submitter of the original IPR disclosure.")
@@ -132,7 +168,20 @@ class GenericDisclosureForm(forms.Form):
if not self.cleaned_data.get('same_as_ii_above'):
if not ( self.cleaned_data.get('submitter_name') and self.cleaned_data.get('submitter_email') ):
raise forms.ValidationError('Submitter information must be provided in section VII')
-
+
+ patent_fields = [ 'patent_'+k for k in ['number', 'inventor', 'title', 'date', ] ]
+ patent_values = [ cleaned_data.get(k) for k in patent_fields ]
+ if any(patent_values) and not all(patent_values):
+ for k in patent_fields:
+ if not cleaned_data.get(k):
+ self.add_error(k, "This field is required if you are filing a patent-specific disclosure.")
+ raise forms.ValidationError("A generic IPR disclosure cannot have any patent-specific information, "
+ "but a patent-specific disclosure must provide full patent information.")
+
+ patent_values = [str(v) for v in patent_values if v ] + [ cleaned_data['patent_notes'] ]
+ cleaned_data['patent_info'] = ('\n'.join(patent_values)).strip()
+ cleaned_data['patent_fields'] = patent_fields
+
return cleaned_data
def save(self, *args, **kwargs):
@@ -140,6 +189,9 @@ class GenericDisclosureForm(forms.Form):
same_as_ii_above = nargs.get('same_as_ii_above')
del nargs['same_as_ii_above']
+ for k in self.cleaned_data['patent_fields'] + ['patent_fields', 'patent_notes']:
+ del nargs[k]
+
if self.cleaned_data.get('patent_info'):
obj = NonDocSpecificIprDisclosure(**nargs)
else:
@@ -160,6 +212,12 @@ class IprDisclosureFormBase(forms.ModelForm):
"""Base form for Holder and ThirdParty disclosures"""
updates = SearchableIprDisclosuresField(required=False, help_text=mark_safe("If this disclosure updates other disclosures identify here which ones. Leave this field blank if this disclosure does not update any prior disclosures. Note: Updates to IPR disclosures must only be made by authorized representatives of the original submitters. Updates will automatically be forwarded to the current Patent Holder's Contact and to the Submitter of the original IPR disclosure."))
same_as_ii_above = forms.BooleanField(required=False)
+ patent_number = forms.CharField(max_length=127, required=True, validators=[ validate_patent_number ],
+ help_text = "Patent publication or application number (2-letter country code followed by serial number)")
+ patent_inventor = forms.CharField(max_length=63, required=True, validators=[ validate_name ], help_text="Inventor name")
+ patent_title = forms.CharField(max_length=63, required=True, validators=[ validate_title ], help_text="Title of invention")
+ patent_date = forms.DateField(required=True, help_text="Date granted or applied for")
+ patent_notes = forms.CharField(max_length=127, required=False, widget=forms.Textarea)
def __init__(self,*args,**kwargs):
super(IprDisclosureFormBase, self).__init__(*args,**kwargs)
@@ -167,6 +225,7 @@ class IprDisclosureFormBase(forms.ModelForm):
self.fields['submitter_email'].required = False
self.fields['compliant'].initial = True
self.fields['compliant'].label = "This disclosure complies with RFC 3979"
+ patent_fields = [ 'patent_'+k for k in ['number', 'inventor', 'title', 'date', ] ]
if "ietfer_name" in self.fields:
self.fields["ietfer_name"].label = "Name"
if "ietfer_contact_email" in self.fields:
@@ -175,7 +234,10 @@ class IprDisclosureFormBase(forms.ModelForm):
self.fields["ietfer_contact_info"].label = "Other info"
self.fields["ietfer_contact_info"].help_text = "Address, phone, etc."
if "patent_info" in self.fields:
- self.fields["patent_info"].help_text = "Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes"
+ self.fields['patent_info'].required = False
+ else:
+ for f in patent_fields:
+ del self.fields[f]
if "licensing" in self.fields:
self.fields["licensing_comments"].label = "Licensing information, comments, notes, or URL for further information"
if "submitter_claims_all_terms_disclosed" in self.fields:
@@ -187,7 +249,7 @@ class IprDisclosureFormBase(forms.ModelForm):
"""This will be overridden"""
model = IprDisclosureBase
fields = '__all__'
-
+
def clean(self):
super(IprDisclosureFormBase, self).clean()
cleaned_data = self.cleaned_data
@@ -198,6 +260,12 @@ class IprDisclosureFormBase(forms.ModelForm):
if not ( self.cleaned_data.get('submitter_name') and self.cleaned_data.get('submitter_email') ):
raise forms.ValidationError('Submitter information must be provided in section VII')
+ patent_fields = [ 'patent_'+k for k in ['number', 'inventor', 'title', 'date', 'notes'] ]
+ patent_values = [ cleaned_data.get(k) for k in patent_fields ]
+ patent_values = [ str(v) for v in patent_values if v ]
+ cleaned_data['patent_info'] = ('\n'.join(patent_values)).strip()
+ cleaned_data['patent_fields'] = patent_fields
+
return cleaned_data
class HolderIprDisclosureForm(IprDisclosureFormBase):
@@ -220,8 +288,7 @@ class HolderIprDisclosureForm(IprDisclosureFormBase):
self.fields['licensing'].queryset = IprLicenseTypeName.objects.exclude(slug='none-selected')
def clean(self):
- super(HolderIprDisclosureForm, self).clean()
- cleaned_data = self.cleaned_data
+ cleaned_data = super(HolderIprDisclosureForm, self).clean()
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
raise forms.ValidationError('You need to specify a contribution in Section IV')
return cleaned_data
@@ -270,8 +337,7 @@ class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
exclude = [ 'by','docs','state','rel' ]
def clean(self):
- super(ThirdPartyIprDisclosureForm, self).clean()
- cleaned_data = self.cleaned_data
+ cleaned_data = super(ThirdPartyIprDisclosureForm, self).clean()
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
raise forms.ValidationError('You need to specify a contribution in Section III')
return cleaned_data
diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py
index 03596953a..aaea72449 100644
--- a/ietf/ipr/tests.py
+++ b/ietf/ipr/tests.py
@@ -292,7 +292,7 @@ class IprTests(TestCase):
ipr = iprs[0]
self.assertEqual(ipr.holder_legal_name, "Test Legal")
self.assertEqual(ipr.state.slug, 'pending')
- self.assertTrue(isinstance(ipr.get_child(),GenericIprDisclosure))
+ self.assertTrue(isinstance(ipr.get_child(), GenericIprDisclosure))
def test_new_specific(self):
"""Add a new specific disclosure. Note: submitter does not need to be logged in.
@@ -314,14 +314,16 @@ class IprTests(TestCase):
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
- "patent_info": "none",
+ "patent_number": "SE12345678901",
+ "patent_inventor": "A. Nonymous",
+ "patent_title": "A method of transfering bits",
+ "patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
"submitter_name": "Test Holder",
"submitter_email": "test@holder.com",
})
self.assertEqual(r.status_code, 200)
- # print r.content
self.assertTrue("Your IPR disclosure has been submitted" in unicontent(r))
iprs = IprDisclosureBase.objects.filter(title__icontains=draft.name)
@@ -329,6 +331,8 @@ class IprTests(TestCase):
ipr = iprs[0]
self.assertEqual(ipr.holder_legal_name, "Test Legal")
self.assertEqual(ipr.state.slug, 'pending')
+ for item in [u'SE12345678901','A method of transfering bits','2000-01-01']:
+ self.assertIn(item, ipr.get_child().patent_info)
self.assertTrue(isinstance(ipr.get_child(),HolderIprDisclosure))
self.assertEqual(len(outbox),1)
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
@@ -352,7 +356,10 @@ class IprTests(TestCase):
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
- "patent_info": "none",
+ "patent_number": "SE12345678901",
+ "patent_inventor": "A. Nonymous",
+ "patent_title": "A method of transfering bits",
+ "patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
"submitter_name": "Test Holder",
@@ -366,6 +373,8 @@ class IprTests(TestCase):
ipr = iprs[0]
self.assertEqual(ipr.holder_legal_name, "Test Legal")
self.assertEqual(ipr.state.slug, "pending")
+ for item in [u'SE12345678901','A method of transfering bits','2000-01-01' ]:
+ self.assertIn(item, ipr.get_child().patent_info)
self.assertTrue(isinstance(ipr.get_child(),ThirdPartyIprDisclosure))
self.assertEqual(len(outbox),1)
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
@@ -391,7 +400,10 @@ class IprTests(TestCase):
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
- "patent_info": "none",
+ "patent_number": "SE12345678901",
+ "patent_inventor": "A. Nonymous",
+ "patent_title": "A method of transfering bits",
+ "patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
"submitter_name": "Test Holder",
@@ -415,7 +427,6 @@ class IprTests(TestCase):
draft = make_test_data()
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
- # successful post
empty_outbox()
r = self.client.post(url, {
"updates": "this is supposed to be an integer",
@@ -426,7 +437,10 @@ class IprTests(TestCase):
"iprdocrel_set-INITIAL_FORMS": 0,
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
"iprdocrel_set-0-revisions": '00',
- "patent_info": "none",
+ "patent_number": "SE12345678901",
+ "patent_inventor": "A. Nonymous",
+ "patent_title": "A method of transfering bits",
+ "patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
"submitter_name": "Test Holder",
diff --git a/ietf/templates/ipr/details_edit.html b/ietf/templates/ipr/details_edit.html
index 2511ef764..f2b3f65f9 100644
--- a/ietf/templates/ipr/details_edit.html
+++ b/ietf/templates/ipr/details_edit.html
@@ -162,7 +162,21 @@
{% cycle section %}. Disclosure of Patent Information{% if form.instance|to_class_name == "ThirdPartyIprDicslosure" %}, if known{% endif %}
i.e., patents or patent applications required to be disclosed by Section 5 of RFC8179
- {% if form.patent_info %}
+ {% if form.patent_number %}
+
+ A. For granted patents or published pending patent applications,
+ please provide the following information:
+
{% endif %}
{% if ipr.licensing %}
@@ -204,23 +287,32 @@
specification, is as follows(select one licensing declaration option only):
- {% if ipr.licensing.slug == "provided-later" %}
+ {% if prev.licensing.slug == "provided-later" or ipr.licensing.slug == "provided-later" %}
- Possible licencing choices a), b), and c) when Licencing Declaration to be Provided Later:
+ Possible licensing choices a), b), and c) when Licensing Declaration to be Provided Later:
{{ prev.licensing_comments|default:"(No information submitted)"|linebreaks }}
{% endif %}
+
Licensing information, comments, notes, or URL for further information
+
{{ ipr.licensing_comments|default:"(No information submitted)"|linebreaks }}
+
+
Note: The individual submitting this template represents and warrants
that he or she is authorized by the Patent Holder to agree to the
@@ -229,33 +321,54 @@
{% elif ipr.statement %}