Sort NamedItems by abbrev first, since that's often what we use.
[chemdb.git] / chemdb / util.py
1 # Copyright
2
3 import os.path as _os_path
4 import re as _re
5
6 from django.template.defaultfilters import slugify as _slugify
7
8 from . import LOG as _LOG
9
10
11 CAS_REGEXP = _re.compile('\A[0-9]{2,}[-][0-9]{2}[-][0-9]\Z')
12
13
14 def valid_CASno(cas_string):
15     """Validate CAS numbers.
16
17     Check `N..NN-NN-N` format, and the `checksum digit`_ for valid CAS
18     number structure.  for
19
20     .. math::
21       N_n .. N_4 N_3 - N_2 N_1 - R
22
23     The checksum digit is
24
25     .. math::
26       R = remainder([sum_{i=1}^n i N_i  ] / 10 )
27
28     .. _checksum digit:
29       http://www.cas.org/expertise/cascontent/registry/checkdig.html
30
31     >>> valid_CASno('107-07-3')
32     True
33     >>> valid_CASno('107-08-3')
34     False
35     >>> valid_CASno('107-083')
36     False
37
38     Sometimes we don't have a CAS number, or a product will contain
39     secret, non-hazardous ingredients.  Therefore we treat the strings
40     `na` and `+secret-non-hazardous` as valid CAS numbers.
41
42     >>> valid_CASno('na')
43     True
44     >>> valid_CASno('+secret-non-hazardous')
45     True
46     """
47     if cas_string in [None, 'na', '+secret-non-hazardous']:
48         return True
49     # check format,  
50     # \A matches the start of the string
51     # \Z matches the end of the string
52     if CAS_REGEXP.match(cas_string) == None:
53         _LOG.debug("invalid CAS# format: '%s'".format(cas_string))
54         return False
55     # generate check digit
56     casdigs = [int(d) for d in ''.join(cas_string.split('-'))]
57     sumdigs = casdigs[:-1]
58     sumdigs.reverse()
59     s = sum([(i+1)*d for i,d in enumerate(sumdigs)])
60     check = s % 10
61     if check == casdigs[-1]:
62         return True
63     _LOG.debug("invalid CAS# check: '{}' (expected {})".format(
64             cas_string, check))
65     return False
66
67 def sanitize_path(string):
68     for a,b in [('..', '-')]:
69         string = string.replace(a, b)
70     return _slugify(string)
71
72 def chemical_upload_to(instance, filename, storage=None):
73     basename,extension = _os_path.splitext(filename)
74     if extension not in ['.pdf', '.html', '.txt']:
75         raise ValueError(filename)
76     return 'msds/{}{}'.format(sanitize_path(instance.abbrev), extension)