diff --git a/ietf/doc/fields.py b/ietf/doc/fields.py
index 275f77dc4..b8f482885 100644
--- a/ietf/doc/fields.py
+++ b/ietf/doc/fields.py
@@ -4,12 +4,16 @@ from django.utils.html import escape
from django import forms
from django.core.urlresolvers import reverse as urlreverse
-import debug # pyflakes:ignore
-
from ietf.doc.models import Document, DocAlias
+from ietf.doc.utils import uppercase_std_abbreviated_name
def select2_id_doc_name_json(objs):
- return json.dumps([{ "id": o.pk, "text": escape(o.name) } for o in objs])
+ return json.dumps([{ "id": o.pk, "text": escape(uppercase_std_abbreviated_name(o.name)) } for o in objs])
+
+# FIXME: select2 version 4 uses a standard select for the AJAX case -
+# switching to that would allow us to derive from the standard
+# multi-select machinery in Django instead of the manual CharField
+# stuff below
class SearchableDocumentsField(forms.CharField):
"""Server-based multi-select field for choosing documents using
@@ -32,7 +36,7 @@ class SearchableDocumentsField(forms.CharField):
super(SearchableDocumentsField, self).__init__(*args, **kwargs)
- self.widget.attrs["class"] = "select2-field"
+ self.widget.attrs["class"] = "select2-field form-control"
self.widget.attrs["data-placeholder"] = hint_text
if self.max_entries != None:
self.widget.attrs["data-max-entries"] = self.max_entries
@@ -43,6 +47,8 @@ class SearchableDocumentsField(forms.CharField):
def prepare_value(self, value):
if not value:
value = ""
+ if isinstance(value, (int, long)):
+ value = str(value)
if isinstance(value, basestring):
pks = self.parse_select2_value(value)
value = self.model.objects.filter(pk__in=pks)
@@ -82,7 +88,26 @@ class SearchableDocumentsField(forms.CharField):
return objs
+class SearchableDocumentField(SearchableDocumentsField):
+ """Specialized to only return one Document."""
+ def __init__(self, model=Document, *args, **kwargs):
+ kwargs["max_entries"] = 1
+ super(SearchableDocumentField, self).__init__(model=model, *args, **kwargs)
+
+ def clean(self, value):
+ return super(SearchableDocumentField, self).clean(value).first()
+
class SearchableDocAliasesField(SearchableDocumentsField):
def __init__(self, model=DocAlias, *args, **kwargs):
super(SearchableDocAliasesField, self).__init__(model=model, *args, **kwargs)
+class SearchableDocAliasField(SearchableDocumentsField):
+ """Specialized to only return one DocAlias."""
+ def __init__(self, model=DocAlias, *args, **kwargs):
+ kwargs["max_entries"] = 1
+ super(SearchableDocAliasField, self).__init__(model=model, *args, **kwargs)
+
+ def clean(self, value):
+ return super(SearchableDocAliasField, self).clean(value).first()
+
+
diff --git a/ietf/ipr/fields.py b/ietf/ipr/fields.py
index 1db327b54..2571a55f9 100644
--- a/ietf/ipr/fields.py
+++ b/ietf/ipr/fields.py
@@ -4,73 +4,58 @@ from django.utils.html import escape
from django import forms
from django.core.urlresolvers import reverse as urlreverse
-import debug # pyflakes:ignore
-
-from ietf.doc.models import DocAlias
from ietf.ipr.models import IprDisclosureBase
-def tokeninput_id_name_json(objs):
- """Returns objects as JSON string.
- NOTE: double quotes in the object name are replaced with single quotes to avoid
- problems with representation of the JSON string in the HTML widget attribute"""
- def format_ipr(x):
- text = x.title.replace('"',"'")
- return escape(u"%s <%s>" % (text, x.time.date().isoformat()))
- def format_doc(x):
- return escape(x.name)
+def select2_id_ipr_title_json(value):
+ return json.dumps([{ "id": o.pk, "text": escape(u"%s <%s>" % (o.title, o.time.date().isoformat())) } for o in value])
- formatter = format_ipr if objs and isinstance(objs[0], IprDisclosureBase) else format_doc
-
- return json.dumps([{ "id": o.pk, "name": formatter(o) } for o in objs])
-
-class AutocompletedIprDisclosuresField(forms.CharField):
- """Tokenizing autocompleted multi-select field for choosing
- IPR disclosures using jquery.tokeninput.js.
+class SearchableIprDisclosuresField(forms.CharField):
+ """Server-based multi-select field for choosing documents using
+ select2.js.
The field uses a comma-separated list of primary keys in a
- CharField element as its API, the tokeninput Javascript adds some
- selection magic on top of this so we have to pass it a JSON
- representation of ids and user-understandable labels."""
+ CharField element as its API with some extra attributes used by
+ the Javascript part."""
def __init__(self,
max_entries=None, # max number of selected objs
model=IprDisclosureBase,
- hint_text="Type in term(s) to search disclosure title",
+ hint_text="Type in terms to search disclosure title",
*args, **kwargs):
kwargs["max_length"] = 1000
self.max_entries = max_entries
self.model = model
- super(AutocompletedIprDisclosuresField, self).__init__(*args, **kwargs)
+ super(SearchableIprDisclosuresField, self).__init__(*args, **kwargs)
- self.widget.attrs["class"] = "tokenized-field"
- self.widget.attrs["data-hint-text"] = hint_text
+ self.widget.attrs["class"] = "select2-field form-control"
+ self.widget.attrs["data-placeholder"] = hint_text
if self.max_entries != None:
self.widget.attrs["data-max-entries"] = self.max_entries
- def parse_tokenized_value(self, value):
+ def parse_select2_value(self, value):
return [x.strip() for x in value.split(",") if x.strip()]
def prepare_value(self, value):
if not value:
value = ""
if isinstance(value, basestring):
- pks = self.parse_tokenized_value(value)
+ pks = self.parse_select2_value(value)
value = self.model.objects.filter(pk__in=pks)
if isinstance(value, self.model):
value = [value]
- self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
+ self.widget.attrs["data-pre"] = select2_id_ipr_title_json(value)
# doing this in the constructor is difficult because the URL
# patterns may not have been fully constructed there yet
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_search")
- return ",".join(str(e.pk) for e in value)
+ return u",".join(unicode(e.pk) for e in value)
def clean(self, value):
- value = super(AutocompletedIprDisclosuresField, self).clean(value)
- pks = self.parse_tokenized_value(value)
+ value = super(SearchableIprDisclosuresField, self).clean(value)
+ pks = self.parse_select2_value(value)
objs = self.model.objects.filter(pk__in=pks)
@@ -83,75 +68,3 @@ class AutocompletedIprDisclosuresField(forms.CharField):
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
return objs
-
-class AutocompletedDraftsField(AutocompletedIprDisclosuresField):
- """Version of AutocompletedPersonsField with the defaults right for Drafts."""
-
- def __init__(self, model=DocAlias, hint_text="Type in name to search draft name",
- *args, **kwargs):
- super(AutocompletedDraftsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
-
- def prepare_value(self, value):
- if not value:
- value = ""
- if isinstance(value, basestring):
- pks = self.parse_tokenized_value(value)
- value = self.model.objects.filter(pk__in=pks)
- if isinstance(value, self.model):
- value = [value]
- if isinstance(value, long):
- value = self.model.objects.filter(pk=value)
-
- self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
-
- # doing this in the constructor is difficult because the URL
- # patterns may not have been fully constructed there yet
- self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_draft_search")
-
- return ",".join(str(e.pk) for e in value)
-
-class AutocompletedDraftField(AutocompletedDraftsField):
- """Version of AutocompletedEmailsField specialized to a single object."""
-
- def __init__(self, *args, **kwargs):
- kwargs["max_entries"] = 1
- super(AutocompletedDraftField, self).__init__(*args, **kwargs)
-
- def clean(self, value):
- return super(AutocompletedDraftField, self).clean(value).first()
-
-class AutocompletedRfcsField(AutocompletedIprDisclosuresField):
- """Version of AutocompletedPersonsField with the defaults right for Drafts."""
-
- def __init__(self, model=DocAlias, hint_text="Type in the RFC number",
- *args, **kwargs):
- super(AutocompletedRfcsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
-
- def prepare_value(self, value):
- if not value:
- value = ""
- if isinstance(value, basestring):
- pks = self.parse_tokenized_value(value)
- value = self.model.objects.filter(pk__in=pks)
- if isinstance(value, self.model):
- value = [value]
- if isinstance(value, long):
- value = self.model.objects.filter(pk=value)
-
- self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
-
- # doing this in the constructor is difficult because the URL
- # patterns may not have been fully constructed there yet
- self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_rfc_search")
-
- return ",".join(str(e.pk) for e in value)
-
-class AutocompletedRfcField(AutocompletedRfcsField):
- """Version of AutocompletedEmailsField specialized to a single object."""
-
- def __init__(self, *args, **kwargs):
- kwargs["max_entries"] = 1
- super(AutocompletedRfcField, self).__init__(*args, **kwargs)
-
- def clean(self, value):
- return super(AutocompletedRfcField, self).clean(value).first()
\ No newline at end of file
diff --git a/ietf/ipr/forms.py b/ietf/ipr/forms.py
index 8f541c353..06032de51 100644
--- a/ietf/ipr/forms.py
+++ b/ietf/ipr/forms.py
@@ -5,9 +5,9 @@ from django.utils.safestring import mark_safe
from django import forms
from ietf.group.models import Group
+from ietf.doc.fields import SearchableDocAliasField
from ietf.ipr.mail import utc_from_string
-from ietf.ipr.fields import (AutocompletedIprDisclosuresField, AutocompletedDraftField,
- AutocompletedRfcField)
+from ietf.ipr.fields import SearchableIprDisclosuresField
from ietf.ipr.models import (IprDocRel, IprDisclosureBase, HolderIprDisclosure,
GenericIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure,
IprLicenseTypeName, IprDisclosureStateName)
@@ -90,7 +90,7 @@ class AddEmailForm(forms.Form):
return self.cleaned_data
class DraftForm(forms.ModelForm):
- document = AutocompletedDraftField(required=False)
+ document = SearchableDocAliasField(label="I-D name/RFC number", required=False, doc_type="draft")
class Meta:
model = IprDocRel
@@ -103,10 +103,10 @@ 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
otherwise create a GenericIprDisclosure object."""
- compliant = forms.CharField(label="This disclosure complies with RFC 3979", required=False)
+ compliant = forms.BooleanField(label="This disclosure complies with RFC 3979", required=False)
holder_legal_name = forms.CharField(max_length=255)
- notes = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
- other_designations = forms.CharField(max_length=255,required=False)
+ notes = forms.CharField(label="Additional notes", max_length=255,widget=forms.Textarea,required=False)
+ other_designations = forms.CharField(label="Designations for other contributions", max_length=255,required=False)
holder_contact_name = forms.CharField(label="Name", max_length=255)
holder_contact_email = forms.EmailField(label="Email")
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):
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")
has_patent_pending = forms.BooleanField(required=False)
statement = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
- updates = AutocompletedIprDisclosuresField(required=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.")
same_as_ii_above = forms.BooleanField(label="Same as in section II above", required=False)
def __init__(self,*args,**kwargs):
@@ -156,7 +156,7 @@ class GenericDisclosureForm(forms.Form):
class IprDisclosureFormBase(forms.ModelForm):
"""Base form for Holder and ThirdParty disclosures"""
- updates = AutocompletedIprDisclosuresField(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."))
+ 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)
def __init__(self,*args,**kwargs):
@@ -212,7 +212,7 @@ class HolderIprDisclosureForm(IprDisclosureFormBase):
def clean(self):
super(HolderIprDisclosureForm, self).clean()
cleaned_data = self.cleaned_data
- if not self.data.get('draft-0-document') and not self.data.get('rfc-0-document') and not cleaned_data.get('other_designations'):
+ 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
@@ -254,12 +254,6 @@ class NotifyForm(forms.Form):
type = forms.CharField(widget=forms.HiddenInput)
text = forms.CharField(widget=forms.Textarea)
-class RfcForm(DraftForm):
- document = AutocompletedRfcField(required=False)
-
- class Meta(DraftForm.Meta):
- exclude = ('revisions',)
-
class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
class Meta:
model = ThirdPartyIprDisclosure
@@ -268,7 +262,7 @@ class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
def clean(self):
super(ThirdPartyIprDisclosureForm, self).clean()
cleaned_data = self.cleaned_data
- if not self.data.get('draft-0-document') and not self.data.get('rfc-0-document') and not cleaned_data.get('other_designations'):
+ 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/models.py b/ietf/ipr/models.py
index 1af00624c..b6dfe2f6a 100644
--- a/ietf/ipr/models.py
+++ b/ietf/ipr/models.py
@@ -182,11 +182,11 @@ from ietf.message.models import Message
class IprDisclosureBase(models.Model):
by = models.ForeignKey(Person) # who was logged in, or System if nobody was logged in
- compliant = models.BooleanField(default=True) # complies to RFC3979
+ compliant = models.BooleanField("Complies to RFC3979", default=True)
docs = models.ManyToManyField(DocAlias, through='IprDocRel')
holder_legal_name = models.CharField(max_length=255)
- notes = models.TextField(blank=True)
- other_designations = models.CharField(blank=True, max_length=255)
+ notes = models.TextField("Additional notes", blank=True)
+ other_designations = models.CharField("Designations for other contributions", blank=True, max_length=255)
rel = models.ManyToManyField('self', through='RelatedIpr', symmetrical=False)
state = models.ForeignKey(IprDisclosureStateName)
submitter_name = models.CharField(max_length=255)
diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py
index 3bf8fdc24..0c31f3c82 100644
--- a/ietf/ipr/tests.py
+++ b/ietf/ipr/tests.py
@@ -283,13 +283,11 @@ class IprTests(TestCase):
"holder_contact_info": "555-555-0100",
"ietfer_name": "Test Participant",
"ietfer_contact_info": "555-555-0101",
- "rfc-TOTAL_FORMS": 1,
- "rfc-INITIAL_FORMS": 0,
- "rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
- "draft-TOTAL_FORMS": 1,
- "draft-INITIAL_FORMS": 0,
- "draft-0-document": "%s" % draft.docalias_set.first().pk,
- "draft-0-revisions": '00',
+ "iprdocrel_set-TOTAL_FORMS": 2,
+ "iprdocrel_set-INITIAL_FORMS": 0,
+ "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",
"has_patent_pending": False,
"licensing": "royalty-free",
@@ -319,13 +317,11 @@ class IprTests(TestCase):
"ietfer_name": "Test Participant",
"ietfer_contact_email": "test@ietfer.com",
"ietfer_contact_info": "555-555-0101",
- "rfc-TOTAL_FORMS": 1,
- "rfc-INITIAL_FORMS": 0,
- "rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
- "draft-TOTAL_FORMS": 1,
- "draft-INITIAL_FORMS": 0,
- "draft-0-document": "%s" % draft.docalias_set.first().pk,
- "draft-0-revisions": '00',
+ "iprdocrel_set-TOTAL_FORMS": 2,
+ "iprdocrel_set-INITIAL_FORMS": 0,
+ "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",
"has_patent_pending": False,
"licensing": "royalty-free",
@@ -333,7 +329,6 @@ class IprTests(TestCase):
"submitter_email": "test@holder.com",
})
self.assertEqual(r.status_code, 200)
- # print r.content
self.assertTrue("Your IPR disclosure has been submitted" in r.content)
iprs = IprDisclosureBase.objects.filter(title__icontains="belonging to Test Legal")
@@ -357,13 +352,11 @@ class IprTests(TestCase):
"holder_contact_info": "555-555-0100",
"ietfer_name": "Test Participant",
"ietfer_contact_info": "555-555-0101",
- "rfc-TOTAL_FORMS": 1,
- "rfc-INITIAL_FORMS": 0,
- "rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
- "draft-TOTAL_FORMS": 1,
- "draft-INITIAL_FORMS": 0,
- "draft-0-document": "%s" % draft.docalias_set.first().pk,
- "draft-0-revisions": '00',
+ "iprdocrel_set-TOTAL_FORMS": 2,
+ "iprdocrel_set-INITIAL_FORMS": 0,
+ "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",
"has_patent_pending": False,
"licensing": "royalty-free",
diff --git a/ietf/ipr/urls.py b/ietf/ipr/urls.py
index 618e5d744..23f5b2aae 100644
--- a/ietf/ipr/urls.py
+++ b/ietf/ipr/urls.py
@@ -10,8 +10,6 @@ urlpatterns = patterns('ietf.ipr.views',
url(r'^admin/$', RedirectView.as_view(url=reverse_lazy('ipr_admin',kwargs={'state':'pending'})),name="ipr_admin_main"),
url(r'^admin/(?P