From c33445121629e3f2c22d1715723b34f6fafbd299 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 1 Nov 2020 08:54:10 -0800 Subject: [PATCH 1/2] code cleanup --- bookwyrm/models/status.py | 5 +++-- bookwyrm/models/user.py | 43 ++++++++++++++++++++------------------- bookwyrm/urls.py | 2 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 36dbb06d..3915dda2 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -59,6 +59,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): @property def ap_tag(self): + ''' books or (eventually) users tagged in a post ''' tags = [] for book in self.mention_books.all(): tags.append(activitypub.Link( @@ -117,7 +118,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): **kwargs ) - def to_activity(self, **kwargs): + def to_activity(self, pure=False): ''' return tombstone if the status is deleted ''' if self.deleted: return activitypub.Tombstone( @@ -126,7 +127,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): deleted=self.deleted_date.isoformat(), published=self.deleted_date.isoformat() ).serialize() - return ActivitypubMixin.to_activity(self, **kwargs) + return ActivitypubMixin.to_activity(self, pure=pure) class GeneratedNote(Status): diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 38442ed7..973d4387 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -167,28 +167,29 @@ class User(OrderedCollectionPageMixin, AbstractUser): return activity_object -@receiver(models.signals.pre_save, sender=User) -def execute_before_save(sender, instance, *args, **kwargs): - ''' populate fields for new local users ''' - # this user already exists, no need to poplate fields - if instance.id: - return - if not instance.local: - # we need to generate a username that uses the domain (webfinger format) - actor_parts = urlparse(instance.remote_id) - instance.username = '%s@%s' % (instance.username, actor_parts.netloc) - return + def save(self, *args, **kwargs): + ''' populate fields for new local users ''' + # this user already exists, no need to populate fields + if self.id: + return + if not self.local: + # generate a username that uses the domain (webfinger format) + actor_parts = urlparse(self.remote_id) + self.username = '%s@%s' % (self.username, actor_parts.netloc) + return - # populate fields for local users - instance.remote_id = 'https://%s/user/%s' % (DOMAIN, instance.username) - instance.localname = instance.username - instance.username = '%s@%s' % (instance.username, DOMAIN) - instance.actor = instance.remote_id - instance.inbox = '%s/inbox' % instance.remote_id - instance.shared_inbox = 'https://%s/inbox' % DOMAIN - instance.outbox = '%s/outbox' % instance.remote_id - if not instance.private_key: - instance.private_key, instance.public_key = create_key_pair() + # populate fields for local users + self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.username) + self.localname = self.username + self.username = '%s@%s' % (self.username, DOMAIN) + self.actor = self.remote_id + self.inbox = '%s/inbox' % self.remote_id + self.shared_inbox = 'https://%s/inbox' % DOMAIN + self.outbox = '%s/outbox' % self.remote_id + if not self.private_key: + self.private_key, self.public_key = create_key_pair() + + super().save(*args, **kwargs) @receiver(models.signals.post_save, sender=User) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 19fdf2e4..78c99ae4 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -17,7 +17,7 @@ status_types = [ 'comment', 'quotation', 'boost', - 'generatedstatus' + 'generatednote' ] status_path = r'%s/(%s)/(?P\d+)' % \ (local_user_path, '|'.join(status_types)) From 4e02a8df998c758b9dd591aaeaa3d0f4e73076f0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 1 Nov 2020 09:16:49 -0800 Subject: [PATCH 2/2] Track when user was last active fixes #10 --- .../migrations/0063_user_last_active_date.py | 18 ++++++++++++++++++ bookwyrm/models/status.py | 15 +++++++++++++++ bookwyrm/models/user.py | 2 ++ bookwyrm/view_actions.py | 6 +++++- bookwyrm/wellknown.py | 17 +++++++++++++++-- 5 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/migrations/0063_user_last_active_date.py diff --git a/bookwyrm/migrations/0063_user_last_active_date.py b/bookwyrm/migrations/0063_user_last_active_date.py new file mode 100644 index 00000000..0cecc37d --- /dev/null +++ b/bookwyrm/migrations/0063_user_last_active_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-11-01 17:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0062_auto_20201031_1936'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='last_active_date', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 3915dda2..5797935e 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -129,6 +129,11 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): ).serialize() return ActivitypubMixin.to_activity(self, pure=pure) + def save(self, *args, **kwargs): + self.user.last_active_date = timezone.now() + self.user.save() + super().save(*args, **kwargs) + class GeneratedNote(Status): ''' these are app-generated messages about user activity ''' @@ -228,6 +233,11 @@ class Favorite(ActivitypubMixin, BookWyrmModel): activity_serializer = activitypub.Like + def save(self, *args, **kwargs): + self.user.last_active_date = timezone.now() + self.user.save() + super().save(*args, **kwargs) + class Meta: ''' can't fav things twice ''' @@ -268,6 +278,11 @@ class ReadThrough(BookWyrmModel): blank=True, null=True) + def save(self, *args, **kwargs): + self.user.last_active_date = timezone.now() + self.user.save() + super().save(*args, **kwargs) + NotificationType = models.TextChoices( 'NotificationType', diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 973d4387..bebd5540 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -69,6 +69,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): remote_id = models.CharField(max_length=255, null=True, unique=True) created_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now=True) + last_active_date = models.DateTimeField(auto_now=True) manually_approves_followers = models.BooleanField(default=False) # ---- activitypub serialization settings for this model ----- # @@ -172,6 +173,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): # this user already exists, no need to populate fields if self.id: return + if not self.local: # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 4fca1685..e9a1171d 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -4,13 +4,15 @@ from PIL import Image import dateutil.parser from dateutil.parser import ParserError + from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required, permission_required +from django.core.exceptions import PermissionDenied from django.core.files.base import ContentFile from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import redirect from django.template.response import TemplateResponse -from django.core.exceptions import PermissionDenied +from django.utils import timezone from bookwyrm import books_manager from bookwyrm import forms, models, outgoing @@ -32,7 +34,9 @@ def user_login(request): password = login_form.data['password'] user = authenticate(request, username=username, password=password) if user is not None: + # successful login login(request, user) + user.last_active_date = timezone.now() return redirect(request.GET.get('next', '/')) login_form.non_field_errors = 'Username or password are incorrect' diff --git a/bookwyrm/wellknown.py b/bookwyrm/wellknown.py index b59256fc..e61ec358 100644 --- a/bookwyrm/wellknown.py +++ b/bookwyrm/wellknown.py @@ -1,4 +1,7 @@ ''' responds to various requests to /.well-know ''' +from datetime import datetime + +from dateutil.relativedelta import relativedelta from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.http import JsonResponse @@ -52,6 +55,16 @@ def nodeinfo(request): status_count = models.Status.objects.filter(user__local=True).count() user_count = models.User.objects.count() + + month_ago = datetime.now() - relativedelta(months=1) + last_month_count = models.User.objects.filter( + last_active_date__gt=month_ago + ).count() + + six_months_ago = datetime.now() - relativedelta(months=6) + six_month_count = models.User.objects.filter( + last_active_date__gt=six_months_ago + ).count() return JsonResponse({ 'version': '2.0', 'software': { @@ -64,8 +77,8 @@ def nodeinfo(request): 'usage': { 'users': { 'total': user_count, - 'activeMonth': user_count, # TODO - 'activeHalfyear': user_count, # TODO + 'activeMonth': last_month_count, + 'activeHalfyear': six_month_count, }, 'localPosts': status_count, },