diff --git a/templates/member/forgot_password/index.html b/templates/member/forgot_password/index.html
index 035be636d0..ae50a725e0 100644
--- a/templates/member/forgot_password/index.html
+++ b/templates/member/forgot_password/index.html
@@ -52,7 +52,7 @@
- {% trans "Comment souhaitez vous retrouvez votre mot de passeĀ ?" %}
+ {% trans "Comment souhaitez-vous retrouver votre mot de passeĀ ?" %}
diff --git a/templates/tutorialv2/view/content.html b/templates/tutorialv2/view/content.html
index 7a65e743d0..17b7527511 100644
--- a/templates/tutorialv2/view/content.html
+++ b/templates/tutorialv2/view/content.html
@@ -109,13 +109,13 @@
{% include "tutorialv2/includes/content/content_pager.part.html" with content=content %}
{% endif %}
- {% if display_config.info_config.show_warn_typo %}
- {% include "tutorialv2/includes/content/warn_typo.part.html" with content=content %}
- {% endif %}
-
{% if display_config.alerts_config.show_alert_button %}
{% include "tutorialv2/includes/alert.html" with content=content current_content_type=current_content_type %}
{% endif %}
+
+ {% if display_config.info_config.show_warn_typo %}
+ {% include "tutorialv2/includes/content/warn_typo.part.html" with content=content %}
+ {% endif %}
{% endblock %}
diff --git a/zds/member/models.py b/zds/member/models.py
index 2c8b50a049..5c0ff057d0 100644
--- a/zds/member/models.py
+++ b/zds/member/models.py
@@ -185,15 +185,6 @@ def get_user_beta_contents_queryset(self, _type=None):
"""
return self.get_user_contents_queryset(_type).filter(sha_beta__isnull=False)
- def get_content_count(self, _type=None):
- """
- :param _type: if provided, request a specific type of content
- :return: the count of contents with this user as author. Count all contents no only published one.
- """
- if self.is_private():
- return 0
- return self.get_user_contents_queryset(_type).count()
-
def get_contents(self, _type=None):
"""
:param _type: if provided, request a specific type of content
@@ -231,12 +222,6 @@ def get_beta_contents(self, _type=None):
"""
return self.get_user_beta_contents_queryset(_type).all()
- def get_tuto_count(self):
- """
- :return: the count of tutorials with this user as author. Count all tutorials, no only published one.
- """
- return self.get_content_count(_type="TUTORIAL")
-
def get_tutos(self):
"""
:return: All tutorials with this user as author.
@@ -269,12 +254,6 @@ def get_beta_tutos(self):
"""
return self.get_beta_contents(_type="TUTORIAL")
- def get_article_count(self):
- """
- :return: the count of articles with this user as author. Count all articles, no only published one.
- """
- return self.get_content_count(_type="ARTICLE")
-
def get_articles(self):
"""
:return: All articles with this user as author.
@@ -307,12 +286,6 @@ def get_beta_articles(self):
"""
return self.get_beta_contents(_type="ARTICLE")
- def get_opinion_count(self):
- """
- :return: the count of opinions with this user as author. Count all opinions, no only published one.
- """
- return self.get_content_count(_type="OPINION")
-
def get_opinions(self):
"""
:return: All opinions with this user as author.
diff --git a/zds/member/tests/tests_models.py b/zds/member/tests/tests_models.py
index 8f5d0c1532..c21c30b114 100644
--- a/zds/member/tests/tests_models.py
+++ b/zds/member/tests/tests_models.py
@@ -62,17 +62,6 @@ def test_get_topic_count(self):
# Should be 1
self.assertEqual(self.user1.get_topic_count(), 1)
- def test_get_tuto_count(self):
- # Start with 0
- self.assertEqual(self.user1.get_tuto_count(), 0)
- # Create Tuto !
- minituto = PublishableContentFactory(type="TUTORIAL")
- minituto.authors.add(self.user1.user)
- minituto.gallery = GalleryFactory()
- minituto.save()
- # Should be 1
- self.assertEqual(self.user1.get_tuto_count(), 1)
-
def test_get_tutos(self):
# Start with 0
self.assertEqual(len(self.user1.get_tutos()), 0)
@@ -140,17 +129,6 @@ def test_get_beta_tutos(self):
self.assertEqual(len(betatetutos), 1)
self.assertEqual(betatetuto, betatetutos[0])
- def test_get_article_count(self):
- # Start with 0
- self.assertEqual(self.user1.get_tuto_count(), 0)
- # Create article !
- minituto = PublishableContentFactory(type="ARTICLE")
- minituto.authors.add(self.user1.user)
- minituto.gallery = GalleryFactory()
- minituto.save()
- # Should be 1
- self.assertEqual(self.user1.get_article_count(), 1)
-
def test_get_articles(self):
# Start with 0
self.assertEqual(len(self.user1.get_articles()), 0)
diff --git a/zds/member/tests/views/tests_moderation.py b/zds/member/tests/views/tests_moderation.py
index ebda4d9d94..06e752db96 100644
--- a/zds/member/tests/views/tests_moderation.py
+++ b/zds/member/tests/views/tests_moderation.py
@@ -527,7 +527,7 @@ def test_filter_member_ip(self):
class IpListingsTests(TestCase):
- """Test the member_from_ip function : listing users from a same IPV4/IPV6 address or same IPV6 network."""
+ """Test the member_from_ip view: listing users from a same IPv4/IPv6 address or same IPv6 network."""
def setUp(self) -> None:
self.staff = StaffProfileFactory().user
@@ -600,6 +600,13 @@ def test_different_ipv6_network(self) -> None:
self.assertNotContains(response, self.user_ipv6_same_ip_2.user.username)
self.assertNotContains(response, self.user_ipv6_same_network.user.username)
+ def test_ip_page_invalid_ip(self) -> None:
+ self.client.force_login(self.staff)
+ for ip in ["foo", "340.340.340.340", "2402:4899:1c8a:d75a:"]:
+ with self.subTest(ip):
+ response = self.client.get(reverse(member_from_ip, args=[ip]))
+ self.assertEqual(response.status_code, 404)
+
def test_access_rights_to_ip_page_as_regular_user(self) -> None:
self.client.force_login(self.regular_user.user)
response = self.client.get(reverse(member_from_ip, args=["0.0.0.0"]))
diff --git a/zds/member/views/moderation.py b/zds/member/views/moderation.py
index 81d12305da..f4e37e2a9d 100644
--- a/zds/member/views/moderation.py
+++ b/zds/member/views/moderation.py
@@ -4,7 +4,8 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
-from django.core.exceptions import PermissionDenied
+from django.core.exceptions import PermissionDenied, ValidationError
+from django.core.validators import validate_ipv46_address
from django.db import transaction
from django.http import Http404, HttpResponseBadRequest
from django.shortcuts import redirect, render, get_object_or_404
@@ -29,7 +30,17 @@
@login_required
@permission_required("member.change_profile", raise_exception=True)
def member_from_ip(request, ip_address):
- """List users connected from a particular IP, and an IPV6 subnetwork."""
+ """List users connected from a particular IP, and an IPv6 subnetwork."""
+
+ # If we don't check if the ip_address has a valid format, two things can
+ # happen:
+ # - the list of members with this IP will be empty (that's fine)
+ # - the call to get_geo_location_from_ip() will raise a ValueError, which
+ # in production will turn into an error 500 (which is not fine)
+ try:
+ validate_ipv46_address(ip_address)
+ except ValidationError:
+ raise Http404(_("Mauvais format d'adresse IP"))
members = Profile.objects.filter(last_ip_address=ip_address).order_by("-last_visit")
context_data = {
@@ -38,9 +49,9 @@ def member_from_ip(request, ip_address):
"ip_location": get_geo_location_from_ip(ip_address),
}
- if ":" in ip_address: # Check if it's an IPV6
+ if ":" in ip_address: # Check if it's an IPv6
network_ip = ipaddress.ip_network(ip_address + "/64", strict=False).network_address # Get the network / block
- # Remove the additional ":" at the end of the network adresse, so we can filter the IP adresses on this network
+ # Remove the additional ":" at the end of the network address, so we can filter the IP adresses on this network
network_ip = str(network_ip)[:-1]
network_members = Profile.objects.filter(last_ip_address__startswith=network_ip).order_by("-last_visit")
context_data["network_members"] = network_members