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