Splits user view out and adds template

This commit is contained in:
Mouse Reeve 2021-06-14 09:57:51 -07:00
parent fd753d47ce
commit 7ae64c2a68
12 changed files with 233 additions and 157 deletions

View file

@ -1,4 +1,4 @@
{% extends 'preferences/preferences_layout.html' %}
{% extends 'preferences/layout.html' %}
{% load i18n %}
{% block title %}{% trans "Blocked Users" %}{{ author.name }}{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'preferences/preferences_layout.html' %}
{% extends 'preferences/layout.html' %}
{% load i18n %}
{% block title %}{% trans "Change Password" %}{% endblock %}

View file

@ -0,0 +1,27 @@
{% extends 'preferences/layout.html' %}
{% load i18n %}
{% block title %}{% trans "Delete Account" %}{% endblock %}
{% block header %}
{% trans "Delete Account" %}
{% endblock %}
{% block panel %}
{% if form.non_field_errors %}
<p class="notification is-danger">{{ form.non_field_errors }}</p>
{% endif %}
<div class="block">
<h2 class="title is-4">{% trans "Permanently delete account" %}</h2>
<p class="help">{% trans "Deleting your account cannot be undone. The username will not be available to register in the future." %}</p>
<form name="delete-account" action="/delete-account" method="post">
<div class="field">
<label class="label" for="id_password">{% trans "Confirm password" %}</label>
<input class="input" type="password" name="password" id="id_password">
</div>
<button type="submit" class="is-danger">Delete</button>
</form>
</div>
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'preferences/preferences_layout.html' %}
{% extends 'preferences/layout.html' %}
{% load i18n %}
{% block title %}{% trans "Edit Profile" %}{% endblock %}
@ -69,4 +69,3 @@
</div>
<div class="block"><button class="button is-primary" type="submit">{% trans "Save" %}</button></div>
</form>
{% endblock %}

View file

@ -18,6 +18,10 @@
{% url 'prefs-password' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Change Password" %}</a>
</li>
<li>
{% url 'prefs-delete' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Delete Account" %}</a>
</li>
</ul>
<h2 class="menu-label">{% trans "Relationships" %}</h2>
<ul class="menu-list">

View file

@ -0,0 +1,110 @@
""" test for app action functionality """
import pathlib
from unittest.mock import patch
from PIL import Image
from django.contrib.auth.models import AnonymousUser
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import forms, models, views
class EditUserViews(TestCase):
"""view user and edit profile"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
self.book = models.Edition.objects.create(title="test")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create(
book=self.book,
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
)
models.SiteSettings.objects.create()
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
def test_edit_user_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.EditUser.as_view()
request = self.factory.get("")
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
def test_edit_user(self):
"""use a form to update a user"""
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name"
form.data["email"] = "wow@email.com"
form.data["preferred_timezone"] = "UTC"
request = self.factory.post("", form.data)
request.user = self.local_user
self.assertIsNone(self.local_user.name)
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
view(request)
self.assertEqual(delay_mock.call_count, 1)
self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com")
def test_edit_user_avatar(self):
"""use a form to update a user"""
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name"
form.data["email"] = "wow@email.com"
form.data["preferred_timezone"] = "UTC"
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg"
)
form.data["avatar"] = SimpleUploadedFile(
image_file, open(image_file, "rb").read(), content_type="image/jpeg"
)
request = self.factory.post("", form.data)
request.user = self.local_user
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
view(request)
self.assertEqual(delay_mock.call_count, 1)
self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com")
self.assertIsNotNone(self.local_user.avatar)
self.assertEqual(self.local_user.avatar.width, 120)
self.assertEqual(self.local_user.avatar.height, 120)
def test_crop_avatar(self):
"""reduce that image size"""
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg"
)
image = Image.open(image_file)
result = views.user.crop_avatar(image)
self.assertIsInstance(result, ContentFile)
image_result = Image.open(result)
self.assertEqual(image_result.size, (120, 120))

View file

@ -1,17 +1,13 @@
""" test for app action functionality """
import pathlib
from unittest.mock import patch
from PIL import Image
from django.contrib.auth.models import AnonymousUser
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.http.response import Http404
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import forms, models, views
from bookwyrm import models, views
from bookwyrm.activitypub import ActivitypubResponse
@ -137,71 +133,3 @@ class UserViews(TestCase):
is_api.return_value = False
with self.assertRaises(Http404):
view(request, "rat")
def test_edit_user_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.EditUser.as_view()
request = self.factory.get("")
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
def test_edit_user(self):
"""use a form to update a user"""
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name"
form.data["email"] = "wow@email.com"
form.data["preferred_timezone"] = "UTC"
request = self.factory.post("", form.data)
request.user = self.local_user
self.assertIsNone(self.local_user.name)
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
view(request)
self.assertEqual(delay_mock.call_count, 1)
self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com")
def test_edit_user_avatar(self):
"""use a form to update a user"""
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name"
form.data["email"] = "wow@email.com"
form.data["preferred_timezone"] = "UTC"
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg"
)
form.data["avatar"] = SimpleUploadedFile(
image_file, open(image_file, "rb").read(), content_type="image/jpeg"
)
request = self.factory.post("", form.data)
request.user = self.local_user
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
view(request)
self.assertEqual(delay_mock.call_count, 1)
self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com")
self.assertIsNotNone(self.local_user.avatar)
self.assertEqual(self.local_user.avatar.width, 120)
self.assertEqual(self.local_user.avatar.height, 120)
def test_crop_avatar(self):
"""reduce that image size"""
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg"
)
image = Image.open(image_file)
result = views.user.crop_avatar(image)
self.assertIsInstance(result, ContentFile)
image_result = Image.open(result)
self.assertEqual(image_result.size, (120, 120))

View file

@ -253,6 +253,7 @@ urlpatterns = [
views.ChangePassword.as_view(),
name="prefs-password",
),
re_path(r"^preferences/delete/?$", views.DeleteAccount.as_view(), name="prefs-delete"),
re_path(r"^preferences/block/?$", views.Block.as_view(), name="prefs-block"),
re_path(r"^block/(?P<user_id>\d+)/?$", views.Block.as_view()),
re_path(r"^unblock/(?P<user_id>\d+)/?$", views.unblock),

View file

@ -6,6 +6,7 @@ from .block import Block, unblock
from .books import Book, EditBook, ConfirmEditBook, Editions
from .books import upload_cover, add_description, switch_edition, resolve_book
from .directory import Directory
from .edit_user import EditUser
from .federation import Federation, FederatedServer
from .federation import AddFederatedServer, ImportServerBlocklist
from .federation import block_server, unblock_server
@ -37,6 +38,6 @@ from .shelf import shelve, unshelve
from .site import Site
from .status import CreateStatus, DeleteStatus, DeleteAndRedraft
from .updates import get_notification_count, get_unread_status_count
from .user import User, EditUser, Followers, Following
from .user import User, Followers, Following
from .user_admin import UserAdmin, UserAdminList
from .wellknown import *

View file

@ -0,0 +1,82 @@
""" edit or delete ones own account"""
from io import BytesIO
from uuid import uuid4
from PIL import Image
from django.contrib.auth.decorators import login_required
from django.core.files.base import ContentFile
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm import forms
# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
class EditUser(View):
"""edit user view"""
def get(self, request):
"""edit profile page for a user"""
data = {
"form": forms.EditUserForm(instance=request.user),
"user": request.user,
}
return TemplateResponse(request, "preferences/edit_user.html", data)
def post(self, request):
"""les get fancy with images"""
form = forms.EditUserForm(request.POST, request.FILES, instance=request.user)
if not form.is_valid():
data = {"form": form, "user": request.user}
return TemplateResponse(request, "preferences/edit_user.html", data)
user = save_user_form(form)
return redirect(user.local_path)
def save_user_form(form):
"""special handling for the user form"""
user = form.save(commit=False)
if "avatar" in form.files:
# crop and resize avatar upload
image = Image.open(form.files["avatar"])
image = crop_avatar(image)
# set the name to a hash
extension = form.files["avatar"].name.split(".")[-1]
filename = "%s.%s" % (uuid4(), extension)
user.avatar.save(filename, image, save=False)
user.save()
return user
def crop_avatar(image):
"""reduce the size and make an avatar square"""
target_size = 120
width, height = image.size
thumbnail_scale = (
height / (width / target_size)
if height > width
else width / (height / target_size)
)
image.thumbnail([thumbnail_scale, thumbnail_scale])
width, height = image.size
width_diff = width - target_size
height_diff = height - target_size
cropped = image.crop(
(
int(width_diff / 2),
int(height_diff / 2),
int(width - (width_diff / 2)),
int(height - (height_diff / 2)),
)
)
output = BytesIO()
cropped.save(output, format=image.format)
return ContentFile(output.getvalue())

View file

@ -14,7 +14,7 @@ from django.views import View
from bookwyrm import forms, models
from bookwyrm.connectors import connector_manager
from .helpers import get_suggested_users
from .user import save_user_form
from .edit_user import save_user_form
# pylint: disable= no-self-use

View file

@ -1,25 +1,17 @@
""" non-interactive pages """
from io import BytesIO
from uuid import uuid4
from PIL import Image
from django.contrib.auth.decorators import login_required
from django.core.files.base import ContentFile
from django.core.paginator import Paginator
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm import forms, models
from bookwyrm import models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import get_user_from_username, is_api_request
from .helpers import privacy_filter
# pylint: disable= no-self-use
# pylint: disable=no-self-use
class User(View):
"""user profile page"""
@ -122,71 +114,3 @@ class Following(View):
"follow_list": paginated.get_page(request.GET.get("page")),
}
return TemplateResponse(request, "user/relationships/following.html", data)
@method_decorator(login_required, name="dispatch")
class EditUser(View):
"""edit user view"""
def get(self, request):
"""edit profile page for a user"""
data = {
"form": forms.EditUserForm(instance=request.user),
"user": request.user,
}
return TemplateResponse(request, "preferences/edit_user.html", data)
def post(self, request):
"""les get fancy with images"""
form = forms.EditUserForm(request.POST, request.FILES, instance=request.user)
if not form.is_valid():
data = {"form": form, "user": request.user}
return TemplateResponse(request, "preferences/edit_user.html", data)
user = save_user_form(form)
return redirect(user.local_path)
def save_user_form(form):
"""special handling for the user form"""
user = form.save(commit=False)
if "avatar" in form.files:
# crop and resize avatar upload
image = Image.open(form.files["avatar"])
image = crop_avatar(image)
# set the name to a hash
extension = form.files["avatar"].name.split(".")[-1]
filename = "%s.%s" % (uuid4(), extension)
user.avatar.save(filename, image, save=False)
user.save()
return user
def crop_avatar(image):
"""reduce the size and make an avatar square"""
target_size = 120
width, height = image.size
thumbnail_scale = (
height / (width / target_size)
if height > width
else width / (height / target_size)
)
image.thumbnail([thumbnail_scale, thumbnail_scale])
width, height = image.size
width_diff = width - target_size
height_diff = height - target_size
cropped = image.crop(
(
int(width_diff / 2),
int(height_diff / 2),
int(width - (width_diff / 2)),
int(height - (height_diff / 2)),
)
)
output = BytesIO()
cropped.save(output, format=image.format)
return ContentFile(output.getvalue())