Skip to content

Commit 1124d6d

Browse files
committed
Summary: Port the remaining parts of the IPR form to Bootstrap, and fix
some bugs in the port - Legacy-Id: 8900
1 parent 543ac4e commit 1124d6d

File tree

11 files changed

+202
-282
lines changed

11 files changed

+202
-282
lines changed

ietf/doc/fields.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
from django import forms
55
from django.core.urlresolvers import reverse as urlreverse
66

7-
import debug # pyflakes:ignore
8-
97
from ietf.doc.models import Document, DocAlias
8+
from ietf.doc.utils import uppercase_std_abbreviated_name
109

1110
def select2_id_doc_name_json(objs):
12-
return json.dumps([{ "id": o.pk, "text": escape(o.name) } for o in objs])
11+
return json.dumps([{ "id": o.pk, "text": escape(uppercase_std_abbreviated_name(o.name)) } for o in objs])
12+
13+
# FIXME: select2 version 4 uses a standard select for the AJAX case -
14+
# switching to that would allow us to derive from the standard
15+
# multi-select machinery in Django instead of the manual CharField
16+
# stuff below
1317

1418
class SearchableDocumentsField(forms.CharField):
1519
"""Server-based multi-select field for choosing documents using
@@ -32,7 +36,7 @@ def __init__(self,
3236

3337
super(SearchableDocumentsField, self).__init__(*args, **kwargs)
3438

35-
self.widget.attrs["class"] = "select2-field"
39+
self.widget.attrs["class"] = "select2-field form-control"
3640
self.widget.attrs["data-placeholder"] = hint_text
3741
if self.max_entries != None:
3842
self.widget.attrs["data-max-entries"] = self.max_entries
@@ -43,6 +47,8 @@ def parse_select2_value(self, value):
4347
def prepare_value(self, value):
4448
if not value:
4549
value = ""
50+
if isinstance(value, (int, long)):
51+
value = str(value)
4652
if isinstance(value, basestring):
4753
pks = self.parse_select2_value(value)
4854
value = self.model.objects.filter(pk__in=pks)
@@ -82,7 +88,26 @@ def clean(self, value):
8288

8389
return objs
8490

91+
class SearchableDocumentField(SearchableDocumentsField):
92+
"""Specialized to only return one Document."""
93+
def __init__(self, model=Document, *args, **kwargs):
94+
kwargs["max_entries"] = 1
95+
super(SearchableDocumentField, self).__init__(model=model, *args, **kwargs)
96+
97+
def clean(self, value):
98+
return super(SearchableDocumentField, self).clean(value).first()
99+
85100
class SearchableDocAliasesField(SearchableDocumentsField):
86101
def __init__(self, model=DocAlias, *args, **kwargs):
87102
super(SearchableDocAliasesField, self).__init__(model=model, *args, **kwargs)
88103

104+
class SearchableDocAliasField(SearchableDocumentsField):
105+
"""Specialized to only return one DocAlias."""
106+
def __init__(self, model=DocAlias, *args, **kwargs):
107+
kwargs["max_entries"] = 1
108+
super(SearchableDocAliasField, self).__init__(model=model, *args, **kwargs)
109+
110+
def clean(self, value):
111+
return super(SearchableDocAliasField, self).clean(value).first()
112+
113+

ietf/ipr/fields.py

Lines changed: 17 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,73 +4,58 @@
44
from django import forms
55
from django.core.urlresolvers import reverse as urlreverse
66

7-
import debug # pyflakes:ignore
8-
9-
from ietf.doc.models import DocAlias
107
from ietf.ipr.models import IprDisclosureBase
118

12-
def tokeninput_id_name_json(objs):
13-
"""Returns objects as JSON string.
14-
NOTE: double quotes in the object name are replaced with single quotes to avoid
15-
problems with representation of the JSON string in the HTML widget attribute"""
16-
def format_ipr(x):
17-
text = x.title.replace('"',"'")
18-
return escape(u"%s <%s>" % (text, x.time.date().isoformat()))
19-
def format_doc(x):
20-
return escape(x.name)
21-
22-
formatter = format_ipr if objs and isinstance(objs[0], IprDisclosureBase) else format_doc
23-
24-
return json.dumps([{ "id": o.pk, "name": formatter(o) } for o in objs])
9+
def select2_id_ipr_title_json(value):
10+
return json.dumps([{ "id": o.pk, "text": escape(u"%s <%s>" % (o.title, o.time.date().isoformat())) } for o in value])
2511

26-
class AutocompletedIprDisclosuresField(forms.CharField):
27-
"""Tokenizing autocompleted multi-select field for choosing
28-
IPR disclosures using jquery.tokeninput.js.
12+
class SearchableIprDisclosuresField(forms.CharField):
13+
"""Server-based multi-select field for choosing documents using
14+
select2.js.
2915
3016
The field uses a comma-separated list of primary keys in a
31-
CharField element as its API, the tokeninput Javascript adds some
32-
selection magic on top of this so we have to pass it a JSON
33-
representation of ids and user-understandable labels."""
17+
CharField element as its API with some extra attributes used by
18+
the Javascript part."""
3419

3520
def __init__(self,
3621
max_entries=None, # max number of selected objs
3722
model=IprDisclosureBase,
38-
hint_text="Type in term(s) to search disclosure title",
23+
hint_text="Type in terms to search disclosure title",
3924
*args, **kwargs):
4025
kwargs["max_length"] = 1000
4126
self.max_entries = max_entries
4227
self.model = model
4328

44-
super(AutocompletedIprDisclosuresField, self).__init__(*args, **kwargs)
29+
super(SearchableIprDisclosuresField, self).__init__(*args, **kwargs)
4530

46-
self.widget.attrs["class"] = "tokenized-field"
47-
self.widget.attrs["data-hint-text"] = hint_text
31+
self.widget.attrs["class"] = "select2-field form-control"
32+
self.widget.attrs["data-placeholder"] = hint_text
4833
if self.max_entries != None:
4934
self.widget.attrs["data-max-entries"] = self.max_entries
5035

51-
def parse_tokenized_value(self, value):
36+
def parse_select2_value(self, value):
5237
return [x.strip() for x in value.split(",") if x.strip()]
5338

5439
def prepare_value(self, value):
5540
if not value:
5641
value = ""
5742
if isinstance(value, basestring):
58-
pks = self.parse_tokenized_value(value)
43+
pks = self.parse_select2_value(value)
5944
value = self.model.objects.filter(pk__in=pks)
6045
if isinstance(value, self.model):
6146
value = [value]
6247

63-
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
48+
self.widget.attrs["data-pre"] = select2_id_ipr_title_json(value)
6449

6550
# doing this in the constructor is difficult because the URL
6651
# patterns may not have been fully constructed there yet
6752
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_search")
6853

69-
return ",".join(str(e.pk) for e in value)
54+
return u",".join(unicode(e.pk) for e in value)
7055

7156
def clean(self, value):
72-
value = super(AutocompletedIprDisclosuresField, self).clean(value)
73-
pks = self.parse_tokenized_value(value)
57+
value = super(SearchableIprDisclosuresField, self).clean(value)
58+
pks = self.parse_select2_value(value)
7459

7560
objs = self.model.objects.filter(pk__in=pks)
7661

@@ -83,75 +68,3 @@ def clean(self, value):
8368
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
8469

8570
return objs
86-
87-
class AutocompletedDraftsField(AutocompletedIprDisclosuresField):
88-
"""Version of AutocompletedPersonsField with the defaults right for Drafts."""
89-
90-
def __init__(self, model=DocAlias, hint_text="Type in name to search draft name",
91-
*args, **kwargs):
92-
super(AutocompletedDraftsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
93-
94-
def prepare_value(self, value):
95-
if not value:
96-
value = ""
97-
if isinstance(value, basestring):
98-
pks = self.parse_tokenized_value(value)
99-
value = self.model.objects.filter(pk__in=pks)
100-
if isinstance(value, self.model):
101-
value = [value]
102-
if isinstance(value, long):
103-
value = self.model.objects.filter(pk=value)
104-
105-
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
106-
107-
# doing this in the constructor is difficult because the URL
108-
# patterns may not have been fully constructed there yet
109-
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_draft_search")
110-
111-
return ",".join(str(e.pk) for e in value)
112-
113-
class AutocompletedDraftField(AutocompletedDraftsField):
114-
"""Version of AutocompletedEmailsField specialized to a single object."""
115-
116-
def __init__(self, *args, **kwargs):
117-
kwargs["max_entries"] = 1
118-
super(AutocompletedDraftField, self).__init__(*args, **kwargs)
119-
120-
def clean(self, value):
121-
return super(AutocompletedDraftField, self).clean(value).first()
122-
123-
class AutocompletedRfcsField(AutocompletedIprDisclosuresField):
124-
"""Version of AutocompletedPersonsField with the defaults right for Drafts."""
125-
126-
def __init__(self, model=DocAlias, hint_text="Type in the RFC number",
127-
*args, **kwargs):
128-
super(AutocompletedRfcsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
129-
130-
def prepare_value(self, value):
131-
if not value:
132-
value = ""
133-
if isinstance(value, basestring):
134-
pks = self.parse_tokenized_value(value)
135-
value = self.model.objects.filter(pk__in=pks)
136-
if isinstance(value, self.model):
137-
value = [value]
138-
if isinstance(value, long):
139-
value = self.model.objects.filter(pk=value)
140-
141-
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
142-
143-
# doing this in the constructor is difficult because the URL
144-
# patterns may not have been fully constructed there yet
145-
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_rfc_search")
146-
147-
return ",".join(str(e.pk) for e in value)
148-
149-
class AutocompletedRfcField(AutocompletedRfcsField):
150-
"""Version of AutocompletedEmailsField specialized to a single object."""
151-
152-
def __init__(self, *args, **kwargs):
153-
kwargs["max_entries"] = 1
154-
super(AutocompletedRfcField, self).__init__(*args, **kwargs)
155-
156-
def clean(self, value):
157-
return super(AutocompletedRfcField, self).clean(value).first()

ietf/ipr/forms.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
from django import forms
66

77
from ietf.group.models import Group
8+
from ietf.doc.fields import SearchableDocAliasField
89
from ietf.ipr.mail import utc_from_string
9-
from ietf.ipr.fields import (AutocompletedIprDisclosuresField, AutocompletedDraftField,
10-
AutocompletedRfcField)
10+
from ietf.ipr.fields import SearchableIprDisclosuresField
1111
from ietf.ipr.models import (IprDocRel, IprDisclosureBase, HolderIprDisclosure,
1212
GenericIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure,
1313
IprLicenseTypeName, IprDisclosureStateName)
@@ -90,7 +90,7 @@ def clean(self):
9090
return self.cleaned_data
9191

9292
class DraftForm(forms.ModelForm):
93-
document = AutocompletedDraftField(required=False)
93+
document = SearchableDocAliasField(label="I-D name/RFC number", required=False, doc_type="draft")
9494

9595
class Meta:
9696
model = IprDocRel
@@ -103,10 +103,10 @@ class GenericDisclosureForm(forms.Form):
103103
"""Custom ModelForm-like form to use for new Generic or NonDocSpecific Iprs.
104104
If patent_info is submitted create a NonDocSpecificIprDisclosure object
105105
otherwise create a GenericIprDisclosure object."""
106-
compliant = forms.CharField(label="This disclosure complies with RFC 3979", required=False)
106+
compliant = forms.BooleanField(label="This disclosure complies with RFC 3979", required=False)
107107
holder_legal_name = forms.CharField(max_length=255)
108-
notes = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
109-
other_designations = forms.CharField(max_length=255,required=False)
108+
notes = forms.CharField(label="Additional notes", max_length=255,widget=forms.Textarea,required=False)
109+
other_designations = forms.CharField(label="Designations for other contributions", max_length=255,required=False)
110110
holder_contact_name = forms.CharField(label="Name", max_length=255)
111111
holder_contact_email = forms.EmailField(label="Email")
112112
holder_contact_info = forms.CharField(label="Other Info (address, phone, etc.)", max_length=255,widget=forms.Textarea,required=False)
@@ -115,7 +115,7 @@ class GenericDisclosureForm(forms.Form):
115115
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")
116116
has_patent_pending = forms.BooleanField(required=False)
117117
statement = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
118-
updates = AutocompletedIprDisclosuresField(required=False)
118+
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. 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.")
119119
same_as_ii_above = forms.BooleanField(label="Same as in section II above", required=False)
120120

121121
def __init__(self,*args,**kwargs):
@@ -156,7 +156,7 @@ def save(self, *args, **kwargs):
156156

157157
class IprDisclosureFormBase(forms.ModelForm):
158158
"""Base form for Holder and ThirdParty disclosures"""
159-
updates = AutocompletedIprDisclosuresField(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."))
159+
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."))
160160
same_as_ii_above = forms.BooleanField(required=False)
161161

162162
def __init__(self,*args,**kwargs):
@@ -212,7 +212,7 @@ def __init__(self, *args, **kwargs):
212212
def clean(self):
213213
super(HolderIprDisclosureForm, self).clean()
214214
cleaned_data = self.cleaned_data
215-
if not self.data.get('draft-0-document') and not self.data.get('rfc-0-document') and not cleaned_data.get('other_designations'):
215+
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
216216
raise forms.ValidationError('You need to specify a contribution in Section IV')
217217
return cleaned_data
218218

@@ -254,12 +254,6 @@ class NotifyForm(forms.Form):
254254
type = forms.CharField(widget=forms.HiddenInput)
255255
text = forms.CharField(widget=forms.Textarea)
256256

257-
class RfcForm(DraftForm):
258-
document = AutocompletedRfcField(required=False)
259-
260-
class Meta(DraftForm.Meta):
261-
exclude = ('revisions',)
262-
263257
class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
264258
class Meta:
265259
model = ThirdPartyIprDisclosure
@@ -268,7 +262,7 @@ class Meta:
268262
def clean(self):
269263
super(ThirdPartyIprDisclosureForm, self).clean()
270264
cleaned_data = self.cleaned_data
271-
if not self.data.get('draft-0-document') and not self.data.get('rfc-0-document') and not cleaned_data.get('other_designations'):
265+
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
272266
raise forms.ValidationError('You need to specify a contribution in Section III')
273267
return cleaned_data
274268

ietf/ipr/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,11 @@ class Meta:
182182

183183
class IprDisclosureBase(models.Model):
184184
by = models.ForeignKey(Person) # who was logged in, or System if nobody was logged in
185-
compliant = models.BooleanField(default=True) # complies to RFC3979
185+
compliant = models.BooleanField("Complies to RFC3979", default=True)
186186
docs = models.ManyToManyField(DocAlias, through='IprDocRel')
187187
holder_legal_name = models.CharField(max_length=255)
188-
notes = models.TextField(blank=True)
189-
other_designations = models.CharField(blank=True, max_length=255)
188+
notes = models.TextField("Additional notes", blank=True)
189+
other_designations = models.CharField("Designations for other contributions", blank=True, max_length=255)
190190
rel = models.ManyToManyField('self', through='RelatedIpr', symmetrical=False)
191191
state = models.ForeignKey(IprDisclosureStateName)
192192
submitter_name = models.CharField(max_length=255)

ietf/ipr/tests.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -283,13 +283,11 @@ def test_new_specific(self):
283283
"holder_contact_info": "555-555-0100",
284284
"ietfer_name": "Test Participant",
285285
"ietfer_contact_info": "555-555-0101",
286-
"rfc-TOTAL_FORMS": 1,
287-
"rfc-INITIAL_FORMS": 0,
288-
"rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
289-
"draft-TOTAL_FORMS": 1,
290-
"draft-INITIAL_FORMS": 0,
291-
"draft-0-document": "%s" % draft.docalias_set.first().pk,
292-
"draft-0-revisions": '00',
286+
"iprdocrel_set-TOTAL_FORMS": 2,
287+
"iprdocrel_set-INITIAL_FORMS": 0,
288+
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
289+
"iprdocrel_set-0-revisions": '00',
290+
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
293291
"patent_info": "none",
294292
"has_patent_pending": False,
295293
"licensing": "royalty-free",
@@ -319,21 +317,18 @@ def test_new_thirdparty(self):
319317
"ietfer_name": "Test Participant",
320318
"ietfer_contact_email": "test@ietfer.com",
321319
"ietfer_contact_info": "555-555-0101",
322-
"rfc-TOTAL_FORMS": 1,
323-
"rfc-INITIAL_FORMS": 0,
324-
"rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
325-
"draft-TOTAL_FORMS": 1,
326-
"draft-INITIAL_FORMS": 0,
327-
"draft-0-document": "%s" % draft.docalias_set.first().pk,
328-
"draft-0-revisions": '00',
320+
"iprdocrel_set-TOTAL_FORMS": 2,
321+
"iprdocrel_set-INITIAL_FORMS": 0,
322+
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
323+
"iprdocrel_set-0-revisions": '00',
324+
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
329325
"patent_info": "none",
330326
"has_patent_pending": False,
331327
"licensing": "royalty-free",
332328
"submitter_name": "Test Holder",
333329
"submitter_email": "test@holder.com",
334330
})
335331
self.assertEqual(r.status_code, 200)
336-
# print r.content
337332
self.assertTrue("Your IPR disclosure has been submitted" in r.content)
338333

339334
iprs = IprDisclosureBase.objects.filter(title__icontains="belonging to Test Legal")
@@ -357,13 +352,11 @@ def test_update(self):
357352
"holder_contact_info": "555-555-0100",
358353
"ietfer_name": "Test Participant",
359354
"ietfer_contact_info": "555-555-0101",
360-
"rfc-TOTAL_FORMS": 1,
361-
"rfc-INITIAL_FORMS": 0,
362-
"rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
363-
"draft-TOTAL_FORMS": 1,
364-
"draft-INITIAL_FORMS": 0,
365-
"draft-0-document": "%s" % draft.docalias_set.first().pk,
366-
"draft-0-revisions": '00',
355+
"iprdocrel_set-TOTAL_FORMS": 2,
356+
"iprdocrel_set-INITIAL_FORMS": 0,
357+
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
358+
"iprdocrel_set-0-revisions": '00',
359+
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
367360
"patent_info": "none",
368361
"has_patent_pending": False,
369362
"licensing": "royalty-free",

0 commit comments

Comments
 (0)