7325e2d32a49fa9fc2eed1b57d866a7ea1baba54
[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         # TODO: placeholder.
33         try:
34             audio_l = self.book.get_audio_length()
35         except:
36             audio_l = 60
37
38         if self.anchor:
39             self.mode = 'text'
40             self.audio_timestamp = self.book.sync_elid(self.anchor)
41         if self.audio_timestamp:
42             self.mode = 'audio'
43             self.anchor = self.book.sync_ts(self.audio_timestamp)
44         return super().save(*args, **kwargs)
45
46     @classmethod
47     def create_from_data(cls, user, data):
48         if data.get('location'):
49             return cls.get_by_location(user, data['location'], create=True)
50         elif data.get('book') and data.get('anchor'):
51             return cls.objects.create(user=user, book=data['book'], anchor=data['anchor'])
52         elif data.get('book') and data.get('audio_timestamp'):
53             return cls.objects.create(user=user, book=data['book'], audio_timestamp=data['audio_timestamp'])
54     
55     @property
56     def timestamp(self):
57         return self.updated_at.timestamp()
58     
59     def location(self):
60         if self.mode == 'text':
61             return f'{self.book.slug}/{self.anchor}'
62         else:
63             return f'{self.book.slug}/audio/{self.audio_timestamp}'
64
65     @classmethod
66     def get_by_location(cls, user, location, create=False):
67         Book = apps.get_model('catalogue', 'Book')
68         try:
69             slug, anchor = location.split('/', 1)
70         except:
71             return None
72         if '/' in anchor:
73             try:
74                 mode, audio_timestamp = anchor.split('/', 1)
75                 assert mode == 'audio'
76                 audio_timestamp = int(audio_timestamp)
77             except:
78                 return None
79             anchor = ''
80             instance = cls.objects.filter(
81                 user=user,
82                 book__slug=slug,
83                 mode=mode,
84                 audio_timestamp=audio_timestamp,
85             ).first()
86         else:
87             mode = 'text'
88             audio_timestamp = None
89             instance = cls.objects.filter(
90                 user=user,
91                 book__slug=slug,
92                 mode='text',
93                 anchor=anchor,
94             ).first()
95         if instance is None and create:
96             try:
97                 book = Book.objects.get(slug=slug)
98             except Book.DoesNotExist:
99                 return None
100             instance = cls.objects.create(
101                 user=user,
102                 book=book,
103                 mode=mode,
104                 anchor=anchor,
105                 audio_timestamp=audio_timestamp,
106             )
107         return instance
108     
109     def get_for_json(self):
110         return {
111             'uuid': self.uuid,
112             'anchor': self.anchor,
113             'note': self.note,
114             'created_at': self.created_at,
115         }
116
117
118 class Quote(models.Model):
119     uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
120     user = models.ForeignKey('auth.User', models.CASCADE)
121     book = models.ForeignKey('catalogue.Book', models.CASCADE)
122     created_at = models.DateTimeField(auto_now_add=True)
123     start_elem = models.CharField(max_length=100, blank=True)
124     end_elem = models.CharField(max_length=100, blank=True)
125     start_offset = models.IntegerField(null=True, blank=True)
126     end_offset = models.IntegerField(null=True, blank=True)
127     text = models.TextField(blank=True)
128
129     def __str__(self):
130         return str(self.uuid)
131
132     def get_for_json(self):
133         return {
134             'uuid': self.uuid,
135             'startElem': self.start_elem,
136             'endElem': self.end_elem,
137             'startOffset': self.start_offset,
138             'startOffset': self.end_offset,
139             'created_at': self.created_at,
140         }
141
142 def from_path(elem, path):
143     def child_nodes(e):
144         if e.text: yield (e, 'text')
145         for child in e:
146             if child.attrib.get('id') != 'toc':
147                 yield (child, None)
148             if child.tail:
149                 yield (child, 'tail')
150     while len(path) > 1:
151         n = path.pop(0)
152         elem = list(child_nodes(elem))[n]
153     return elem
154             
155             
156