Skip to content

Commit 5e44f30

Browse files
committed
Merge branch 'miscs'
2 parents 597fde3 + 42aac51 commit 5e44f30

File tree

7 files changed

+64
-43
lines changed

7 files changed

+64
-43
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## Unreleased
44

55
- Correct footer background on dark mode.
6+
- Prevent articles to be read on scroll before initial scroll to top on page load.
7+
- Force re-authentication before managing tokens.
8+
- Allow users to change their passwords.
69

710
## 24.12.6
811

legadilo/conftest.py

+3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
from http import HTTPStatus
1717

1818
import pytest
19+
from allauth.conftest import reauthentication_bypass as allauth_reauthentication_bypass
1920
from django.urls import reverse
2021

2122
from legadilo.core.models import Timezone
2223
from legadilo.users.models import User
2324
from legadilo.users.tests.factories import UserFactory, UserSettingsFactory
2425

26+
reauthentication_bypass = allauth_reauthentication_bypass
27+
2528

2629
@pytest.fixture(autouse=True)
2730
def _setup_settings(settings, tmpdir):

legadilo/static/js/list_of_articles.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
return;
5959
}
6060

61+
console.log("Setting up read on scroll.");
6162
// Force a scroll to top after reloading the page: previously read articles won’t be there
6263
// anymore and the back button of the browser will preserve scroll. We may end up marking some
6364
// articles as read when we shouldn’t. Clicking the back button on the details page doesn’t have
@@ -66,12 +67,16 @@
6667
// correctly triggers the scroll to top code. However, when the browser is reopened, it did not.
6768
// it seems that the browser is restoring the scroll with a little delay after the scroll to
6869
// top had run. Hence, this timeout block. Improve this if you have a cleaner solution.
69-
setTimeout(() => window.scroll(0, 0), 500);
70-
71-
// Wait before reading on scroll: the user may scroll up again!
72-
const readOnScrollDebounced = debounce(readOnScroll, 1_000);
73-
document.addEventListener("scrollend", readOnScrollDebounced);
74-
console.log("Read on scroll setup!");
70+
// We don’t start marking articles as read until the scroll is done: it can take a while and on
71+
// some occasions, we may start marking articles as read before the scroll is done.
72+
setTimeout(() => {
73+
window.scroll(0, 0);
74+
// Wait before reading on scroll: the user may scroll up again! We don’t want to mark articles
75+
// as read in this case.
76+
const readOnScrollDebounced = debounce(readOnScroll, 1_000);
77+
document.addEventListener("scrollend", readOnScrollDebounced);
78+
console.log("Read on scroll setup!");
79+
}, 500);
7580
};
7681

7782
const readOnScroll = () => {

legadilo/templates/users/user_detail.html

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ <h2>{{ object.email }}</h2>
2525
<a class="btn btn-primary"
2626
href="{% url 'users:update_settings' %}"
2727
role="button">{% translate "My settings" %}</a>
28+
<a class="btn btn-primary"
29+
href="{% url 'account_change_password' %}"
30+
role="button">{% translate "Change password" %}</a>
2831
<a class="btn btn-primary" href="{% url 'mfa_index' %}" role="button">{% translate "Two-Factor Authentication" %}</a>
2932
</div>
3033
<div class="col-sm-12 mt-1">

legadilo/users/tests/views/test_manage_tokens_views.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ def test_list_not_logged_in(self, client):
4141

4242
assert_redirected_to_login_page(response)
4343

44-
def test_list(self, logged_in_sync_client, other_user, django_assert_num_queries):
44+
def test_list(
45+
self, logged_in_sync_client, other_user, django_assert_num_queries, reauthentication_bypass
46+
):
4547
ApplicationTokenFactory(user=other_user)
4648

47-
with django_assert_num_queries(8):
49+
with django_assert_num_queries(8), reauthentication_bypass():
4850
response = logged_in_sync_client.get(self.url)
4951

5052
assert response.status_code == HTTPStatus.OK
@@ -54,9 +56,9 @@ def test_list(self, logged_in_sync_client, other_user, django_assert_num_queries
5456
assert list(response.context_data["tokens"]) == [self.application_token]
5557

5658
def test_create_token_invalid_form(
57-
self, user, logged_in_sync_client, django_assert_num_queries
59+
self, user, logged_in_sync_client, django_assert_num_queries, reauthentication_bypass
5860
):
59-
with django_assert_num_queries(8):
61+
with django_assert_num_queries(8), reauthentication_bypass():
6062
response = logged_in_sync_client.post(self.url, data={})
6163

6264
assert response.status_code == HTTPStatus.BAD_REQUEST
@@ -65,8 +67,10 @@ def test_create_token_invalid_form(
6567
assert response.context_data["new_application_token_secret"] is None
6668
assert response.context_data["form"].errors == {"title": ["This field is required."]}
6769

68-
def test_create_token(self, user, logged_in_sync_client, django_assert_num_queries):
69-
with django_assert_num_queries(11):
70+
def test_create_token(
71+
self, user, logged_in_sync_client, django_assert_num_queries, reauthentication_bypass
72+
):
73+
with django_assert_num_queries(11), reauthentication_bypass():
7074
response = logged_in_sync_client.post(self.url, data={"title": "Test token"})
7175

7276
assert response.status_code == HTTPStatus.OK
@@ -81,8 +85,10 @@ def test_create_token(self, user, logged_in_sync_client, django_assert_num_queri
8185
assert len(response.context_data["new_application_token_secret"]) == 67
8286
assert list(response.context_data["tokens"]) == [new_token, self.application_token]
8387

84-
def test_create_duplicated_token(self, logged_in_sync_client, django_assert_num_queries):
85-
with django_assert_num_queries(12):
88+
def test_create_duplicated_token(
89+
self, logged_in_sync_client, django_assert_num_queries, reauthentication_bypass
90+
):
91+
with django_assert_num_queries(12), reauthentication_bypass():
8692
response = logged_in_sync_client.post(
8793
self.url, data={"title": self.application_token.title}
8894
)
@@ -101,9 +107,9 @@ def test_create_duplicated_token(self, logged_in_sync_client, django_assert_num_
101107
]
102108

103109
def test_create_token_with_validity_end(
104-
self, user, logged_in_sync_client, django_assert_num_queries
110+
self, user, logged_in_sync_client, django_assert_num_queries, reauthentication_bypass
105111
):
106-
with django_assert_num_queries(11):
112+
with django_assert_num_queries(11), reauthentication_bypass():
107113
response = logged_in_sync_client.post(
108114
self.url, data={"title": "Test token", "validity_end": "2024-11-24 12:00:00Z"}
109115
)
@@ -116,11 +122,11 @@ def test_create_token_with_validity_end(
116122
assert new_token.validity_end == utcdt(2024, 11, 24, 12)
117123

118124
def test_create_token_with_validity_end_in_timezone(
119-
self, user, logged_in_sync_client, django_assert_num_queries
125+
self, user, logged_in_sync_client, django_assert_num_queries, reauthentication_bypass
120126
):
121127
new_york_tz, _created = Timezone.objects.get_or_create(name="America/New_York")
122128

123-
with django_assert_num_queries(12):
129+
with django_assert_num_queries(12), reauthentication_bypass():
124130
response = logged_in_sync_client.post(
125131
self.url,
126132
data={

legadilo/users/views/manage_tokens_views.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
from http import HTTPStatus
1717

18+
from allauth.account.decorators import reauthentication_required
1819
from django import forms
1920
from django.contrib import messages
2021
from django.contrib.auth.decorators import login_required
@@ -56,7 +57,7 @@ def clean(self):
5657
)
5758

5859

59-
@login_required
60+
@reauthentication_required
6061
@require_http_methods(["GET", "POST"])
6162
def manage_tokens_view(request: AuthenticatedHttpRequest) -> TemplateResponse:
6263
form = CreateTokenForm(initial={"timezone": request.user.settings.timezone})

uv.lock

+24-24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)