internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: sotetsuk/pgx

Issue:
[Backgammon] Use `_rng_key`



Discussion:
@nissymori あとこれもお願いします。

すべての環境のstateに `_rng_key` があるので、乱数使う場合これを毎回splitして更新してつかってください！
backgammonに入ってるbackgammonだけのrngは消して下さい！

Resolving patch:
diff --git a/pgx/backgammon.py b/pgx/backgammon.py
--- a/pgx/backgammon.py
+++ b/pgx/backgammon.py
@@ -38,7 +38,6 @@ class State(v1.State):
     # --- Backgammon specific ---
     # points(24) bar(2) off(2). black+, white-
     _board: jnp.ndarray = jnp.zeros(28, dtype=jnp.int8)
-    _rng: jax.random.KeyArray = jnp.zeros(2, dtype=jnp.uint16)
     _dice: jnp.ndarray = jnp.zeros(2, dtype=jnp.int16)  # 0~5: 1~6
     _playable_dice: jnp.ndarray = jnp.zeros(
         4, dtype=jnp.int16
@@ -95,7 +94,7 @@ def _init(rng: jax.random.KeyArray) -> State:
     legal_action_mask: jnp.ndarray = _legal_action_mask(board, playable_dice)
     state = State(  # type: ignore
         current_player=current_player,
-        _rng=rng3,
+        _rng_key=rng3,
         _board=board,
         terminated=terminated,
         _dice=dice,
@@ -189,7 +188,7 @@ def _update_by_action(state: State, action: jnp.ndarray) -> State:
     Update state by action
     """
     is_no_op = action // 6 == 0
-    rng = state._rng
+    rng_key = state._rng_key
     current_player: jnp.ndarray = state.current_player
     terminated: jnp.ndarray = state.terminated
     board: jnp.ndarray = _move(state._board, action)
@@ -203,7 +202,7 @@ def _update_by_action(state: State, action: jnp.ndarray) -> State:
         lambda: state,
         lambda: state.replace(  # type: ignore
             current_player=current_player,
-            _rng=rng,
+            _rng_key=rng_key,
             terminated=terminated,
             _board=board,
             _turn=state._turn,
@@ -245,7 +244,7 @@ def _change_turn(state: State) -> State:
     """
     Change turn and return new state.
     """
-    rng1, rng2 = jax.random.split(state._rng)
+    rng1, rng2 = jax.random.split(state._rng_key)
     board: jnp.ndarray = _flip_board(state._board)
     turn: jnp.ndarray = (state._turn + 1) % 2
     current_player: jnp.ndarray = (state.current_player + 1) % 2
@@ -256,7 +255,7 @@ def _change_turn(state: State) -> State:
     legal_action_mask: jnp.ndarray = _legal_action_mask(board, dice)
     return state.replace(  # type: ignore
         current_player=current_player,
-        _rng=rng2,
+        _rng_key=rng2,
         _board=board,
         terminated=terminated,
         _turn=turn,


Test patch:
diff --git a/tests/test_backgammon.py b/tests/test_backgammon.py
--- a/tests/test_backgammon.py
+++ b/tests/test_backgammon.py
@@ -89,7 +89,7 @@ def make_test_state(
 ):
     return State(
         current_player=current_player,
-        _rng=rng,
+        _rng_key=rng,
         _board=board,
         _turn=turn,
         _dice=dice,
Repository: sotetsuk/pgx

Issue:
Add `pgx.__version__`



Discussion:

Resolving patch:
diff --git a/pgx/__init__.py b/pgx/__init__.py
--- a/pgx/__init__.py
+++ b/pgx/__init__.py
@@ -6,6 +6,8 @@
 )
 from pgx.v1 import Env, EnvId, State, available_envs, make
 
+__version__ = "0.8.0"
+
 __all__ = [
     # v1 api components
     "State",
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,6 @@
 
 setup(
     name="pgx",
-    version="0.7.4",
     description="GPU/TPU-accelerated parallel game simulators for reinforcement learning (RL)",
     long_description=long_description,
     long_description_content_type="text/markdown",


Test patch:
Repository: sotetsuk/pgx

Issue:
[Backgammon] Fix background visualization
Color of triangle is wrong


Discussion:

Resolving patch:
diff --git a/pgx/_src/dwg/backgammon.py b/pgx/_src/dwg/backgammon.py
--- a/pgx/_src/dwg/backgammon.py
+++ b/pgx/_src/dwg/backgammon.py
@@ -40,7 +40,16 @@ def _make_backgammon_dwg(dwg, state: BackgammonState, config):  # noqa: C901
             p2 = ((i + 2 - 12) * GRID_SIZE, 14 * GRID_SIZE)
             p3 = ((i + 1.5 - 12) * GRID_SIZE, 8 * GRID_SIZE)
 
-        fill_color = color_set.p1_color if i % 2 == 0 else color_set.text_color
+        if i < 12:
+            if i % 2 == 0:
+                fill_color = color_set.p1_color
+            else:
+                fill_color = color_set.text_color
+        else:
+            if i % 2 == 1:
+                fill_color = color_set.p1_color
+            else:
+                fill_color = color_set.text_color
 
         board_g.add(
             dwg.polygon(


Test patch:
Repository: vas3k/vas3k.club

Issue:
Search pagination has broken links
Links in pagination don't include `query` param so they get redirected to landing page
Search pagination has broken links
Links in pagination don't include `query` param so they get redirected to landing page


Discussion:
Clearly a bug. We need to handle GET params better in the [paginator template](https://github.com/vas3k/vas3k.club/blob/e0d71c69a9238107a261b9d51e7a6d8dafae10ef/frontend/html/common/paginator.html#L10). They're overwriting query params right now.
@vas3k given pagination is used in different places it's either:
- some `extra_params` argument for pagination
- change search to have /search/:query url and page param would work ok
- custom template for search pagination with extra `query` coming from context (clearly, I'm no django expert). 

Any preferences?
I like the 2nd, if you can get it work with the search forms. If not, we can make a custom template filter to parse the current URI and add the GET parameter instead of overwriting it.
Clearly a bug. We need to handle GET params better in the [paginator template](https://github.com/vas3k/vas3k.club/blob/e0d71c69a9238107a261b9d51e7a6d8dafae10ef/frontend/html/common/paginator.html#L10). They're overwriting query params right now.
@vas3k given pagination is used in different places it's either:
- some `extra_params` argument for pagination
- change search to have /search/:query url and page param would work ok
- custom template for search pagination with extra `query` coming from context (clearly, I'm no django expert). 

Any preferences?
I like the 2nd, if you can get it work with the search forms. If not, we can make a custom template filter to parse the current URI and add the GET parameter instead of overwriting it.

Resolving patch:
diff --git a/posts/templatetags/query_params.py b/posts/templatetags/query_params.py
new file mode 100644
--- /dev/null
+++ b/posts/templatetags/query_params.py
@@ -0,0 +1,12 @@
+from urllib.parse import urlencode
+
+from django import template
+
+register = template.Library()
+
+
+@register.simple_tag(takes_context=True)
+def append_query_param(context, **kwargs):
+    query_params = dict(context.request.GET.items())
+    query_params.update(kwargs)
+    return "?" + urlencode(query_params)


Test patch:
Repository: vas3k/vas3k.club

Issue:
Только часть id до дефиса выделена когда тебя @тэгнули
![image](https://user-images.githubusercontent.com/1266401/82743081-c3293b00-9d5d-11ea-97cd-c1bb986a8b64.png)
https://vas3k.club/post/2295/#comment-8177cee9-5bef-49bf-bade-44deea61e5d5





Discussion:

Resolving patch:
diff --git a/common/regexp.py b/common/regexp.py
--- a/common/regexp.py
+++ b/common/regexp.py
@@ -1,6 +1,6 @@
 import re
 
-USERNAME_RE = re.compile(r"(?:\s|\n|^)@([A-Za-z0-9_]{3,})")
+USERNAME_RE = re.compile(r"(?:\s|\n|^)@([A-Za-z0-9_-]{3,})")
 IMAGE_RE = re.compile(r"(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|jpeg|gif|png)")
 VIDEO_RE = re.compile(r"(http(s?):)([/|.|\w|\s|-])*\.(?:mov|mp4)")
 YOUTUBE_RE = re.compile(


Test patch:
Repository: vas3k/vas3k.club

Issue:
[Digest] Notifications for wrong user
(я не разработчик, поэтому могу только пожаловаться)

В последнем дайджесте мне пришло уведомление, что меня _упомянули_ но упомянули совсем не меня. Похоже на какую то коллизию имён. 

умные люди говорят, что проблема здесь: https://github.com/vas3k/vas3k.club/blob/master/notifications/views.py#L152

скрин из дайджеста
![image](https://user-images.githubusercontent.com/5258641/83161066-f3face80-a0d5-11ea-9d8b-420853b446bc.png)

при этом мой ник просто Tim


Discussion:
Привет!

Да, имеет место быть такой баг, занес на Трелло. Спасибо, что обратил внимание на него :)
Возможно, его даже пофиксил кто-то в истории PR. Надо проверить. Баг все еще повторяется? 
А, я наврал, пофиксили другое. Ладно :)

Resolving patch:
diff --git a/notifications/views.py b/notifications/views.py
--- a/notifications/views.py
+++ b/notifications/views.py
@@ -149,7 +149,7 @@ def daily_digest(request, user_slug):
     # Mentions
     mentions = Comment.visible_objects() \
         .filter(**created_at_condition) \
-        .filter(text__contains=f"@{user.slug}", is_deleted=False)\
+        .filter(text__regex=fr"@\y{user.slug}\y", is_deleted=False)\
         .exclude(reply_to__author=user)\
         .order_by("-upvotes")[:5]
 


Test patch:
Repository: vas3k/vas3k.club

Issue:
Сломался check_PR экшн на новые пуллреквесты
Вот здесь все пошло не так после пары изменений в requirements и докерфайлах: https://github.com/vas3k/vas3k.club/blob/master/.github/workflows/CI.yml

Из-за этого все новые пуллреквесты красненькие и мержить их приходится только суровой админской рукой. Надо бы переосмыслить этот CI как-нибудь. У кого есть идеи?

По сути мне важны линтеры и чтобы докер с новым кодом успешно поднимался. Остального пока нет.


Discussion:

Resolving patch:
diff --git a/utils/images.py b/utils/images.py
--- a/utils/images.py
+++ b/utils/images.py
@@ -29,7 +29,7 @@ def upload_image_bytes(
 
         try:
             image.save(saved_image)
-        except OSError:
+        except OSError as ex:
             log.warning(f"Error saving image data: {ex}")
             return None
 


Test patch:
Repository: vas3k/vas3k.club

Issue:
На странице профиля в блоке "последние комментарии" видны комменты к удаленным(скрытым) постам
Замечено, если откомментировать пост, который позже скрыли, то в блоке "последние комментарии" этот коммент все равно остается. 

Предполагаю, что нужно всего лишь поправить фильтр запроса на комментарии, добавив проверку на видимость связанного поста.

Чуть позже смогу завести проект у себя на локалке и сделаю PR :)


Discussion:
Да, и также видны скрытые проекты в разделе черновики. 
Например админы скрыли пост - у товарища https://vas3k.club/user/yuhenobi/ - 
но проект виден в профиле.
Я решил, что это фича, а не баг и не открывал даже таску.

(хотя то Not found по ссылке их профиля -это уже баг.  тогда надо бы показывать, что проект скрыт\удален\в черновиках, пример скрытого проекта из профиля выше: https://vas3k.club/project/2790/ )


@vas3k, будем с этим что-то делать?
Вообще, выглядит несложно.  

---

Если кто захочет взяться вот тут сделано для юзеров: https://github.com/vas3k/vas3k.club/blame/3250518f7a052541666db0b14ebd7ca1ca3c93f9/auth/helpers.py#L69
- [ ] надо сделать аналогично для проектов.

Комит 3250518f7a052541666db0b14ebd7ca1ca3c93f9

Resolving patch:
diff --git a/users/views.py b/users/views.py
--- a/users/views.py
+++ b/users/views.py
@@ -85,7 +85,7 @@ def profile(request, user_slug):
     active_tags = {t.tag_id for t in UserTag.objects.filter(user=user).all()}
     achievements = UserAchievement.objects.filter(user=user).select_related("achievement")
     expertises = UserExpertise.objects.filter(user=user).all()
-    comments = Comment.visible_objects().filter(author=user).order_by("-created_at")[:3]
+    comments = Comment.visible_objects().filter(author=user, post__is_visible=True).order_by("-created_at")[:3]
     posts = Post.objects_for_user(request.me)\
         .filter(author=user, is_visible=True)\
         .exclude(type__in=[Post.TYPE_INTRO, Post.TYPE_PROJECT])


Test patch:
Repository: vas3k/vas3k.club

Issue:
Некорректный расчёт счетчика новых комментариев
В посте был прочитан один комментарий, при следующем посещении страницы добавились новые комментарии, два из которых были удалены, по всей видимости они были использованы в расчете количества новых комментариев:

![image](https://user-images.githubusercontent.com/131331/83412052-27b15d80-a422-11ea-8b71-303aa7c1d55c.png)



Discussion:

Resolving patch:
diff --git a/comments/views.py b/comments/views.py
--- a/comments/views.py
+++ b/comments/views.py
@@ -51,7 +51,7 @@ def create_comment(request, post_slug):
             # update the shitload of counters :)
             request.me.update_last_activity()
             Comment.update_post_counters(post)
-            PostView.increment_unread_comments(post)
+            PostView.increment_unread_comments(comment)
             PostView.register_view(
                 request=request,
                 user=request.me,
@@ -148,10 +148,12 @@ def delete_comment(request, comment_id):
     if not comment.is_deleted:
         # delete comment
         comment.delete(deleted_by=request.me)
+        PostView.decrement_unread_comments(comment)
     else:
         # undelete comment
         if comment.deleted_by == request.me or request.me.is_moderator:
             comment.undelete()
+            PostView.increment_unread_comments(comment)
         else:
             raise AccessDenied(
                 title="Нельзя!",
diff --git a/posts/models.py b/posts/models.py
--- a/posts/models.py
+++ b/posts/models.py
@@ -360,10 +360,15 @@ def register_anonymous_view(cls, request, post):
         return post_view
 
     @classmethod
-    def increment_unread_comments(cls, post):
-        PostView.objects.filter(post=post, user__isnull=False)\
+    def increment_unread_comments(cls, comment):
+        PostView.objects.filter(post=comment.post, last_view_at__lt = comment.created_at, user__isnull=False)\
             .update(unread_comments=F("unread_comments") + 1)
 
+    @classmethod
+    def decrement_unread_comments(cls, comment):
+        PostView.objects.filter(post=comment.post, last_view_at__lt = comment.created_at, user__isnull=False)\
+            .update(unread_comments=F("unread_comments") - 1)
+
 
 class PostSubscription(models.Model):
     id = models.UUIDField(primary_key=True, default=uuid4, editable=False)


Test patch:
Repository: vas3k/vas3k.club

Issue:
Некорректные ссылки на подписку-отписку в еженедельном дайджесте на сайте
В почте ссылки нормально заинтерполировались а вот на сайте имеем ссылку вида
`https://vas3k.club/notifications/switch/nope/%user_id%/%secret_code%/`

Багу смотреть тут: https://vas3k.club/weekly_digest/2020_19/


Discussion:
Надо скрывать их на сайте
@vas3k как сгенерировать дайджест для теста? 

Я могу получить отрендерный дайджест через `/notifications/renderer/digest/weekly/`, но как его превратить в пост?
@nlopin попробуй в консоленьке набрать `pyenv run python3 manage.py send_weekly_digest`

А вообще вот https://github.com/vas3k/vas3k.club/blob/master/notifications/management/commands/send_weekly_digest.py#L41

Resolving patch:
diff --git a/notifications/management/commands/send_weekly_digest.py b/notifications/management/commands/send_weekly_digest.py
--- a/notifications/management/commands/send_weekly_digest.py
+++ b/notifications/management/commands/send_weekly_digest.py
@@ -35,6 +35,13 @@ def handle(self, *args, **options):
 
         digest_html = digest_html_response.text
 
+        no_footer_digest_response = requests.get(digest_url, params={"no_footer": 1})
+        if no_footer_digest_response.status_code > 400:
+            log.error("Weekly digest without footer error: bad status code", extra={"html": no_footer_digest_response.text})
+            return
+
+        no_footer_digest_html = no_footer_digest_response.text
+
         # save digest as a post
         issue = (datetime.utcnow() - settings.LAUNCH_DATE).days // 7
         year, week, _ = (datetime.utcnow() - timedelta(days=7)).isocalendar()
@@ -44,8 +51,8 @@ def handle(self, *args, **options):
             defaults=dict(
                 author=User.objects.filter(slug="vas3k").first(),
                 title=f"Клубный журнал. Итоги недели. Выпуск #{issue}",
-                html=digest_html,
-                text=digest_html,
+                html=no_footer_digest_html,
+                text=no_footer_digest_html,
                 is_pinned_until=datetime.utcnow() + timedelta(days=1),
                 is_visible=True,
                 is_public=True,
diff --git a/notifications/views.py b/notifications/views.py
--- a/notifications/views.py
+++ b/notifications/views.py
@@ -252,6 +252,8 @@ def weekly_digest(request):
     if not author_intro and not posts and not comments:
         raise Http404()
 
+    is_footer_excluded = "no_footer" in request.GET
+
     return render(request, "emails/weekly.html", {
         "posts": posts,
         "comments": comments,
@@ -262,5 +264,6 @@ def weekly_digest(request):
         "top_video_post": top_video_post,
         "featured_post": featured_post,
         "author_intro": author_intro,
-        "issue_number": (end_date - settings.LAUNCH_DATE).days // 7
+        "issue_number": (end_date - settings.LAUNCH_DATE).days // 7,
+        "is_footer_excluded": is_footer_excluded
     })


Test patch:
Repository: vas3k/vas3k.club

Issue:
csrf logout
Логаут работает через `get` и нет проверки на `csrf` токена


Discussion:

Resolving patch:
diff --git a/auth/views/auth.py b/auth/views/auth.py
--- a/auth/views/auth.py
+++ b/auth/views/auth.py
@@ -3,6 +3,7 @@
 
 from django.conf import settings
 from django.core.cache import cache
+from django.http import HttpResponseNotAllowed
 from django.shortcuts import redirect, render
 
 from auth.helpers import auth_required, set_session_cookie
@@ -32,6 +33,8 @@ def login(request):
 
 @auth_required
 def logout(request):
+    if request.method != 'POST':
+        return HttpResponseNotAllowed(['POST'])
     token = request.COOKIES.get("token")
     Session.objects.filter(token=token).delete()
     cache.delete(f"token:{token}:session")


Test patch:
Repository: vas3k/vas3k.club

Issue:
Не инвертируется цвет кнопок в тёмной теме
Браузер: Safari, iOS 13.5.1
В тёмной теме плохо виден текст кнопки «Выход» и сортировки постов.

<img src="https://user-images.githubusercontent.com/18095933/88450324-5a069680-ce78-11ea-9213-a9a78e0cecb7.PNG" width="50%">
<img src="https://user-images.githubusercontent.com/18095933/88450328-5e32b400-ce78-11ea-9bd4-72612add40a7.PNG" width="50%">

В светлой теме все окей:
<img src="https://user-images.githubusercontent.com/18095933/88450342-81f5fa00-ce78-11ea-9944-fb4beda059b1.PNG" width="50%">
<img src="https://user-images.githubusercontent.com/18095933/88450344-83bfbd80-ce78-11ea-99de-81c33a23f805.PNG" width="50%">




Discussion:

Resolving patch:
diff --git a/club/settings.py b/club/settings.py
--- a/club/settings.py
+++ b/club/settings.py
@@ -14,6 +14,7 @@
 SECRET_KEY = os.getenv("SECRET_KEY") or "wow so secret"
 DEBUG = (os.getenv("DEBUG") == "true")  # SECURITY WARNING: don"t run with debug turned on in production!
 
+# HINT: add "*" or your local IP to access appllication from a different device, e.g. a phone
 ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0", "vas3k.club", "dev.vas3k.club"]
 INTERNAL_IPS = ["127.0.0.1"]
 


Test patch:
Repository: vas3k/vas3k.club

Issue:
500 Server error, если при создании ссылки подсунуть вместо ссылки обычный текст
## Сейчас сделаю MR с фиксом для этой проблемы, там одна строчка :)

![image](https://user-images.githubusercontent.com/13254668/89395939-a2df0a80-d716-11ea-9a04-87d991cf5550.png)


Discussion:

Resolving patch:
diff --git a/posts/forms/compose.py b/posts/forms/compose.py
--- a/posts/forms/compose.py
+++ b/posts/forms/compose.py
@@ -106,7 +106,7 @@ class Meta:
     def clean(self):
         cleaned_data = super().clean()
 
-        parsed_url = parse_url_preview(cleaned_data["url"])
+        parsed_url = parse_url_preview(cleaned_data.get("url"))
         if parsed_url:
             self.instance.metadata = dict(parsed_url._asdict())
             self.instance.url = parsed_url.url


Test patch:
Repository: vas3k/vas3k.club

Issue:
Использовать счётчик символов из intro в постах тоже
Благодаря отличному PR https://github.com/vas3k/vas3k.club/pull/329 у нас есть Vue-компонент [InputLengthCounter](https://github.com/vas3k/vas3k.club/blob/master/frontend/static/js/components/InputLengthCounter.vue), который красиво считает символы в [форме под intro](https://github.com/vas3k/vas3k.club/blob/master/frontend/html/users/intro.html#L158).

Хотелось бы теперь иметь такой же в формах некоторых постов. Например для проектов и ссылок. Там существуют органичения на минимальную длину, потому хотелось бы это как-то явно показывать.

Вот тут: https://github.com/vas3k/vas3k.club/blob/master/frontend/html/posts/compose/project.html#L63

Проблема в том, что компонент работает с голой texarea, а при написании постов используется редактор EasyMDE (внутри использует Codemirror), который не шлёт нужных ивентов onchange (хотя и обновляет как-то текстарею). Нужна помощь фронтенд-богов чтобы решить данную задачу :( 


Discussion:
@vas3k Могу взять это на себя 🏄‍♂️ 
@dimabory давай, было бы круто!
@vas3k Ты сказал, что там существуют ограничения на минимальную длину, но это ограничение только для проектов https://github.com/vas3k/vas3k.club/blob/638a44957fc9ae86b2eaeff3b4e91e7b0fcf44e9/posts/forms/compose.py#L221-L225

Для ссылок есть только `maxlength` условие https://github.com/vas3k/vas3k.club/blob/638a44957fc9ae86b2eaeff3b4e91e7b0fcf44e9/posts/forms/compose.py#L86-L89
@dimabory возможно, я же не могу помнить вообще всё :) Добавь туда ограничение, скажем, в минимум 500 символов.

Resolving patch:
diff --git a/posts/forms/compose.py b/posts/forms/compose.py
--- a/posts/forms/compose.py
+++ b/posts/forms/compose.py
@@ -82,10 +82,13 @@ class PostLinkForm(PostForm):
         label="TL;DR",
         required=True,
         max_length=50000,
+        min_length=500,
         widget=forms.Textarea(
             attrs={
+                "minlength": 500,
                 "maxlength": 50000,
                 "class": "markdown-editor-full",
+                "data-listen": "keyup",
                 "placeholder": "Напишите TL;DR чтобы сэкономить другим время."
                                "\n\nКоротко расскажите о чем ссылка и почему все должны её прочитать. ",
             }
@@ -222,6 +225,7 @@ class PostProjectForm(PostForm):
                 "maxlength": 500000,
                 "minlength": 1500,
                 "class": "markdown-editor-full",
+                "data-listen": "keyup",
                 "placeholder": "Расскажите подробности о вашем проекте!"
                                "\n- В чем его суть и как он помогает людям?"
                                "\n- Как появилась идея?"


Test patch:
Repository: vas3k/vas3k.club

Issue:
Баг с пажинацией в разделе «Коммьюнити»
STR:
1. Открыть https://vas3k.club/people/
2. Отфильтровать по первым 4 фильтрам
3. Перейти на 2 страницу выдачи

ER: Фильтры сохранены
AR: Фильтры частично потеряны

Видео https://youtu.be/08cz2TOq60A


Discussion:

Resolving patch:
diff --git a/posts/templatetags/query_params.py b/posts/templatetags/query_params.py
--- a/posts/templatetags/query_params.py
+++ b/posts/templatetags/query_params.py
@@ -1,4 +1,4 @@
-from urllib.parse import urlencode
+from copy import deepcopy
 
 from django import template
 
@@ -7,6 +7,6 @@
 
 @register.simple_tag(takes_context=True)
 def append_query_param(context, **kwargs):
-    query_params = dict(context.request.GET.items())
+    query_params = deepcopy(context.request.GET)
     query_params.update(kwargs)
-    return "?" + urlencode(query_params)
+    return "?" + query_params.urlencode()


Test patch: