2 from django.apps import apps
3 from django.db import models
4 from django.utils.timezone import now
5 from social.syncable import Syncable
8 class Bookmark(Syncable, models.Model):
9 uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
10 user = models.ForeignKey('auth.User', models.CASCADE)
11 book = models.ForeignKey('catalogue.Book', models.CASCADE)
12 anchor = models.CharField(max_length=100, blank=True)
13 audio_timestamp = models.IntegerField(null=True, blank=True)
14 mode = models.CharField(max_length=64, choices=[
18 created_at = models.DateTimeField(auto_now_add=True)
19 note = models.TextField(blank=True)
20 updated_at = models.DateTimeField(auto_now=True)
21 reported_timestamp = models.DateTimeField(default=now)
22 deleted = models.BooleanField(default=False)
31 def save(self, *args, **kwargs):
32 audio_l = self.book.get_audio_length()
34 if self.anchor and self.audio_timestamp is None:
36 self.audio_timestamp = self.book.sync_elid(self.anchor)
37 elif not self.anchor and self.audio_timestamp is not None:
39 self.anchor = self.book.sync_ts(self.audio_timestamp) or ''
40 return super().save(*args, **kwargs)
43 def create_from_data(cls, user, data):
44 if data.get('location'):
45 return cls.get_by_location(user, data['location'], create=True)
46 elif data.get('book') and data.get('anchor'):
47 return cls.objects.create(user=user, book=data['book'], anchor=data['anchor'])
48 elif data.get('book') and data.get('audio_timestamp'):
49 return cls.objects.create(user=user, book=data['book'], audio_timestamp=data['audio_timestamp'])
53 return self.updated_at.timestamp()
56 if self.mode == 'text':
57 return f'{self.book.slug}/{self.anchor}'
59 return f'{self.book.slug}/audio/{self.audio_timestamp}'
62 def get_by_location(cls, user, location, create=False):
63 Book = apps.get_model('catalogue', 'Book')
65 slug, anchor = location.split('/', 1)
70 mode, audio_timestamp = anchor.split('/', 1)
71 assert mode == 'audio'
72 audio_timestamp = int(audio_timestamp)
76 instance = cls.objects.filter(
80 audio_timestamp=audio_timestamp,
84 audio_timestamp = None
85 instance = cls.objects.filter(
91 if instance is None and create:
93 book = Book.objects.get(slug=slug)
94 except Book.DoesNotExist:
96 instance = cls.objects.create(
101 audio_timestamp=audio_timestamp,
105 def get_for_json(self):
108 'anchor': self.anchor,
110 'created_at': self.created_at,
114 class Quote(Syncable, models.Model):
115 uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
116 user = models.ForeignKey('auth.User', models.CASCADE)
117 book = models.ForeignKey('catalogue.Book', models.CASCADE)
118 created_at = models.DateTimeField(auto_now_add=True)
119 start_elem = models.CharField(max_length=100, blank=True)
120 end_elem = models.CharField(max_length=100, blank=True)
121 start_offset = models.IntegerField(null=True, blank=True)
122 end_offset = models.IntegerField(null=True, blank=True)
123 text = models.TextField(blank=True)
124 updated_at = models.DateTimeField(auto_now=True)
125 reported_timestamp = models.DateTimeField(default=now)
126 deleted = models.BooleanField(default=False)
133 return str(self.uuid)
137 return self.updated_at.timestamp()
139 def get_for_json(self):
142 'startElem': self.start_elem,
143 'endElem': self.end_elem,
144 'startOffset': self.start_offset,
145 'startOffset': self.end_offset,
146 'created_at': self.created_at,
149 def from_path(elem, path):
151 if e.text: yield (e, 'text')
153 if child.attrib.get('id') != 'toc':
156 yield (child, 'tail')
159 elem = list(child_nodes(elem))[n]