Merge pull request #270 from mouse-reeve/user-active

User active
This commit is contained in:
Mouse Reeve 2020-11-01 11:07:04 -08:00 committed by GitHub
commit a242a476d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 27 deletions

View file

@ -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),
),
]

View file

@ -59,6 +59,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
@property @property
def ap_tag(self): def ap_tag(self):
''' books or (eventually) users tagged in a post '''
tags = [] tags = []
for book in self.mention_books.all(): for book in self.mention_books.all():
tags.append(activitypub.Link( tags.append(activitypub.Link(
@ -117,7 +118,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
**kwargs **kwargs
) )
def to_activity(self, **kwargs): def to_activity(self, pure=False):
''' return tombstone if the status is deleted ''' ''' return tombstone if the status is deleted '''
if self.deleted: if self.deleted:
return activitypub.Tombstone( return activitypub.Tombstone(
@ -126,7 +127,12 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
deleted=self.deleted_date.isoformat(), deleted=self.deleted_date.isoformat(),
published=self.deleted_date.isoformat() published=self.deleted_date.isoformat()
).serialize() ).serialize()
return ActivitypubMixin.to_activity(self, **kwargs) 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): class GeneratedNote(Status):
@ -227,6 +233,11 @@ class Favorite(ActivitypubMixin, BookWyrmModel):
activity_serializer = activitypub.Like 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: class Meta:
''' can't fav things twice ''' ''' can't fav things twice '''
@ -267,6 +278,11 @@ class ReadThrough(BookWyrmModel):
blank=True, blank=True,
null=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 = models.TextChoices(
'NotificationType', 'NotificationType',

View file

@ -69,6 +69,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
remote_id = models.CharField(max_length=255, null=True, unique=True) remote_id = models.CharField(max_length=255, null=True, unique=True)
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
last_active_date = models.DateTimeField(auto_now=True)
manually_approves_followers = models.BooleanField(default=False) manually_approves_followers = models.BooleanField(default=False)
# ---- activitypub serialization settings for this model ----- # # ---- activitypub serialization settings for this model ----- #
@ -167,28 +168,30 @@ class User(OrderedCollectionPageMixin, AbstractUser):
return activity_object return activity_object
@receiver(models.signals.pre_save, sender=User) def save(self, *args, **kwargs):
def execute_before_save(sender, instance, *args, **kwargs): ''' populate fields for new local users '''
''' populate fields for new local users ''' # this user already exists, no need to populate fields
# this user already exists, no need to poplate fields if self.id:
if instance.id: return
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
# populate fields for local users if not self.local:
instance.remote_id = 'https://%s/user/%s' % (DOMAIN, instance.username) # generate a username that uses the domain (webfinger format)
instance.localname = instance.username actor_parts = urlparse(self.remote_id)
instance.username = '%s@%s' % (instance.username, DOMAIN) self.username = '%s@%s' % (self.username, actor_parts.netloc)
instance.actor = instance.remote_id return
instance.inbox = '%s/inbox' % instance.remote_id
instance.shared_inbox = 'https://%s/inbox' % DOMAIN # populate fields for local users
instance.outbox = '%s/outbox' % instance.remote_id self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.username)
if not instance.private_key: self.localname = self.username
instance.private_key, instance.public_key = create_key_pair() 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) @receiver(models.signals.post_save, sender=User)

View file

@ -17,7 +17,7 @@ status_types = [
'comment', 'comment',
'quotation', 'quotation',
'boost', 'boost',
'generatedstatus' 'generatednote'
] ]
status_path = r'%s/(%s)/(?P<status_id>\d+)' % \ status_path = r'%s/(%s)/(?P<status_id>\d+)' % \
(local_user_path, '|'.join(status_types)) (local_user_path, '|'.join(status_types))

View file

@ -4,13 +4,15 @@ from PIL import Image
import dateutil.parser import dateutil.parser
from dateutil.parser import ParserError from dateutil.parser import ParserError
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, permission_required 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.core.files.base import ContentFile
from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template.response import TemplateResponse 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 books_manager
from bookwyrm import forms, models, outgoing from bookwyrm import forms, models, outgoing
@ -32,7 +34,9 @@ def user_login(request):
password = login_form.data['password'] password = login_form.data['password']
user = authenticate(request, username=username, password=password) user = authenticate(request, username=username, password=password)
if user is not None: if user is not None:
# successful login
login(request, user) login(request, user)
user.last_active_date = timezone.now()
return redirect(request.GET.get('next', '/')) return redirect(request.GET.get('next', '/'))
login_form.non_field_errors = 'Username or password are incorrect' login_form.non_field_errors = 'Username or password are incorrect'

View file

@ -1,4 +1,7 @@
''' responds to various requests to /.well-know ''' ''' 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 HttpResponseBadRequest, HttpResponseNotFound
from django.http import JsonResponse from django.http import JsonResponse
@ -52,6 +55,16 @@ def nodeinfo(request):
status_count = models.Status.objects.filter(user__local=True).count() status_count = models.Status.objects.filter(user__local=True).count()
user_count = models.User.objects.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({ return JsonResponse({
'version': '2.0', 'version': '2.0',
'software': { 'software': {
@ -64,8 +77,8 @@ def nodeinfo(request):
'usage': { 'usage': {
'users': { 'users': {
'total': user_count, 'total': user_count,
'activeMonth': user_count, # TODO 'activeMonth': last_month_count,
'activeHalfyear': user_count, # TODO 'activeHalfyear': six_month_count,
}, },
'localPosts': status_count, 'localPosts': status_count,
}, },