root/trunk/clinicaltrials/registry/models.py @ 165

Revision 165, 15.2 kB (checked in by luciano.ramalho, 3 years ago)

implemented help text editing via the assistance app

  • Property svn:executable set to *
Line 
1from django.db import models, IntegrityError
2
3from django.utils.translation import ugettext_lazy as _
4
5from datetime import datetime
6import string
7from random import choice
8from time import sleep
9
10from utilities import safe_truncate
11
12from vocabulary.models import CountryCode, StudyPhase, StudyType, RecruitmentStatus
13from vocabulary.models import InterventionCode, TrialNumberIssuingAuthority
14
15from registry import choices
16
17# remove digits that look like letters and vice-versa
18# remove vowels to avoid forming words
19BASE28 = ''.join(d for d in string.digits+string.ascii_lowercase
20                   if d not in '1l0aeiou')
21TRIAL_ID_PREFIX = 'RBR'
22TRIAL_ID_DIGITS = 6
23TRIAL_ID_TRIES = 3
24
25def generate_trial_id(prefix, num_digits):
26    s = choice(string.digits) # start with a numeric digit
27    s += ''.join(choice(BASE28) for i in range(1, num_digits))
28    return '-'.join([prefix, s[:num_digits/2], s[num_digits/2:]])
29
30class ClinicalTrial(models.Model):
31    # TRDS 1
32    trial_id = models.CharField(_('Primary Id Number'), null=True, unique=True,
33                                max_length=255, editable=False)
34    # TRDS 2
35    date_registration = models.DateField(_('Date of Registration'), null=True,
36                                         editable=False, db_index=True)
37    # TRDS 10a
38    scientific_title = models.TextField(_('Scientific Title'),
39                                        max_length=2000)
40    # TRDS 10b
41    scientific_acronym = models.CharField(_('Scientific Acronym'), blank=True,
42                                          max_length=255)
43    # TRDS 5
44    primary_sponsor = models.OneToOneField('Institution', null=True, blank=True,
45                                        verbose_name=_('Primary Sponsor'))
46    # TRDS 9a
47    public_title = models.TextField(_('Public Title'), blank=True,
48                                    max_length=2000)
49    # TRDS 9b
50    acronym = models.CharField(_('Acronym'), blank=True, max_length=255)
51
52    # TRDS 12a
53    hc_freetext = models.TextField(_('Health Condition(s)'), blank=True,
54                                   max_length=8000)
55    # TRDS 13a
56    i_freetext = models.TextField(_('Intervention(s)'), blank=True,
57                                   max_length=8000)
58    # TRDS 14a
59    inclusion_criteria = models.TextField(_('Inclusion Criteria'), blank=True,
60                                          max_length=8000)
61    # TRDS 14b
62    gender = models.CharField(_('Gender (inclusion sex)'), max_length=1,
63                              choices=choices.INCLUSION_GENDER,
64                              default=choices.INCLUSION_GENDER[0][0])
65    # TRDS 14c
66    agemin_value = models.PositiveIntegerField(_('Inclusion Minimum Age'),
67                                               default=0)
68    agemin_unit = models.CharField(_('Minimum Age Unit'), max_length=1,
69                                   choices=choices.INCLUSION_AGE_UNIT,
70                                   default=choices.INCLUSION_AGE_UNIT[0][0])
71    # TRDS 14d
72    agemax_value = models.PositiveIntegerField(_('Inclusion Maximum Age'),
73                                               default=0)
74    agemax_unit = models.CharField(_('Maximum Age Unit'), max_length=1,
75                                   choices=choices.INCLUSION_AGE_UNIT,
76                                   default=choices.INCLUSION_AGE_UNIT[0][0])
77    # TRDS 14e
78    exclusion_criteria = models.TextField(_('Exclusion Criteria'), blank=True,
79                                          max_length=8000)
80    # TRDS 15a
81    study_type = models.ForeignKey(StudyType, null=True, blank=True,
82                                   verbose_name=_('Study Type'))
83
84    # TRDS 15b
85    study_design = models.TextField(_('Study Design'), blank=True,
86                                          max_length=1000)
87    # TRDS 15c
88    phase = models.ForeignKey(StudyPhase, null=True, blank=True,
89                              verbose_name=_('Study Phase'))
90
91    # TRDS 16a,b (type_enrollment="anticipated")
92    date_enrollment_anticipated = models.CharField( # yyyy-mm or yyyy-mm-dd
93        _('Anticipated Date of First Enrollment'), max_length=10, blank=True)
94
95    # TRDS 16a,b (type_enrollment="actual")
96    date_enrollment_actual = models.CharField( # yyyy-mm or yyyy-mm-dd
97        _('Actual Date of First Enrollment'), max_length=10, blank=True)
98
99    # TRDS 17
100    target_sample_size = models.PositiveIntegerField(_('Target Sample Size'),
101                                                       default=0)
102    # TRDS 18
103    recruitment_status = models.ForeignKey(RecruitmentStatus, null=True, blank=True,
104                                           verbose_name=_('Recruitment Status'))
105
106    ################################### internal use, administrative fields ###
107    created = models.DateTimeField(default=datetime.now, editable=False)
108    updated = models.DateTimeField(_('Last Update'), null=True, editable=False)
109    exported = models.DateTimeField(null=True, editable=False)
110    status = models.CharField(_('Status'), max_length=64,
111                              choices=choices.TRIAL_RECORD_STATUS,
112                              default=choices.TRIAL_RECORD_STATUS[0][0])
113    staff_note = models.CharField(_('Record Note (staff use only)'),
114                                  max_length='255',
115                                  blank=True)
116
117    class Meta:
118        ordering = ['-updated',]
119
120    def save(self):
121        if self.id:
122            self.updated = datetime.now()
123        if self.status == choices.PUBLISHED_STATUS and not self.trial_id:
124            for i in range(TRIAL_ID_TRIES):
125                self.trial_id = generate_trial_id(TRIAL_ID_PREFIX, TRIAL_ID_DIGITS)
126                try:
127                    super(ClinicalTrial, self).save()
128                except IntegrityError:
129                    if i < TRIAL_ID_TRIES:
130                        sleep(2**i) # wait to try again
131                    else:
132                        raise # all tries exhausted: give up
133                else:   
134                    break # no need to try again
135        else:   
136            super(ClinicalTrial, self).save()
137
138    def identifier(self):
139        return self.trial_id or '(req:%s)' % self.pk
140
141    def short_title(self):
142        if self.scientific_acronym:
143            tit = u'%s - %s' % (self.scientific_acronym,
144                                self.scientific_title)
145        else:
146            tit = self.scientific_title
147        return safe_truncate(tit, 120)
148
149    def __unicode__(self):
150        return u'%s %s' % (self.identifier(), self.short_title())
151
152    def trial_id_display(self):
153        ''' return the trial id or an explicit message it is None '''
154        if self.trial_id:
155            return self.trial_id
156        else:
157            msg = 'not assigned (request #%)' % self.pk
158
159    def record_status(self):
160        return self.submission.status
161
162    #TRDS 3 - Secondarty ID Numbers
163    def trial_number(self):
164        return self.trialnumber_set.all().select_related();
165
166    # TRDS 4 - Source(s) of Monetary Support
167    def support_sources(self):
168        return self.trialsupportsource_set.all()
169
170    # TRDS 6 - Secondary Sponsor(s)
171    def secondary_sponsors(self):
172        return self.trialsecondarysponsor_set.all()
173
174    def updated_str(self):
175        return self.updated.strftime('%Y-%m-%d %H:%M')
176    updated_str.short_description = _('Updated')
177
178    def related_contacts(self, relation):
179        ''' return set of Contacts related to this trial with a
180            given relationship
181        '''
182        return (r.contact for r in
183                self.trialcontact_set.filter(relation=relation).select_related())
184
185    def related_health_conditions(self, aspect, level):
186        ''' return set of hc-code or keywords related to this trial with a
187            given relationship
188        '''
189        return self.descriptor_set.filter(aspect=aspect, level=level).select_related()
190
191
192    # TRDS 7 - Contact for Public Queries
193
194    def public_contacts(self):
195        ''' return set of Contacts related to this trial with
196            relation='PublicContact'
197        '''
198        return self.related_contacts('PublicContact')
199
200    # TRDS 8 - Contact for Scientific Queries
201
202    def scientific_contacts(self):
203        ''' return set of Contacts related to this trial with
204            relation='ScientificContact'
205        '''
206        return self.related_contacts('ScientificContact')
207
208    #TRDS 12b - HC-CODE
209    def hc_code(self):
210        ''' return set of HC-Code related to this trial with
211            aspect = 'HealthCondition'
212            level  = 'general'
213        '''
214        return self.related_health_conditions('HealthCondition','general')
215
216    #TRDS 12c - HC-Keyword
217    def hc_keyword(self):
218        ''' return set of HC-Code related to this trial with
219            aspect = 'HealthCondition'
220            level  = 'specific'
221        '''
222        return self.related_health_conditions('HealthCondition','specific')
223   
224    #TRDS 13b - Invetion Code
225    def intervention_code(self):
226        ''' return set of Intervention Code related to this trial with
227        '''
228        return (r.i_code for r in
229                self.trialinterventioncode_set.all().select_related())
230
231    #TRDS 13c - Invention Keyword
232    def intervention_keyword(self):
233        ''' return set of Intervention Keyword related to this trial with
234        '''
235        return self.descriptor_set.filter(aspect='intervention').select_related()
236
237    #TRDS 19 - Primary Outcomes
238    def primary_outcomes(self):
239        ''' return set of Primary Outcomes related to this trial with
240        '''
241        return self.outcome_set.filter(interest='primary').select_related()
242
243    #TRDS 20 - Secondary Outcomes
244    def secondary_outcomes(self):
245        ''' return set of Secondary Outcomes related to this trial with
246        '''
247        return self.outcome_set.filter(interest='secondary').select_related()
248
249
250
251################################### Entities linked to a Clinical Trial ###
252
253# TRDS 3 - Secondary Identifying Numbers
254
255class TrialNumber(models.Model):
256    trial = models.ForeignKey(ClinicalTrial)
257    issuing_authority = models.CharField(_('Issuing Authority'),
258                                         max_length=255, db_index=True,
259                                         choices=TrialNumberIssuingAuthority.choices())
260    id_number = models.CharField(_('Secondary Id Number'),
261                                max_length=255, db_index=True)
262
263    def __unicode__(self):
264        return u'%s: %s' % (self.issuing_authority, self.id_number)
265
266# TRDS 6 - Secondary Sponsor(s)
267class TrialSecondarySponsor(models.Model):
268    trial = models.ForeignKey(ClinicalTrial)
269    institution = models.ForeignKey('Institution')
270
271    def __unicode__(self):
272        return u'%s: %s' % (self.trial, self.institution)
273
274# TRDS 4 - Source(s) of Monetary Support
275class TrialSupportSource(models.Model):
276    trial = models.ForeignKey(ClinicalTrial)
277    institution = models.ForeignKey('Institution')
278
279    def __unicode__(self):
280        return u'%s: %s' % (self.trial, self.institution)
281
282# TRDS 5 - Primary Sponsor
283
284class Institution(models.Model):
285    name = models.CharField(_('Name'), max_length=255)
286    address = models.TextField(_('Postal Address'), max_length=1500, blank=True)
287    country = models.ForeignKey(CountryCode, verbose_name=_('Country'))
288
289    def __unicode__(self):
290        return safe_truncate(self.name, 80)
291
292# TRDS 7 - Contact for Public Queries
293# TRDS 8 - Contact for Scientific Queries
294
295class Contact(models.Model):
296    firstname = models.CharField(_('First Name'), max_length=50)
297    middlename = models.CharField(_('Middle Name'), max_length=50, blank=True)
298    lastname = models.CharField(_('Last Name'), max_length=50)
299    email = models.EmailField(_('E-mail'), max_length=255)
300    affiliation = models.ForeignKey(Institution, null=True, blank=True,
301                                    verbose_name=_('Affiliation'))
302    address = models.CharField(_('Address'), max_length=255, blank=True)
303    city = models.CharField(_('City'), max_length=255, blank=True)
304    country = models.ForeignKey(CountryCode, null=True, blank=True,
305                                verbose_name=_('Country'),)
306    zip = models.CharField(_('Postal Code'), max_length=50, blank=True)
307    telephone = models.CharField(_('Telephone'), max_length=255, blank=True)
308
309    def name(self):
310        names = self.firstname + u' ' + self.middlename + u' ' + self.lastname
311        return u' '.join(names.split())
312
313    def __unicode__(self):
314        return self.name()
315
316class TrialContact(models.Model):
317    trial = models.ForeignKey(ClinicalTrial)
318    contact = models.ForeignKey(Contact)
319    relation = models.CharField(_('Relationship'), max_length=255,
320                            choices = choices.CONTACT_RELATION)
321    status = models.CharField(_('Status'), max_length=255,
322                            choices = choices.CONTACT_STATUS,
323                            default = choices.CONTACT_STATUS[0][0])
324
325    def __unicode__(self):
326        return u'%s, %s: %s (%s)' % (self.relation, self.trial.short_title(),
327                                     self.contact.name(), self.status)
328
329# TRDS 11 - Countries of Recruitment
330
331class RecruitmentCountry(models.Model):
332    trial = models.ForeignKey(ClinicalTrial)
333    country = models.ForeignKey(CountryCode, verbose_name=_('Country'))
334
335    class Meta:
336        verbose_name_plural = _('Recruitment Countries')
337
338    def __unicode__(self):
339        return self.country.description
340
341# TRDS 13b - Intervention(s), intervention code
342
343class TrialInterventionCode(models.Model):
344    trial = models.ForeignKey(ClinicalTrial)
345    i_code = models.ForeignKey(InterventionCode)
346
347    class Meta:
348        order_with_respect_to = 'trial'
349
350    def __unicode__(self):
351        return u'%s: %s' % (self.trial.short_title(), self.i_code.label)
352
353# TRDS 19 - Primary Outcome(s)
354# TRDS 20 - Key Secondary Outcome(s)
355
356class Outcome(models.Model):
357    trial = models.ForeignKey(ClinicalTrial)
358    interest = models.CharField(_('Interest'), max_length=32,
359                               choices=choices.OUTCOME_INTEREST,
360                               default = choices.OUTCOME_INTEREST[0][0])
361    description = models.TextField(_('Outcome Description'), max_length=8000)
362
363    class Meta:
364        order_with_respect_to = 'trial'
365
366    def __unicode__(self):
367        return safe_truncate(self.description, 80)
368
369class Descriptor(models.Model):
370    aspect = models.CharField(_('Trial Aspect'), max_length=255,
371                        choices=choices.TRIAL_ASPECT)
372    vocabulary = models.CharField(_('Vocabulary'), max_length=255,
373                        choices=choices.DESCRIPTOR_VOCABULARY)
374    version = models.CharField(_('Version'), max_length=64, blank=True)
375    level = models.CharField(_('Level'), max_length=64,
376                        choices=choices.DESCRIPTOR_LEVEL)
377    code = models.CharField(_('Code'), max_length=255)
378    text = models.CharField(_('Text'), max_length=255, blank=True)
379
380    def __unicode__(self):
381        return u'[%s] %s: %s' % (self.vocabulary, self.code, self.text)
382
383class GeneralDescriptor(models.Model):
384    trial = models.ForeignKey(ClinicalTrial)
385    descriptor = models.ForeignKey(Descriptor)
386
387    class Meta:
388        order_with_respect_to = 'descriptor'
389
390    def trial_identifier(self):
391        return self.trial.identifier()
392
393class SpecificDescriptor(models.Model):
394    trial = models.ForeignKey(ClinicalTrial)
395    descriptor = models.ForeignKey(Descriptor)
396
397    class Meta:
398        order_with_respect_to = 'descriptor'
399
400    def trial_identifier(self):
401        return self.trial.identifier()
Note: See TracBrowser for help on using the browser.