else:
            return u"<new %s>" % (self._meta.verbose_name,)

    @models.permalink
    def get_absolute_url(self):
        return ("case_detail", [str(self.id)])

    def get_edit_url(self):
        return reverse("edit_case", args=[self.id])

    class Meta:
        app_label = "incidents"
        ordering = ("date", "current_yearnumber__year", "current_yearnumber__number", "id")


guard_deletes(Animal, Case, "animal")


class YearCaseNumber(models.Model):
    """\
    A little table to do the bookkeeping when assigning yearly-numbers to cases.
    'year' is a year, 'case' is a case, 'number' is any yearly_number held by
    that case for that year, including it's current one.
    
    Assigning unique numbers to each case in a year is complicated; once a
    case-number in a given year has been assigned to a case, it mustn't ever be
    assigned to a different one, even if that case is changed to a different
    year or merged with another case. Ideally, if a case was assigned, say,
    2003#67 and then it's date was changed to 2004, it would be assigned the
    next unused yearly_number for 2004. If it was then changed back to 2003, it
    would be assigned #67 again. Thus, this table stores all past and current
    home_port = models.CharField(
        max_length= 511,
        blank= True,
    )
    flag = models.ForeignKey(
        Country,
        blank= True,
        null= True,
    )
    description = models.TextField(
        blank= True,
    )
    def __unicode__(self):
        ret = 'unnamed vessel'
        if self.name:
            ret = "%s" % self.name
        ret += " ("
        if self.id: # unsaved entries won't have IDs
            ret += "vessel %i" % self.id
        else:
            ret += "unsaved vessel"
        if self.flag:
            ret += ", "
            ret += "%s" % self.flag.iso
        ret += ')'
        return ret

guard_deletes(Contact, VesselInfo, 'contact')
guard_deletes(Country, VesselInfo, 'flag')

    )
    speed = models.FloatField(
        blank= True,
        null= True,
        help_text= "In knots, over ground, at the time of the strike",
        # yes there's really no way to convert from over-ground to over-water,
        # but it's either one or the other. GPS gives you over-ground.
    )

    import_notes = models.TextField(
        blank= True,
        editable= False, # note that this only means it's not editable in the admin interface
        help_text= "field to be filled in by import scripts for data they can't assign to a particular field",
    )

guard_deletes(Contact, StrikingVesselInfo, 'captain')
    
class Shipstrike(Case):

    @models.permalink
    def get_absolute_url(self):
        return('shipstrike_detail', [str(self.id)])

    def get_edit_url(self):
        return reverse('edit_shipstrike', args=[self.id])

class ShipstrikeObservation(ObservationExtension):

    striking_vessel = models.OneToOneField(
        StrikingVesselInfo,
        blank= True,
    @models.permalink
    def get_absolute_url(self):
        return ('taxon_detail', [str(self.id)]) 

    def scientific_name(self):
        '''\
        Returns just the name for taxa with rank of genus or above. For those below, returns the "G. species" style.
        '''

        if self.is_binomial() and self.supertaxon:
            # go up the taxon tree looking for a taxon with rank == Genus. if 
            # we find one, print out it's initial, plus the names of each taxon 
            # we found on the way with rank of Species or below.
            nomens = self.name
            t = self.supertaxon
            while t.rank < self.ITIS_RANKS['Genus']:
                if t.rank <= self.ITIS_RANKS['Species']:
                    nomens = t.name + ' ' + nomens
                if not t.supertaxon:
                    break
                t = t.supertaxon
            if t.rank == self.ITIS_RANKS['Genus']:
                return u'%s. %s' % (t.name[0], nomens)
        return self.name

    def __unicode__(self):
        return u'%s %s' % (self.name, self.get_rank_display())

guard_deletes(Taxon, Taxon, 'supertaxon')

from cetacean_incidents.apps.delete_guard import guard_deletes

from cetacean_incidents.apps.documents.models import Documentable

class Tag(models.Model):
    
    entry = models.ForeignKey(
        Documentable,
    )
    
    user = models.ForeignKey(
        User,
    )
    
    datetime_tagged = models.DateTimeField(
        auto_now_add=True,
    )
    
    tag_text = models.CharField(
        db_index=True,
        max_length=1024,
    )

    def __unicode__(self):
        return u"tag on %s: \u201c%s\u201d" % (self.entry.specific_instance(), self.tag_text)

# Allow deletion of a Documentable to casecade to Tags view Tags.entry
guard_deletes(User, Tag, 'user')

    roughness = models.FloatField(
        blank= True,
        null= True,
        help_text= "Indicate the uncertainty of the coordinates, in meters. Should be the radius of the circle around the coordinates that contains the actual location with 95% certainty. For GPS-determined coordinates, this will be a few tens of meters. For rough estimates, note that 1 mile = 1,609.344 meters (user interfaces should handle unit conversion). When plotting locations on a map, a cirle of this size (centered at the coordinates) should be used instead of a single point, so as to not give a false sense of accuracy."
    )

    import_notes = models.TextField(
        blank= True,
        editable= False, # note that this only means it's not editable in the admin interface or ModelForm-generated forms
        help_text= "field to be filled in by import scripts for data they can't assign to a particular field",
    )
    
    @property
    def has_data(self):
        return reduce(operator.__or__, (
            bool(self.description),
            bool(self.country),
            bool(self.waters),
            bool(self.state),
            bool(self.coordinates),
        ))
    
    def __unicode__(self):
        if self.coordinates:
            return unicode(self.coordinates)
        
        return u"<#%s>" % unicode(self.pk)
    
guard_deletes(Country, Location, 'country')

    def path(self):
        return None
    
    @models.permalink
    def get_absolute_url(self):
        return ('view_document', (self.id,))
    
    def __unicode__(self):
        id_string = '#{0.id:06}'.format(self) if self.id else '(no ID)'
        doctype_string = unicode(self.document_type) if self.document_type else u'document'
        return u'%s %s' % (doctype_string, id_string)
    
    class Meta:
        ordering = ('document_type', 'id')

guard_deletes(DocumentType, Document, 'document_type')
guard_deletes(Documentable, Document, 'attached_to')

_uploads_dir_name = 'uploads'
_uploads_dir = path.join(_storage_dir, _uploads_dir_name)
_checkdir(_uploads_dir)
_uploads_url = settings.MEDIA_URL + '{0}/{1}/'.format(_storage_dir_name, _uploads_dir_name)
upload_storage = FileSystemStorage(
    location= _uploads_dir,
    base_url= _uploads_url,
)

class UploadedFile(Document):
    
    # TODO this could be moved to Document itself
    @classmethod
        
        # TODO animal.html uses animal.dead, which depends on today's date
        opts['cache_deps'] |= CacheDependency(
            update= {self: TestList([True])},
            delete= {self: TestList([True])},
        )
        
        return opts
    
    def __unicode__(self):
        if self.field_number:
            return self.field_number
        if self.name:
            return self.name
        if self.id:
            return "unnamed animal #%06d" % self.id
        return "unnamed, unsaved animal"
    
    @models.permalink
    def get_absolute_url(self):
        return ('animal_detail', [str(self.id)]) 
    
    objects = AnimalManager()
    
    class Meta:
        app_label = 'incidents'
        ordering = ('field_number', 'name', 'id')

guard_deletes(Taxon, Animal, 'determined_taxon')

        blank= True,
        null= True,
    )
    
    datetime_missing = UncertainDateTimeField(
        blank= True,
        null= True,
        verbose_name= 'date gear went missing',
    )

    class Meta:
        permissions = (
            ("view_gearowner", "Can view gear owner"),
        )

guard_deletes(Location, GearOwner, 'location_gear_set')

class GearTarget(models.Model):
    name = models.CharField(
        max_length= 1024,
    )
    
    definition = models.TextField(
        blank= True,
        null= True,
        help_text= "Detailed definition of which species are targeted.",
    )
    
    def __unicode__(self):
        return self.name if self.name else unicode(self.id)
    
    class Meta:
        app_label = "incidents"
        ordering = ["datetime_observed", "datetime_reported", "id"]


# prevent deletes from cascading to an Observation
for m, rm, fn in (
    (Animal, Observation, "animal"),
    (Contact, Observation, "observer"),
    (Contact, Observation, "reporter"),
    (Taxon, Observation, "taxon"),
    (Location, Observation, "location"),
    (VesselInfo, Observation, "observer_vessel"),
):
    guard_deletes(m, rm, fn)


class ObservationExtension(models.Model):
    """\
    Classes that want to add sets of fields to Observations can subclass this.
    E.g. entanglement\u2010specific fields (which will only be needed if an
    Observation is relevant to an Entanglement Case) can be put in a subclass of
    this. It doesn't make sense to subclass Observation for that, since an
    Observation instance may have multiple ObservationExtensions that go with
    it.
    """

    # EntanglementObservation and ShipstrikeObservation used to be subclasses of
    # Observation, but now that Observations can be for multiple cases, an
    # Observation could be for both an Entanglement and a Shipstrike. But we