9aa4e9c5d11b08ebf3a50df5455b005436462871
[chemdb.git] / chemdb / models.py
1 # Copyright
2
3 from django.db import models as _models
4 from django.forms import ValidationError as _ValidationError
5
6 from . import LOG as LOG
7 from . import util as _util
8
9
10 class NamedItem (_models.Model):
11     name = _models.CharField(max_length=100)
12     abbrev = _models.CharField('abbreviation', max_length=20)
13
14     class Meta:
15         abstract = True
16         ordering = ['name']
17
18     def __unicode__(self):
19         return u'{0.abbrev}'.format(self)
20
21
22 class NFPASpecial (NamedItem):
23     """An NFPA Special rating (e.g. 'OX', '-W-', 'SA', ...).
24     """
25     symbol = _models.CharField(max_length=3, blank=True, null=True)
26
27     def __unicode__(self):
28         if self.symbol:
29             return u'{0.symbol}'.format(self)
30         return super(NFPASpecial, self).__unicode__()
31
32
33 class CASNumber (NamedItem):
34     "Chemical Abstracts Service registery number"
35     cas = _models.CharField(
36         'CAS#', max_length=20, unique=True)
37
38     def clean(self):
39         if not _util.valid_CASno(self.cas):
40             raise _ValidationError("invalid CAS number '{}'".format(self.cas))
41
42
43 class Chemical (NamedItem):
44     """A chemical (in the abstract, not an instance of the chemical)
45
46     Separating ``Chemical``\s from ``ChemicalInstance``\s avoids
47     duplicate information (e.g. you can have two bottles of acetic
48     acid).
49     """
50     cas = _models.ManyToManyField(
51         CASNumber, blank=True, null=True, related_name='chemicals')
52     msds = _models.FileField(
53         'Material safety data sheet', upload_to=_util.chemical_upload_to,
54         blank=True, null=True)
55     health = _models.PositiveIntegerField(
56         'NFPA health rating', blank=True, null=True)
57     fire = _models.PositiveIntegerField(
58         'NFPA fire rating', blank=True, null=True)
59     reactivity = _models.PositiveIntegerField(
60         'NFPA reactivity rating', blank=True, null=True)
61     special = _models.ManyToManyField(
62         NFPASpecial, blank=True, null=True, related_name='chemicals')
63     mutagen = _models.NullBooleanField()
64     carcinogen = _models.NullBooleanField()
65     teratogen = _models.NullBooleanField()
66     note = _models.TextField('notes', blank=True, null=True)
67
68     def cas_numbers(self):
69         if self.cas.count() == 0:
70             return 'unknown'
71         return ', '.join(cas.cas for cas in self.cas.all())
72
73     def specials(self):
74         return ' '.join(str(special) for special in self.special.all())
75
76
77 class Location (NamedItem):
78     "A chemical storage location (e.g. 'acidic liquids')"
79     pass
80
81
82 class Vendor (NamedItem):
83     "A chemical supplier"
84     url = _models.URLField('URL', blank=True, null=True)
85     note = _models.TextField('notes', blank=True, null=True)
86
87
88 class ChemicalInstance (_models.Model):
89     """An instance of a ``Chemical``
90
91     For example, 1L of acetic acid from Vendor X.
92     """
93     chemical = _models.ForeignKey(Chemical, related_name='chemical_instances')
94     location = _models.ForeignKey(Location, related_name='chemical_instances')
95     amount = _models.CharField(max_length=100)
96     vendor = _models.ForeignKey(Vendor, related_name='chemical_instances')
97     catalog = _models.CharField('vendor catalog number', max_length=100)
98     received = _models.DateField(auto_now_add=True, editable=True)
99     disposed = _models.DateField(blank=True, null=True)
100
101     class Meta:
102         ordering = ['chemical', 'received', 'disposed', 'id']