Coverage for rest_api/models.py: 92%

249 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-10 03:22 +0000

1from enum import Enum 

2 

3from django.db import models 

4from django.db.models import Q 

5from django.db.models import UniqueConstraint 

6from django.utils import timezone 

7from django.utils.translation import gettext_lazy as _ 

8 

9 

10class Sequence(models.Model): 

11 """ 

12 Represents a genetic sequence (sample) identified by a unique hash. 

13 

14 Attributes: 

15 seqhash (CharField): Identifier for the sequence (max length 200). 

16 length (IntegerField, optional): Length of the sequence 

17 init_upload_date (DateTimeField, auto_now=True): Timestamp of the initial upload. 

18 last_update_date (DateTimeField, optional): Timestamp of the last update to the sequence. 

19 """ 

20 

21 seqhash = models.CharField(max_length=200) 

22 name = models.CharField(unique=True, max_length=200) 

23 init_upload_date = models.DateTimeField(auto_now=True) 

24 last_update_date = models.DateTimeField(blank=True, null=True) 

25 length = models.IntegerField(blank=True, null=True) 

26 

27 class Meta: 

28 db_table = "sequence" 

29 indexes = [ 

30 models.Index(fields=["name"]), 

31 models.Index(fields=["init_upload_date"]), 

32 models.Index(fields=["last_update_date"]), 

33 ] 

34 

35 

36class Alignment(models.Model): 

37 """ 

38 Represents an alignment between a sequence and a replicon. 

39 

40 Attributes: 

41 replicon (ForeignKey): Reference to the associated Replicon 

42 sequence (ForeignKey): Reference to the Sequence aligned with Replicon 

43 

44 Constraints: 

45 - Unique constraint on (replicon, sequence) to prevent duplicate alignments. 

46 

47 Indexes: 

48 - Index on (replicon, sequence) to optimize queries. 

49 """ 

50 

51 replicon = models.ForeignKey("Replicon", models.CASCADE) 

52 sequence = models.ForeignKey("Sequence", models.CASCADE, related_name="alignments") 

53 

54 class Meta: 

55 indexes = [ 

56 models.Index( 

57 fields=["replicon", "sequence"], 

58 ) 

59 ] 

60 constraints = [ 

61 UniqueConstraint( 

62 name="unique_alignment", 

63 fields=["replicon", "sequence"], 

64 ), 

65 ] 

66 db_table = "alignment" 

67 

68 

69class AnnotationType(models.Model): 

70 """ 

71 Represents an annotation for nucleotide mutations, classified using SnpEff. 

72 

73 Attributes: 

74 seq_ontology (CharField): Sequence ontology term describing the annotation (max length 50). 

75 region (CharField, optional): Genomic region affected (max length 50, nullable). 

76 impact (CharField): Impact classification (e.g., HIGH, MODERATE, LOW) (max length 20). 

77 mutations (ManyToManyField): Related nucleotide mutations annotated with this type. 

78 

79 Constraints: 

80 - Ensures unique annotations based on (seq_ontology, region, impact). 

81 - Allows uniqueness for (seq_ontology, impact) when region is NULL. 

82 """ 

83 

84 seq_ontology = models.CharField(max_length=50) 

85 region = models.CharField(max_length=50, blank=True, null=True) 

86 impact = models.CharField(max_length=20) 

87 mutations = models.ManyToManyField("NucleotideMutation", related_name="annotations") 

88 

89 def __str__(self) -> str: 

90 return f"{self.seq_ontology} {self.impact} {self.region if self.region else ''}".strip() 

91 

92 class Meta: 

93 db_table = "annotation_type" 

94 constraints = [ 

95 UniqueConstraint( 

96 name="unique_annotation", 

97 fields=["seq_ontology", "region", "impact"], 

98 ), 

99 UniqueConstraint( 

100 name="unique_annotation_null_region", 

101 fields=["seq_ontology", "impact"], 

102 condition=models.Q(region__isnull=True), 

103 ), 

104 ] 

105 

106 

107class Replicon(models.Model): 

108 """ 

109 Represents a replicon, a genetic element/segment/chromosome 

110 Filled with information from the gene bank file via gbk_import 

111 

112 Attributes: 

113 length (BigIntegerField, optional): Length of the replicon in base pairs. 

114 sequence (TextField): Full nucleotide sequence of the replicon. 

115 accession (CharField, unique, required): (NCBI) accession of sequence. 

116 description (CharField, optional): Additional description of the replicon. 

117 type (CharField, optional): Type of replicon (e.g., chromosome, plasmid). 

118 segment_number (BigIntegerField, optional): Segment number if applicable. 

119 reference (ForeignKey): id of linked reference genome. 

120 

121 Constraints: 

122 - Ensures unique accession numbers if provided. 

123 """ 

124 

125 length = models.BigIntegerField(blank=True, null=True) 

126 sequence = models.TextField() 

127 accession = models.CharField(max_length=50, unique=True) 

128 description = models.CharField(max_length=400, blank=True, null=True) 

129 type = models.CharField(max_length=50, blank=True, null=True) 

130 segment_number = models.BigIntegerField(blank=True, null=True) 

131 reference = models.ForeignKey("Reference", models.CASCADE) 

132 

133 class Meta: 

134 db_table = "replicon" 

135 

136 

137class Gene(models.Model): 

138 """ 

139 Represents a gene located on a replicon. 

140 Filled with information from the gene bank file via gbk_import 

141 

142 Attributes: 

143 description (CharField, optional): Functional description of the gene (max length 100). 

144 start (BigIntegerField): Start position on the replicon (counting starts with 0). 

145 end (BigIntegerField): End position on the replicon. 

146 forward_strand (BooleanField): Indicates if the gene is on the forward strand. 

147 symbol (CharField, optional): Gene symbol/name or abbreviation (max length 50). 

148 accession (CharField): unique gene identifier, locus_tag or gene name (=symbol) per replicon 

149 sequence (TextField, optional): Nucleotide sequence of the gene. 

150 replicon (ForeignKey): Reference to the replicon where the gene is located. 

151 type (TextChoices, optional): Classification of the gene (e.g., CDS, rRNA, tRNA). 

152 

153 Constraints: 

154 - Ensures each gene is associated with a specific replicon. 

155 """ 

156 

157 description = models.CharField(max_length=100, blank=True, null=True) 

158 start = models.BigIntegerField() 

159 end = models.BigIntegerField() 

160 forward_strand = models.BooleanField() 

161 symbol = models.CharField(max_length=50, unique=False, blank=False, null=False) 

162 accession = models.CharField(max_length=100, blank=False, null=False) 

163 sequence = models.TextField(blank=True, null=True) 

164 replicon = models.ForeignKey(Replicon, models.CASCADE) 

165 

166 class GeneTypes(models.TextChoices): 

167 CDS = "CDS" 

168 rRNA = "rRNA" 

169 tRNA = "tRNA" 

170 ncRNA = "ncRNA" 

171 miRNA = "miRNA" 

172 misc_RNA = "misc_RNA" 

173 # tmRNA = "tmRNA" 

174 

175 type = models.CharField(choices=GeneTypes, blank=True, null=True) 

176 

177 class Meta: 

178 db_table = "gene" 

179 constraints = [ 

180 models.UniqueConstraint( 

181 fields=["replicon", "accession"], name="unique_accession_per_replicon" 

182 ), 

183 ] 

184 

185 

186class CDS(models.Model): 

187 """ 

188 Represents a Coding Sequence (CDS) associated with a gene. 

189 Filled with information from the gene bank file via gbk_import 

190 

191 Attributes: 

192 accession (CharField, required, unique): Unique accession nid of gene. 

193 sequence (TextField, optional): Nucleotide sequence of the CDS. 

194 gene (ForeignKey): Reference to the associated gene (CASCADE deletion). 

195 description (CharField, optional): Functional description of the CDS (max length 100). 

196 

197 Constraints: 

198 - Ensures each CDS is linked to a specific gene. 

199 - Enforces uniqueness on the accession number if provided. 

200 """ 

201 

202 accession = models.CharField(max_length=50, unique=True) 

203 sequence = models.TextField(blank=True, null=True) 

204 gene = models.ForeignKey(Gene, models.CASCADE) 

205 description = models.CharField(max_length=100, blank=True, null=True) 

206 

207 class Meta: 

208 db_table = "cds" 

209 

210 

211class CDSSegment(models.Model): 

212 """ 

213 Represents a segment of a Coding Sequence (CDS), useful for cases where 

214 - a CDS is split across multiple regions of a gene 

215 - or multiple CDSs from one gene 

216 - or ribosomal slippage 

217 Filled with information from the gene bank file via gbk_import 

218 

219 Attributes: 

220 cds (ForeignKey): Reference to the associated CDS (CASCADE deletion). 

221 order (BigIntegerField): Defines the sequential order of segments for final CDS product. 

222 start (BigIntegerField): Start position of the segment within the nt sequence. 

223 end (BigIntegerField): End position of the segment within the nt sequence. 

224 forward_strand (BooleanField): Indicates whether the segment is on the forward strand. 

225 

226 Constraints: 

227 - Ensures each segment of a CDS has a unique order within the same CDS. 

228 """ 

229 

230 cds = models.ForeignKey(CDS, models.CASCADE, related_name="cds_segments") 

231 order = models.BigIntegerField() 

232 start = models.BigIntegerField() 

233 end = models.BigIntegerField() 

234 forward_strand = models.BooleanField() 

235 

236 class Meta: 

237 db_table = "cds_segment" 

238 constraints = [ 

239 UniqueConstraint( 

240 name="unique_cds_segment", 

241 fields=["cds", "order"], 

242 ), 

243 ] 

244 

245 

246class Peptide(models.Model): 

247 """ 

248 Represents peptides that are generated from larger cds by cutting them into different parts 

249 Filled with information from the gene bank file via gbk_import 

250 

251 Attributes: 

252 cds (ForeignKey): Reference to the associated CDS (CASCADE deletion). 

253 description (CharField, optional): Description (product tag) of peptide=final virus protein (enum mat_peptide or sig_peptide). 

254 

255 """ 

256 

257 cds = models.ForeignKey(CDS, models.CASCADE, related_name="peptides") 

258 description = models.CharField(max_length=100, blank=True, null=True) 

259 

260 class PeptideTypes(models.TextChoices): 

261 mat_peptide = "mat_peptide" 

262 sig_peptide = "sig_peptide" 

263 

264 type = models.CharField(choices=PeptideTypes) 

265 

266 class Meta: 

267 db_table = "peptide" 

268 

269 

270class PeptideSegment(models.Model): 

271 """ 

272 Represents a segment of a Peptide, if CDS has joins, here too 

273 

274 Filled with information from the gene bank file via gbk_import 

275 

276 Attributes: 

277 cds (ForeignKey): Reference to the associated CDS (CASCADE deletion). 

278 order (BigIntegerField): Defines the sequential order of segments for final CDS product. 

279 start (BigIntegerField): Start position of the segment within the nt sequence. 

280 end (BigIntegerField): End position of the segment within the nt sequence. 

281 forward_strand (BooleanField): Indicates whether the segment is on the forward strand. 

282 

283 Constraints: 

284 - Ensures each segment of a CDS has a unique order within the same CDS. 

285 """ 

286 

287 peptide = models.ForeignKey( 

288 Peptide, models.CASCADE, related_name="peptide_segments" 

289 ) 

290 order = models.BigIntegerField() 

291 start = models.BigIntegerField() 

292 end = models.BigIntegerField() 

293 start_cds = models.BigIntegerField() 

294 end_cds = models.BigIntegerField() 

295 

296 class Meta: 

297 db_table = "peptide_segment" 

298 constraints = [ 

299 UniqueConstraint( 

300 name="unique_peptide_segment", 

301 fields=["peptide", "order"], 

302 ), 

303 ] 

304 

305 

306class GeneSegment(models.Model): 

307 """ 

308 Represents a segment of a gene, useful for split or modular genes. 

309 Filled with information from the gene bank file via gbk_import 

310 

311 Attributes: 

312 gene (ForeignKey): Reference to the associated gene (CASCADE deletion). 

313 order (BigIntegerField): Defines the order of segments within the gene. 

314 start (BigIntegerField): Start position of the segment within the gene. 

315 end (BigIntegerField): End position of the segment within the gene. 

316 forward_strand (BooleanField): Indicates if the segment is on the forward strand. 

317 

318 Constraints: 

319 - Ensures each gene segment has a unique order within its gene. 

320 """ 

321 

322 gene = models.ForeignKey(Gene, models.CASCADE) 

323 order = models.BigIntegerField() 

324 start = models.BigIntegerField() 

325 end = models.BigIntegerField() 

326 forward_strand = models.BooleanField() 

327 

328 class Meta: 

329 db_table = "gene_segment" 

330 constraints = [ 

331 UniqueConstraint( 

332 name="unique_gene_segment", 

333 fields=["gene", "order"], 

334 ), 

335 ] 

336 

337 

338class Lineage(models.Model): 

339 """ 

340 Represents the hierarchical lineage information of a pathogen. 

341 Import via cli command and import_lineage.py 

342 

343 Attributes: 

344 name (CharField): The name of the lineage. 

345 parent (ForeignKey): A reference to the parent lineage (if any), forming a tree-like structure. 

346 

347 Methods: 

348 get_sublineages: Returns all sublineages, including direct children and recursive descendants. 

349 get_sublineages_from_list (static): A helper method to retrieve sublineages from a list of lineages. 

350 

351 Constraints: 

352 - Ensures uniqueness of lineage name and parent combination. 

353 - Allows lineage to have unique names with a null parent. 

354 """ 

355 

356 name = models.CharField(max_length=50) # not unique because of recombinants 

357 parent = models.ForeignKey("self", models.CASCADE, blank=True, null=True) 

358 reference = models.ForeignKey("Reference", models.CASCADE) 

359 

360 def get_sublineages(self) -> set: 

361 lineages = set([self]) 

362 lineages.update( 

363 Lineage.get_sublineages_from_list( 

364 Lineage.objects.filter(name=self.name, reference=self.reference_id) 

365 ) 

366 ) 

367 return lineages 

368 

369 @staticmethod 

370 def get_sublineages_from_list(lineages): 

371 # Iterative breadth-first descent over the name-based parent links. 

372 # Values are materialised each round (plain lists, not lazy querysets) to 

373 # avoid pathologically nested subqueries, and a visited-guard guarantees 

374 # termination. Stays within the same reference(s): lineage names collide 

375 # across pathogens (e.g. RSV-A "A" vs Influenza H3N2 "A"). 

376 ref_ids = set(lineages.values_list("reference_id", flat=True)) 

377 frontier = set(lineages.values_list("name", flat=True)) 

378 seen_names = set(frontier) 

379 lineages_set = set() 

380 while frontier: 

381 children = list( 

382 Lineage.objects.filter( 

383 parent__name__in=frontier, reference_id__in=ref_ids 

384 ) 

385 ) 

386 lineages_set.update(children) 

387 frontier = {c.name for c in children if c.name not in seen_names} 

388 seen_names.update(frontier) 

389 return lineages_set 

390 

391 def __str__(self) -> str: 

392 return self.name 

393 

394 class Meta: 

395 db_table = "lineage" 

396 constraints = [ 

397 UniqueConstraint( 

398 name="unique_lineage", 

399 fields=["name", "parent", "reference"], 

400 ), 

401 UniqueConstraint( 

402 name="unique_lineage_parent_null", 

403 fields=["name", "reference"], 

404 condition=models.Q(parent__isnull=True), 

405 ), 

406 ] 

407 

408 

409class Reference(models.Model): 

410 """ 

411 Represents a reference of a organism used for sequence alignment. 

412 Filled with information from the gene bank file via gbk_import 

413 

414 Attributes: 

415 name (CharField, optional, unique): Name of the gene bank file (max length 50). 

416 accession (CharField, required, unique): Accession number of the gene bank file (max length 50). 

417 description (CharField, optional): Description of the reference (max length 400). 

418 organism (CharField, required): Name of the organism from which the reference is derived (max length 50). 

419 mol_type (CharField, optional): Type of molecule (e.g., DNA, RNA) for the reference (max length 50). 

420 isolate (CharField, optional): Isolate or strain information (max length 50). 

421 host (CharField, optional): Host organism for the reference sequence (max length 50). 

422 db_xref (CharField, optional, unique): Database cross-reference identifier (max length 50). 

423 country (CharField, optional): Country of origin of the sample (max length 50). 

424 collection_date (DateField, optional): Date when the sample was collected. 

425 translation_id (IntegerField): ID used for translation of the reference sequence. 

426 

427 Constraints: 

428 - Ensures unique identifiers for name, accession, and db_xref. 

429 """ 

430 

431 name = models.CharField(max_length=600, unique=True, blank=True, null=True) 

432 accession = models.CharField(max_length=50, unique=True) 

433 description = models.CharField(max_length=400, blank=True, null=True) 

434 organism = models.CharField(max_length=50) 

435 mol_type = models.CharField(max_length=50, blank=True, null=True) 

436 isolate = models.CharField(max_length=50, blank=True, null=True) 

437 host = models.CharField(max_length=50, blank=True, null=True) 

438 db_xref = models.CharField(max_length=50, blank=True, null=True, unique=True) 

439 country = models.CharField(max_length=50, blank=True, null=True) 

440 collection_date = models.DateField(blank=True, null=True) 

441 translation_id = models.IntegerField() 

442 

443 class Meta: 

444 db_table = "reference" 

445 

446 

447class Property(models.Model): 

448 """ 

449 Represents a customizable property that can be defined by users to capture flexible data characteristics. 

450 

451 Attributes: 

452 name (CharField, unique): The name of the property, must be unique. 

453 datatype (CharField): The data type of the property (e.g., integer, string, etc.). 

454 querytype (CharField, optional): The type of query that can be performed with the property (e.g., range, exact match). 

455 description (CharField, optional): A description of the property for documentation purposes. 

456 default (CharField, optional): provide default property value 

457 

458 Constraints: 

459 - Ensures that the property name is unique. 

460 """ 

461 

462 name = models.CharField(max_length=50, unique=True) 

463 datatype = models.CharField(max_length=50) 

464 querytype = models.CharField(max_length=50, blank=True, null=True) 

465 description = models.CharField(max_length=400, blank=True, null=True) 

466 default = models.CharField(max_length=50, blank=True, null=True) 

467 

468 class Meta: 

469 db_table = "property" 

470 

471 

472class Sample(models.Model): 

473 """ 

474 Represents a sample that has undergone sequencing and via cli: alignment, mutation calling, and annotation. 

475 Metadata fields (sequencing_tech, country, host, zip_code, lab, lineage, collection_date, data_set) filled via metadata import of csv. 

476 Other fields via sample_entry_job.py, sample_import.py 

477 

478 Attributes: 

479 name (CharField, unique): Unique identifier for the sample. 

480 datahash (CharField): (Not used at the moment) hash all metadata (include property metadata) for comparison before updating 

481 sequences (ManyToManyField): The sequences associated with the sample. 

482 sequencing_tech (CharField, optional): Sequencing technology used. 

483 country (CharField, optional): Country of origin for the sample. 

484 host (CharField, optional): The host organism of the sample. 

485 zip_code (CharField, optional): The postal code where the sample was collected. 

486 lab (CharField, optional): The lab where the sample was processed. 

487 lineage (CharField, optional): Lineage identifier for the sample. 

488 genome_completeness (CharField, optional): Indicator of the genome completeness. 

489 collection_date (DateField, optional): The date the sample was collected. 

490 data_set (CharField, optional): The data set the sample is part of, e.g. rKI, Gisaid. 

491 properties (ManyToManyField, optional): User-defined properties assigned to the sample. 

492 

493 Constraints: 

494 - Ensures that each sample has a unique name. 

495 """ 

496 

497 name = models.CharField(max_length=100, unique=True) 

498 lineage = models.CharField(max_length=50, blank=True, null=True) 

499 genome_completeness = models.CharField(max_length=50, blank=True, null=True) 

500 collection_date = models.DateField(blank=True, null=True) 

501 data_set = models.CharField(max_length=50, blank=True, null=True) 

502 sequencing_tech = models.CharField(max_length=50, blank=True, null=True) 

503 country = models.CharField(max_length=50, blank=True, null=True) 

504 host = models.CharField(max_length=50, blank=True, null=True) 

505 zip_code = models.CharField(max_length=50, blank=True, null=True) 

506 lab = models.CharField(max_length=50, blank=True, null=True) 

507 init_upload_date = models.DateTimeField(auto_now=True) 

508 last_update_date = models.DateTimeField(blank=True, null=True) 

509 datahash = models.CharField(max_length=50) 

510 

511 sequences = models.ManyToManyField(Sequence, related_name="samples") 

512 

513 class Meta: 

514 db_table = "sample" 

515 indexes = [ 

516 models.Index(fields=["name"]), 

517 models.Index(fields=["sequencing_tech"]), 

518 models.Index(fields=["country"]), 

519 models.Index(fields=["host"]), 

520 models.Index(fields=["zip_code"]), 

521 models.Index(fields=["lab"]), 

522 models.Index(fields=["lineage"]), 

523 models.Index(fields=["genome_completeness"]), 

524 models.Index(fields=["collection_date"]), 

525 models.Index(fields=["data_set"]), 

526 models.Index(fields=["init_upload_date"]), 

527 models.Index(fields=["last_update_date"]), 

528 ] 

529 

530 def save(self, *args, **kwargs): 

531 if self.pk: # Check if this is an update to an existing record 

532 self.last_update_date = timezone.now() 

533 super().save(*args, **kwargs) 

534 

535 

536class Sample2Property(models.Model): 

537 """ 

538 Represents a relationship between a sample and a property, allowing the association 

539 of multiple properties to a single sample, with flexibility in value types. 

540 

541 Attributes: 

542 property (ForeignKey): The property being associated with the sample. 

543 sample (ForeignKey): The sample to which the property is associated. 

544 value_integer (BigIntegerField, optional): Integer value for the property. 

545 value_float (FloatField, optional): Floating-point value for the property. 

546 value_text (TextField, optional): Text value for the property. 

547 value_varchar (CharField, optional): Varchar value for the property. 

548 value_blob (BinaryField, optional): Binary data value for the property. 

549 value_date (DateField, optional): Date value for the property. 

550 value_zip (CharField, optional): Zip code value for the property. 

551 

552 Constraints: 

553 - Ensures that each combination of property and sample is unique. 

554 """ 

555 

556 property = models.ForeignKey(Property, models.CASCADE) 

557 sample = models.ForeignKey(Sample, models.CASCADE, related_name="properties") 

558 value_integer = models.BigIntegerField(blank=True, null=True) 

559 value_float = models.FloatField(blank=True, null=True) 

560 value_text = models.TextField(blank=True, null=True) 

561 value_varchar = models.CharField(max_length=400, blank=True, null=True) 

562 value_blob = models.BinaryField(blank=True, null=True) 

563 value_date = models.DateField(blank=True, null=True) 

564 value_zip = models.CharField(max_length=100, blank=True, null=True) 

565 

566 class Meta: 

567 db_table = "sample2property" 

568 constraints = [ 

569 UniqueConstraint( 

570 name="unique_property2sample", 

571 fields=["property", "sample"], 

572 ), 

573 ] 

574 

575 

576class NucleotideMutation(models.Model): 

577 """ 

578 Represents a nucleotide mutation within a replicon, storing the reference nt 

579 and alternative nucleotides, along with mutation position(s). Stores alignments 

580 that contain this mutation. 

581 

582 Attributes: 

583 replicon (ForeignKey): The replicon in which the mutation is located. 

584 ref (TextField): The reference nucleotide(s) . 

585 alt (TextField): The alternative nucleotide. 

586 start (BigIntegerField): The starting position of the mutation in ref(couting is 0 based). 

587 end (BigIntegerField): The ending position of the mutation in ref. 

588 is_frameshift (BooleanField): Indicates if the mutation is a frameshift mutation. 

589 alignments (ManyToManyField): Many-to-many relationship with Alignment (representing samples) instances. 

590 

591 Constraints: 

592 - Ensures that the combination of `ref`, `alt`, `start`, `end`, and `replicon` 

593 is unique for each mutation in the replicon. 

594 """ 

595 

596 replicon = models.ForeignKey(Replicon, models.CASCADE, blank=True, null=True) 

597 ref = models.TextField() 

598 alt = models.TextField() 

599 start = models.BigIntegerField() 

600 end = models.BigIntegerField() 

601 is_frameshift = models.BooleanField(default=False) 

602 

603 alignments = models.ManyToManyField( 

604 Alignment, 

605 related_name="nucleotide_mutations", 

606 ) 

607 

608 def __str__(self) -> str: 

609 return f"{self.start}-{self.end} {self.ref}>{self.alt} {self.is_frameshift}" 

610 

611 class Meta: 

612 db_table = "nucleotide_mutation" 

613 indexes = [ 

614 models.Index(fields=["start"]), 

615 models.Index(fields=["end"]), 

616 models.Index(fields=["ref"]), 

617 models.Index(fields=["alt"]), 

618 ] 

619 constraints = [ 

620 UniqueConstraint( 

621 name="unique_nt_mutation", 

622 fields=["ref", "alt", "start", "end", "replicon"], 

623 ) 

624 ] 

625 

626 

627class AminoAcidMutation(models.Model): 

628 """ 

629 Represents an amino acid mutation, storing information about the mutation's 

630 position in ref, the reference and alternative amino acids, 

631 and links to nucleotide mutations resulting in cds mutation. 

632 Alignments represent samples with this cds mutation 

633 

634 Attributes: 

635 cds (ForeignKey): The coding sequence in which the mutation is located. 

636 ref (TextField): The reference amino acid 

637 alt (TextField): The alternative amino acid/ the mutation 

638 start (BigIntegerField): The start position of the mutation in ref. 

639 end (BigIntegerField): The end position of the mutation in ref. 

640 parent (ManyToManyField): Many-to-many relationship with nucleotide mutations (multiple mutations can result in one amino acid mutation). 

641 alignments (ManyToManyField): Many-to-many relationship with alignments (samples) containing this mutation. 

642 

643 Constraints: 

644 - Ensures that the combination of `ref`, `alt`, `start`, `end`, `cds`, and `replicon` 

645 is unique for each mutation in the replicon and CDS. 

646 """ 

647 

648 cds = models.ForeignKey(CDS, models.CASCADE) 

649 ref = models.TextField() 

650 alt = models.TextField() 

651 start = models.BigIntegerField() 

652 end = models.BigIntegerField() 

653 parent = models.ManyToManyField(NucleotideMutation) 

654 alignments = models.ManyToManyField( 

655 Alignment, 

656 related_name="amino_acid_mutations", 

657 ) 

658 

659 def __str__(self) -> str: 

660 return f"{self.start}-{self.end} {self.ref}>{self.alt}" 

661 

662 class Meta: 

663 db_table = "amino_acid_mutation" 

664 indexes = [ 

665 models.Index(fields=["cds"]), 

666 models.Index(fields=["start"]), 

667 models.Index(fields=["end"]), 

668 models.Index(fields=["ref"]), 

669 models.Index(fields=["alt"]), 

670 ] 

671 constraints = [ 

672 UniqueConstraint( 

673 name="unique_aa_mutation", 

674 fields=["ref", "alt", "start", "end", "cds"], 

675 ), 

676 ] 

677 

678 

679class ProcessingJob(models.Model): 

680 class ImportType(models.TextChoices): 

681 QUEUED = "Q", _("Queued") 

682 IN_PROGRESS = "IP", _("In Progress") 

683 COMPLETED = "C", _("Completed") 

684 FAILED = "F", _("Failed") 

685 

686 job_name = models.CharField(max_length=255, unique=True) 

687 status = models.CharField( 

688 max_length=2, 

689 choices=ImportType.choices, 

690 default=ImportType.QUEUED, 

691 ) 

692 entry_time = models.DateTimeField(auto_now=True, unique=True) 

693 

694 class Meta: 

695 db_table = "processing_job" 

696 

697 

698class FileProcessing(models.Model): 

699 file_name = models.CharField(max_length=255, unique=True) 

700 processing_job = models.ForeignKey( 

701 "ProcessingJob", on_delete=models.CASCADE, related_name="files" 

702 ) 

703 

704 class Meta: 

705 db_table = "file_processing" 

706 

707 

708class ImportLog(models.Model): 

709 class ImportType(models.TextChoices): 

710 UNKNOWN = "NUL", _("Unknown") 

711 SAMPLE = "SMP", _("Sample") 

712 ANNOTATION = "ANN", _("Annotation") 

713 GENEBANK = "GBK", _("Genebank") 

714 SAMPLE_ANNOTATION_ARCHIVE = "SAA", _("Sample Annotation Archive") 

715 PROPERTY = "PTY", _("Property") 

716 

717 type = models.CharField( 

718 max_length=3, 

719 choices=ImportType.choices, 

720 default=ImportType.UNKNOWN, 

721 ) 

722 file = models.ForeignKey( 

723 FileProcessing, 

724 to_field="file_name", 

725 on_delete=models.CASCADE, 

726 ) 

727 updated = models.DateTimeField(auto_now=True) 

728 success = models.BooleanField() 

729 exception_text = models.TextField(blank=True, null=True) 

730 stack_trace = models.TextField(blank=True, null=True) 

731 

732 class Meta: 

733 db_table = "import_log" 

734 constraints = [ 

735 UniqueConstraint( 

736 name="unique_import_log", 

737 fields=["file", "updated"], 

738 ), 

739 ]