fixes
[wolnelektury.git] / src / bookmarks / models.py
1 import uuid
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
6
7
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=[
15         ('text', 'text'),
16         ('audio', 'audio'),
17     ], default='text')
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)
23
24     syncable_fields = [
25         'deleted', 'note',
26     ]
27
28     def __str__(self):
29         return str(self.uuid)
30
31     def save(self, *args, **kwargs):
32         audio_l = self.book.get_audio_length()
33         if audio_l:
34             if self.anchor and self.audio_timestamp is None:
35                 self.mode = 'text'
36                 self.audio_timestamp = self.book.sync_elid(self.anchor)
37             elif not self.anchor and self.audio_timestamp is not None:
38                 self.mode = 'audio'
39                 self.anchor = self.book.sync_ts(self.audio_timestamp) or ''
40         return super().save(*args, **kwargs)
41
42     @classmethod
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'])
50     
51     @property
52     def timestamp(self):
53         return self.updated_at.timestamp()
54     
55     def location(self):
56         if self.mode == 'text':
57             return f'{self.book.slug}/{self.anchor}'
58         else:
59             return f'{self.book.slug}/audio/{self.audio_timestamp}'
60
61     @classmethod
62     def get_by_location(cls, user, location, create=False):
63         Book = apps.get_model('catalogue', 'Book')
64         try:
65             slug, anchor = location.split('/', 1)
66         except:
67             return None
68         if '/' in anchor:
69             try:
70                 mode, audio_timestamp = anchor.split('/', 1)
71                 assert mode == 'audio'
72                 audio_timestamp = int(audio_timestamp)
73             except:
74                 return None
75             anchor = ''
76             instance = cls.objects.filter(
77                 user=user,
78                 book__slug=slug,
79                 mode=mode,
80                 audio_timestamp=audio_timestamp,
81             ).first()
82         else:
83             mode = 'text'
84             audio_timestamp = None
85             instance = cls.objects.filter(
86                 user=user,
87                 book__slug=slug,
88                 mode='text',
89                 anchor=anchor,
90             ).first()
91         if instance is None and create:
92             try:
93                 book = Book.objects.get(slug=slug)
94             except Book.DoesNotExist:
95                 return None
96             instance = cls.objects.create(
97                 user=user,
98                 book=book,
99                 mode=mode,
100                 anchor=anchor,
101                 audio_timestamp=audio_timestamp,
102             )
103         return instance
104     
105     def get_for_json(self):
106         return {
107             'uuid': self.uuid,
108             'anchor': self.anchor,
109             'note': self.note,
110             'created_at': self.created_at,
111         }
112
113
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)
127
128     syncable_fields = [
129         'deleted',
130     ]
131
132     def __str__(self):
133         return str(self.uuid)
134
135     @property
136     def timestamp(self):
137         return self.updated_at.timestamp()
138
139     def get_for_json(self):
140         return {
141             'uuid': self.uuid,
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,
147         }
148
149 def from_path(elem, path):
150     def child_nodes(e):
151         if e.text: yield (e, 'text')
152         for child in e:
153             if child.attrib.get('id') != 'toc':
154                 yield (child, None)
155             if child.tail:
156                 yield (child, 'tail')
157     while len(path) > 1:
158         n = path.pop(0)
159         elem = list(child_nodes(elem))[n]
160     return elem