Merged in ^/personal/henrik/6.68.4-ipr@14609 from henrik@levkowetz.com:

Changes to the IPR disclosure pages, requested during WG Chairs Lunch where the changes to IPR handling introduced by RFC8179 were presented.
 - Legacy-Id: 14610
This commit is contained in:
Henrik Levkowetz 2018-02-01 15:41:34 +00:00
commit a4cc4bba2e
11 changed files with 468 additions and 123 deletions

View file

@ -1,3 +1,30 @@
ietfdb (6.72.0) ietf; urgency=medium
**Updated IPR disclosure pages**
This release introduces changes to the IPR disclosure pages, requested
during WG Chairs Lunch where the changes to IPR handling introduced by
RFC8179 were presented. From the commit log:
* Changed the IPR disclosure page for IPR disclosure updates to show both
the previous and current disclosure details side-by-side. Fixes issue
#2414.
* Rewrote text_to_dict() and dict_to_text() to support unicode without
RFC2822 encoding issues. Added initial values in IPR update forms, from
the original disclosure, in order to make updates easier. Addresses issue
#2413.
* Added a new section for IPR disclosures on related documents to the IPR
document search result page. Fixes issue #2412.
* Changed the patent information text fields to individual fields for
patent number, inventor, title, date and notes, with validation. Fixes
issue #2411.
-- Henrik Levkowetz <henrik@levkowetz.com> 01 Feb 2018 07:10:21 -0800
ietfdb (6.71.1) ietf; urgency=low
This is a small bugfix release related to the new non-wg mailing list index

View file

@ -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
@ -13,6 +17,7 @@ from ietf.ipr.models import (IprDocRel, IprDisclosureBase, HolderIprDisclosure,
IprLicenseTypeName, IprDisclosureStateName)
from ietf.message.models import Message
from ietf.utils.fields import DatepickerDateField
from ietf.utils.text import dict_to_text
# ----------------------------------------------------------------
# Globals
@ -101,6 +106,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 +144,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 <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,7 +169,21 @@ 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_fields += ['patent_notes']
patent_info = dict([ (k.replace('patent_','').capitalize(), cleaned_data.get(k)) for k in patent_fields if cleaned_data.get(k) ] )
cleaned_data['patent_info'] = dict_to_text(patent_info).strip()
cleaned_data['patent_fields'] = patent_fields
return cleaned_data
def save(self, *args, **kwargs):
@ -140,6 +191,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',]:
del nargs[k]
if self.cleaned_data.get('patent_info'):
obj = NonDocSpecificIprDisclosure(**nargs)
else:
@ -160,6 +214,12 @@ class IprDisclosureFormBase(forms.ModelForm):
"""Base form for Holder and ThirdParty disclosures"""
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."))
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 +227,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 +236,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 +251,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 +262,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_info = dict([ (k.replace('patent_','').capitalize(), cleaned_data.get(k)) for k in patent_fields if cleaned_data.get(k) ] )
cleaned_data['patent_info'] = dict_to_text(patent_info).strip()
cleaned_data['patent_fields'] = patent_fields
return cleaned_data
class HolderIprDisclosureForm(IprDisclosureFormBase):
@ -220,8 +290,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 +339,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

View file

@ -39,7 +39,7 @@ class IprTests(TestCase):
title="Statement regarding rights Update",
holder_legal_name="Native Martians United",
state_id='pending',
patent_info='US12345',
patent_info='Number: US12345\nTitle: A method of transfering bits\nInventor: A. Nonymous\nDate: 2000-01-01',
holder_contact_name='Update Holder',
holder_contact_email='update_holder@acme.com',
licensing_id='royalty-free',
@ -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'])
@ -374,8 +383,13 @@ class IprTests(TestCase):
def test_update(self):
draft = make_test_data()
original_ipr = IprDisclosureBase.objects.get(title='Statement regarding rights')
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
# get
url = urlreverse("ietf.ipr.views.update", kwargs={ "id": original_ipr.id })
r = self.client.get(url)
self.assertContains(r, "Statement regarding rights")
#url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
# successful post
empty_outbox()
r = self.client.post(url, {
@ -391,7 +405,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 +432,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 +442,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",

View file

@ -29,11 +29,11 @@ def iprs_from_docs(aliases,**kwargs):
iprdocrels += alias.document.ipr(**kwargs)
return list(set([i.disclosure for i in iprdocrels]))
def related_docs(alias):
def related_docs(alias, relationship=['replaces', 'obs']):
"""Returns list of related documents"""
results = list(alias.document.docalias_set.all())
rels = alias.document.all_relations_that_doc(['replaces','obs'])
rels = alias.document.all_relations_that_doc(relationship)
for rel in rels:
rel_aliases = list(rel.target.document.docalias_set.all())
@ -42,4 +42,6 @@ def related_docs(alias):
x.related = rel
x.relation = rel.relationship.revname
results += rel_aliases
return list(set(results))
return list(set(results))

View file

@ -7,7 +7,7 @@ from django.conf import settings
from django.contrib import messages
from django.urls import reverse as urlreverse
from django.db.models import Q
from django.forms.models import inlineformset_factory
from django.forms.models import inlineformset_factory, model_to_dict
from django.forms.formsets import formset_factory
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404, redirect
@ -30,6 +30,7 @@ from ietf.ipr.models import (IprDisclosureStateName, IprDisclosureBase,
RelatedIpr,IprEvent)
from ietf.ipr.utils import (get_genitive, get_ipr_summary,
iprs_from_docs, related_docs)
from ietf.mailtrigger.utils import gather_address_lists
from ietf.message.models import Message
from ietf.message.utils import infer_message
from ietf.name.models import IprLicenseTypeName
@ -37,7 +38,7 @@ from ietf.person.models import Person
from ietf.secr.utils.document import get_rfc_num, is_draft
from ietf.utils.draft_search import normalize_draftname
from ietf.utils.mail import send_mail, send_mail_message
from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.text import text_to_dict
# ----------------------------------------------------------------
# Globals
@ -531,7 +532,16 @@ def new(request, type, updates=None):
else:
if updates:
form = ipr_form_mapping[type](initial={'updates':str(updates)})
original = IprDisclosureBase(id=updates).get_child()
initial = model_to_dict(original)
initial.update({'updates':str(updates), })
patent_info = text_to_dict(initial['patent_info'])
if patent_info.keys():
patent_dict = dict([ ('patent_'+k.lower(), v) for k,v in patent_info.items() ])
else:
patent_dict = {'patent_notes': initial['patent_info']}
initial.update(patent_dict)
form = ipr_form_mapping[type](initial=initial)
else:
form = ipr_form_mapping[type]()
disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
@ -619,7 +629,8 @@ def search(request):
docid = request.GET.get("id") or request.GET.get("id_document_tag") or ""
docs = doc = None
iprs = []
related_iprs = []
# set states
states = request.GET.getlist('state',('posted','removed'))
if states == ['all']:
@ -647,10 +658,12 @@ def search(request):
# one match
if len(start) == 1:
first = start[0]
doc = str(first)
doc = first.document
docs = related_docs(first)
iprs = iprs_from_docs(docs,states=states)
template = "ipr/search_doc_result.html"
updated_docs = related_docs(first, ['updates',])
related_iprs = list(set(iprs_from_docs(updated_docs, states=states)) - set(iprs))
# multiple matches, select just one
elif start:
docs = start
@ -723,11 +736,12 @@ def search(request):
return render(request, template, {
"q": q,
"iprs": iprs,
"docs": docs,
"doc": doc,
"form":form,
"states":states
"iprs": iprs,
"docs": docs,
"doc": doc,
"form": form,
"states": states,
"related_iprs": related_iprs,
})
return HttpResponseRedirect(request.path)
@ -755,13 +769,18 @@ def show(request, id):
elif ipr.state.slug != 'posted':
raise Http404
updates_iprs = ipr.relatedipr_source_set.all().order_by('source__time')
prev_rel = updates_iprs.last()
prev = prev_rel.target.get_child() if prev_rel else None
return render(request, "ipr/details_view.html", {
'ipr': ipr,
'prev': prev,
'in_force_ipr_rfc': ipr_rfc_number(ipr.time, ipr.is_thirdparty),
'tabs': get_details_tabs(ipr, 'Disclosure'),
'choices_abc': [ i.desc for i in IprLicenseTypeName.objects.filter(slug__in=['no-license', 'royalty-free', 'reasonable', ]) ],
'updates_iprs': ipr.relatedipr_source_set.all(),
'updated_by_iprs': ipr.relatedipr_target_set.filter(source__state="posted")
'updates_iprs': updates_iprs,
'updated_by_iprs': ipr.relatedipr_target_set.filter(source__state="posted").order_by('target__time')
})
def showlist(request):

View file

@ -753,6 +753,23 @@ blockquote {
}
.table-condensed th.ipr-label {
overflow: hidden;
clear: left;
text-align: right;
text-overflow: ellipsis;
padding-right: 1em;
vertical-align: top;
}
.table-condensed .ipr-prev {
color: #777;
background-color: #f0f0f0;
vertical-align: top;
}
.table-condensed .ipr-this {
vertical-align: top;
}
.rfcmarkup div {
margin-top: 1em;
}

View file

@ -162,7 +162,21 @@
<h2>{% cycle section %}. Disclosure of Patent Information{% if form.instance|to_class_name == "ThirdPartyIprDicslosure" %}, if known{% endif %}
<small>i.e., patents or patent applications required to be disclosed by Section 5 of RFC8179</small></h2>
{% if form.patent_info %}
{% if form.patent_number %}
<p>
A. For granted patents or published pending patent applications,
please provide the following information:
</p>
{% bootstrap_field form.patent_number layout='horizontal' %}
{% bootstrap_field form.patent_inventor layout='horizontal' %}
{% bootstrap_field form.patent_title layout='horizontal' %}
{% bootstrap_field form.patent_date layout='horizontal' %}
{% bootstrap_field form.patent_notes layout='horizontal' %}
<p>B. Does your disclosure relate to an unpublished pending patent application?</p>
{% bootstrap_field form.has_patent_pending layout='horizontal' %}
{% elif form.patent_info %}
<p>
A. For granted patents or published pending patent applications,
please provide the following information:

View file

@ -73,20 +73,27 @@
of the original IPR disclosure.</p>
{% endif %}
{% if updated_by_iprs %}
<h2>Updated by</h2>
{% for item in updated_by_iprs %}
<p>
This IPR disclosure has been updated by IPR disclosure ID #{{ item.source.id }},
"<a href="{% url "ietf.ipr.views.show" id=item.source.id %}">{{ item.source.title }}</a>".
</p>
{% endfor %}
{% endif %}
{% if updates_iprs %}
{% if updates_iprs or updated_by_iprs%}
<h2>Updates</h2>
{% if updated_by_iprs %}
<dl class="dl-horizontal">
<dt>Updated by</dt>
<dd>
{% for item in updated_by_iprs %}
<div>
IPR Disclosure ID #{{ item.source.id }},
{% if item.source.state.slug == "removed" %}
"{{ item.source.title }}" (which was removed at the request of the submitter)
{% else %}
"<a href="{% url "ietf.ipr.views.show" id=item.source.id %}">{{ item.source.title }}</a>"
{% endif %}
</div>
{% endfor %}
</dl>
{% endif %}
{% if updates_iprs %}
<dl class="dl-horizontal">
<dt>Updates</dt>
<dd>
@ -101,6 +108,7 @@
</div>
{% endfor %}
</dl>
{% endif %}
{% endif %}
{% if user|has_role:"Secretariat" and ipr.update_notified_date %}
@ -113,65 +121,128 @@
{% if ipr|to_class_name == "ThirdPartyIprDisclosure" %}Possible{% endif %}
Patent Holder/Applicant ("Patent Holder")</h2>
<dl class="dl-horizontal">
<dt>Holder legal name</dt>
<dd>{{ ipr.holder_legal_name }}</dd>
</dl>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.holder_legal_name }}</td>{% endif %}
<th class="col-md-2 ipr-label">Holder legal name</th>
<td class="col-md-5 ipr-this">{{ ipr.holder_legal_name }}</td>
</tr>
</table>
{% if ipr.holder_contact_name or ipr.holder_contact_info %}
<h2>{% cycle section %}. Patent Holder's Contact for License Application</h2>
<h2>{% cycle section %}. Patent Holder's Contact for Licence Application</h2>
<dl class="dl-horizontal">
<dt>Holder contact name</dt>
<dd>{{ ipr.holder_contact_name }}</dd>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<dt>Holder contact email</dt>
<dd>{{ ipr.holder_contact_email }}</dd>
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.holder_contact_name }}</td>{% endif %}
<th class="col-md-2 ipr-label">Holder contact name</th>
<td class="col-md-5 ipr-this">{{ ipr.holder_contact_name }}</td>
</tr>
<dt>Holder contact info</dt>
<dd>{{ ipr.holder_contact_info|linebreaks }}</dd>
</dl>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.holder_contact_email }}</td>{% endif %}
<th class="col-md-2 ipr-label">Holder contact email</th>
<td class="col-md-5 ipr-this">{{ ipr.holder_contact_email }}</td>
</tr>
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.holder_contact_info|linebreaks }}</td>{% endif %}
<th class="col-md-2 ipr-label">Holder contact info</th>
<td class="col-md-5 ipr-this">{{ ipr.holder_contact_info|linebreaks }}</td>
</tr>
</table>
{% endif %}
{% if ipr.ietfer_name or ipr.ietfer_contact_email or ipr.ietfer_contact_info %}
<h2>{% cycle section %}. Contact Information for the IETF Participant Whose Personal Belief Triggered this Disclosure</h2>
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ ipr.ietfer_name }}</dd>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.ietfer_name }}</td>{% endif %}
<th class="col-md-2 ipr-label">Name</th>
<td class="col-md-5 ipr-this">{{ ipr.ietfer_name }}</td>
</tr>
<dt>Email</dt>
<dd>{{ ipr.ietfer_contact_email }}</dd>
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.ietfer_contact_email }}</td>{% endif %}
<th class="col-md-2 ipr-label">Email</th>
<td class="col-md-5 ipr-this">{{ ipr.ietfer_contact_email }}</td>
</tr>
<dt>Other info</dt>
<dd>{{ ipr.ietfer_contact_info|linebreaks }}</dd>
</dl>
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.ietfer_contact_info|linebreaks }}</td>{% endif %}
<th class="col-md-2 ipr-label">Other info</th>
<td class="col-md-5 ipr-this">{{ ipr.ietfer_contact_info|linebreaks }}</td>
</tr>
</table>
{% endif %}
{% if ipr.iprdocrel_set.all or ipr.other_designations %}
<h2>{% cycle section %}. IETF Document or Other Contribution to Which this IPR Disclosure Relates</h2>
<dl class="dl-horizontal">
{% for iprdocrel in ipr.iprdocrel_set.all %}
<dt>{{ iprdocrel.doc_type }}</dt>
<dd>
<div>{{ iprdocrel.formatted_name }} ("{{ iprdocrel.document.document.title }}")</div>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if iprdocrel.revisions %}
<div>Revisions: {{ iprdocrel.revisions }}</div>
{% endif %}
{% if prev %}
<td class="col-md-5 ipr-prev">
{% for iprdocrel in prev.iprdocrel_set.all %}
<b>{{ iprdocrel.doc_type }}:</b>
{{ iprdocrel.formatted_name }} ("{{ iprdocrel.document.document.title }}")
{% if iprdocrel.sections %}
<div>Sections: {{ iprdocrel.sections }}</div>
{% endif %}
</dd>
{% endfor %}
{% if iprdocrel.revisions %}
<div>Revisions: {{ iprdocrel.revisions }}</div>
{% endif %}
{% if ipr.other_designations %}
<dt>Designations for Other Contributions</dt>
<dd>{{ ipr.other_designations }}</dd>
{% endif %}
</dl>
{% if iprdocrel.sections %}
<div>Sections: {{ iprdocrel.sections }}</div>
{% endif %}
{% endfor %}
{% if prev.other_designations %}
<b>Designations for Other Contributions</b>
{{ prev.other_designations }}
{% endif %}
</td>
{% endif %}
<th class="col-md-2 ipr-label"></th>
<td class="col-md-5 ipr-this">
{% for iprdocrel in ipr.iprdocrel_set.all %}
<b>{{ iprdocrel.doc_type }}:</b>
{{ iprdocrel.formatted_name }} ("{{ iprdocrel.document.document.title }}")
{% if iprdocrel.revisions %}
<div>Revisions: {{ iprdocrel.revisions }}</div>
{% endif %}
{% if iprdocrel.sections %}
<div>Sections: {{ iprdocrel.sections }}</div>
{% endif %}
{% endfor %}
{% if ipr.other_designations %}
<dt>Designations for Other Contributions</dt>
<dd>{{ ipr.other_designations }}</dd>
{% endif %}
</td>
<tr>
</table>
{% endif %}
{% if ipr.patent_info or ipr.has_patent_pending %}
@ -179,17 +250,29 @@
<p>A. For granted patents or published pending patent applications, please provide the following information:</p>
<dl class="dl-horizontal">
<dt>Patent, Serial, Publication, Registration, or Application/File number(s)</dt>
<dd>{{ ipr.patent_info|linebreaks }}</dd>
</dl>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.patent_info|linebreaks }}</td>{% endif %}
<th class="col-md-2 ipr-label">Patent, Serial, Publication, Registration, or Application/File number(s)</th>
<td class="col-md-5 ipr-this">{{ ipr.patent_info|linebreaks }}</td>
</tr>
</table>
<p>B. Does this disclosure relate to an unpublished pending patent application?:</p>
<dl class="dl-horizontal">
<dt>Has patent pending</dt>
<dd>{{ ipr.has_patent_pending|yesno:"Yes,No" }}</dd>
</dl>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.has_patent_pending|yesno:"Yes,No" }}</td>{% endif %}
<th class="col-md-2 ipr-label">Has patent pending</th>
<td class="col-md-5 ipr-this">{{ ipr.has_patent_pending|yesno:"Yes,No" }}</td>
</tr>
</table>
{% endif %}
{% if ipr.licensing %}
@ -204,23 +287,32 @@
specification, is as follows(select one licensing declaration option only):
</p>
{% if ipr.licensing.slug == "provided-later" %}
{% if prev.licensing.slug == "provided-later" or ipr.licensing.slug == "provided-later" %}
<div>
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:
<ul style="list-style: none">
{% for desc in choices_abc %}
<li>{{ desc}}</li>
{% endfor %}
</ul>
</p>
</div>
{% endif %}
<dl class="dl-horizontal">
<dt>Licensing</dt>
<dd>{% if ipr.licensing.slug == "provided-later" %}{{ ipr.licensing.desc|slice:"2:"|slice:":117" }}){% else %}{{ ipr.licensing.desc|slice:"2:" }}{% endif %}</dd>
<dt>Licensing information, comments, notes, or URL for further information</dt>
<dd>{{ ipr.licensing_comments|default:"(No information submitted)"|linebreaks }}</dd>
</dl>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{% if prev.licensing.slug == "provided-later" %}{{ prev.licensing.desc|slice:"2:"|slice:":117" }}){% else %}{{ prev.licensing.desc|slice:"2:" }}{% endif %}</td>{% endif %}
<th class="col-md-2 ipr-label">Licensing</th>
<td class="col-md-5 ipr-this">{% if ipr.licensing.slug == "provided-later" %}{{ ipr.licensing.desc|slice:"2:"|slice:":117" }}){% else %}{{ ipr.licensing.desc|slice:"2:" }}{% endif %}</td>
</tr>
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.licensing_comments|default:"(No information submitted)"|linebreaks }}</td>{% endif %}
<th class="col-md-2 ipr-label">Licensing information, comments, notes, or URL for further information</th>
<td class="col-md-5 ipr-this">{{ ipr.licensing_comments|default:"(No information submitted)"|linebreaks }}</td>
</tr>
</table>
<p>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 %}
<h2>{% cycle section %}. Statement</h2>
<dl class="dl-horizontal">
<dt>Statement</dt>
<dd>{{ ipr.statement|linebreaks }}</dd>
</dl>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.statement|linebreaks }}</td>{% endif %}
<th class="col-md-2 ipr-label">Statement</th>
<td class="col-md-5 ipr-this">{{ ipr.statement|linebreaks }}</td>
</tr>
</table>
{% endif %}
<h2>{% cycle section %}. Contact Information of Submitter of this Form</h2>
<dl class="dl-horizontal">
<dt>Submitter name</dt>
<dd>{{ ipr.submitter_name }}</dd>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.submitter_name }}</td>{% endif %}
<th class="col-md-2 ipr-label">Submitter name</th>
<td class="col-md-5 ipr-this">{{ ipr.submitter_name }}</td>
</tr>
<dt>Submitter email</dt>
<dd>{{ ipr.submitter_email }}</dd>
</dl>
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.submitter_email }}</td>{% endif %}
<th class="col-md-2 ipr-label">Submitter email</th>
<td class="col-md-5 ipr-this">{{ ipr.submitter_email }}</td>
</tr>
</table>
{% if ipr.notes %}
<h2>{% cycle section %}. Other Notes</h2>
<dl class="dl-horizontal">
<dt>Additional notes</dt>
<dd>{{ ipr.notes|linebreaks }}</dd>
</dl>
<table class="table-condensed col-md-12">
{% if prev %}
<tr><th class="ipr-prev">Previous (<a href="{% url "ietf.ipr.views.show" id=prev.id %}">#{{prev.id}}</a>)</th><th></th><th>This (#{{ipr.id}})</th></tr>
{% endif %}
<tr>
{% if prev %}<td class="col-md-5 ipr-prev">{{ prev.notes|linebreaks }}</td>{% endif %}
<th class="col-md-2 ipr-label">Additional notes</th>
<td class="col-md-5 ipr-this">{{ ipr.notes|linebreaks }}</td>
</tr>
</table>
{% endif %}
</div>
<p class="help-block">Only those sections of the relevant entry form where the submitter provided information are displayed.</p>
</div>
{% endblock content %}

View file

@ -8,7 +8,7 @@
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
{% endblock %}
{% block search_header %}Draft search results{% endblock %}
{% block search_header %}Document IPR search results <small>for {{doc}}</small>{% endblock %}
{% block search_result %}
<p class="alert alert-info">Total number of IPR disclosures found: <b>{{ iprs|length }}</b>.</p>
@ -35,6 +35,12 @@
{% endfor %}
</tbody>
</table>
{% if related_iprs %}
<p>
<i>IPR declarations exist for related documents, <a href="#related">see below</a>.
These cannot be assumed to apply to the current document without closer inspection.</i>
</p>
{% endif %}
{% endif %}
<p class="alert alert-info">Total number of documents searched: <b>{{ docs|length}}</b>.</p>
@ -85,8 +91,35 @@
{% endfor %}
</table>
{% if related_iprs %}
<p class="alert alert-info" id="related">Total number of possibly related IPR disclosures found: <b>{{ related_iprs|length }}</b>.</p>
<table class="table table-condensed table-striped tablesorter">
<thead>
<tr>
<th>Date</th>
<th>ID</th>
<th>Statement</th>
</tr>
</thead>
<tbody>
{% for ipr in related_iprs %}
<tr>
<td class="text-nowrap">{{ ipr.time|date:"Y-m-d" }}</td>
<td>{{ ipr.id }}</td>
<td><a href="{% url "ietf.ipr.views.show" id=ipr.id %}">{{ ipr.title }}</a>
{% if ipr.updates %} <br/>(Updates ID#: {% for upd in ipr.updates %}{{upd.target_id}}{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endblock %}
{% block js %}
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -331,7 +331,7 @@ def make_test_data():
title="Statement regarding rights",
holder_legal_name="Native Martians United",
state=IprDisclosureStateName.objects.get(slug='posted'),
patent_info='US12345',
patent_info='Number: US12345\nTitle: A method of transfering bits\nInventor: A. Nonymous\nDate: 2000-01-01',
holder_contact_name='George',
holder_contact_email='george@acme.com',
holder_contact_info='14 Main Street\nEarth',

View file

@ -140,4 +140,37 @@ def decode(raw):
text = raw.decode('latin-1')
#
return text
def text_to_dict(t):
"Converts text with RFC2822-formatted header fields into a dictionary-like object."
# ensure we're handed a unicode parameter
assert isinstance(t, six.text_type)
d = {}
# Return {} for malformed input
if not len(t.lstrip()) == len(t):
return {}
lines = t.splitlines()
items = []
# unfold folded lines
for l in lines:
if l[0].isspace():
if items:
items[-1] += l
else:
return {}
else:
items.append(l)
for i in items:
if re.match('^[A-Za-z0-9-]+: ', i):
k, v = i.split(': ', 1)
d[k] = v
else:
return {}
return d
def dict_to_text(d):
"Convert a dictionary to RFC2822-formatted text"
t = ""
for k, v in d.items():
t += "%s: %s\n" % (k, v)
return t