1 '''
2 Whole-cell data model
3
4 Author: Jonathan Karr, jkarr@stanford.edu
5 Affiliation: Covert Lab, Department of Bioengineering, Stanford University
6 Last updated: 2012-07-17
7 '''
8
9 from __future__ import unicode_literals
10 from Bio.Seq import Seq
11 from Bio.Alphabet import IUPAC
12 from django.contrib.auth.models import User
13 from django.core import validators
14 from django.core.exceptions import ValidationError, ObjectDoesNotExist
15 from django.core.urlresolvers import reverse
16 from django.db import models
17 from django.db.models import Model, OneToOneField, CharField, IntegerField, URLField, PositiveIntegerField, FloatField, ForeignKey, BooleanField, SlugField, ManyToManyField, TextField, DateTimeField, options, permalink, SET_NULL, Min, Max
18 from django.db.models.query import EmptyQuerySet
19 from django.utils.http import urlencode
20 from itertools import chain
21 from public.templatetags.templatetags import set_time_zone
22 import math
23 import re
24 import settings
25 import subprocess
26
27 ''' BEGIN: choices '''
28
29 CHOICES_DIRECTION = (
30 ('f', 'Forward'),
31 ('r', 'Reverse'),
32 )
33
34 CHOICES_REFERENCE_TYPE = (
35 ('article', 'Article'),
36 ('book', 'Book'),
37 ('thesis', 'Thesis'),
38 ('misc', 'Miscellaneous'),
39 )
40
41 CHOICES_STRANDEDNESS = (
42 ('dsDNA', 'dsDNA'),
43 ('ssDNA', 'ssDNA'),
44 ('xsDNA', 'xsDNA'),
45 )
46
47 CHOICES_VMAX_UNITS = (
48 ('1/min', '1/min'),
49 ('U/mg', 'U/mg'),
50 )
51
52 CHOICES_HOMOLOG_SPECIES = (
53 ('B. subtilis', 'B. subtilis'),
54 ('E. coli', 'E. coli'),
55 ('M. hyopneumoniae', 'M. hyopneumoniae'),
56 ('M. mobile', 'M. mobile'),
57 ('M. pneumoniae', 'M. pneumoniae'),
58 ('S. coelicolor', 'S. coelicolor'),
59 ('S. oneidensis', 'S. oneidensis'),
60 )
61
62 HOMOLOG_SPECIES_URLS = {
63 "B. subtilis": "http://www.genome.jp/dbget-bin/www_bget?bsu:%s",
64 "E. coli": "http://www.genome.jp/dbget-bin/www_bget?eco:%s",
65 "M. hyopneumoniae": "http://www.genome.jp/dbget-bin/www_bget?mhj:%s",
66 "M. mobile": "http://www.genome.jp/dbget-bin/www_bget?mmo:%s",
67 "M. pneumoniae": "http://www.genome.jp/dbget-bin/www_bget?mpn:%s",
68 "S. coelicolor": "http://www.genome.jp/dbget-bin/www_bget?sco:%s",
69 "S. oneidensis": "http://www.genome.jp/dbget-bin/www_bget?son:%s",
70 }
71
72 CHOICES_CROSS_REFERENCE_SOURCES = (
73 ('ATCC', 'ATCC'),
74 ('BiGG', 'BiGG'),
75 ('BioCyc', 'BioCyc'),
76 ('BioProject', 'BioProject'),
77 ('CAS', 'CAS'),
78 ('ChEBI', 'ChEBI'),
79 ('CMR', 'CMR'),
80 ('EC', 'EC'),
81 ('GenBank', 'GenBank'),
82 ('ISBN', 'ISBN'),
83 ('KEGG', 'KEGG'),
84 ('KNApSAcK', 'KNApSAcK'),
85 ('LipidBank', 'LipidBank'),
86 ('LIPIDMAPS', 'LIPIDMAPS'),
87 ('PDB', 'PDB'),
88 ('PDBCCD', 'PDBCCD'),
89 ('PubChem', 'PubChem'),
90 ('PubMed', 'PubMed'),
91 ('RefSeq', 'RefSeq'),
92 ('SABIO-RK', 'SABIO-RK'),
93 ('SwissProt', 'SwissProt'),
94 ('Taxonomy', 'Taxonomy'),
95 ('ThreeDMET', 'ThreeDMET'),
96 ('URL', 'URL'),
97 )
98
99 CROSS_REFERENCE_SOURCE_URLS = {
100 "ATCC": "http://www.atcc.org/ATCCAdvancedCatalogSearch/ProductDetails/tabid/452/Default.aspx?Template=bacteria&ATCCNum=%s",
101 "BiGG": "http://bigg.ucsd.edu/bigg/postMet.pl?organism=3307911&organism=1461534&organism=222668&organism=3277493&organism=2795088&organism=2423527&organism=1823466&compartment_list=any&pathway_list=any&name_text=%s",
102 "BioCyc": "http://biocyc.org/MGEN243273/NEW-IMAGE?type=GENE&object=%s",
103 "BioProject": "http://www.ncbi.nlm.nih.gov/bioproject/%s",
104 "CAS": "http://www.ncbi.nlm.nih.gov/sites/entrez?db=pccompound&term=%s",
105 "ChEBI": "http://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:%s",
106 "CMR": "http://cmr.jcvi.org/tigr-scripts/CMR/shared/GenePage.cgi?locus=%s",
107 "EC": "http://www.expasy.ch/enzyme/%s",
108 "GenBank": "http://www.ncbi.nlm.nih.gov/sites/gquery?term=%s",
109 "ISBN": "http://isbndb.com/search-all.html?kw=%s",
110 "KEGG": "http://www.genome.jp/dbget-bin/www_bget?cpd:%s",
111 "KNApSAcK": "http://kanaya.naist.jp/knapsack_jsp/information.jsp?word=%s",
112 "LipidBank": "http://lipidbank.jp/cgi-bin/detail.cgi?id=%s",
113 "LIPIDMAPS": "http://www.lipidmaps.org/data/get_lm_lipids_dbgif.php?LM_ID=%s",
114 "PDB": "http://www.pdb.org/pdb/explore/explore.do?structureId=%s",
115 "PDBCCD": "http://www.ebi.ac.uk/pdbe-srv/pdbechem/chemicalCompound/show/%s",
116 "PubChem": "http://pubchem.ncbi.nlm.nih.gov/summary/summary.cgi?viewopt=PubChem&sid=%s",
117 "PubMed": "http://www.ncbi.nlm.nih.gov/pubmed/%s",
118 "RefSeq": "http://www.ncbi.nlm.nih.gov/nuccore/%s",
119 "SABIO-RK": "http://sabio.villa-bosch.de/kineticLawEntry.jsp?kinlawid=%s&viewData=true",
120 "SwissProt": "http://www.uniprot.org/uniprot/%s",
121 "Taxonomy": "http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=%s",
122 "ThreeDMET": "http://www.3dmet.dna.affrc.go.jp/bin2/show_data.e?acc=%s",
123 "URL": "%s",
124 }
125
126 CHOICES_GENETIC_CODE = (
127 ('1', 'Standard'),
128 ('2', 'Vertebrate'),
129 ('3', 'Yeast'),
130 ('4', 'Mold, protozoa, coelenterate mitochondria, mycoplasma, and spiroplasma'),
131 ('5', 'Invertebrate mitochondria'),
132 ('6', 'Ciliate, dasycladacean and hexamita'),
133 ('9', 'Echinoderm and flatworm mitochondria'),
134 ('10', 'Euplotid'),
135 ('11', 'Bacteria, archaea and plant plastids'),
136 ('12', 'Alternative yeast'),
137 ('13', 'Ascidian mitochondria'),
138 ('14', 'Alternative flatworm mitochondria'),
139 ('15', 'Blepharisma'),
140 ('16', 'Chlorophycean mitochondria'),
141 ('21', 'Trematode mitochondria'),
142 ('22', 'Scenedesmus obliquus mitochondria'),
143 ('23', 'Thraustochytrium mitochondria'),
144 ('24', 'Pterobranchia mitochondria'),
145 )
146
147 CHOICES_REACTION_DIRECTION = (
148 ('f', 'Forward'),
149 ('b', 'Backward'),
150 ('r', 'Reversible'),
151 )
152
153 CHOICES_SIGNAL_SEQUENCE_LOCATION = (
154 ('N', 'N-terminus'),
155 ('C', 'C-terminus'),
156 )
157
158 CHOICES_SIGNAL_SEQUENCE_TYPE = (
159 ('lipoprotein', 'Lipoprotein'),
160 ('secretory', 'Secretory'),
161 )
162
163 ''' END: CHOICES '''
164
165
166 options.DEFAULT_NAMES = options.DEFAULT_NAMES + (
167 'concrete_entry_model',
168 'hide_public',
169 'fieldsets',
170 'field_list',
171 'facet_fields',
172 'clean',
173 'validate_unique',
174 )
175
176 ''' BEGIN: validators '''
178 validators.RegexValidator(regex=r'^[ACGT]+$', message='Enter a valid DNA sequence consisting of only the letters A, C, G, and T')(seq)
179
181 if direction == 'f':
182 prop_name = 'kinetics_forward'
183 else:
184 prop_name = 'kinetics_backward'
185 kinetics = reaction[prop_name]
186
187 wids = []
188 for s in reaction['stoichiometry']:
189 if (direction == 'f' and s['coefficient'] < 0) or (direction == 'r' and s['coefficient'] > 0):
190 wids.append(s['molecule'])
191
192
193 usedKmMax = 0
194 usedVmax = 0
195 for match in re.finditer(r'([a-z][a-z0-9_]*)', kinetics['rate_law'], flags=re.I):
196 if match.group(1) == 'Vmax':
197 usedVmax = 1
198 elif match.group(1)[0:2] == 'Km':
199 if len(match.group(1)) == 2:
200 usedKmMax = max(usedKmMax, 1);
201 elif match.group(1)[2:].isnumeric():
202 usedKmMax = max(usedKmMax, int(float(match.group(1)[2:])))
203 else:
204 raise ValidationError({prop_name: 'Invalid rate law'})
205 elif match.group(1) not in wids:
206 raise ValidationError({prop_name: 'Invalid rate law, unknown molecule: "%s"' % (match.group(1), )})
207
208
209 if usedVmax and kinetics['vmax'] is None:
210 raise ValidationError({prop_name: 'Invalid rate law'})
211 if usedVmax and kinetics['vmax_unit'] not in ["1/min", "U/mg"]:
212 raise ValidationError({prop_name: 'Invalid rate law'})
213
214
215 if not ((kinetics['rate_law'] is None or kinetics['rate_law'] == '') or (kinetics['km'] == '' and usedKmMax == 0) or (kinetics['km'] != '' and usedKmMax == len(kinetics['km'].split(', ')))):
216 raise ValidationError({prop_name: 'Invalid rate law'})
217
218 '''
219 Test cases:
220 tests = [
221 "!(ATP)",
222 " ( ATP ) ",
223 "CTP ( ATP ) ",
224 "CTP | ( ATP ) ",
225 "CTP | ( ATP & !ATP & GTP ) ",
226 "CTP | ( ATP & !ATP & GTP)( ) ",
227 "CTP | ( ATP & !ATP & GTP ) | ()",
228 "CTP | ( ATP & !ATP & GTP ) | (H)",
229 "CTP | ( ATP & !ATP & GTP ) | (H) K",
230 "!!CTP",
231 "CTP | !!( ATP & !ATP & GTP ) | (H) & K",
232 "CTP | !( ATP & !ATP & GTP ) | (H) & K",
233 "CTP | !( ATP & (!ATP | UTP) & GTP ) | (H) & K",
234 ]
235 for test in tests:
236 try:
237 models.parse_regulatory_rule(test)
238 except:
239 print test
240 '''
242 from public.helpers import getModel, getEntry
243 import settings
244
245 pre = ''
246 blocks = []
247 posts = []
248 pattern = '%s'
249 begin = 0
250 end = 0
251 sense = 0
252
253 equation = equation or ''
254 equation = equation.replace(" ", "")
255
256 match = re.match(r'^([!\-]{0,1})\((.+)\)$', equation)
257 if match:
258 pattern = pattern.replace('%s', match.group(1) + '(%s)')
259 equation = match.group(2)
260
261 match = re.match(r'^([!\-]{0,1})(.+)$', equation)
262 if match:
263 pattern = pattern.replace('%s', match.group(1) + '%s')
264 equation = match.group(2)
265
266 if equation == '':
267 raise ValidationError({'regulatory_rule': 'Invalid regulatory rule'})
268
269
270 for i in range(len(equation)):
271 if equation[i] == '(':
272 sense += 1
273 if equation[i] == ')':
274 sense -= 1
275 if sense < 0:
276 raise ValidationError({'regulatory_rule': 'Invalid regulatory rule'})
277
278 if sense == 0:
279 if len(blocks) == 0:
280 pre = equation[0:begin]
281 if equation[i] in ["|", "&", "-", "+"]:
282 blocks.append(equation[begin:i])
283 posts.append(equation[i])
284 begin = i + 1
285 elif equation[i:i+2] in [">=", "<=", "=="]:
286 blocks.append(equation[begin:i])
287 posts.append(equation[i:i+1])
288 begin = i + 2
289 elif equation[i] in [">", "<"]:
290 blocks.append(equation[begin:i])
291 posts.append(equation[i])
292 begin = i + 1
293 blocks.append(equation[begin:])
294 posts.append('')
295
296
297 if sense != 0:
298 raise ValidationError({'regulatory_rule': 'Invalid regulatory rule'})
299
300
301 if len(blocks) == 1:
302 if '(' in blocks[0]:
303 raise ValidationError({'regulatory_rule': 'Invalid regulatory rule'})
304 elif '!' in blocks[0]:
305 raise ValidationError({'regulatory_rule': 'Invalid regulatory rule'})
306 elif blocks[0].isnumeric() or blocks[0] in ["true", "false"]:
307 return pattern % (pre + ' '.join([block + ' ' + post for block, post in zip(blocks, posts)]))
308
309 wid = blocks[0]
310 molecule = None
311 if all_obj_data is None:
312 molecule = getEntry(species_wid=species_wid, wid=wid)
313 elif all_obj_data.has_key(wid):
314 molecule = all_obj_data[wid]
315
316 if molecule is not None:
317 if isinstance(molecule, Entry):
318 molecule_model = molecule.__class__
319 else:
320 molecule_model = getModel(molecule['model_type'])
321 if issubclass(molecule_model, Molecule):
322 return pattern % (pre + ' '.join([block + ' ' + post for block, post in zip(blocks, posts)]))
323 else:
324 raise ValidationError({'regulatory_rule': 'Invalid regulatory rule, referenced invalid object %s' % wid})
325 try:
326 obj = Molecule.objects.get(species__wid=species_wid, wid=wid)
327 blocks[0] = '<a href="%s">%s</a>' % (obj.get_absolute_url(), wid, )
328 return pattern % (pre + ' '.join([block + ' ' + post for block, post in zip(blocks, posts)]))
329 except:
330 pass
331 raise ValidationError({'regulatory_rule': 'Invalid object "%s" in regulatory rule' % wid})
332
333
334 for i in range(len(blocks)):
335 blocks[i] = parse_regulatory_rule(blocks[i], all_obj_data, species_wid)
336
337 return pattern % (pre + ' '.join([block + ' ' + post for block, post in zip(blocks, posts)]))
338
339 ''' END: validators '''
343 user = OneToOneField(User)
344 affiliation = CharField(max_length=255, blank=True, default='', verbose_name='Affiliation')
345 website = URLField(max_length=255, blank=True, default='', verbose_name='Website')
346 phone = CharField(max_length=255, blank=True, default='', verbose_name='Phone')
347 address = CharField(max_length=255, blank=True, default='', verbose_name='Address')
348 city = CharField(max_length=255, blank=True, default='', verbose_name='City')
349 state = CharField(max_length=255, blank=True, default='', verbose_name='State')
350 zip = CharField(max_length=255, blank=True, default='', verbose_name='Zip')
351 country = CharField(max_length=255, blank=True, default='', verbose_name='Country')
352
358
359 ''' BEGIN: helper models '''
361 value = TextField(blank=True, default='', verbose_name='Value')
362 units = CharField(max_length=255, blank=True, null=True, verbose_name='Units')
363 is_experimentally_constrained = BooleanField(verbose_name='Is experimentally <br/>constrained')
364 species = CharField(max_length=255, blank=True, null=True, verbose_name='Species')
365 media = CharField(max_length=255, blank=True, null=True, verbose_name='Media')
366 pH = FloatField(blank=True, null=True, verbose_name='pH')
367 temperature = FloatField(blank=True, null=True, verbose_name='Temperature (C)')
368 comments = TextField(blank=True, default='', verbose_name='Comments')
369 references = ManyToManyField('Reference', blank=True, null=True, related_name='evidence', verbose_name='References')
370
371 species_component = ForeignKey('SpeciesComponent', verbose_name='Species omponent', related_name='+')
372
374 arr = []
375 for field in self.__class__._meta.fields:
376 if not field.auto_created and getattr(self, field.name) is not None and not (isinstance(getattr(self, field.name), (str, unicode, )) and getattr(self, field.name) == ''):
377 arr.append('%s: %s' % (field.name, unicode(getattr(self, field.name))))
378 for field in self.__class__._meta.many_to_many:
379 arr.append('%s: %s' % (field.name, unicode(getattr(self, field.name).all())))
380 return ', '.join(arr)
381
386
387 -class EntryData(Model):
388
389 - def __unicode__(self):
390 arr = []
391 txt = unicode('')
392 nFields = 0
393 for field in self.__class__._meta.fields:
394 if not field.auto_created:
395 nFields += 1
396 try:
397 txt = unicode(getattr(self, field.name))
398 arr.append('%s: %s' % (field.name, txt))
399 except ObjectDoesNotExist:
400 pass
401 for field in self.__class__._meta.many_to_many:
402 nFields += 1
403 txt = unicode(getattr(self, field.name).all())
404 arr.append('%s: %s' % (field.name, txt))
405
406 if nFields == 1:
407 return txt
408 else:
409 return ', '.join(arr)
410
413
414 -class EvidencedEntryData(EntryData):
415 evidence = ManyToManyField(Evidence, blank=True, null=True, verbose_name='Evidence')
416
419
420
421 -class EntryBooleanData(EvidencedEntryData):
422 value = BooleanField(verbose_name='Value')
423
428
429 -class EntryCharData(EvidencedEntryData):
430 value = CharField(max_length = 255, blank=True, default='', verbose_name='Value')
431 units = CharField(max_length = 255, blank=True, default='', verbose_name='Units')
432
437
438 -class EntryFloatData(EvidencedEntryData):
439 value = FloatField(verbose_name='Value')
440 units = CharField(max_length = 255, blank=True, default='', verbose_name='Units')
441
446
447 -class EntryPositiveFloatData(EvidencedEntryData):
448 value = FloatField(verbose_name='Value', validators=[validators.MinValueValidator(0)])
449 units = CharField(max_length = 255, blank=True, default='', verbose_name='Units')
450
455
456 -class EntryTextData(EvidencedEntryData):
457 value = TextField(blank=True, default='', verbose_name='Value')
458 units = CharField(max_length = 255, blank=True, default='', verbose_name='Units')
459
464
479
481 concentration = FloatField(verbose_name='Concentration (mmol gDCW<sup>-1</sup>)', validators=[validators.MinValueValidator(0)])
482 compartment = ForeignKey('Compartment', related_name='biomass_compositions', verbose_name='Compartment')
483
488
489 -class Codon(EvidencedEntryData):
499
509
518
528
538
547
556
558 rate_law = CharField(blank=True, default='', max_length=255, verbose_name='Rate law')
559 km = CharField(max_length=255, blank=True, verbose_name='K<sub>m</sub> (μM)', validators=[validators.RegexValidator(r'^([0-9\.]+)(, [0-9\.]+)*$')])
560 vmax = FloatField(blank=True, null=True, verbose_name='V<sub>max</sub>', validators=[validators.MinValueValidator(0)])
561 vmax_unit = CharField(blank=True, max_length=255, choices=CHOICES_VMAX_UNITS, verbose_name='V<sub>max</sub> Unit')
562
572
577
586
596
606
616
627
639
649
659
671
679
680 ''' END: helper models '''
681
682
683 ''' BEGIN: Base classes for all knowledge base objects '''
685 model_type = CharField(max_length=255, editable=False, verbose_name='Model')
686 wid = SlugField(max_length=150, verbose_name='WID', validators=[validators.validate_slug])
687 name = CharField(max_length=255, blank=True, default='', verbose_name='Name')
688 synonyms = ManyToManyField(Synonym, blank=True, null=True, related_name='entry', verbose_name='Synonyms')
689 cross_references = ManyToManyField(CrossReference, blank=True, null=True, related_name='cross_referenced_entries', verbose_name='Cross references')
690 comments = TextField(blank=True, default='', verbose_name='Comments')
691
692 created_user = ForeignKey(User, related_name='+', editable=False, verbose_name='Created user')
693 created_date = DateTimeField(auto_now=False, auto_now_add=True, verbose_name='Created date')
694
695 last_updated_user = ForeignKey(User, related_name='+', editable=False, verbose_name='Last updated user')
696 last_updated_date = DateTimeField(auto_now=True, auto_now_add=True, verbose_name='Last updated date')
697
698 - def __unicode__(self):
700
701 - def natural_key(self):
703
704 - def save(self, *args, **kwargs):
705 setattr(self, 'model_type', self.__class__.__name__)
706 super(Entry, self).save(*args, **kwargs)
707
708
709 - def get_as_html_synonyms(self, is_user_anonymous):
710 return format_list_html([x.name for x in self.synonyms.all()], comma_separated=True)
711
712 - def get_as_html_cross_references(self, is_user_anonymous):
713 results = []
714 for cr in self.cross_references.all():
715 results.append('%s: <a href="%s">%s</a>' % (cr.source, CROSS_REFERENCE_SOURCE_URLS[cr.source] % cr.xid, cr.xid))
716 return format_list_html(results, separator=', ')
717
718 - def get_as_html_created_user(self, is_user_anonymous):
719 if is_user_anonymous:
720 return '%s' % (self.created_date.strftime("%Y-%m-%d %H:%M:%S"))
721 else:
722 return '<a href="%s">%s %s</a> on %s' % (self.created_user.get_absolute_url(), self.created_user.first_name, self.created_user.last_name, self.created_date.strftime("%Y-%m-%d %H:%M:%S"))
723
724 - def get_as_html_last_updated_user(self, is_user_anonymous):
725 if is_user_anonymous:
726 return '%s' % (self.last_updated_date.strftime("%Y-%m-%d %H:%M:%S"))
727 else:
728 return '<a href="%s">%s %s</a> on %s' % (self.last_updated_user.get_absolute_url(), self.last_updated_user.first_name, self.last_updated_user.last_name, self.last_updated_date.strftime("%Y-%m-%d %H:%M:%S"))
729
730
732 concrete_entry_model = False
733 fieldsets = [
734 ('Type', {'fields': ['model_type']}),
735 ('Name', {'fields': ['wid', 'name', 'synonyms', 'cross_references']}),
736 ('Comments', {'fields': ['comments']}),
737 ('Metadata', {'fields': [{'verbose_name': 'Created', 'name': 'created_user'}, {'verbose_name': 'Last updated', 'name': 'last_updated_user'}]}),
738 ]
739 field_list = [
740 'id', 'wid', 'name', 'synonyms', 'cross_references', 'comments', 'created_user', 'created_date', 'last_updated_user', 'last_updated_date',
741 ]
742 facet_fields = []
743 ordering = ['wid']
744 get_latest_by = 'createdDate'
745 verbose_name = 'Entry'
746 verbose_name_plural = 'Entries'
747
749
750 parent_ptr_entry = OneToOneField(Entry, related_name='child_ptr_species_component', parent_link=True, verbose_name='Entry')
751
752
753 species = ForeignKey('Species', related_name='components', verbose_name='Species')
754 type = ManyToManyField('Type', blank=True, null=True, related_name='members', verbose_name='Type')
755 references = ManyToManyField('Reference', blank=True, null=True, related_name='referenced_entries', verbose_name='References')
756
757
758 @permalink
760 return ('public.views.detail', (), {'species_wid':self.species.wid, 'wid': self.wid})
761
764
765
771
779
781 results = {}
782 for r in self.get_all_references():
783 key = r.authors + ' ' + r.editors
784 results[key] = r.get_citation(True)
785
786 keys = results.keys()
787 keys.sort()
788 ordered_results = []
789 for key in keys:
790 ordered_results.append(results[key])
791 return format_list_html(ordered_results, numbered=True, force_list=True)
792
793
809
811
812 parent_ptr_species_component = OneToOneField(SpeciesComponent, related_name='child_ptr_molecule', parent_link=True, verbose_name='Species component')
813
814
815
816
820
823
826
829
830
833
836
839
842
844 if hasattr(self, 'pi'):
845 return self.pi
846 else:
847 return self.get_pi()
848
857
866
868 results = []
869 for obj in self.protein_complex_biosythesis_participants.all():
870 if len(obj.protein_complexes.all()) == 0:
871 continue
872 pc = obj.protein_complexes.all()[0]
873 results.append('<a href="%s">%s</a><br/>%s' % (pc.get_absolute_url(), pc.name, pc.get_as_html_biosynthesis(is_user_anonymous)))
874 return format_list_html(results)
875
876
897
899
900 parent_ptr_molecule = OneToOneField(Molecule, related_name='child_ptr_protein', parent_link=True, verbose_name='Molecule')
901
902
903 prosthetic_groups = ManyToManyField(ProstheticGroupParticipant, blank=True, null=True, related_name='proteins', verbose_name='Prosthetic groups')
904 chaperones = ManyToManyField('self', symmetrical=False, blank=True, null=True, related_name='chaperone_substrates', verbose_name='Chaperones')
905 dna_footprint = ForeignKey(DNAFootprint, null=True, blank=True, related_name='proteins', verbose_name='DNA footprint')
906 regulatory_rule = ForeignKey(EntryCharData, null=True, blank=True, on_delete=SET_NULL, verbose_name='Regulatory rule', related_name='+')
907
908
914
920
926
931
936
942
951
954
955
957 concrete_entry_model = False
958 fieldsets = [
959 ('Type', {'fields': ['model_type']}),
960 ('Name', {'fields': ['wid', 'name', 'synonyms', 'cross_references']}),
961 ('Classification', {'fields': ['type']}),
962 ('Structure', {'fields': ['prosthetic_groups', 'chaperones', 'dna_footprint']}),
963 ('Regulation', {'fields': ['regulatory_rule']}),
964