Add AccessionAgency and LibraryAccession
authorDiane Trout <diane@ghic.org>
Thu, 21 May 2015 23:11:29 +0000 (16:11 -0700)
committerDiane Trout <diane@ghic.org>
Thu, 21 May 2015 23:11:29 +0000 (16:11 -0700)
For all your library accessioning needs.

htsworkflow/templates/sample_header.html
samples/admin.py
samples/migrations/0002_accessionagency_libraryaccession.py [new file with mode: 0644]
samples/models.py
samples/samples_factory.py
samples/test_samples.py

index a198cef383aa91e5b0a19c5ebd68413c3da928e4..651349f01b88dafff3f762c60edaf0f2840c0c86 100644 (file)
         </li>
       {% endfor %}
     </ul>
-  </div>
+    {% if lib.libraryaccession_set %}
+    <b>Accessions</b>:
+    <ul rel="libns:accession">
+      <li>
+        {% for accession in lib.libraryaccession_set.all %}
+        <a href="{{ accession.url }}">{{ accession }}</a>
+      {% endfor %}
+      </li>
+    </ul>
+    {% endif %}
+    </div>
   <div class="library_sample_detail">
     <h2>Sample Details</h2>
     <b>Species</b>:
index 58a4934d8d6f2781dc1187b5f71d44ff047d7b44..048c2cc110b85a66d507010714cef59368a3c2f2 100644 (file)
@@ -7,6 +7,8 @@ from django.contrib.auth.forms import UserCreationForm, UserChangeForm
 from django.template import Context, Template
 
 from .models import (
+    AccessionAgency,
+    LibraryAccession,
     Antibody,
     Cellline,
     Condition,
@@ -26,6 +28,26 @@ from bcmagic.utils import print_zpl_socket
 admin.site.disable_action('delete_selected')
 
 
+class AccessionAgencyOptions(admin.ModelAdmin):
+    model = AccessionAgency
+
+
+class LibraryAccessionOptions(admin.ModelAdmin):
+    model = LibraryAccession
+    search_fields = ('accession', 'library')
+    list_filter = ('agency', 'created',)
+    list_display = ('accession', 'library', 'created')
+
+
+class LibraryAccessionInline(admin.TabularInline):
+    model = LibraryAccession
+    fieldsets = (
+        (None, {
+            'fields': ('agency', 'accession', 'accession_url')
+        }),
+    )
+
+
 class AffiliationOptions(admin.ModelAdmin):
     list_display = ('name', 'contact', 'email')
     fieldsets = (
@@ -146,6 +168,7 @@ class LibraryOptions(admin.ModelAdmin):
         'cell_line__cellline_name',
         'library_species__scientific_name',
         'library_species__common_name',
+        'libraryaccession__accession',
     )
     list_display = (
         'id',
@@ -198,6 +221,7 @@ class LibraryOptions(admin.ModelAdmin):
     )
     inlines = [
         LaneLibraryInline,
+        LibraryAccessionInline,
     ]
     actions = ['action_print_library_labels']
 
@@ -274,6 +298,8 @@ class TagOptions(admin.ModelAdmin):
         }),
     )
 
+admin.site.register(AccessionAgency, AccessionAgencyOptions)
+admin.site.register(LibraryAccession, LibraryAccessionOptions)
 admin.site.register(Library, LibraryOptions)
 admin.site.register(Affiliation, AffiliationOptions)
 admin.site.register(Antibody, AntibodyOptions)
diff --git a/samples/migrations/0002_accessionagency_libraryaccession.py b/samples/migrations/0002_accessionagency_libraryaccession.py
new file mode 100644 (file)
index 0000000..583737c
--- /dev/null
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.core.validators
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('samples', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AccessionAgency',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
+                ('name', models.CharField(max_length=255)),
+                ('homepage', models.URLField(blank=True)),
+                ('library_template', models.URLField(blank=True)),
+            ],
+            options={
+                'verbose_name_plural': 'Accession Agencies',
+            },
+            bases=(models.Model,),
+        ),
+        migrations.CreateModel(
+            name='LibraryAccession',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
+                ('accession', models.CharField(db_index=True, validators=[django.core.validators.RegexValidator('^[-A-Za-z0-9:.]*$', message='Please only use letters, digits, and :.-')], max_length=255)),
+                ('url', models.URLField(blank=True, null=True)),
+                ('created', models.DateTimeField()),
+                ('agency', models.ForeignKey(to='samples.AccessionAgency')),
+                ('library', models.ForeignKey(to='samples.Library')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=(models.Model,),
+        ),
+    ]
index 9307aa6c97babc76b37803f7041a35e6e22303e0..950ad094177ae88f8fa780d85393ac64a9a5af9c 100644 (file)
@@ -6,11 +6,59 @@ from django.contrib.auth.models import User
 from django.core import urlresolvers
 from django.db.models.signals import post_save
 from django.db import connection
+from django.core.validators import RegexValidator
 import six
 
 logger = logging.getLogger(__name__)
 
 
+class AccessionAgency(models.Model):
+    """An organization one submits data to
+    """
+    name = models.CharField(max_length=255)
+    homepage = models.URLField(blank=True)
+    library_template = models.URLField(blank=True)
+
+    class Meta:
+        verbose_name_plural = 'Accession Agencies'
+
+    def __str__(self):
+        return self.name
+
+
+class Accession(models.Model):
+    """Track accession IDs assigned to our objects.
+    """
+    accession = models.CharField(
+        max_length=255,
+        db_index=True,
+        validators=[RegexValidator(
+            "^[-A-Za-z0-9:.]*$",
+            message="Please only use letters, digits, and :.-")]
+    )
+    url = models.URLField(blank=True, null=True)
+    agency = models.ForeignKey(AccessionAgency)
+    created = models.DateTimeField()
+
+    class Meta:
+        abstract = True
+
+    def update_url(self, template):
+        if template and not self.url:
+            self.url = template.format(self.accession)
+
+    def __str__(self):
+        return str(self.agency) + ":" + self.accession
+
+
+class LibraryAccession(Accession):
+    library = models.ForeignKey('Library')
+
+    def save(self, *args, **kwargs):
+        self.update_url(self.agency.library_template)
+        super(LibraryAccession, self).save(*args, **kwargs)
+
+
 class Antibody(models.Model):
     antigene = models.CharField(max_length=500, db_index=True)
     # New field Aug/20/08
index 7640c1938bcfb740fd9e759076504601f38ef8cd..50838c380089651adf4e60e89441d70b1d0354b3 100644 (file)
@@ -113,3 +113,20 @@ class LibraryFactory(DjangoModelFactory):
     undiluted_concentration = '5.01'
     hidden = False
     library_type = SubFactory(LibraryTypeFactory)
+
+
+class AccessionAgencyFactory(DjangoModelFactory):
+    class Meta:
+        model = models.AccessionAgency
+
+    name = FuzzyText(prefix="agency ")
+    homepage = FuzzyText(prefix="http://", suffix=".example.com")
+    library_template = LazyAttribute(lambda o: "%s/library/{}" % (o.homepage,))
+
+
+class LibraryAccessionFactory(DjangoModelFactory):
+    class Meta:
+        model = models.LibraryAccession
+
+    accession = FuzzyText(prefix="ACC")
+    agency = SubFactory(AccessionAgencyFactory)
index 8b34082d1f6be768171d9eba37a6eb8c02e44b50..262f4f95a20db7ca5356eca3b16ed83dbc9d92a4 100644 (file)
@@ -4,13 +4,18 @@ import datetime
 import json
 from unittest import skipUnless
 
+from django.core.exceptions import ValidationError
 from django.test import TestCase, RequestFactory
 from django.utils.encoding import smart_text, smart_str
 
 from .models import Affiliation, ExperimentType, Species, Library
-from .views import library_dict, library_json, library
-from .samples_factory import *
-
+from .views import library_dict
+from .samples_factory import (
+    AffiliationFactory,
+    LibraryAccessionFactory,
+    LibraryFactory,
+    SpeciesFactory,
+)
 from htsworkflow.auth import apidata
 from htsworkflow.util.conversion import str_or_none
 from htsworkflow.util.ethelp import validate_xhtml
@@ -32,6 +37,51 @@ except ImportError as e:
     HAVE_RDF = False
 
 
+class LibraryAccessionTestCase(TestCase):
+    def test_validator(self):
+        library = LibraryFactory()
+        acc = LibraryAccessionFactory(library_id=library.id)
+        acc.clean_fields()
+        accession = acc.accession
+        # test a variety of escape characters one at a time
+        for c in "<>'\"&;":
+            acc.accession = accession + c
+            self.assertRaises(ValidationError, acc.clean_fields)
+
+    def test_library_save_hook(self):
+        library = LibraryFactory()
+        acc = LibraryAccessionFactory(library_id=library.id)
+
+        self.assertEquals(acc.url[:len(acc.agency.homepage)],
+                          acc.agency.homepage)
+        self.assertEquals(acc.url[len(acc.agency.homepage):],
+                          '/library/'+acc.accession)
+
+    @skipUnless(HAVE_RDF, "No RDF Support")
+    def test_have_accession(self):
+        library = LibraryFactory()
+        acc = LibraryAccessionFactory(library_id=library.id)
+        lib_response = self.client.get(library.get_absolute_url())
+        lib_content = smart_text(lib_response.content)
+
+        model = get_model()
+        load_string_into_model(model, 'rdfa', lib_content)
+
+        body = """prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+        prefix libns: <http://jumpgate.caltech.edu/wiki/LibraryOntology#>
+
+        select ?library ?accession
+        where {
+           ?library libns:accession ?accession
+        }"""
+        query = RDF.SPARQLQuery(body)
+        accessions = []
+        for row in query.execute(model):
+            accessions.append(str(row['accession']))
+        self.assertEqual(len(accessions), 1)
+        self.assertEqual(accessions[0], acc.url)
+
+
 class LibraryTestCase(TestCase):
     def testOrganism(self):
         human = SpeciesFactory(common_name='human')
@@ -346,6 +396,7 @@ def get_rdf_memory_model():
 def suite():
     from unittest import TestSuite, defaultTestLoader
     suite = TestSuite()
+    suite.addTests(defaultTestLoader.loadTestsFromTestCase(LibraryAccessionTestCase))
     suite.addTests(defaultTestLoader.loadTestsFromTestCase(LibraryTestCase))
     suite.addTests(defaultTestLoader.loadTestsFromTestCase(SampleWebTestCase))
     suite.addTests(defaultTestLoader.loadTestsFromTestCase(TestRDFaLibrary))