mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 19:41:11 +00:00
add notifs and error handling for user export/import
This commit is contained in:
parent
781b01a007
commit
20114b0059
9 changed files with 126 additions and 24 deletions
34
bookwyrm/migrations/0183_auto_20231021_2050.py
Normal file
34
bookwyrm/migrations/0183_auto_20231021_2050.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-10-21 20:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bookwyrm', '0182_merge_20230905_2240'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='notification',
|
||||||
|
name='related_user_export',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.bookwyrmexportjob'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='childjob',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('pending', 'Pending'), ('active', 'Active'), ('complete', 'Complete'), ('stopped', 'Stopped'), ('failed', 'Failed')], default='pending', max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notification',
|
||||||
|
name='notification_type',
|
||||||
|
field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('MENTION', 'Mention'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost'), ('IMPORT', 'Import'), ('USER_IMPORT', 'User Import'), ('USER_EXPORT', 'User Export'), ('ADD', 'Add'), ('REPORT', 'Report'), ('LINK_DOMAIN', 'Link Domain'), ('INVITE', 'Invite'), ('ACCEPT', 'Accept'), ('JOIN', 'Join'), ('LEAVE', 'Leave'), ('REMOVE', 'Remove'), ('GROUP_PRIVACY', 'Group Privacy'), ('GROUP_NAME', 'Group Name'), ('GROUP_DESCRIPTION', 'Group Description')], max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='parentjob',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('pending', 'Pending'), ('active', 'Active'), ('complete', 'Complete'), ('stopped', 'Stopped'), ('failed', 'Failed')], default='pending', max_length=50, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -41,8 +41,9 @@ def start_export_task(**kwargs):
|
||||||
json_data = json_export(job.user)
|
json_data = json_export(job.user)
|
||||||
tar_export(json_data, job.user, job.export_data)
|
tar_export(json_data, job.user, job.export_data)
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
logger.exception("Job %s Failed with error: %s", job.id, err)
|
logger.exception("User Export Job %s Failed with error: %s", job.id, err)
|
||||||
job.set_status("failed")
|
job.set_status("failed")
|
||||||
|
job.set_status("complete") # need to explicitly set this here to trigger notifications
|
||||||
job.save(update_fields=["export_data"])
|
job.save(update_fields=["export_data"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from django.db.models import FileField, JSONField, CharField
|
from django.db.models import FileField, JSONField, CharField
|
||||||
|
@ -18,7 +19,8 @@ from bookwyrm.models.job import (
|
||||||
create_child_job,
|
create_child_job,
|
||||||
)
|
)
|
||||||
from bookwyrm.utils.tar import BookwyrmTarFile
|
from bookwyrm.utils.tar import BookwyrmTarFile
|
||||||
import json
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BookwyrmImportJob(ParentJob):
|
class BookwyrmImportJob(ParentJob):
|
||||||
|
@ -43,27 +45,33 @@ def start_import_task(**kwargs):
|
||||||
if job.complete:
|
if job.complete:
|
||||||
return
|
return
|
||||||
|
|
||||||
archive_file.open("rb")
|
try:
|
||||||
with BookwyrmTarFile.open(mode="r:gz", fileobj=archive_file) as tar:
|
archive_file.open("rb")
|
||||||
job.import_data = json.loads(tar.read("archive.json").decode("utf-8"))
|
with BookwyrmTarFile.open(mode="r:gz", fileobj=archive_file) as tar:
|
||||||
|
job.import_data = json.loads(tar.read("archive.json").decode("utf-8"))
|
||||||
|
|
||||||
if "include_user_profile" in job.required:
|
if "include_user_profile" in job.required:
|
||||||
update_user_profile(job.user, tar, job.import_data.get("user"))
|
update_user_profile(job.user, tar, job.import_data.get("user"))
|
||||||
if "include_user_settings" in job.required:
|
if "include_user_settings" in job.required:
|
||||||
update_user_settings(job.user, job.import_data.get("user"))
|
update_user_settings(job.user, job.import_data.get("user"))
|
||||||
if "include_goals" in job.required:
|
if "include_goals" in job.required:
|
||||||
update_goals(job.user, job.import_data.get("goals"))
|
update_goals(job.user, job.import_data.get("goals"))
|
||||||
if "include_saved_lists" in job.required:
|
if "include_saved_lists" in job.required:
|
||||||
upsert_saved_lists(job.user, job.import_data.get("saved_lists"))
|
upsert_saved_lists(job.user, job.import_data.get("saved_lists"))
|
||||||
if "include_follows" in job.required:
|
if "include_follows" in job.required:
|
||||||
upsert_follows(job.user, job.import_data.get("follows"))
|
upsert_follows(job.user, job.import_data.get("follows"))
|
||||||
if "include_blocks" in job.required:
|
if "include_blocks" in job.required:
|
||||||
upsert_user_blocks(job.user, job.import_data.get("blocked_users"))
|
upsert_user_blocks(job.user, job.import_data.get("blocked_users"))
|
||||||
|
|
||||||
process_books(job, tar)
|
process_books(job, tar)
|
||||||
|
|
||||||
job.save()
|
job.set_status("complete") # set here to trigger notifications
|
||||||
archive_file.close()
|
job.save()
|
||||||
|
archive_file.close()
|
||||||
|
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
logger.exception("User Import Job %s Failed with error: %s", job.id, err)
|
||||||
|
job.set_status("failed")
|
||||||
|
|
||||||
|
|
||||||
def process_books(job, tar):
|
def process_books(job, tar):
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Job(models.Model):
|
||||||
|
|
||||||
self.__terminate_job()
|
self.__terminate_job()
|
||||||
|
|
||||||
if reason and reason is "failed":
|
if reason and reason == "failed":
|
||||||
self.status = self.Status.FAILED
|
self.status = self.Status.FAILED
|
||||||
else:
|
else:
|
||||||
self.status = self.Status.STOPPED
|
self.status = self.Status.STOPPED
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from . import Boost, Favorite, GroupMemberInvitation, ImportJob, LinkDomain
|
from . import Boost, Favorite, GroupMemberInvitation, ImportJob, BookwyrmImportJob, LinkDomain
|
||||||
|
from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob
|
||||||
from . import ListItem, Report, Status, User, UserFollowRequest
|
from . import ListItem, Report, Status, User, UserFollowRequest
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +23,8 @@ class Notification(BookWyrmModel):
|
||||||
|
|
||||||
# Imports
|
# Imports
|
||||||
IMPORT = "IMPORT"
|
IMPORT = "IMPORT"
|
||||||
|
USER_IMPORT = "USER_IMPORT"
|
||||||
|
USER_EXPORT = "USER_EXPORT"
|
||||||
|
|
||||||
# List activity
|
# List activity
|
||||||
ADD = "ADD"
|
ADD = "ADD"
|
||||||
|
@ -44,7 +47,7 @@ class Notification(BookWyrmModel):
|
||||||
NotificationType = models.TextChoices(
|
NotificationType = models.TextChoices(
|
||||||
# there has got be a better way to do this
|
# there has got be a better way to do this
|
||||||
"NotificationType",
|
"NotificationType",
|
||||||
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION}",
|
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {USER_IMPORT} {USER_EXPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION}",
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||||
|
@ -61,6 +64,7 @@ class Notification(BookWyrmModel):
|
||||||
)
|
)
|
||||||
related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True)
|
related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True)
|
||||||
related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True)
|
related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True)
|
||||||
|
related_user_export = models.ForeignKey("BookwyrmExportJob", on_delete=models.CASCADE, null=True)
|
||||||
related_list_items = models.ManyToManyField(
|
related_list_items = models.ManyToManyField(
|
||||||
"ListItem", symmetrical=False, related_name="notifications"
|
"ListItem", symmetrical=False, related_name="notifications"
|
||||||
)
|
)
|
||||||
|
@ -222,6 +226,36 @@ def notify_user_on_import_complete(
|
||||||
related_import=instance,
|
related_import=instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=BookwyrmImportJob)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def notify_user_on_user_import_complete(
|
||||||
|
sender, instance, *args, update_fields=None, **kwargs
|
||||||
|
):
|
||||||
|
"""we imported your user details! aren't you proud of us"""
|
||||||
|
update_fields = update_fields or []
|
||||||
|
if not instance.complete or "complete" not in update_fields:
|
||||||
|
return
|
||||||
|
Notification.objects.create(
|
||||||
|
user=instance.user,
|
||||||
|
notification_type=Notification.USER_IMPORT
|
||||||
|
)
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=BookwyrmExportJob)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def notify_user_on_user_export_complete(
|
||||||
|
sender, instance, *args, update_fields=None, **kwargs
|
||||||
|
):
|
||||||
|
"""we imported your user details! aren't you proud of us"""
|
||||||
|
update_fields = update_fields or []
|
||||||
|
if not instance.complete or "complete" not in update_fields:
|
||||||
|
print("RETURNING", instance.status)
|
||||||
|
return
|
||||||
|
print("NOTIFYING")
|
||||||
|
Notification.objects.create(
|
||||||
|
user=instance.user,
|
||||||
|
notification_type=Notification.USER_EXPORT,
|
||||||
|
related_user_export=instance,
|
||||||
|
)
|
||||||
|
|
||||||
@receiver(models.signals.post_save, sender=Report)
|
@receiver(models.signals.post_save, sender=Report)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
|
|
@ -133,7 +133,7 @@
|
||||||
<td>{{ job.updated_date }}</td>
|
<td>{{ job.updated_date }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
{% if job.status == "stopped" %}
|
{% if job.status == "stopped" or job.status == "failed" %}
|
||||||
class="tag is-danger"
|
class="tag is-danger"
|
||||||
{% elif job.status == "pending" %}
|
{% elif job.status == "pending" %}
|
||||||
class="tag is-warning"
|
class="tag is-warning"
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
{% include 'notifications/items/follow_request.html' %}
|
{% include 'notifications/items/follow_request.html' %}
|
||||||
{% elif notification.notification_type == 'IMPORT' %}
|
{% elif notification.notification_type == 'IMPORT' %}
|
||||||
{% include 'notifications/items/import.html' %}
|
{% include 'notifications/items/import.html' %}
|
||||||
|
{% elif notification.notification_type == 'USER_IMPORT' %}
|
||||||
|
{% include 'notifications/items/user_import.html' %}
|
||||||
|
{% elif notification.notification_type == 'USER_EXPORT' %}
|
||||||
|
{% include 'notifications/items/user_export.html' %}
|
||||||
{% elif notification.notification_type == 'ADD' %}
|
{% elif notification.notification_type == 'ADD' %}
|
||||||
{% include 'notifications/items/add.html' %}
|
{% include 'notifications/items/add.html' %}
|
||||||
{% elif notification.notification_type == 'REPORT' %}
|
{% elif notification.notification_type == 'REPORT' %}
|
||||||
|
|
11
bookwyrm/templates/notifications/items/user_export.html
Normal file
11
bookwyrm/templates/notifications/items/user_export.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block icon %}
|
||||||
|
<span class="icon icon-list"></span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
{% url 'prefs-export-file' notification.related_user_export.task_id as url %}
|
||||||
|
{% blocktrans %}Your <a download href="{{ url }}">user export</a> is ready.{% endblocktrans %}
|
||||||
|
{% endblock %}
|
10
bookwyrm/templates/notifications/items/user_import.html
Normal file
10
bookwyrm/templates/notifications/items/user_import.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block icon %}
|
||||||
|
<span class="icon icon-list"></span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
{% blocktrans %}Your user import is complete.{% endblocktrans %}
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue