from htsworkflow.frontend.experiments import models
from htsworkflow.frontend.experiments import experiments
from htsworkflow.frontend.auth import apidata
+from htsworkflow.util.ethelp import validate_xhtml
from htsworkflow.pipelines.test.simulate_runfolder import TESTDATA_DIR
u'11061',u'11062',u'11063',u'11064']
self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
response = self.client.get('/admin/experiments/flowcell/153/')
+
tree = fromstring(response.content)
for i in range(0,8):
xpath_expression = '//input[@id="id_lane_set-%d-library"]'
"""
self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
response = self.client.get('/library/11070/')
+ self.assertEqual(response.status_code, 200)
+ status = validate_xhtml(response.content)
+ if status is not None: self.assertTrue(status)
+
tree = fromstring(response.content)
flowcell_spans = tree.xpath('//span[@property="libns:flowcell_id"]',
namespaces=NSMAP)
self.assertEqual(failed_fc_a.get('href'), '/flowcell/30012AAXX/')
fc_response = self.client.get(failed_fc_a.get('href'))
self.assertEqual(fc_response.status_code, 200)
+ status = validate_xhtml(response.content)
+ if status is not None: self.assertTrue(status)
+
fc_lane_response = self.client.get('/flowcell/30012AAXX/8/')
self.assertEqual(fc_lane_response.status_code, 200)
+ status = validate_xhtml(response.content)
+ if status is not None: self.assertTrue(status)
+
def test_pooled_multiplex_id(self):
fc_dict = experiments.flowcell_information('42JU1AAXX')
model = get_model()
- expected = {1: ['11034'],
- 2: ['11036'],
- 3: ['12044','11045'],
- 4: ['11047','13044'],
- 5: ['11055'],
- 6: ['11067'],
- 7: ['11069'],
- 8: ['11070']}
+ expected = {'1': ['11034'],
+ '2': ['11036'],
+ '3': ['12044','11045'],
+ '4': ['11047','13044'],
+ '5': ['11055'],
+ '6': ['11067'],
+ '7': ['11069'],
+ '8': ['11070']}
url = '/flowcell/42JU1AAXX/'
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
+ status = validate_xhtml(response.content)
+ if status is not None: self.assertTrue(status)
+
ns = urljoin('http://localhost', url)
load_string_into_model(model, 'rdfa', response.content, ns=ns)
body = """prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
'./span[@property="libns:sequencer_model"]')
self.assertEqual(len(model), 1)
self.assertEqual(model[0].text, 'Illumina Genome Analyzer IIx')
+
+ def test_flowcell_with_rdf_validation(self):
+ from htsworkflow.util.rdfhelp import add_default_schemas, \
+ dump_model, \
+ get_model, \
+ load_string_into_model
+ from htsworkflow.util.rdfinfer import Infer
+
+ model = get_model()
+ add_default_schemas(model)
+ inference = Infer(model)
+
+ url ='/flowcell/FC12150/'
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ status = validate_xhtml(response.content)
+ if status is not None: self.assertTrue(status)
+
+ load_string_into_model(model, 'rdfa', response.content)
+
+ errmsgs = list(inference.run_validation())
+ self.assertEqual(len(errmsgs), 2)
+ for errmsg in errmsgs:
+ self.assertEqual(errmsg, 'Missing type for: http://localhost/')
+
+ def test_lane_with_rdf_validation(self):
+ from htsworkflow.util.rdfhelp import add_default_schemas, \
+ dump_model, \
+ get_model, \
+ load_string_into_model
+ from htsworkflow.util.rdfinfer import Infer
+
+ model = get_model()
+ add_default_schemas(model)
+ inference = Infer(model)
+
+ url = '/lane/1193'
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ status = validate_xhtml(response.content)
+ if status is not None: self.assertTrue(status)
+
+ load_string_into_model(model, 'rdfa', response.content)
+
+ errmsgs = list(inference.run_validation())
+ self.assertEqual(len(errmsgs), 2)
+ for errmsg in errmsgs:
+ self.assertEqual(errmsg, 'Missing type for: http://localhost/')
from htsworkflow.frontend.auth import apidata
from htsworkflow.util.conversion import unicode_or_none
-
+from htsworkflow.util.ethelp import validate_xhtml
class LibraryTestCase(TestCase):
fixtures = ['test_samples.json']
response = self.client.get('/library/10981/')
self.assertEqual(response.status_code, 200)
- load_string_into_model(model, 'rdfa', response.content)
+ content = response.content
+ load_string_into_model(model, 'rdfa', content)
body = """prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
prefix libns: <http://jumpgate.caltech.edu/wiki/LibraryOntology#>
self.assertEqual(fromTypedNode(r['gel_cut']), 400)
self.assertEqual(fromTypedNode(r['made_by']), u'Igor')
+ state = validate_xhtml(content)
+ if state is not None: self.assertTrue(state)
+
def test_library_index_rdfa(self):
from htsworkflow.util.rdfhelp import \
add_default_schemas, get_model, load_string_into_model
self.assertEqual(count, len(Library.objects.filter(hidden=False)))
+ state = validate_xhtml(response.content)
+ if state is not None: self.assertTrue(state)
# The django test runner flushes the database between test suites not cases,
# so to be more compatible with running via nose we flush the database tables
# of interest before creating our sample data.
"http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
version="XHTML+RDFa 1.0"
- xmlns:xml="http://www.w3.org/1999/xhtml"
+ xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
{{ content }}
{% endblock %}
{% block sidebar %}{% endblock %}
- <br class="clear" />
</div>
<!-- END Content -->
{% endblock %}
{% block content %}
-<div id="flowcell_detail">
+<div>
{% include "experiments/flowcell_header.html" %}
- <div class="htswdetail">
+ <div class="htswdetail" typeof="libns:IlluminaFlowcell" resource="{{flowcell.get_absolute_url}}">
<h2>Lanes</h2>
<table>
<thead>
</thead>
<tbody>
{% for lane in lanes %}
- <tr rel="libns:has_lane">
- <div typeof="libns:IlluminaLane" about="{{lane.get_absolute_url}}">
- <td>
- <a href="{{lane.get_absolute_url}}">
- <span property="libns:lane_number" datatype="xsd:decimal"
- >{{lane.lane_number}}</span>
+ <tr>
+ <td rel="libns:has_lane">
+ <a href="{{lane.get_absolute_url}}" typeof="libns:IlluminaLane">
+ <span property="libns:lane_number">{{lane.lane_number}}</span>
</a>
</td>
- <div rel="libns:library">
- <div typeof="libns:Library" about="{{lane.library.get_absolute_url}}">
- <td>
- <a href="{{lane.library.get_absolute_url}}">
+ <td rel="libns:library"
+ about="{{lane.get_absolute_url}}"
+ resource="{{lane.library.get_absolute_url}}">
+ <a typeof="libns:Library" href="{{lane.library.get_absolute_url}}">
<span property="libns:library_id"
>{{lane.library.id}}</span></a>
{% if user.is_staff %}
src="/media/img/admin/icon_changelink.gif"/>
</a>{% endif %}
</td>
- <td><a href="{{lane.library.get_absolute_url}}">
- <span property="libns:name">{{lane.library.library_name}}</span>
- </a></td>
- <td rel="libns:species">
+ <td>
+ <a href="{{lane.library.get_absolute_url}}">
+ <span property="libns:name"
+ about="{{lane.library.get_absolute_url}}"
+ >{{lane.library.library_name}}</span>
+ </a>
+ </td>
+ <td about="{{lane.library.get_absolute_url}}" rel="libns:species">
<a href="{{lane.library.library_species.get_absolute_url}}"
typeof="libns:Species">
- <span property="libns:species_name">{{ lane.library.library_species.scientific_name }}</span></a></td>
- </div> <!-- end library class -->
- </div> <!-- end library relation -->
- <td><span property="libns:comment">{{lane.comment}}</span></td>
- </div> <!-- end lane -->
+ <span property="libns:species_name">{{ lane.library.library_species.scientific_name }}</span></a>
+ </td>
+ <td about="{{lane.get_absolute_url}}">
+ <span property="libns:comment">{{lane.comment}}</span>
+ </td>
</tr>
{% endfor %}
</tbody>
</table>
- </div>
<div class="htsw_flowcell_ivc">
{% for run in flowcell.datarun_set.all %}
<h2>Run {{ run.runfolder_name }}</h2>
+ {% if run.lane_files %}
<table>
<thead>
<tr>
<td>IVC Percent Base</td>
<td>IVC Percent Base All</td>
<td>IVC Percent Base Called</td>
+ </tr>
</thead>
<tbody>
{% for lane_id, lane_file_set in run.lane_files.items %}
- {% if lane_file_set.ivc_all %}
+ {% if lane_file_set.ivc_all %}
<tr>
<td>{{ lane_id }}</td>
<td>
<a href="{{ lane_file_set.ivc_all.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC All"
+ src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
</td>
<td>
<a href="{{ lane_file_set.ivc_call.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC Call"
+ src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
</a>
</td>
<td>
<a href="{{ lane_file_set.ivc_percent_base.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base"
+ src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
</a>
</td>
<td>
<a href="{{ lane_file_set.ivc_percent_all.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base All"
+ src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
</a>
</td>
<td>
<a href="{{ lane_file_set.ivc_percent_call.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
+ <img height="84" width="126"
+ alt="Lane {{lane_id }} IVC % Base Called"
+ src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
+ {% endif %}
{% endfor %}
</div>
+ </div>
</div>
+<!-- end flowcell_detail -->
{% endblock %}
- <div class="flowcell_identity">
+<div class="flowcell_identity" typeof="libns:IlluminaFlowcell" resource="{{flowcell.get_absolute_url}}">
<h2>About this Flowcell</h2>
<b>Flowcell</b>:
- <a href="{{flowcell.get_absolute_url}}" property="libns:flowcell_id">{{flowcell.flowcell_id}}</a>{% if user.is_staff %}<a href="{{flowcell.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif" alt="Edit"/></a>{% endif%}
- <br rel="rdf:type" resource="http://jumpgate.caltech.edu/wiki/LibraryOntology#IlluminaFlowcell"/>
+ <a href="{{flowcell.get_absolute_url}}"><span property="libns:flowcell_id">{{flowcell.flowcell_id}}</span></a>{% if user.is_staff %}<a href="{{flowcell.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif" alt="Edit"/></a>{% endif%}
+ <br/>
<div rel="libns:sequenced_by">
<div typeof="libns:Sequencer"
about="{{flowcell.sequencer.get_absolute_url}}">
<b>Type</b>:
<span property="libns:flowcell_type">{{flowcell.flowcell_type}}</span><br/>
<b>Read Length</b>:
- <span property="libns:read_length" datatype="xsd:decimal">{{flowcell.read_length}}</span><br/>
+ <span property="libns:read_length" datatype="xsd:integer">{{flowcell.read_length}}</span><br/>
<b>Control Lane</b>:
- <span property="libns:control_lane" datatype="xsd:decimal">{{flowcell.control_lane}}</span><br/>
+ <span property="libns:control_lane">{{flowcell.control_lane}}</span><br/>
<b>Notes</b>:
<pre property="libns:flowcell_notes">{{flowcell.notes}}</pre>
- </div>
+ </div>
{% endblock %}
{% block content %}
-<div id="lane_detail" class="htswdetail" rel="rdf:type" resource="libns:IlluminaLane">
+<div id="lane_detail" class="htswdetail" typeof="libns:IlluminaLane" resource="{{lane.get_absolute_url}}">
<div rel="libns:flowcell" resource="{{flowcell.get_absolute_url}}">
{% include "experiments/flowcell_header.html" %}
</div>
<div class="flowcell_lane_detail">
<h2>About this lane</h2>
<b>Lane</b>:
- <span property="libns:lane_number" datatype="xsd:decimal">{{lane.lane_number}}</span><br/>
+ <span property="libns:lane_number">{{lane.lane_number}}</span><br/>
<b>pM</b>:
<span property="libns:pM" datatype="xsd:decimal">{{ lane.pM }}</span><br/>
+ {% if lane.cluster_estimate %}
<b>Cluster Estimate</b>:
<span property="libns:cluster_estimate" datatype="xsd:decimal"
- content="{{lane.cluster_estimate}}">{{ lane.cluster_estimate|intcomma }}</span><br/>
+ content="{{lane.cluster_estimate}}">{{ lane.cluster_estimate|intcomma }}</span><br/>{% endif %}
+ {% if lane.status %}
<b>Lane Status</b>:
- <span property="libns:status">{{ lane.status }}</span><br/>
+ <span property="libns:status">{{ lane.status }}</span><br/>{% endif %}
+ {% if lane.comment %}
<b>Comments</b>:
- <span property="libns:comment">{{ lane.comment }}</span><br/>
+ <span property="libns:comment">{{ lane.comment }}</span><br/>{% endif %}
</div>
<hr/>
{% include "sample_header.html" %}
<td>{{lane_number}}</td>
<td>
<a href="{{ lane_file_set.ivc_all.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC All"
+ src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
</td>
<td>
<a href="{{ lane_file_set.ivc_call.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC Call"
+ src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
</a>
</td>
<td>
<a href="{{ lane_file_set.ivc_percent_base.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base"
+ src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
</a>
</td>
<td>
<a href="{{ lane_file_set.ivc_percent_all.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
+ <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base All"
+ src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
</a>
</td>
<td>
<a href="{{ lane_file_set.ivc_percent_call.get_absolute_url }}">
- <img height="84" width="126" src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
+ <img height="84" width="126"
+ alt="Lane {{lane_id }} IVC % Base Called"
+ src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
</a>
</td>
</tr>
+ {% else %}
+ <tr><td colspan="6">No data</td></tr>
{% endif %}
{% endfor %}
</tbody>
<h2>Library Name</h2>
<b>Library ID</b>:
<a href="{{lib.get_absolute_url}}"><span property="libns:library_id">{{ lib.id }}</span></a>
- {% if user.is_staff %}<a href="{{lib.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif"/></a>{% endif %}
+ {% if user.is_staff %}<a href="{{lib.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif" alt="Edit"/></a>{% endif %}
<br />
<b>Name</b>:
<span property="libns:name">{{ lib.library_name }}</span>
<br/>
{% if lib.gel_cut_size %}
<b>Gel Cut Size</b>:
- <span property="libns:gel_cut" datatype="xsd:decimal">{{ lib.gel_cut_size }}</span>
+ <span property="libns:gel_cut" datatype="xsd:integer">{{ lib.gel_cut_size }}</span>
<br/>
{% endif %}
{% if lib.insert_size %}
<b>Insert Size</b>:
- <span property="libns:insert_size" datatype="xsd:decimal">{{ lib.insert_size }}</span>
+ <span property="libns:insert_size" datatype="xsd:integer">{{ lib.insert_size }}</span>
<br/>
{% endif %}
{% if lib.undiluted_concentration %}
</tr>
</thead>
<tbody>
+ {% if eland_results %}
{% for result in eland_results %}
<tr about="{{result.flowcell.get_absolute_url}}">
<td property="libns:date" content="{{result.run_date|date:'Y-m-d\TH:i:s'}}" datatype="xsd:dateTime">{{ result.run_date|date}}</td>
</td>
</tr>
{% endfor %}
+ {% else %}
+ <tr><td colspan="8">No data</td></tr>
+ {% endif %}
</tbody>
</table>
</tr>
</thead>
<tbody>
-
+ {% if lane_summary_list %}
{# ls short for lane summary #}
{% for ls in lane_summary_list %}
<tr about="{{ls.lane.get_absolute_url}}">
<td>{{ ls.repeat_reads|intcomma }}</td>
</tr>
{% endfor %}
- </tbody>
- </table>
+ {% else %}
+ <tr><td colspan="20">No data</td></tr>
+ {% endif %}
+ </tbody>
+ </table>
<h2>Flowcell Notes</h2>
<table>
<td>Comment</td>
</tr>
</thead>
+ {% if lib.lane_set.all %}
<tbody>
{% for lane in lib.lane_set.all %}
<tr rel="libns:has_lane" resource="{{lane.get_absolute_url}}">
</tr>
{% endfor %}
</tbody>
+ {% endif %}
</table>
<br/>
<hr/>
+"""ElementTree helper functions
"""
-ElementTree helper functions
-"""
+import logging
+import os
+LOGGER = logging.getLogger(__name__)
+
+import lxml.etree
+try:
+ XHTML_RDF_DTD = lxml.etree.DTD(external_id='-//W3C//DTD XHTML+RDFa 1.0//EN')
+except lxml.etree.DTDParseError as e:
+ LOGGER.warn("Unable to load XHTML DTD %s" % (str(e),))
+
def indent(elem, level=0):
"""
reformat an element tree to be 'pretty' (indented)
def flatten(elem, include_tail=0):
"""
- Extract the text from an element tree
+ Extract the text from an element tree
(AKA extract the text that not part of XML tags)
"""
text = elem.text or ""
if include_tail and elem.tail: text += elem.tail
return text
+def validate_xhtml(html, base_url='http://localhost'):
+ """Helper for validating xhtml, mostly intended for test code
+
+ Defaults to assuming XHTML+RDFa
+ Returns None if there was a problem configuring validation
+ Logs messages from lxml.etree using python logging
+ Returns True if it passed validation
+ and False if it fails.
+ """
+ if XHTML_RDF_DTD is None:
+ return None
+
+ try:
+ root = lxml.etree.fromstring(html, base_url=base_url)
+ except lxml.etree.ParseError as e:
+ LOGGER.warn("Unable to parse document: %s" % (str(e),))
+ return False
+
+ if XHTML_RDF_DTD.validate(root):
+ # so unlikely.
+ return True
+
+ isgood = True
+ for msg in XHTML_RDF_DTD.error_log.filter_from_errors():
+ # I have no idea how to suppress this error
+ # but I need the xmlns attributes for of my RDFa 1.0 encoding
+ if 'ERROR:VALID:DTD_UNKNOWN_ATTRIBUTE' in str(msg):
+ continue
+ else:
+ LOGGER.error(msg)
+ isgood = False
+
+ return isgood
rdfs:domain htswlib:Sequencer ;
rdfs:range rdfs:Literal .
+# lane properties
+htswlib:status
+ a rdf:Proprety ;
+ rdfs:comment "Operators opinion of lane status, e.g. were there spots in the pictures" ;
+ rdfs:label "Status" ;
+ rdfs:domain htswlib:IlluminaLane ;
+ rdfs:range htswlib:Literal .
+
+htswlib:cluster_estimate
+ a rdf:Proprety ;
+ rdfs:comment "Estimate of clusters per tile" ;
+ rdfs:label "Cluster Estimate" ;
+ rdfs:domain htswlib:IlluminaLane ;
+ rdfs:range htswlib:Literal .
+
+htswlib:pM
+ a rdf:Proprety ;
+ rdfs:comment "picoMolarity" ;
+ rdfs:label "picoMolarity" ;
+ rdfs:domain htswlib:IlluminaLane ;
+ rdfs:range htswlib:Literal .
+
# library only properties
htswlib:library_id
rdfs:domain htswlib:Library ;
rdfs:range rdfs:Literal .
+htswlib:condition
+ a rdf:Property ;
+ rdfs:comment "Describes what treatment has been applied to the cells" ;
+ rdfs:label "Condition" ;
+ rdfs:domain htswlib:Library ;
+ rdfs:range rdfs:Literal .
+
htswlib:stopping_point
a rdf:Property ;
rdfs:comment "Protocol stopping point" ;