X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/9dc1452931f801d6ad00d1238531769b3887a820..29b00497f9103bb31f9e236bdf5844b9a6fa79ea:/src/social/models.py?ds=inline diff --git a/src/social/models.py b/src/social/models.py index dc9a09a1b..862db4cdd 100644 --- a/src/social/models.py +++ b/src/social/models.py @@ -1,6 +1,8 @@ # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Wolne Lektury. See NOTICE for more information. # +from datetime import datetime +import uuid from oauthlib.common import urlencode, generate_token from random import randint from django.db import models @@ -9,9 +11,11 @@ from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.core.mail import send_mail from django.urls import reverse -from django.utils.timezone import now +from django.utils.timezone import now, utc from catalogue.models import Book +from catalogue.utils import get_random_hash from wolnelektury.utils import cached_render, clear_cached_renders +from .syncable import Syncable class BannerGroup(models.Model): @@ -176,6 +180,16 @@ class CarouselItem(models.Model): return self.banner or self.banner_group.get_banner() +class UserProfile(models.Model): + user = models.OneToOneField(User, models.CASCADE) + notifications = models.BooleanField(default=False) + + @classmethod + def get_for(cls, user): + obj, created = cls.objects.get_or_create(user=user) + return obj + + class UserConfirmation(models.Model): user = models.ForeignKey(User, models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) @@ -203,12 +217,12 @@ class UserConfirmation(models.Model): ).send() - -class Progress(models.Model): +class Progress(Syncable, models.Model): user = models.ForeignKey(User, models.CASCADE) book = models.ForeignKey('catalogue.Book', models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + reported_timestamp = models.DateTimeField() deleted = models.BooleanField(default=False) last_mode = models.CharField(max_length=64, choices=[ ('text', 'text'), @@ -223,23 +237,31 @@ class Progress(models.Model): implicit_audio_percent = models.FloatField(null=True, blank=True) implicit_audio_timestamp = models.FloatField(null=True, blank=True) + syncable_fields = [ + 'deleted', + 'last_mode', 'text_anchor', 'audio_timestamp' + ] + class Meta: unique_together = [('user', 'book')] + @property + def timestamp(self): + return self.updated_at.timestamp() + @classmethod - def sync(cls, user, slug, ts, data): - obj, _created = cls.objects.get_or_create(user=user, book__slug=slug) - if _created or obj.updated_at < ts: - if data is not None: - obj.deleted = False - for k, v in data.items(): - setattr(obj, k, v) - else: - obj.deleted = True - obj.save() + def create_from_data(cls, user, data): + return cls.objects.create( + user=user, + book=data['book'], + reported_timestamp=now(), + ) def save(self, *args, **kwargs): - audio_l = self.book.get_audio_length() + try: + audio_l = self.book.get_audio_length() + except: + audio_l = 60 if self.text_anchor: self.text_percent = 33 if audio_l: @@ -255,7 +277,7 @@ class Progress(models.Model): return super().save(*args, **kwargs) -class UserList(models.Model): +class UserList(Syncable, models.Model): slug = models.SlugField(unique=True) user = models.ForeignKey(User, models.CASCADE) name = models.CharField(max_length=1024) @@ -263,8 +285,11 @@ class UserList(models.Model): public = models.BooleanField(default=False) deleted = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField() + updated_at = models.DateTimeField(auto_now=True) + reported_timestamp = models.DateTimeField() + syncable_fields = ['name', 'public', 'deleted'] + def get_absolute_url(self): return reverse( 'tagged_object_list', @@ -273,18 +298,24 @@ class UserList(models.Model): def __str__(self): return self.name - + @property def url_chunk(self): return f'polka/{self.slug}' + @classmethod + def create_from_data(cls, user, data): + return cls.create(user, data['name']) + @classmethod def create(cls, user, name): + n = now() return cls.objects.create( user=user, name=name, slug=get_random_hash(name), - updated_at=now() + updated_at=n, + reported_timestamp=n, ) @classmethod @@ -305,12 +336,14 @@ class UserList(models.Model): favorites=True ) except cls.DoesNotExist: + n = now() if create: return cls.objects.create( user=user, favorites=True, - slug=get_random_hash(name), - updated_at=now() + slug=get_random_hash('favorites'), + updated_at=n, + reported_timestamp=n, ) else: return None @@ -332,13 +365,25 @@ class UserList(models.Model): return ls.userlistitem_set.filter(deleted=False, book=book).exists() def append(self, book): - # TODO: check for duplicates? - self.userlistitem_set.create( + n = now() + items = self.userlistitem_set.filter( book=book, - order=self.userlistitem_set.aggregate(m=models.Max('order'))['m'] + 1, - updated_at=now(), ) + if items.exists(): + items.update( + deleted=False, + reported_timestamp=n, + ) + item = items.first() + else: + item = self.userlistitem_set.create( + book=book, + order=(self.userlistitem_set.aggregate(m=models.Max('order'))['m'] or 0) + 1, + updated_at=n, + reported_timestamp=n, + ) book.update_popularity() + return item def remove(self, book): self.userlistitem_set.filter(book=book).update( @@ -362,12 +407,14 @@ class UserList(models.Model): return [item.book for item in self.userlistitem_set.exclude(deleted=True).exclude(book=None)] -class UserListItem(models.Model): +class UserListItem(Syncable, models.Model): list = models.ForeignKey(UserList, models.CASCADE) + uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False, blank=True) order = models.IntegerField() deleted = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField() + updated_at = models.DateTimeField(auto_now=True) + reported_timestamp = models.DateTimeField() book = models.ForeignKey('catalogue.Book', models.SET_NULL, null=True, blank=True) fragment = models.ForeignKey('catalogue.Fragment', models.SET_NULL, null=True, blank=True) @@ -375,3 +422,21 @@ class UserListItem(models.Model): bookmark = models.ForeignKey('bookmarks.Bookmark', models.SET_NULL, null=True, blank=True) note = models.TextField(blank=True) + + syncable_fields = ['order', 'deleted', 'book', 'fragment', 'quote', 'bookmark', 'note'] + + @classmethod + def create_from_data(cls, user, data): + if data.get('favorites'): + l = UserList.get_favorites_list(user, create=True) + else: + l = data['list'] + try: + assert l.user == user + except AssertionError: + return + return l.append(book=data['book']) + + @property + def favorites(self): + return self.list.favorites