Skip to content

Commit

Permalink
Merge branch 'dev' into fix-endfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud-D authored Aug 26, 2023
2 parents 02e6bfa + b6fd744 commit e6c8e5e
Show file tree
Hide file tree
Showing 29 changed files with 8,325 additions and 92 deletions.
13 changes: 7 additions & 6 deletions doc/source/front-end/template-tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,24 @@ Ce filtre formate une date au format ``DateTime`` destiné à être affiché sur

Ce filtre effectue la même chose que ``format_date`` mais à destination des ``tooltip``.

``humane_time``
---------------
``date_from_timestamp``
-----------------------

Formate une date au format *Nombre de seconde depuis Epoch* en un élément lisible. Ainsi :
Convertit une date au format *Nombre de seconde depuis Epoch* en un objet
accepté par les autres filtres de ce module. Ainsi :

.. sourcecode:: html+django

{% load date %}
{{ date_epoch|humane_time }}
{{ date_epoch|date_from_timestamp|format_date }}

sera rendu :

.. sourcecode:: text

jeudi 01 janvier 1970 à 00h00
jeudi 01 janvier 1970 à 00h02

…si le contenu de ``date_epoch`` était de ``42``.
…si le contenu de ``date_epoch`` était de ``122``.

``from_elasticsearch_date``
---------------------------
Expand Down
4 changes: 2 additions & 2 deletions templates/tutorialv2/goals/mass-edit-goals.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
<h3>Filtrer</h3>
<ul>
<li><a href="{% url "content:mass-edit-goals" %}" class="{% if all %}selected{% endif %}">Toutes ({{ num_all }})</a></li>
<li><a href="{% url "content:mass-edit-goals" %}?non-classes" class="{% if only_not_classified %}selected{% endif %}">Sans objectif ({{ num_not_classified }})</a></li>
<li><a href="{{ url_not_classified }}" class="{% if only_not_classified %}selected{% endif %}">Sans objectif ({{ num_not_classified }})</a></li>
{% for goal in goals %}
<li>
<a href="{% url "content:mass-edit-goals" %}?objectif_{{ goal.id }}" class="{% if current_filter_pk == goal.pk %}selected{% endif %}">{{ goal.name }} ({{ goal.num_contents }})</a>
<a href="{% url "content:mass-edit-goals" %}?{{ goal.slug }}" class="{% if current_filter_pk == goal.pk %}selected{% endif %}">{{ goal.name }} ({{ goal.num_contents }})</a>
</li>
{% endfor %}
</ul>
Expand Down
4 changes: 2 additions & 2 deletions templates/tutorialv2/goals/view-goals.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
<div class="mobile-menu-bloc mobile-all-links" data-title="Filtrer">
<h3>Filtrer</h3>
<ul>
<li><a href="{% url "content:view-goals" %}?non-classes" class="{% if only_not_classified %}selected{% endif %}">Sans objectif ({{ num_not_classified }})</a></li>
<li><a href="{{ url_not_classified }}" class="{% if only_not_classified %}selected{% endif %}">Sans objectif ({{ num_not_classified }})</a></li>
{% for goal in goals %}
<li>
<a href="{% url "content:view-goals" %}?objectif_{{ goal.id }}" class="{% if current_filter_pk == goal.pk %}selected{% endif %}">{{ goal.name }} ({{ goal.num_contents }})</a>
<a href="{% url "content:view-goals" %}?{{ goal.slug }}" class="{% if current_filter_pk == goal.pk %}selected{% endif %}">{{ goal.name }} ({{ goal.num_contents }})</a>
</li>
{% endfor %}
<li><a href="{% url "content:view-goals" %}" class="{% if all %}selected{% endif %}">Toutes ({{ num_all }})</a></li>
Expand Down
2 changes: 1 addition & 1 deletion templates/tutorialv2/includes/goals.part.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
Warning: whitespace in the loop below is crucial to ensure correct rendering.
That's why it is written as a one-liner. Take care when modifying it.
{% endcomment %}
{% for goal in goals %}{% if not forloop.first %}, {% endif %}<a href="{% url "content:view-goals" %}?objectif_{{ goal.id }}">{{ goal }}</a>{% endfor %}
{% for goal in goals %}{% if not forloop.first %}, {% endif %}<a href="{% url "content:view-goals" %}?{{ goal.slug }}">{{ goal }}</a>{% endfor %}
</p>
{% endif %}
4 changes: 2 additions & 2 deletions templates/tutorialv2/view/history.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ <h2 class="subtitle">
{% endif %}
</td>
<td>
{{ commit.authored_date|humane_time }}
{{ commit.authored_date|date_from_timestamp|format_date }}
</td>
<td>
<a href="{% url "content:view" content.pk content.slug %}?version={{ commit.hexsha }}" >
Expand Down Expand Up @@ -161,7 +161,7 @@ <h2 class="subtitle">
{% trans "mettre à jour" %}
{% endif %}
{% endcaptureas %}
{% blocktrans with action=action date_version=commit.authored_date|humane_time content_title=content.title %}
{% blocktrans with action=action date_version=commit.authored_date|date_from_timestamp|format_date content_title=content.title %}
Êtes-vous certain de vouloir <strong>{{ action }}</strong> la bêta pour le contenu
"<em>{{ content_title }}</em>" dans sa version de {{ date_version }} ?
{% endblocktrans %}
Expand Down
17 changes: 12 additions & 5 deletions zds/forum/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ class TopicForm(forms.Form, FieldValidatorMixin):
label=_("Tag(s) séparés par une virgule (exemple: python,django,web)"),
max_length=64,
required=False,
widget=forms.TextInput(
attrs={"data-autocomplete": '{ "type": "multiple", "fieldname": "title", "url": "/api/tags/?search=%s" }'}
),
widget=forms.TextInput(),
)

text = forms.CharField(
Expand All @@ -44,6 +42,15 @@ class TopicForm(forms.Form, FieldValidatorMixin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.fields["tags"].widget.attrs.update(
{
"data-autocomplete": '{ "type": "multiple", "fieldname": "title", "url": "'
+ reverse("api:utils:tags-list")
+ '?search=%s" }'
}
)

self.helper = FormHelper()
self.helper.form_class = "content-wrapper"
self.helper.form_method = "post"
Expand All @@ -53,11 +60,11 @@ def __init__(self, *args, **kwargs):
Field("subtitle", autocomplete="off"),
Field("tags"),
HTML(
"""<div id="topic-suggest" style="display:none;" url="/rechercher/sujets-similaires/">
"""<div id="topic-suggest" style="display:none;" url="{}">
<label>{}</label>
<div id="topic-result-container" data-neither="{}"></div>
</div>""".format(
_("Sujets similaires au vôtre :"), _("Aucun résultat")
reverse("search:similar"), _("Sujets similaires au vôtre :"), _("Aucun résultat")
)
),
CommonLayoutEditor(),
Expand Down
31 changes: 31 additions & 0 deletions zds/forum/tests/tests_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from django.contrib.auth.models import Group
from django.test import TestCase

from zds.forum.tests.factories import create_category_and_forum
from zds.forum.utils import get_authorized_forums_pk
from zds.member.tests.factories import ProfileFactory, StaffProfileFactory


class GetAuthorizedForumsTests(TestCase):
def test_get_authorized_forums_pk(self):
user = ProfileFactory().user
staff = StaffProfileFactory().user

# 1. Create a hidden forum belonging to a hidden staff group:
group = Group.objects.create(name="Les illuminatis anonymes de ZdS")
_, hidden_forum = create_category_and_forum(group)

staff.groups.add(group)
staff.save()

# 2. Create a public forum:
_, public_forum = create_category_and_forum()

# 3. Regular user can access only the public forum:
self.assertEqual(get_authorized_forums_pk(user), [public_forum.pk])

# 4. Staff user can access all forums:
self.assertEqual(sorted(get_authorized_forums_pk(staff)), sorted([hidden_forum.pk, public_forum.pk]))

# 5. By default, only public forums are available:
self.assertEqual(get_authorized_forums_pk(None), [public_forum.pk])
19 changes: 18 additions & 1 deletion zds/forum/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.views.generic import CreateView
from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import gettext as _
from zds.forum.models import Topic, Post
from zds.forum.models import Forum, Topic, Post
from zds.member.views import get_client_ip
from zds.utils.misc import contains_utf8mb4
from zds.utils.mixins import QuoteMixin
Expand Down Expand Up @@ -198,3 +198,20 @@ def post(self, request, *args, **kwargs):

def create_forum(self, form_class, **kwargs):
raise NotImplementedError("`create_forum()` must be implemented.")


def get_authorized_forums_pk(user):
"""
Find forums the user is allowed to visit.
:param user: concerned user.
:return: pk of authorized forums
"""
forums_pub = Forum.objects.filter(groups__isnull=True).all()
if user and user.is_authenticated:
forums_private = Forum.objects.filter(groups__isnull=False, groups__in=user.groups.all()).all()
list_forums = list(forums_pub | forums_private)
else:
list_forums = list(forums_pub)

return [f.pk for f in list_forums]
5 changes: 2 additions & 3 deletions zds/member/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,8 @@ def find_username_skeleton(username):
skeleton = ""
for ch in username:
homoglyph = hg.Homoglyphs(languages={"fr"}, strategy=hg.STRATEGY_LOAD).to_ascii(ch)
if len(homoglyph) > 0:
if homoglyph[0].strip() != "":
skeleton += homoglyph[0]
if len(homoglyph) > 0 and homoglyph[0].strip() != "":
skeleton += homoglyph[0]
return skeleton.lower()


Expand Down
11 changes: 11 additions & 0 deletions zds/member/tests/tests_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ def test_username_slash_register_form(self):
form = RegisterForm(data=data)
self.assertFalse(form.is_valid())

def test_username_character_null(self):
ProfileFactory()
data = {
"email": "test@gmail.com",
"username": "foo\x00bar",
"password": "ZePassword",
"password_confirm": "ZePassword",
}
form = RegisterForm(data=data)
self.assertFalse(form.is_valid())


class UnregisterFormTest(TestCase):
"""
Expand Down
12 changes: 10 additions & 2 deletions zds/member/validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator
from django.core.validators import EmailValidator, ProhibitNullCharactersValidator
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -77,12 +77,20 @@ def validate_zds_username(value, check_username_available=True):
:param value: value to validate (str or None)
:return:
"""

# If the character \x00 is in the username, the homoglyphs library called
# in Profile.find_username_skeleton() will raise a ValueError (the bug has
# been reported: https://github.com/yamatt/homoglyphs/issues/6). To prevent
# this, we call this validator which will raise a ValidationError if \x00 is
# in the username.
ProhibitNullCharactersValidator()(value)

msg = None
user_count = User.objects.filter(username=value).count()
skeleton_user_count = Profile.objects.filter(username_skeleton=Profile.find_username_skeleton(value)).count()
if "," in value:
msg = _("Le nom d'utilisateur ne peut contenir de virgules")
if "/" in value:
elif "/" in value:
msg = _("Le nom d'utilisateur ne peut contenir de barres obliques")
elif contains_utf8mb4(value):
msg = _("Le nom d'utilisateur ne peut pas contenir des caractères utf8mb4")
Expand Down
9 changes: 7 additions & 2 deletions zds/mp/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ class PrivateTopicForm(forms.Form, ParticipantsStringValidator, TitleValidator,
label=_("Participants"),
widget=forms.TextInput(
attrs={
"placeholder": _("Les participants doivent " "être séparés par une virgule."),
"placeholder": _("Les participants doivent être séparés par une virgule."),
"required": "required",
"data-autocomplete": '{ "type": "multiple", "url": "/api/membres/?search=%s" }',
}
),
)
Expand All @@ -40,6 +39,12 @@ class PrivateTopicForm(forms.Form, ParticipantsStringValidator, TitleValidator,

def __init__(self, username, *args, **kwargs):
super().__init__(*args, **kwargs)

self.fields["participants"].widget.attrs.update(
{
"data-autocomplete": '{ "type": "multiple", "url": "' + reverse("api:member:list") + '?search=%s" }',
}
)
self.helper = FormHelper()
self.helper.form_class = "content-wrapper"
self.helper.form_method = "post"
Expand Down
2 changes: 1 addition & 1 deletion zds/notification/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class NotificationAdmin(admin.ModelAdmin):

list_display = ("subscription", "pubdate", "is_read", "is_dead", "sender")
list_filter = ("is_read", "is_dead")
search_fields = ("subscription__user__username, sender__username", "url", "title")
search_fields = ("subscription__user__username", "sender__username", "url", "title")
raw_id_fields = ("subscription", "sender")


Expand Down
8 changes: 4 additions & 4 deletions zds/searchv2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from zds.searchv2.forms import SearchForm
from zds.searchv2.models import ESIndexManager
from zds.utils.paginator import ZdSPagingListView
from zds.utils.templatetags.authorized_forums import get_authorized_forums
from zds.forum.utils import get_authorized_forums_pk
from functools import reduce


Expand All @@ -38,7 +38,7 @@ def get(self, request, *args, **kwargs):

results = []
if self.index_manager.connected_to_es and self.search_query:
self.authorized_forums = get_authorized_forums(self.request.user)
self.authorized_forums = get_authorized_forums_pk(self.request.user)

search_queryset = Search()
query = (
Expand Down Expand Up @@ -90,7 +90,7 @@ def get(self, request, *args, **kwargs):
excluded_content_ids = request.GET.get("excluded", "").split(",")
results = []
if self.index_manager.connected_to_es and self.search_query:
self.authorized_forums = get_authorized_forums(self.request.user)
self.authorized_forums = get_authorized_forums_pk(self.request.user)

search_queryset = Search()
if len(excluded_content_ids) > 0 and excluded_content_ids != [""]:
Expand Down Expand Up @@ -174,7 +174,7 @@ def get_queryset(self):

if self.search_query:
# Searches forums the user is allowed to visit
self.authorized_forums = get_authorized_forums(self.request.user)
self.authorized_forums = get_authorized_forums_pk(self.request.user)

search_queryset = Search()

Expand Down
2 changes: 2 additions & 0 deletions zds/tutorialv2/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,13 @@ class ContentReviewTypeAdmin(admin.ModelAdmin):
class GoalAdmin(admin.ModelAdmin):
list_display = ["name", "description"]
ordering = ["position"]
prepopulated_fields = {"slug": ("name",)}


class LabelAdmin(admin.ModelAdmin):
list_display = ["name", "description"]
ordering = ["name"]
prepopulated_fields = {"slug": ("name",)}


admin.site.register(PublishableContent, PublishableContentAdmin)
Expand Down
Loading

0 comments on commit e6c8e5e

Please sign in to comment.