internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: zestedesavoir/zds-site

Issue:
Bouton "Comparer avec la version en ligne" quand les versions sont identiques
**Description du bug**

Dans un tutoriel publié j'ai le bouton "Comparer avec la version en ligne" dans la sidebar "Actions" (à gauche) alors que **les deux versions sont identiques**

**Comment reproduire ?**

La liste des étapes qui permet de reproduire le bug :

1. Allez sur un tutoriel publié dont la version en ligne et publié sont identique (le sien ~~ou celui d'un autre en étant staff~~) ;
2. Constater que le bouton est présent et que les deux versions sont identiques dans le lien du bouton `?from=cacc1f5d99201aa1977b3a95889611dc3ee7f9ff&to=cacc1f5d99201aa1977b3a95889611dc3ee7f9ff`

**Comportement attendu**

Vérifier que les versions soient différentes : 
 - Si elles sont différentes, on affiche le bouton ;
 - Si elles sont identique, on masque le bouton. ***OU remplacer le bouton** par un texte : "Version identique".*

**Capture d'écran**

![image](https://user-images.githubusercontent.com/18501150/115628167-81ec3300-a300-11eb-8723-77daebcee712.png)



Discussion:

Resolving patch:
diff --git a/zds/tutorialv2/mixins.py b/zds/tutorialv2/mixins.py
--- a/zds/tutorialv2/mixins.py
+++ b/zds/tutorialv2/mixins.py
@@ -221,7 +221,7 @@ def get_context_data(self, **kwargs):
 
 class SingleContentDetailViewMixin(SingleContentViewMixin, DetailView):
     """
-    This enhanced DetailView ensure,
+    This enhanced DetailView ensures,
 
     - by rewriting `get()`, that:
         * `self.object` contains the result of `get_object()` (as it must be if `get()` is not rewritten)


Test patch:
Repository: zestedesavoir/zds-site

Issue:
Incohérence des tags entre la bibliothèque et les billets
Pour un même tag `un zeste de culture` on a : 

- `/bibliotheque/?tag=un-zeste-de-culture` ([lien](https://zestedesavoir.com/bibliotheque/?tag=un-zeste-de-culture))
- `/billets/?tag=un zeste de culture` ([lien](https://zestedesavoir.com/billets/?tag=un%20zeste%20de%20culture))

À noter aussi que pour les tags de la bibliothèque, le titre de la page est "Toutes les publications • Bibliothèque • Zeste de Savoir" et ne mentionne pas le tag.


Discussion:
C'est un bug. il faut appliquer le slug tout le temps.

Resolving patch:
diff --git a/zds/tutorialv2/views/lists.py b/zds/tutorialv2/views/lists.py
--- a/zds/tutorialv2/views/lists.py
+++ b/zds/tutorialv2/views/lists.py
@@ -74,7 +74,7 @@ def get_queryset(self):
             queryset = queryset.filter(content__subcategory__in=[self.subcategory])
 
         if "tag" in self.request.GET:
-            self.tag = get_object_or_404(Tag, title=self.request.GET.get("tag").lower().strip())
+            self.tag = get_object_or_404(Tag, slug=slugify(self.request.GET.get("tag").lower().strip()))
             # TODO: fix me
             # different tags can have same slug such as C/C#/C++, as a first version we get all of them
             queryset = queryset.filter(content__tags__in=[self.tag])


Test patch:
diff --git a/zds/tutorialv2/tests/tests_opinion_views.py b/zds/tutorialv2/tests/tests_opinion_views.py
--- a/zds/tutorialv2/tests/tests_opinion_views.py
+++ b/zds/tutorialv2/tests/tests_opinion_views.py
@@ -5,6 +5,7 @@
 from django.test import TestCase
 from django.utils.translation import gettext_lazy as _
 
+from zds.forum.tests.factories import TagFactory
 from zds.gallery.tests.factories import UserGalleryFactory
 from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
 from zds.tutorialv2.tests.factories import (
@@ -860,3 +861,8 @@ def test_opinion_alert(self):
 
         alert = Alert.objects.get(pk=alert.pk)
         self.assertTrue(alert.solved)
+
+    def test_tag_list_is_slug(self):
+        tag = TagFactory(title="Test Slug")
+        result = self.client.get(reverse("opinion:list"), {"tag": tag.slug})
+        self.assertEqual(result.status_code, 200)
Repository: zestedesavoir/zds-site

Issue:
Lien erroné dans le MP d'ajout à la rédaction d'un contenu
**Description du bug**

Le second lien du MP que l'on reçoit lorsqu'on est ajouté comme auteur à un contenu mène vers une page 404.


**Comment reproduire ?**

La liste des étapes qui permet de reproduire le bug :

1. Avec `user1`, créer un billet
2. Ajouter `user2` comme co-auteur du billet
3. Se connecter comme `user2`et consulter le MP reçu correspondant à l'ajout comme auteur au billet
4. Dans le contenu du MP, le second lien (_Il a été ajouté à la liste de vos contenus en rédaction **ici**._) mène vers une 404.

**Comportement attendu**

Je ne sais pas vraiment quel lien est attendu ici... La liste des contenus en cours de rédaction ?



Discussion:
Le message envoyé utilise ce template : https://github.com/zestedesavoir/zds-site/blob/dev/templates/tutorialv2/messages/add_author_pm.md

Le problème se situe dans le lien contenu dans le paramètre `index`. Il est initialisé à cet endroit : https://github.com/zestedesavoir/zds-site/blob/01ee0fae1923e145deb354e88468306cb8278068/zds/tutorialv2/views/authors.py#L48

On voit qu'on passe comme argument l'identifiant de l'utilisateur pour fabriquer l'URL, alors qu'on attend son pseudo, comme spécifié ici : https://github.com/zestedesavoir/zds-site/blob/01ee0fae1923e145deb354e88468306cb8278068/zds/tutorialv2/urls/urls_opinions.py#L40

Il faut donc remplacer `user.pk` par `user.username`.

Resolving patch:
diff --git a/zds/tutorialv2/views/authors.py b/zds/tutorialv2/views/authors.py
--- a/zds/tutorialv2/views/authors.py
+++ b/zds/tutorialv2/views/authors.py
@@ -46,7 +46,9 @@ def form_valid(self, form):
                     self.object.validation_private_message.add_participant(user)
                 all_authors_pk.append(user.pk)
                 if user != self.request.user:
-                    url_index = reverse(self.object.type.lower() + ":find-" + self.object.type.lower(), args=[user.pk])
+                    url_index = reverse(
+                        self.object.type.lower() + ":find-" + self.object.type.lower(), args=[user.username]
+                    )
                     send_mp(
                         bot,
                         [user],


Test patch:
Repository: zestedesavoir/zds-site

Issue:
Notification persistante dans un cas complexe
**Description du bug**

Dans un cas très particulier, on peut avoir encore une notification persistante lié aux abonnements des forums privés. Cela ne se produit pas quand on ajoute ou retire un utilisateur d'un groupe ayant accès à un forum privé (ce soucis là a été corrigé il y a quelques temps) mais dans le cas beaucoup moins fréquent où l'on modifie le statut public ou privé d'un forum.

**Comment reproduire ?**

1. Ajouter l'utilisateur Tintin au groupe Explorateurs, mais ne pas y ajouter l'utilisateur Dupont
1. Avec Dupont, suivre le forum public Marché du lundi 
2. Via la page d'administration (`/admin`), restreindre le forum Marché du lundi au groupe Explorateurs
3. Avec Tintin, créer un nouveau sujet dans le forum Marché du lundi
4. Constater que Dupont reçoit une notification persistante

**Comportement attendu**

Dupont ne reçoit pas de notification.



Discussion:

Resolving patch:
diff --git a/zds/notification/receivers.py b/zds/notification/receivers.py
--- a/zds/notification/receivers.py
+++ b/zds/notification/receivers.py
@@ -329,7 +329,7 @@ def new_topic_event(sender, *, instance, created=True, **__):
 
         subscriptions = NewTopicSubscription.objects.get_subscriptions(topic.forum)
         for subscription in subscriptions:
-            if subscription.user != topic.author:
+            if subscription.user != topic.author and topic.forum.can_read(subscription.user):
                 subscription.send_notification(content=topic, sender=topic.author)
 
 


Test patch:
diff --git a/zds/notification/tests/tests_basics.py b/zds/notification/tests/tests_basics.py
--- a/zds/notification/tests/tests_basics.py
+++ b/zds/notification/tests/tests_basics.py
@@ -425,6 +425,25 @@ def test_notifications_on_a_tag_subscribed(self):
         notifications = Notification.objects.filter(object_id=topic2.pk, is_read=False).all()
         self.assertEqual(0, len(notifications))
 
+    def test_no_notification_on_new_topic_in_hidden_forum(self):
+        """
+        When a user subscribes to a forum that gets hidden and a topic is created in that forum, no notification is sent
+        """
+        # Subscribe.
+        user1 = ProfileFactory().user
+        user2 = ProfileFactory().user
+
+        group = Group.objects.create(name="Restricted")
+        user2.groups.add(group)
+        user2.save()
+        category, forum = create_category_and_forum(group)
+
+        NewTopicSubscription.objects.toggle_follow(forum, user1)
+        topic1 = TopicFactory(forum=forum, author=user2)
+
+        notifications = Notification.objects.filter(object_id=topic1.pk, is_read=False).all()
+        self.assertEqual(0, len(notifications))
+
     def test_no_notification_on_a_tag_subscribed_in_hidden_forum(self):
         """
         When a user subscribes to a tag and a topic is created using that tag in a hidden forum, no notification is sent
Repository: zestedesavoir/zds-site

Issue:
Dans les messages privés, les votes sont anonymes
### Description du bug

Dans les messages privés, les votes que l'on peut effectuer sur les messages sont anonymisés, contrairement au reste du site.

De plus, ce qui est peut-être une conséquence, lors du chargement initial de la page, ils ne sont pas en couleur pleine si c'est nos commentaires. Cela dit, ils le deviennent si on survole la page.

Voici une illustration — au début, la page vient d'être rechargée sans plus d'interaction avec cette dernière. Il y a deux éléments à observer attentivement. C'est un message que j'ai moi-même plussoyé, le pouce devrait donc être vert. On notera qu'il ne l'est pas, mais qu'il le devient au survol. Ensuite, l'élément premier de ce rapport : le vote est anonyme, alors qu'il ne devrait pas l'être.

![mp-bug](https://user-images.githubusercontent.com/1417570/183977681-bd9a80be-1692-4eea-84b9-314e6bb1f22a.gif)

Si jamais cela peut avoir une importance, ce souci s'est produit dans un MP pré-existant la dernière version (mais les messages datent d'aujourd'hui).

### Comment reproduire ?

La liste des étapes qui permet de reproduire le bug :

1. Connecté·e, créez une discussion avec quelqu'un (ou réutilisez une discussion existante).
2. Depuis l'un des comptes faisant partie de la discussion, plussoyez (ou moinssoyez) un message.
3. Actualisez la page.
4. Constatez que : 
    1. le pouce n'est pas vert tant qu'il n'a pas été survolé, alors que c'est votre vote ;
    2. le vote est anonyme.
5. Constatez que depuis l'autre ou les autres comptes faisant partie de la discussion, le vote est également anonyme.

### Comportement attendu

Les pouces sont directement verts (ou rouges) quand c'est notre vote, et les votes ne sont pas anonymes.

### Informations techniques

- OS : (décorrélé)
- Navigateur : Firefox 104 (bêta – _developer edition_)
- Version/environnement de ZdS : production — `30.3-hiderigami/30d5131`



Discussion:

Resolving patch:
diff --git a/zds/mp/models.py b/zds/mp/models.py
--- a/zds/mp/models.py
+++ b/zds/mp/models.py
@@ -444,11 +444,7 @@ def set_user_vote(self, user, vote):
     def get_votes(self):
         """Get the non-anonymous votes"""
         if not hasattr(self, "votes"):
-            self.votes = (
-                PrivatePostVote.objects.filter(private_post=self, id__gt=settings.VOTES_ID_LIMIT)
-                .select_related("user")
-                .all()
-            )
+            self.votes = PrivatePostVote.objects.filter(private_post=self).select_related("user").all()
 
         return self.votes
 


Test patch:
Repository: zestedesavoir/zds-site

Issue:
Dans les messages privés, les votes sont anonymes
### Description du bug

Dans les messages privés, les votes que l'on peut effectuer sur les messages sont anonymisés, contrairement au reste du site.

De plus, ce qui est peut-être une conséquence, lors du chargement initial de la page, ils ne sont pas en couleur pleine si c'est nos commentaires. Cela dit, ils le deviennent si on survole la page.

Voici une illustration — au début, la page vient d'être rechargée sans plus d'interaction avec cette dernière. Il y a deux éléments à observer attentivement. C'est un message que j'ai moi-même plussoyé, le pouce devrait donc être vert. On notera qu'il ne l'est pas, mais qu'il le devient au survol. Ensuite, l'élément premier de ce rapport : le vote est anonyme, alors qu'il ne devrait pas l'être.

![mp-bug](https://user-images.githubusercontent.com/1417570/183977681-bd9a80be-1692-4eea-84b9-314e6bb1f22a.gif)

Si jamais cela peut avoir une importance, ce souci s'est produit dans un MP pré-existant la dernière version (mais les messages datent d'aujourd'hui).

### Comment reproduire ?

La liste des étapes qui permet de reproduire le bug :

1. Connecté·e, créez une discussion avec quelqu'un (ou réutilisez une discussion existante).
2. Depuis l'un des comptes faisant partie de la discussion, plussoyez (ou moinssoyez) un message.
3. Actualisez la page.
4. Constatez que : 
    1. le pouce n'est pas vert tant qu'il n'a pas été survolé, alors que c'est votre vote ;
    2. le vote est anonyme.
5. Constatez que depuis l'autre ou les autres comptes faisant partie de la discussion, le vote est également anonyme.

### Comportement attendu

Les pouces sont directement verts (ou rouges) quand c'est notre vote, et les votes ne sont pas anonymes.

### Informations techniques

- OS : (décorrélé)
- Navigateur : Firefox 104 (bêta – _developer edition_)
- Version/environnement de ZdS : production — `30.3-hiderigami/30d5131`



Discussion:

Resolving patch:
diff --git a/zds/mp/views.py b/zds/mp/views.py
--- a/zds/mp/views.py
+++ b/zds/mp/views.py
@@ -24,7 +24,7 @@
 from zds.mp.utils import send_mp, send_message_mp
 from zds.utils.paginator import ZdSPagingListView
 from .forms import PrivateTopicForm, PrivatePostForm, PrivateTopicEditForm
-from .models import PrivateTopic, PrivateTopicRead, PrivatePost, mark_read, NotReachableError
+from .models import PrivateTopic, PrivateTopicRead, PrivatePost, mark_read, NotReachableError, PrivatePostVote
 
 
 class PrivateTopicList(ZdSPagingListView):
@@ -255,6 +255,10 @@ def get_context_data(self, **kwargs):
         else:
             context["user_can_modify"] = []
 
+        votes = PrivatePostVote.objects.filter(user_id=self.request.user.pk, private_post__in=context["posts"]).all()
+        context["user_like"] = [vote.private_post_id for vote in votes if vote.positive]
+        context["user_dislike"] = [vote.private_post_id for vote in votes if not vote.positive]
+
         return context
 
     def get_queryset(self):


Test patch:
Repository: zestedesavoir/zds-site

Issue:
Impossible aux membres connectés avec un compte Google de se désinscrire et de créer un mot de passe
> Bonjour,
> 
> je me suis connecté avec le mauvais compte google… Mais il est impossible de me désinscrire car l’on me demande mon mot de passe actuel, inexistant…

[Depuis le forum](https://zestedesavoir.com/forums/sujet/16588/impossible-aux-personnes-connectees-avec-un-compte-google-de-se-desinscrire-et-de-creer-un-mot-de-passe/)


Discussion:
On peut gérer ce cas en utilisant la méthode `User.has_usable_password()` qui vaudra `False` pour les comptes sans mot de passe créé via les réseaux sociaux et `True` pour ceux avec mot de passe.

Il faudra modifier la classe `PasswordRequiredForm` du fichier `zds/utils/forms.py`, en modifiant la validation (ne pas retourner d'erreur pour ces comptes-là) et si possible l'affichage (ne pas afficher le champ pour ces comptes là).

Je n'ai pas réussi à faire fonctionner la connexion via les réseaux sociaux en local donc le plus simple sera de tester directement en bêta.

Resolving patch:
diff --git a/zds/member/forms.py b/zds/member/forms.py
--- a/zds/member/forms.py
+++ b/zds/member/forms.py
@@ -411,7 +411,7 @@ def __init__(self, user, *args, **kwargs):
             Field("username", value=user.username),
             Field("email", value=user.email),
             Field("options"),
-            Field("password"),
+            self.insert_password_required_field(),
             ButtonHolder(
                 StrictButton(_("Enregistrer"), type="submit"),
             ),
@@ -449,7 +449,7 @@ def __init__(self, user, *args, **kwargs):
         self.user = user
 
         self.helper.layout = Layout(
-            Field("password"),
+            self.insert_password_required_field(),
             HTML(
                 _(
                     """
@@ -491,7 +491,7 @@ def __init__(self, user, *args, **kwargs):
         self.user = user
 
         self.helper.layout = Layout(
-            Field("password"),
+            self.insert_password_required_field(),
             Field("password_new"),
             Field("password_confirm"),
             ButtonHolder(
diff --git a/zds/utils/forms.py b/zds/utils/forms.py
--- a/zds/utils/forms.py
+++ b/zds/utils/forms.py
@@ -168,11 +168,17 @@ class PasswordRequiredForm(forms.Form):
         widget=forms.PasswordInput,
     )
 
+    def insert_password_required_field(self):
+        if self.user.has_usable_password():
+            return Field("password")
+        else:
+            del self.fields["password"]
+
     def clean(self):
         cleaned_data = super().clean()
         password = cleaned_data.get("password")
 
-        if password and self.user:
+        if password and self.user and self.user.has_usable_password():
             user_exist = authenticate(username=self.user.username, password=password)
             # Check if the user exist.
             if not user_exist and password != "":


Test patch:
Repository: zestedesavoir/zds-site

Issue:
Possible erreur 500 à la résolution d'une alerte sur un contenu qui n'est plus public
Rapporté par Sentry. J'ai eu du mal à comprendre comment le bug a pu se produire, mais j'ai réussi à le reproduire (d'une façon peut-être un peu tirée par les cheveux...).

**Comment reproduire ?**

1. Se connecter en tant que `user1`
2. Signaler un billet
3. Se connecter en tant que `staff`
4. Ouvrir la page du billet signalé dans deux onglets différents
5. Sur un des onglets, dépublier le billet
6. Sur l'autre onglet, résoudre l'alerte (ne pas recharger la page juste avant, le billet n'est en fait plus publié, c'est là qu'est l'astuce)

Une erreur 500 va alors apparaître. Elle provient d'ici : https://github.com/zestedesavoir/zds-site/blob/c06671c4901a95c30f31067c09d5e4526fd86575/zds/tutorialv2/views/alerts.py#L88
Le contenu n'a plus de version publique, donc plus d'URL publique, et `content.get_absolute_url_online()` renvoie alors `''`.

La correction de ce bug passe sans doute par la vérification si l'alerte est déjà résolue ou si le contenu signalé a bien une version publique : si l'une de ces conditions n'est pas remplie, une erreur 404 devrait être levée.



Discussion:
> J'ai eu du mal à comprendre comment le bug a pu se produire

Il est possible que ça ait été un billet-spam signalé par quelqu'un et deux modérateurs ont souhaité le modérer en même temps.

Resolving patch:
diff --git a/zds/tutorialv2/views/alerts.py b/zds/tutorialv2/views/alerts.py
--- a/zds/tutorialv2/views/alerts.py
+++ b/zds/tutorialv2/views/alerts.py
@@ -62,6 +62,9 @@ def post(self, request, *args, **kwargs):
         except (KeyError, ValueError):
             raise Http404("L'alerte n'existe pas.")
 
+        if alert.solved:
+            raise Http404("L'alerte a déjà été résolue.")
+
         resolve_reason = ""
         msg_title = ""
         msg_content = ""


Test patch:
diff --git a/zds/tutorialv2/tests/tests_utils.py b/zds/tutorialv2/tests/tests_utils.py
--- a/zds/tutorialv2/tests/tests_utils.py
+++ b/zds/tutorialv2/tests/tests_utils.py
@@ -556,7 +556,7 @@ def test_no_alert_on_unpublish(self):
         reaction = ContentReactionFactory(
             related_content=published, author=ProfileFactory().user, position=1, pubdate=datetime.datetime.now()
         )
-        Alert.objects.create(
+        alert = Alert.objects.create(
             scope="CONTENT",
             comment=reaction,
             text="a text",
@@ -569,6 +569,15 @@ def test_no_alert_on_unpublish(self):
         unpublish_content(published, staff)
         self.assertEqual(0, get_header_notifications(staff)["alerts"]["total"])
 
+        # Try to solve the alert anyway (related to #6478):
+        self.client.force_login(self.staff)
+        result = self.client.post(
+            reverse("content:resolve-content", kwargs={"pk": published.pk}),
+            {"alert_pk": alert.pk, "text": "Anéfé!"},
+            follow=False,
+        )
+        self.assertEqual(result.status_code, 404)
+
     def tearDown(self):
         super().tearDown()
         PublicatorRegistry.registry = self.old_registry
Repository: zestedesavoir/zds-site

Issue:
Autoriser le staff à afficher les biographies des membres bannis
Hello,

**Description du besoin**

Il pourrait être pratique d’autoriser le staff à lire une biographie bannie. Dans le cas où un membre conteste son bannissement, on n’a plus accès au contenu qui nous a poussé à le bannir pour vérifier ce qui a motivé la décision. Je ne vois pas, comme ça, de grosse raison pour l’empêcher.



Discussion:
Similaire à #6204

Resolving patch:
diff --git a/zds/member/views/profile.py b/zds/member/views/profile.py
--- a/zds/member/views/profile.py
+++ b/zds/member/views/profile.py
@@ -172,6 +172,11 @@ def get_context_data(self, **kwargs):
             .distinct()
             .count()
         )
+        context["hide_because_banned"] = (
+            not profile.end_ban_read
+            and not profile.can_read
+            and not self.request.user.has_perm("member.change_profile")
+        )
         context["content_reactions_count"] = ContentReaction.objects.filter(author=usr).count()
         context["hide_forum_activity"] = (
             profile.hide_forum_activity


Test patch:
Repository: zestedesavoir/zds-site

Issue:
Faciliter la programmation des Unes en modifiant le type de la date de publication.
À ce jour, quand on créer une Unes il faut remplir le champ "Date de publication" avec un format texte de style "2023/08/21 10:00". C'est assez désagréable à remplir. 

![image](https://github.com/zestedesavoir/zds-site/assets/8135797/c256ac28-bd4b-4c04-a701-5c4455f742f1)
Les propositions sont des Unes que j'ai déjà faite.

En ajoutant le type "datetime-local" à cette input on pourrait accèder aux interfaces natives des navigateurs/OS pour ce genre d'input.
![Exemple avec Firefox](https://github.com/zestedesavoir/zds-site/assets/8135797/f6516bdc-1416-44fc-8150-f94885ad4b28)



Discussion:

Resolving patch:
diff --git a/zds/featured/forms.py b/zds/featured/forms.py
--- a/zds/featured/forms.py
+++ b/zds/featured/forms.py
@@ -31,20 +31,8 @@ class Meta:
     )
 
     pubdate = forms.DateTimeField(
-        label=_("Date de publication (exemple: 25/12/2015 15:00 ou 2015-12-25T15:00)"),
-        input_formats=[
-            "%d/%m/%Y %H:%M:%S",
-            "%Y-%m-%d %H:%M:%S",  # full format with second
-            "%Y-%m-%dT%H:%M",  # datetime field format
-            "%Y-%m-%d %H:%M",
-            "%d/%m/%Y %H:%M",  # without second
-            "%Y-%m-%d",
-            "%d/%m/%Y",  # day only
-        ],
-        widget=forms.DateTimeInput(
-            attrs={"placeholder": _("Exemple : 25/12/2016 10:00"), "type": "text"},
-            format="%d/%m/%Y %H:%M",  # datetime field format
-        ),
+        label=_("Date de publication (exemple: 25/12/2015 15:00)"),
+        widget=forms.DateTimeInput(attrs={"type": "datetime-local"}),
     )
 
     request = forms.IntegerField(widget=forms.HiddenInput(), required=False)


Test patch: