Skip to content

Commit 5a1f3ea

Browse files
committed
Changed the patent information text fields to individual fields for patent number, inventor, title, date and notes, with validation. Fixes issue ietf-tools#2411.
- Legacy-Id: 14499
1 parent 5b178aa commit 5a1f3ea

3 files changed

Lines changed: 111 additions & 17 deletions

File tree

ietf/ipr/forms.py

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import datetime
22
import email
33

4-
from django.utils.safestring import mark_safe
4+
55
from django import forms
6+
from django.core.validators import RegexValidator
7+
from django.utils.safestring import mark_safe
8+
9+
import debug # pyflakes:ignore
610

711
from ietf.group.models import Group
812
from ietf.doc.fields import SearchableDocAliasField
@@ -101,6 +105,31 @@ class Meta:
101105
}
102106
help_texts = { 'sections': 'Sections' }
103107

108+
validate_patent_number = RegexValidator(
109+
regex="^(([A-Z][A-Z]\d{6,12}|[A-Z][A-Z]\d{4}(\w{1,2}\d{5,7})?)[, ]*)+$",
110+
message="Please enter one or more patent publication or application numbers as country code and serial number, e.g.: WO2017123456." )
111+
112+
def validate_string(s, letter_min, digit_min, space_min, message):
113+
letter_count = 0
114+
space_count = 0
115+
digit_count = 0
116+
s = s.strip()
117+
for c in s:
118+
if c.isalpha():
119+
letter_count += 1
120+
if c.isspace():
121+
space_count += 1
122+
if not (letter_count >= letter_min and digit_count >= digit_min and space_count >= space_min):
123+
raise forms.ValidationError(message)
124+
125+
def validate_name(name):
126+
return validate_string(name, letter_min=3, space_min=1, digit_min=0,
127+
message="This doesn't look like a name. Please enter the actual inventor name.")
128+
129+
def validate_title(title):
130+
return validate_string(title, letter_min=15, space_min=2, digit_min=0,
131+
message="This doesn't look like a patent title. Please enter the actual patent title.")
132+
104133
class GenericDisclosureForm(forms.Form):
105134
"""Custom ModelForm-like form to use for new Generic or NonDocSpecific Iprs.
106135
If patent_info is submitted create a NonDocSpecificIprDisclosure object
@@ -114,7 +143,14 @@ class GenericDisclosureForm(forms.Form):
114143
holder_contact_info = forms.CharField(label="Other Info (address, phone, etc.)", max_length=255,widget=forms.Textarea,required=False, strip=False)
115144
submitter_name = forms.CharField(max_length=255,required=False)
116145
submitter_email = forms.EmailField(required=False)
117-
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)
146+
#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)
147+
patent_number = forms.CharField(max_length=127, required=False, validators=[ validate_patent_number ],
148+
help_text = "Patent publication or application number (2-letter country code followed by serial number)")
149+
patent_inventor = forms.CharField(max_length=63, required=False, validators=[ validate_name ], help_text="Inventor name")
150+
patent_title = forms.CharField(max_length=63, required=False, validators=[ validate_title ], help_text="Title of invention")
151+
patent_date = forms.DateField(required=False, help_text="Date granted or applied for")
152+
patent_notes = forms.CharField(max_length=127, required=False, widget=forms.Textarea)
153+
118154
has_patent_pending = forms.BooleanField(required=False)
119155
statement = forms.CharField(max_length=255,widget=forms.Textarea,required=False, strip=False)
120156
updates = SearchableIprDisclosuresField(required=False, help_text="If this disclosure <strong>updates</strong> other disclosures identify here which ones. Leave this field blank if this disclosure does not update any prior disclosures. <strong>Note</strong>: 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,14 +168,30 @@ def clean(self):
132168
if not self.cleaned_data.get('same_as_ii_above'):
133169
if not ( self.cleaned_data.get('submitter_name') and self.cleaned_data.get('submitter_email') ):
134170
raise forms.ValidationError('Submitter information must be provided in section VII')
135-
171+
172+
patent_fields = [ 'patent_'+k for k in ['number', 'inventor', 'title', 'date', ] ]
173+
patent_values = [ cleaned_data.get(k) for k in patent_fields ]
174+
if any(patent_values) and not all(patent_values):
175+
for k in patent_fields:
176+
if not cleaned_data.get(k):
177+
self.add_error(k, "This field is required if you are filing a patent-specific disclosure.")
178+
raise forms.ValidationError("A generic IPR disclosure cannot have any patent-specific information, "
179+
"but a patent-specific disclosure must provide full patent information.")
180+
181+
patent_values = [str(v) for v in patent_values if v ] + [ cleaned_data['patent_notes'] ]
182+
cleaned_data['patent_info'] = ('\n'.join(patent_values)).strip()
183+
cleaned_data['patent_fields'] = patent_fields
184+
136185
return cleaned_data
137186

138187
def save(self, *args, **kwargs):
139188
nargs = self.cleaned_data.copy()
140189
same_as_ii_above = nargs.get('same_as_ii_above')
141190
del nargs['same_as_ii_above']
142191

192+
for k in self.cleaned_data['patent_fields'] + ['patent_fields', 'patent_notes']:
193+
del nargs[k]
194+
143195
if self.cleaned_data.get('patent_info'):
144196
obj = NonDocSpecificIprDisclosure(**nargs)
145197
else:
@@ -160,13 +212,20 @@ class IprDisclosureFormBase(forms.ModelForm):
160212
"""Base form for Holder and ThirdParty disclosures"""
161213
updates = SearchableIprDisclosuresField(required=False, help_text=mark_safe("If this disclosure <strong>updates</strong> 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."))
162214
same_as_ii_above = forms.BooleanField(required=False)
215+
patent_number = forms.CharField(max_length=127, required=True, validators=[ validate_patent_number ],
216+
help_text = "Patent publication or application number (2-letter country code followed by serial number)")
217+
patent_inventor = forms.CharField(max_length=63, required=True, validators=[ validate_name ], help_text="Inventor name")
218+
patent_title = forms.CharField(max_length=63, required=True, validators=[ validate_title ], help_text="Title of invention")
219+
patent_date = forms.DateField(required=True, help_text="Date granted or applied for")
220+
patent_notes = forms.CharField(max_length=127, required=False, widget=forms.Textarea)
163221

164222
def __init__(self,*args,**kwargs):
165223
super(IprDisclosureFormBase, self).__init__(*args,**kwargs)
166224
self.fields['submitter_name'].required = False
167225
self.fields['submitter_email'].required = False
168226
self.fields['compliant'].initial = True
169227
self.fields['compliant'].label = "This disclosure complies with RFC 3979"
228+
patent_fields = [ 'patent_'+k for k in ['number', 'inventor', 'title', 'date', ] ]
170229
if "ietfer_name" in self.fields:
171230
self.fields["ietfer_name"].label = "Name"
172231
if "ietfer_contact_email" in self.fields:
@@ -175,7 +234,10 @@ def __init__(self,*args,**kwargs):
175234
self.fields["ietfer_contact_info"].label = "Other info"
176235
self.fields["ietfer_contact_info"].help_text = "Address, phone, etc."
177236
if "patent_info" in self.fields:
178-
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"
237+
self.fields['patent_info'].required = False
238+
else:
239+
for f in patent_fields:
240+
del self.fields[f]
179241
if "licensing" in self.fields:
180242
self.fields["licensing_comments"].label = "Licensing information, comments, notes, or URL for further information"
181243
if "submitter_claims_all_terms_disclosed" in self.fields:
@@ -187,7 +249,7 @@ class Meta:
187249
"""This will be overridden"""
188250
model = IprDisclosureBase
189251
fields = '__all__'
190-
252+
191253
def clean(self):
192254
super(IprDisclosureFormBase, self).clean()
193255
cleaned_data = self.cleaned_data
@@ -198,6 +260,12 @@ def clean(self):
198260
if not ( self.cleaned_data.get('submitter_name') and self.cleaned_data.get('submitter_email') ):
199261
raise forms.ValidationError('Submitter information must be provided in section VII')
200262

263+
patent_fields = [ 'patent_'+k for k in ['number', 'inventor', 'title', 'date', 'notes'] ]
264+
patent_values = [ cleaned_data.get(k) for k in patent_fields ]
265+
patent_values = [ str(v) for v in patent_values if v ]
266+
cleaned_data['patent_info'] = ('\n'.join(patent_values)).strip()
267+
cleaned_data['patent_fields'] = patent_fields
268+
201269
return cleaned_data
202270

203271
class HolderIprDisclosureForm(IprDisclosureFormBase):
@@ -220,8 +288,7 @@ def __init__(self, *args, **kwargs):
220288
self.fields['licensing'].queryset = IprLicenseTypeName.objects.exclude(slug='none-selected')
221289

222290
def clean(self):
223-
super(HolderIprDisclosureForm, self).clean()
224-
cleaned_data = self.cleaned_data
291+
cleaned_data = super(HolderIprDisclosureForm, self).clean()
225292
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
226293
raise forms.ValidationError('You need to specify a contribution in Section IV')
227294
return cleaned_data
@@ -270,8 +337,7 @@ class Meta:
270337
exclude = [ 'by','docs','state','rel' ]
271338

272339
def clean(self):
273-
super(ThirdPartyIprDisclosureForm, self).clean()
274-
cleaned_data = self.cleaned_data
340+
cleaned_data = super(ThirdPartyIprDisclosureForm, self).clean()
275341
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
276342
raise forms.ValidationError('You need to specify a contribution in Section III')
277343
return cleaned_data

ietf/ipr/tests.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ def test_new_generic(self):
292292
ipr = iprs[0]
293293
self.assertEqual(ipr.holder_legal_name, "Test Legal")
294294
self.assertEqual(ipr.state.slug, 'pending')
295-
self.assertTrue(isinstance(ipr.get_child(),GenericIprDisclosure))
295+
self.assertTrue(isinstance(ipr.get_child(), GenericIprDisclosure))
296296

297297
def test_new_specific(self):
298298
"""Add a new specific disclosure. Note: submitter does not need to be logged in.
@@ -314,21 +314,25 @@ def test_new_specific(self):
314314
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
315315
"iprdocrel_set-0-revisions": '00',
316316
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
317-
"patent_info": "none",
317+
"patent_number": "SE12345678901",
318+
"patent_inventor": "A. Nonymous",
319+
"patent_title": "A method of transfering bits",
320+
"patent_date": "2000-01-01",
318321
"has_patent_pending": False,
319322
"licensing": "royalty-free",
320323
"submitter_name": "Test Holder",
321324
"submitter_email": "test@holder.com",
322325
})
323326
self.assertEqual(r.status_code, 200)
324-
# print r.content
325327
self.assertTrue("Your IPR disclosure has been submitted" in unicontent(r))
326328

327329
iprs = IprDisclosureBase.objects.filter(title__icontains=draft.name)
328330
self.assertEqual(len(iprs), 1)
329331
ipr = iprs[0]
330332
self.assertEqual(ipr.holder_legal_name, "Test Legal")
331333
self.assertEqual(ipr.state.slug, 'pending')
334+
for item in [u'SE12345678901','A method of transfering bits','2000-01-01']:
335+
self.assertIn(item, ipr.get_child().patent_info)
332336
self.assertTrue(isinstance(ipr.get_child(),HolderIprDisclosure))
333337
self.assertEqual(len(outbox),1)
334338
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
@@ -352,7 +356,10 @@ def test_new_thirdparty(self):
352356
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
353357
"iprdocrel_set-0-revisions": '00',
354358
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
355-
"patent_info": "none",
359+
"patent_number": "SE12345678901",
360+
"patent_inventor": "A. Nonymous",
361+
"patent_title": "A method of transfering bits",
362+
"patent_date": "2000-01-01",
356363
"has_patent_pending": False,
357364
"licensing": "royalty-free",
358365
"submitter_name": "Test Holder",
@@ -366,6 +373,8 @@ def test_new_thirdparty(self):
366373
ipr = iprs[0]
367374
self.assertEqual(ipr.holder_legal_name, "Test Legal")
368375
self.assertEqual(ipr.state.slug, "pending")
376+
for item in [u'SE12345678901','A method of transfering bits','2000-01-01' ]:
377+
self.assertIn(item, ipr.get_child().patent_info)
369378
self.assertTrue(isinstance(ipr.get_child(),ThirdPartyIprDisclosure))
370379
self.assertEqual(len(outbox),1)
371380
self.assertTrue('New IPR Submission' in outbox[0]['Subject'])
@@ -391,7 +400,10 @@ def test_update(self):
391400
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
392401
"iprdocrel_set-0-revisions": '00',
393402
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
394-
"patent_info": "none",
403+
"patent_number": "SE12345678901",
404+
"patent_inventor": "A. Nonymous",
405+
"patent_title": "A method of transfering bits",
406+
"patent_date": "2000-01-01",
395407
"has_patent_pending": False,
396408
"licensing": "royalty-free",
397409
"submitter_name": "Test Holder",
@@ -415,7 +427,6 @@ def test_update_bad_post(self):
415427
draft = make_test_data()
416428
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
417429

418-
# successful post
419430
empty_outbox()
420431
r = self.client.post(url, {
421432
"updates": "this is supposed to be an integer",
@@ -426,7 +437,10 @@ def test_update_bad_post(self):
426437
"iprdocrel_set-INITIAL_FORMS": 0,
427438
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
428439
"iprdocrel_set-0-revisions": '00',
429-
"patent_info": "none",
440+
"patent_number": "SE12345678901",
441+
"patent_inventor": "A. Nonymous",
442+
"patent_title": "A method of transfering bits",
443+
"patent_date": "2000-01-01",
430444
"has_patent_pending": False,
431445
"licensing": "royalty-free",
432446
"submitter_name": "Test Holder",

ietf/templates/ipr/details_edit.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,21 @@ <h2>{% cycle section %}. IETF document or other contribution to which this IPR d
162162
<h2>{% cycle section %}. Disclosure of Patent Information{% if form.instance|to_class_name == "ThirdPartyIprDicslosure" %}, if known{% endif %}
163163
<small>i.e., patents or patent applications required to be disclosed by Section 5 of RFC8179</small></h2>
164164

165-
{% if form.patent_info %}
165+
{% if form.patent_number %}
166+
<p>
167+
A. For granted patents or published pending patent applications,
168+
please provide the following information:
169+
</p>
170+
{% bootstrap_field form.patent_number layout='horizontal' %}
171+
{% bootstrap_field form.patent_inventor layout='horizontal' %}
172+
{% bootstrap_field form.patent_title layout='horizontal' %}
173+
{% bootstrap_field form.patent_date layout='horizontal' %}
174+
{% bootstrap_field form.patent_notes layout='horizontal' %}
175+
176+
<p>B. Does your disclosure relate to an unpublished pending patent application?</p>
177+
{% bootstrap_field form.has_patent_pending layout='horizontal' %}
178+
179+
{% elif form.patent_info %}
166180
<p>
167181
A. For granted patents or published pending patent applications,
168182
please provide the following information:

0 commit comments

Comments
 (0)