mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 11:31:08 +00:00
commit
a242a476d9
6 changed files with 81 additions and 27 deletions
18
bookwyrm/migrations/0063_user_last_active_date.py
Normal file
18
bookwyrm/migrations/0063_user_last_active_date.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue