Commit b532f9a1 authored by Nicolas Noé's avatar Nicolas Noé
Browse files

Basic taxonomy management.

parent 4fe4bf74
from django.contrib import admin
from django import forms
from .models import Specimen, SpecimenLocation, Person, Fixation, Station, Expedition, SpecimenPicture
from mptt.admin import DraggableMPTTAdmin
from .models import Specimen, SpecimenLocation, Person, Fixation, Station, Expedition, SpecimenPicture, Taxon
from .widgets import LatLongWidget
......@@ -66,4 +69,8 @@ class ExpeditionAdmin(admin.ModelAdmin):
class SpecimenPictureAdmin(admin.ModelAdmin):
fields = ('specimen', 'image', 'high_interest')
@admin.register(Taxon)
class TaxonAdmin(DraggableMPTTAdmin):
pass
admin.site.site_header = 'Astapor administration'
\ No newline at end of file
......@@ -11,7 +11,6 @@ from specimens.models import Person, SpecimenLocation, Specimen, Fixation, Exped
MODELS_TO_TRUNCATE = [Station, Expedition, Fixation, Person, SpecimenLocation, Specimen]
# TODO: document use of this script:
# - export Google Sheet (specimens) as CSV (separator: comma)
# - Column name is important, not column order
# - Lat/lon use comma as a separator
......@@ -35,7 +34,7 @@ def get_or_create_station_and_expedition(station_name, expedition_name):
class Command(BaseCommand):
help = 'Initial data import to populate the tables'
help = 'Import specimens from a CSV file and attach them to the existing taxonomy.'
def add_arguments(self, parser):
parser.add_argument('csv_file')
......
import csv
from django.core.management.base import BaseCommand
from specimens.models import TaxonRank, Taxon, TaxonStatus, SPECIES_RANK_NAME, SUBGENUS_RANK_NAME
MODELS_TO_TRUNCATE = [Taxon, TaxonRank, TaxonStatus]
def create_initial_ranks():
TaxonRank.objects.bulk_create([
TaxonRank(name='Kingdom'),
TaxonRank(name='Phylum'),
TaxonRank(name='Class'),
TaxonRank(name='Order'),
TaxonRank(name='Family'),
TaxonRank(name='Genus'),
TaxonRank(name=SUBGENUS_RANK_NAME),
TaxonRank(name=SPECIES_RANK_NAME)
])
class Command(BaseCommand):
help = 'Import taxonomy from a CSV file.'
def add_arguments(self, parser):
parser.add_argument('csv_file')
parser.add_argument(
'--truncate',
action='store_true',
dest='truncate',
default=False,
help='Truncate all tables prior to import',
)
def handle(self, *args, **options):
self.stdout.write('Importing data from file...')
with open(options['csv_file']) as csv_file:
if options['truncate']:
for model in MODELS_TO_TRUNCATE:
self.stdout.write('Truncate model {name} ...'.format(name=model.__name__), ending='')
model.objects.all().delete()
self.stdout.write(self.style.SUCCESS('OK'))
self.stdout.write('Creating initial ranks...')
create_initial_ranks()
for i, row in enumerate(csv.DictReader(csv_file, delimiter=',')):
self.stdout.write('Processing row #{i}...'.format(i=i), ending='')
species_status, _ = TaxonStatus.objects.get_or_create(name=row['Status'])
# Starting from the higher ranks
kingdom, _ = Taxon.objects.get_or_create(name=row['Kingdom'].strip(),
rank=TaxonRank.objects.get(name="Kingdom"))
phylum, _ = Taxon.objects.get_or_create(name=row['Phylum'].strip(),
rank=TaxonRank.objects.get(name="Phylum"),
parent=kingdom)
class_taxon, _ = Taxon.objects.get_or_create(name=row['Class'].strip(),
rank=TaxonRank.objects.get(name="Class"),
parent=phylum)
order_taxon, _ = Taxon.objects.get_or_create(name=row['Order'].strip(),
rank=TaxonRank.objects.get(name="Order"),
parent=class_taxon)
family, _ = Taxon.objects.get_or_create(name=row['Family'].strip(),
rank=TaxonRank.objects.get(name="Family"),
parent=order_taxon)
genus, _ = Taxon.objects.get_or_create(name=row['Genus'].strip(),
rank=TaxonRank.objects.get(name="Genus"),
parent=family)
# Subgenus rank is optional
subgenus_source = row['Subgenus'].strip()
if subgenus_source:
subgenus, _ = Taxon.objects.get_or_create(name=subgenus_source,
rank=TaxonRank.objects.get(name=SUBGENUS_RANK_NAME),
parent=genus)
species_parent = subgenus if subgenus_source else genus
species, _ = Taxon.objects.get_or_create(name=row['Species'].strip(),
rank=TaxonRank.objects.get(name=SPECIES_RANK_NAME),
parent=species_parent,
status=species_status,
aphia_id=row['Aphia_ID'].strip(),
authority=row['Authority'].strip())
self.stdout.write(self.style.SUCCESS('OK'))
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-21 07:43
# Generated by Django 1.11.2 on 2017-06-22 09:08
from __future__ import unicode_literals
import django.contrib.gis.db.models.fields
import django.contrib.postgres.fields.ranges
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
......@@ -78,6 +79,47 @@ class Migration(migrations.Migration):
('expedition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='specimens.Expedition')),
],
),
migrations.CreateModel(
name='Taxon',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('aphia_id', models.IntegerField(blank=True, null=True)),
('authority', models.CharField(blank=True, max_length=100, null=True)),
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(db_index=True, editable=False)),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='specimens.Taxon')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TaxonRank',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='TaxonStatus',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.AddField(
model_name='taxon',
name='rank',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='specimens.TaxonRank'),
),
migrations.AddField(
model_name='taxon',
name='status',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='specimens.TaxonStatus'),
),
migrations.AddField(
model_name='specimen',
name='specimen_location',
......
from django.contrib.gis.db import models
from django.contrib.postgres.fields import FloatRangeField
from mptt.models import MPTTModel, TreeForeignKey
UNKNOWN_STATION_NAME = '<Unknown>' # Sometimes we need a "fake" station to link Specimen to Expedition
# Ranks: Sometimes names are used as an identifier...
SPECIES_RANK_NAME = "Species"
SUBGENUS_RANK_NAME = "Subgenus"
class TaxonRank(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class TaxonStatus(models.Model):
name = models.CharField(max_length=100)
class Taxon(MPTTModel):
name = models.CharField(max_length=100)
rank = models.ForeignKey(TaxonRank)
status = models.ForeignKey(TaxonStatus, null=True, blank=True)
aphia_id = models.IntegerField(null=True, blank=True)
authority = models.CharField(max_length=100, null=True, blank=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
def is_species(self):
return self.rank.name == SPECIES_RANK_NAME
def is_subgenus(self):
return self.rank.name == SUBGENUS_RANK_NAME
def __str__(self):
# Specific representation for Species
if self.is_species():
if self.parent.is_subgenus():
name = "{genus_name} ({subgenus_name}) {species_name}".format(species_name=self.name,
subgenus_name=self.parent.name,
genus_name=self.parent.parent.name)
else:
name = "{genus_name} {species_name} ".format(species_name=self.name, genus_name=self.parent.name)
else:
name = self.name
return "{name} [{rank}]".format(name=name, rank=self.rank)
class MPTTMeta:
order_insertion_by = ['name']
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
......
......@@ -37,6 +37,7 @@ INSTALLED_APPS = [
'django.contrib.gis',
'export_action',
'specimens',
'mptt',
]
MIDDLEWARE = [
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment