Guest User

Untitled

a guest
May 23rd, 2018
142
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.71 KB | None | 0 0
  1. # coding: utf8 #
  2.  
  3. from django.db import models
  4. from django.db.models import Q
  5. from django.utils.translation import ugettext_lazy as _
  6.  
  7. from core.models import Language, License, Server
  8.  
  9.  
  10.  
  11.  
  12. class WorkServer(models.Model):
  13. """
  14. Intermediary table for ManyToMany relationship between Work and Server
  15. """
  16.  
  17. is_primary = models.BooleanField(_("Whether the server is canonical (primary) or not (mirror)"))
  18. work = models.ForeignKey('Work')
  19. server = models.ForeignKey(Server)
  20.  
  21.  
  22. #TODO: Should we have a WorkProperty model which allows arbitrary key/value pairs?
  23.  
  24. class Work(models.Model):
  25. """
  26. Represents an OSIS work, such as the Bible or a non-biblical work such as
  27. the Qur'an or the Mishnah.
  28. """
  29.  
  30. title = models.CharField(max_length=255)
  31. abbreviation = models.CharField(max_length=255, blank=True)
  32. description = models.TextField(blank=True)
  33. source_url = models.URLField(_("URL where this resource was originally obtained"), blank=True)
  34.  
  35. variants_for_work = models.ForeignKey("self", null=True, default=None, verbose_name=_("Parent work that this work provides variants for"))
  36. variant_bit = models.PositiveSmallIntegerField(_("The bit mask that is anded with Token.variant_bits and Structure.variant_bits to query only those which belong to the work."), default=0b00000001)
  37.  
  38. #TODO: Allow multiple kinds of identifiers for the work:
  39. # Including ISBN, URL, URN, in addition to OSIS. OSIS needs to be unique,
  40. # and its parts should be deconstructable.
  41.  
  42. #Bible.en.Publisher.ABC.2010
  43. #TODO: this should be pulled from osis.TYPES
  44. TYPES = (
  45. ('Bible', 'Bible'),
  46. )
  47. #TODO: use short_name?
  48. osis_slug = models.SlugField(_("OSIS identifier which should correspond to the abbreviation, like NIV, ESV, or KJV"), max_length=16, db_index=True)
  49. type = models.CharField(max_length=16, choices=TYPES, null=False, db_index=True)
  50. language = models.ForeignKey(Language, null=True, db_index=True)
  51. #Note: In the case of Daniel, both Hebrew and Aramaic in the book, though
  52. # Hebrew is predominat; in this case, Work.language would still be 'hbo'; the
  53. # language of the individual Tokens in the work can be indicated in a
  54. # separate TokenMetadata model. This would allow multiple languages to be
  55. # associated with a Token as there may be different opinions about what
  56. # language it is!
  57.  
  58.  
  59. #TODO: Better way to do attribution for various roles like creator, contributor, publisher in ManyToMany relation
  60. # (Also need slugs for the URL)
  61. publisher = models.CharField(blank=True, max_length=128, db_index=True)
  62. creator = models.TextField(blank=True)
  63.  
  64. #TODO: Better system for storing multiple dates for a work, e.g. OSIS
  65. # date tlements: edition, eversion, imprint, original
  66. publish_date = models.DateField(_("When the work was published"), null=True, db_index=True)
  67. import_date = models.DateField(_("When the work was imported into the models."), null=True)
  68.  
  69. #TODO: pub_date instead?
  70. #edition
  71. #version
  72.  
  73. #Concatenation of previous fields:
  74. #osis_id = models.CharField(max_length=32, choices=TYPES, null=False, db_index=True)
  75. #def get_osis_id(self):
  76. # _osis_id = []
  77. # if self.type:
  78. # _osis_id.append(self.type)
  79. # if self.language:
  80. # _osis_id.append(self.language.code)
  81. # if self.osis_slug:
  82. # _osis_id.append(self.osis_slug)
  83. # if self.publish_date:
  84. # _osis_id.append(self.publish_date.year)
  85. # return ".".join(_osis_id)
  86. #osis_id = property(get_osis_id)
  87.  
  88. copyright = models.TextField(blank=True)
  89. license = models.ForeignKey(License, null=True)
  90.  
  91. servers = models.ManyToManyField(Server, through=WorkServer)
  92.  
  93.  
  94. def lookup_osis_ref(self, start_osis_id, end_osis_id = None, variant_bits = None):
  95. if not end_osis_id:
  96. end_osis_id = start_osis_id
  97.  
  98. # Token and Structure objects are only associated with non-diff
  99. # works, that is where variants_for_work is None
  100. main_work = self
  101. if self.variants_for_work is not None:
  102. main_work = self.variants_for_work
  103.  
  104. # Allow the variant_bits to be customized to include structures and
  105. # tokens from other variants of this work
  106. if variant_bits is None:
  107. variant_bits = self.variant_bit
  108.  
  109. # Get the structure for the start
  110. structures = Structure.objects.select_related(depth=1).filter(
  111. work = main_work,
  112. start_token__isnull = False,
  113. osis_id = start_osis_id
  114. ).extra(where=["variant_bits & %s != 0"], params=[variant_bits])
  115. if len(structures) == 0:
  116. raise Exception("Start structure with osisID %s not found" % start_osis_id)
  117. start_structure = structures[0]
  118.  
  119. # Get the structure for the end
  120. if start_osis_id != end_osis_id:
  121. structures = Structure.objects.select_related(depth=1).filter(
  122. work = main_work,
  123. end_token__isnull = False,
  124. osis_id = end_osis_id
  125. ).extra(where=["variant_bits & %s != 0"], params=[variant_bits])
  126. if len(structures) == 0:
  127. raise Exception("End structure with osisID %s not found" % end_osis_id)
  128. end_structure = structures[0]
  129. else:
  130. end_structure = start_structure
  131.  
  132. # Now grab all structures from the work which have start/end_token or
  133. # start/end_marker whose positions are less
  134. concurrent_structures = Structure.objects.select_related(depth=1).filter(work = main_work).filter(
  135. # Structures that are contained within start_structure and end_structure
  136. (
  137. Q(start_token__position__lte = start_structure.start_token.position)
  138. &
  139. Q(end_token__position__gte = end_structure.end_token.position)
  140. )
  141. |
  142. # Structures that only start inside of the selected range
  143. Q(
  144. start_token__position__gte = start_structure.start_token.position,
  145. start_token__position__lte = end_structure.end_token.position
  146. )
  147. |
  148. # Structures that only end inside of the selected range (excluding markers)
  149. Q(
  150. end_token__position__gte = start_structure.start_token.position,
  151. end_token__position__lte = end_structure.end_token.position
  152. )
  153. ).extra(where=["%s.variant_bits & %%s != 0" % Structure._meta.db_table], params=[variant_bits])
  154.  
  155. # Now indicate if the structures are shadowed (virtual)
  156. for struct in concurrent_structures:
  157. if struct.start_token.position < start_structure.start_token.position:
  158. struct.shadow = struct.shadow | Structure.SHADOW_START
  159. if struct.end_token.position > end_structure.end_token.position:
  160. struct.shadow = struct.shadow | Structure.SHADOW_END
  161.  
  162. # Now get all tokens that exist between the beginning of start_structure
  163. # and the end of end_structure
  164.  
  165. # Get all of the tokens between the start and end and who have variant
  166. # bits that match the requested variant bits
  167. tokens = Token.objects.filter(
  168. work = main_work,
  169. position__gte = start_structure.start_token.position,
  170. position__lte = end_structure.end_token.position
  171. ).extra(where=['variant_bits & %s != 0'], params=[variant_bits])
  172.  
  173. # Indicate which of the beginning queried tokens are markers (should be none since verse)
  174. for token in tokens:
  175. if token.position >= start_structure.start_token.position:
  176. break
  177. token.is_structure_marker = True
  178.  
  179. # Indicate which of the ending queried tokens are markers (should be none since verse)
  180. for token in reversed(tokens):
  181. if token.position <= end_structure.end_token.position:
  182. break
  183. token.is_structure_marker = True
  184.  
  185. return {
  186. 'start_structure': start_structure,
  187. 'end_structure': end_structure,
  188. 'tokens': tokens,
  189. 'concurrent_structures': concurrent_structures
  190. }
  191.  
  192. def __unicode__(self):
  193. return self.title
  194.  
  195. class Meta:
  196. unique_together = (
  197. ('type', 'language', 'publisher', 'osis_slug', 'publish_date'),
  198. )
  199.  
  200.  
  201. class Token(models.Model):
  202. """
  203. An atomic unit of text, such as a word, punctuation mark, or whitespace
  204. line break. Corresponds to OSIS w elements.
  205. """
  206.  
  207. data = models.CharField(_("Unicode data in Normalization Form C (NFC)"), max_length=255, db_index=True)
  208.  
  209. WORD = 1
  210. PUNCTUATION = 2
  211. WHITESPACE = 3
  212. TYPE_NAMES = {
  213. WORD: 'word',
  214. PUNCTUATION: 'punctuation',
  215. WHITESPACE: 'whitespace'
  216. }
  217. TYPE_CHOICES = (
  218. (WORD, TYPE_NAMES[WORD]),
  219. (PUNCTUATION, TYPE_NAMES[PUNCTUATION]),
  220. (WHITESPACE, TYPE_NAMES[WHITESPACE]),
  221. )
  222. type = models.PositiveSmallIntegerField(_("A general hint as to what the token data represents"), choices=TYPE_CHOICES, default=WORD, db_index=True)
  223. type_name = property(lambda self: self.TYPE_NAMES[self.type])
  224.  
  225. position = models.PositiveIntegerField(db_index=True)
  226. work = models.ForeignKey(Work)
  227. variant_bits = models.PositiveSmallIntegerField(_("Bitwise anded with Work.variant_bit to determine if belongs to work."), default=0b00000001)
  228. #unified_token = models.ForeignKey('self', null=True, help_text="The token in the merged/unified work that represents this token.")
  229.  
  230. is_structure_marker = None #This boolean is set when querying via Structure.get_tokens
  231.  
  232. #TODO: Make type an array?
  233. def get_structures(self, types = [], including_markers = False): #types = [Structure.VERSE]
  234. """
  235. Get the structures that this token is a part of.
  236. """
  237. raise Exception(_("Not implemented yet"))
  238.  
  239. #TODO: This is where internal linked data connects with the data out in the world through hyperlinks
  240. #TODO: How do you query for isblank=False? Whe not say null=True?
  241. relative_source_url = models.CharField(_("Relative URL for where this token came from (e.g. including XPointer); %base% refers to work.src_url"), max_length=255, blank=True)
  242.  
  243. def get_source_url(self):
  244. if not self.relative_source_url:
  245. return None
  246.  
  247. structures = Structure.objects.filter(
  248. Q(start_token__position__lte = self.position),
  249. Q(end_token__position__gte = self.position),
  250. source_url__isnull = False,
  251. #source_url__ne = "",
  252. #source_url__isblank = False,
  253. work = self.work
  254. ).extra(where=["%s.variant_bits & %%s != 0" % Structure._meta.db_table], params=[self.variant_bits])
  255.  
  256.  
  257. if not len(structures):
  258. base = self.work.source_url
  259. else:
  260. #TODO: If multiple structures have source_urls?
  261. base = structures[0].source_url
  262. #TODO: What if the base isn't desired?
  263.  
  264. return base + self.relative_source_url
  265. source_url = property(get_source_url)
  266.  
  267. class Meta:
  268. ordering = ['position']
  269. unique_together = (
  270. ('position', 'work'),
  271. )
  272.  
  273. def __unicode__(self):
  274. return self.data
  275.  
  276.  
  277. #class StructureType(models.Model):
  278. # pass
  279.  
  280.  
  281. class Structure(models.Model):
  282. """
  283. Represent supra-segmental structures in the text, various markup; really
  284. what this needs to do is represent every non-w element in OSIS.
  285. """
  286.  
  287. #TODO: We need to be able to represent a lot more than this! To faithfully
  288. # store OSIS, it will need be able to represent every feature.
  289. # The various structure types each need to have a certain number of
  290. # possible attribues? Idea: why not just store the OSIS element name in
  291. # one field, and then store all of the the attributes in another? When
  292. # serializing out the data as-is into XML, it would result in
  293. # overlapping hierarchies, so then whichever structure is desired could
  294. # then be presented.
  295.  
  296. # IDEA: Instead of making constants, typenames, and choices: why not make
  297. # another model called StructureType which is a ForeignKey?
  298. # PTA: Because, unless you plan to change the key data, it is probably more
  299. # more efficient and clean to have them just loaded into memory via
  300. # constants.
  301.  
  302.  
  303. TYPE_CHOICES = (
  304.  
  305.  
  306. # Element list
  307. 'a',
  308. 'abbr',
  309. 'actor',
  310. 'caption',
  311. 'castGroup',
  312. 'castItem',
  313. 'castList',
  314. 'catchWord',
  315. 'cell',
  316. 'chapter',
  317. 'closer',
  318. 'contributor',
  319. 'coverage',
  320. 'creator',
  321. 'date',
  322. 'description',
  323. #'div', (no need for this since promoting div[type] to full element)
  324. 'divineName',
  325. 'figure',
  326. 'foreign',
  327. 'format',
  328. 'head',
  329. 'header',
  330. 'hi',
  331. 'identifier',
  332. 'index',
  333. 'inscription',
  334. 'item',
  335. 'l',
  336. 'label',
  337. 'language',
  338. 'lb',
  339. 'lg',
  340. 'list',
  341. 'mentioned',
  342. #'milestone', (n/a since all strucutres milestoned)
  343. #'milestoneEnd', (n/a since all strucutres milestoned)
  344. #'milestoneStart', (n/a since all strucutres milestoned)
  345. 'name',
  346. 'note',
  347. 'osis',
  348. 'osisCorpus',
  349. 'osisText',
  350. 'p',
  351. 'publisher',
  352. 'q',
  353. 'rdg',
  354. 'rdgGrp',
  355. 'refSystem',
  356. 'reference',
  357. 'relation',
  358. 'revisionDesc',
  359. 'rights',
  360. 'role',
  361. 'roleDesc',
  362. 'row',
  363. 'salute',
  364. 'scope',
  365. 'seg',
  366. 'seq',
  367. 'signed',
  368. 'source',
  369. 'speaker',
  370. 'speech',
  371. 'subject',
  372. 'table',
  373. 'teiHeader',
  374. 'title',
  375. 'titlePage',
  376. 'transChange',
  377. 'type',
  378. 'verse',
  379. 'w',
  380. 'work',
  381.  
  382. # Promoting div[type] elements to elements of the name [type]
  383. 'acknowledgement',
  384. 'afterword',
  385. 'annotant',
  386. 'appendix',
  387. 'article',
  388. 'back',
  389. 'body',
  390. 'book',
  391. 'bookGroup',
  392. #'chapter', (use existing element above)
  393. 'colophon',
  394. 'commentary',
  395. 'concordance',
  396. 'coverPage',
  397. 'dedication',
  398. 'devotional',
  399. 'entry',
  400. 'front',
  401. 'gazetteer',
  402. 'glossary',
  403. 'imprimatur',
  404. #'index', (use existing elemen above)
  405. 'introduction',
  406. 'majorSection',
  407. 'map',
  408. #'paragraph', (use existing element above)
  409. 'part',
  410. 'preface',
  411. 'section',
  412. 'subSection',
  413. 'summary',
  414. #'titlePage', (use existing element above)
  415.  
  416. # New elements
  417. 'page', # used to preserve page boundaries; TEI?
  418.  
  419. # Proposed
  420. 'doubted', #level1 and level2? rdg?
  421. )
  422. #type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, db_index=True)
  423. #type_name = property(lambda self: self.TYPE_NAMES[self.type])
  424.  
  425. # Question: what about using XMLField? Or storing attributes via GeoDjango.DictionaryField
  426. element = models.CharField(max_length=32, blank=False, help_text=_("The name of the OSIS "), choices=TYPE_CHOICES)
  427. #attributes =
  428.  
  429. osis_id = models.CharField(max_length=32, blank=True, db_index=True)
  430. work = models.ForeignKey(Work, help_text=_("Must be same as start/end_*_token.work. Must not be a variant work; use the variant_bits to select for it"))
  431. variant_bits = models.PositiveSmallIntegerField(default=0b00000001, help_text=_("Bitwise anded with Work.variant_bit to determine if belongs to work."))
  432.  
  433. source_url = models.CharField(max_length=255, blank=True, help_text=_("URL for where this structure came from; used for base to Token.relative_source_url"))
  434.  
  435. # title?
  436. # parent?
  437. position = models.PositiveIntegerField(help_text=_("The order where this appears in the work."))
  438.  
  439. numerical_start = models.PositiveIntegerField(null=True, help_text=_("A number that may be associated with this structure, such as a chapter or verse number; corresponds to OSIS @n attribute."))
  440. numerical_end = models.PositiveIntegerField(null=True, help_text=_("If the structure spans multiple numerical designations, this is used"))
  441.  
  442. start_token = models.ForeignKey(Token, null=True, related_name='start_token_structure_set', help_text=_("The token that starts the structure's content; this may or may not include the start_marker, like quotation marks. <del>If null, then tokens should be discovered via StructureToken.</del>"))
  443. end_token = models.ForeignKey(Token, null=True, related_name='end_token_structure_set', help_text=_("Same as start_token, but for the end."))
  444.  
  445. #Used to demarcate the inclusive start point for the structure; marks any typographical feature used to markup the structure in the text (e.g. quotation marks).
  446. start_marker = models.ForeignKey(Token, null=True, related_name='start_marker_structure_set', help_text=_("The optional token that marks the start of the structure; this marker may be included (inside) in the start_token/end_token range as in the example of quotation marks, or it may excluded (outside) as in the case of paragraph markers which are double linebreaks. Outside markers may overlap (be shared) among multiple paragraphs' start/end_markers, whereas inside markers may not."))
  447. end_marker = models.ForeignKey(Token, null=True, related_name='end_marker_structure_set', help_text=_("Same as start_marker, but for the end."))
  448.  
  449. @property
  450. def tokens(self, include_outside_markers = False, variant_bits = None):
  451. if include_outside_markers:
  452. raise Exception(_("include_outside_markers not implemented yet"))
  453. if variant_bits is None:
  454. variant_bits = self.variant_bits
  455.  
  456. # Get the tokens from a range
  457. #if self.start_token is not None:
  458. assert(self.end_marker is not None)
  459.  
  460. # Get all of the tokens between the marker start and marker end
  461. # and who have variant bits that match the requested variant bits
  462. tokens = Token.objects.filter(
  463. work = self.work,
  464. position__gte = self.start_token.position,
  465. position__lte = self.end_token.position
  466. ).extra(where=['variant_bits & %s != 0'], params=[variant_bits])
  467.  
  468. # Indicate which of the beginning queried tokens are markers
  469. #for token in tokens:
  470. # if token.position >= start_structure.start_token.position:
  471. # break
  472. # token.is_structure_marker = True
  473. #
  474. ## Indicate which of the ending queried tokens are markers
  475. #for token in reversed(tokens):
  476. # if token.position <= end_structure.end_token.position:
  477. # break
  478. # token.is_structure_marker = True
  479.  
  480. return tokens
  481.  
  482. # Get the tokens which are not consecutive (Feature is disabled until deemed necessary)
  483. #else:
  484. # items = StructureToken.objects.extra(where=["token__variant_bits & %s != 0"], params=[variant_bits])
  485. # tokens = []
  486. # for item in items:
  487. # items.token.is_structure_marker = item.is_marker
  488. # tokens.append(items.token)
  489. # return tokens
  490.  
  491. SHADOW_NONE = 0b0000
  492. SHADOW_START = 0b0001
  493. SHADOW_END = 0b0010
  494. SHADOW_BOTH = 0b0011
  495. SHADOW_NAMES = {
  496. SHADOW_NONE:'none',
  497. SHADOW_START:'start',
  498. SHADOW_END:'end',
  499. SHADOW_BOTH:'both'
  500. }
  501. SHADOW_CHOICES = (
  502. (SHADOW_NONE, SHADOW_NAMES[SHADOW_NONE]),
  503. (SHADOW_START, SHADOW_NAMES[SHADOW_START]),
  504. (SHADOW_END, SHADOW_NAMES[SHADOW_END]),
  505. (SHADOW_BOTH, SHADOW_NAMES[SHADOW_BOTH])
  506. )
  507. shadow = SHADOW_NONE
  508. shadow_name = property(lambda self: self.SHADOW_NAMES[self.shadow])
  509.  
  510. is_milestoned = False
  511.  
  512. #TODO: Include a type filter?
  513. def get_related_structures(self, types = [], shadow = SHADOW_NONE):
  514. """
  515. Get the structures that are related to this structure.
  516.  
  517. types is a list of TYPE that should be returned. Empty means all.
  518.  
  519. If shadow = SHADOW_NONE, then only sub-structures are returned;
  520. If shadow = SHADOW_BOTH, then only super-structures are returned.
  521. If shadow = SHADOW_START, then only structures that start before
  522. If shadow = SHADOW_END, then only structures that end after
  523. """
  524. raise Exception(_("Not built yet"))
  525.  
  526.  
  527. class Meta:
  528. ordering = ['position'] #, 'variant_number'
  529. unique_together = (
  530. ('type', 'position', 'start_token'), #???
  531. )
  532.  
  533. def __unicode__(self):
  534. if self.osis_id:
  535. return self.osis_id
  536. elif self.type == self.PARAGRAPH:
  537. return u"¶" + self.start_token.data + u" … " + self.end_token.data
  538. elif self.type == self.UNCERTAIN1:
  539. return u"[]"
  540. else:
  541. return self.type
  542.  
  543.  
  544. class StructureAttribute(models.Model):
  545. """
  546. Basically an OSIS XML attribute for an associated structure which is an OSIS XML element.
  547.  
  548. The attributes allowed for a Structure should be constrained based on the schema.
  549. """
  550. structure = models.ForeignKey(Structure, blank=False, help_text=_("The structure that the attributes are for"))
  551. name = models.CharField(max_length=32, blank=False, help_text=_("The name of the attribute. May include a limited number of discrete XML namespace prefixes, e.g. 'xml:'."))
  552. TYPE_CHOICES = (
  553. ('string', "String"),
  554. ('boolean', "Boolean"),
  555. ('datetime', "Date/Time"),
  556. ('langiage', "Language"),
  557. ('osisRef', "osisRef"),
  558. ('osisID', "osisID"),
  559. ('ID', "ID"), #does this even make sense?
  560. )
  561. type = models.CharField(max_length=32, blank=False, choices=TYPE_CHOICES)
  562. value = models.TextField()
  563.  
  564.  
  565.  
  566.  
  567. # This is an alternative to the above and it allows non-consecutive tokens to be
  568. # included in a structure. But it incurs a huge amount of overhead. If
  569. # start_token is null, then it could be assumed that a structure's tokens should
  570. # be found via StructureToken
  571. #class StructureToken(models.Model):
  572. # """
  573. # Non-consecutive tokens can be assigned to a Structure via this model.
  574. # """
  575. # structure = models.ForeignKey(Structure)
  576. # token = models.ForeignKey(Token)
  577. # is_marker = models.BooleanField(default=False, help_text=_("Whether the token is any such typographical feature which marks up the structure in the text, such as a quotation mark."))
  578.  
  579.  
  580. ################################################################################
  581. # Linked Token Data
  582. ################################################################################
  583.  
  584.  
  585. # These relate to interlinearization; these could also be used for unification
  586. # instead of relying on Token.unified_token
  587. # TODO: This can also be used to associate words with a note; otherwise, start_token/end_token would be used
  588. class TokenLinkage(models.Model):
  589. """
  590. Anchor point to link together multiple TokenLinkageItems
  591. """
  592. #TODO: We need to have a type field, e.g. translation
  593. # And it would be good to have a strength field from 0.0 to 1.0
  594. UNIFICATION = 1
  595. TRANSLATION = 2
  596. CROSS_REFERENCE = 3
  597. TYPE_CHOICES = (
  598. (UNIFICATION, _("Link the result of unification/merging algorithm")),
  599. (TRANSLATION, _("Incoming token is translation of outgoing token")),
  600. (CROSS_REFERENCE, _("Related passage")),
  601. )
  602. type = models.PositiveIntegerField(null=True, choices=TYPE_CHOICES, help_text=_("The kind of linkage"))
  603. key = models.CharField(db_index=True, max_length=100, help_text=_("Key (hash) of each item's token's unambiguous work osisID and position sorted, utilizing ranges whenever possible. Must be updated whenever an item is added or removed. Examples: ESV.2001:4-8;KJV.1611:3-6;NIV:2,4,6-9. Note how contiguous tokens are indicated with a hyphen range, how non-contiguous tokens are separated by commas, and how tokens from different works are separated by semicolons. All are sorted; the token items of a TokenLinkage do not have position. There is only one key for one collection of tokens."))
  604. #TODO: The type is not factored in here.
  605.  
  606.  
  607. class TokenLinkageItem(models.Model):
  608. """
  609. Tokens from different works can be linked together by instantiating
  610. TokenLinkageItems and associating them with the same TokenLinkage.
  611. If the token being linked is not local, then the `token_work` and
  612. `token_position` must be defined and `token` must be null. Otherwise, if
  613. `token` is not null then `token_work`
  614. """
  615. linkage = models.ForeignKey(TokenLinkage)
  616.  
  617. #Note: token.Meta.unique_together == ('position', 'work') so it can be used
  618. # as a composite key. This can be employed instead of token =
  619. # models.ForeignKey(Token) so that the actual token referenced may
  620. # exist on another system whose internal ID is unknown.
  621. # Constraint for Django 1.2:
  622. # token.position == token_position &&
  623. # token.work == token_work
  624. token_position = models.PositiveIntegerField(db_index=True)
  625. token_work = models.ForeignKey(Work)
  626. #token = models.ForeignKey(Token, null=True)
  627. #UPDATE: Remove `token` completely? Otherwise, if not removed
  628. # we could have an ON UPDATE trigger for token that updates
  629. # all TokenLinkageItems that point to it
  630.  
  631. weight = models.DecimalField(null=True, help_text=_("The strength of the linkage; value between 0.0 and 1.0."), max_digits=3, decimal_places=2)
  632. INCOMING = 0b01
  633. OUTGOING = 0b10
  634. BIDIRECTIONAL = 0b11
  635. DIRECTIONALITY_CHOICES = (
  636. (INCOMING, _("Incoming unidirectional link")),
  637. (OUTGOING, _("Outgoing unidirectional link")),
  638. (BIDIRECTIONAL, _("Bidirectional link")),
  639. )
  640. directionality = models.PositiveIntegerField(default=BIDIRECTIONAL, choices=DIRECTIONALITY_CHOICES, help_text=_("Whether the link"))
Add Comment
Please, Sign In to add comment