Skip to content

Commit

Permalink
yourlabs#1333 clone forwards
Browse files Browse the repository at this point in the history
When Django creates a Form instance from a form class, the fields and widgets are _copied_ instead of instantiated. This means that every field/widget must implement __deepcopy__ properly to create clones of any references it holds.

Implement `__deepcopy__` in the `WidgetMixin` and copy the `forward` list to prevent forwards from leaking into unrelated forms when using a common base class.
  • Loading branch information
ercpe committed Mar 23, 2024
1 parent 3c7d0f4 commit cc3f7f5
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 1 deletion.
10 changes: 9 additions & 1 deletion src/dal/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ class WidgetMixin(object):
def __init__(self, url=None, forward=None, *args, **kwargs):
"""Instanciate a widget with a URL and a list of fields to forward."""
self.url = url
self.forward = forward or []
if forward:
self.forward = list(forward).copy()
else:
self.forward = []
self.placeholder = kwargs.get("attrs", {}).get("data-placeholder")
super(WidgetMixin, self).__init__(*args, **kwargs)

def __deepcopy__(self, memo):
clone = super().__deepcopy__(memo)
clone.forward = self.forward.copy()
return clone

def build_attrs(self, *args, **kwargs):
"""Build HTML attributes for the widget."""
attrs = super(WidgetMixin, self).build_attrs(*args, **kwargs)
Expand Down
30 changes: 30 additions & 0 deletions test_project/tests/test_widgets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test the base widget."""
from django.contrib.auth import get_user_model

from dal import forward
from dal.autocomplete import Select2
from dal.widgets import Select, WidgetMixin

Expand All @@ -10,6 +12,9 @@
from django import http
from django import test
from django.urls import re_path as url

from dal_select2.widgets import ModelSelect2

try:
from django.urls import reverse
except ImportError:
Expand Down Expand Up @@ -158,3 +163,28 @@ def render_forward_conf(self, id):
# attrs with id
observed = widget.render('myname', '', attrs={'id': 'myid'})
self.assertEqual(observed, 'myid')

def test_forwards_are_cloned(self):
User = get_user_model()

class BaseForm(forms.Form):
choice_field = forms.ModelChoiceField(User.objects.all(), widget=ModelSelect2(url='/somewhere'))

class Form1(BaseForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['choice_field'].widget.forward.append(forward.Const(True, 'filter_a'))

class Form2(BaseForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['choice_field'].widget.forward.append(forward.Const(True, 'filter_b'))

form1 = Form1()
form2 = Form2()

self.assertEqual(1, len(form1.fields['choice_field'].widget.forward))
self.assertEqual('filter_a', form1.fields['choice_field'].widget.forward[0].dst)

self.assertEqual(1, len(form2.fields['choice_field'].widget.forward))
self.assertEqual('filter_b', form2.fields['choice_field'].widget.forward[0].dst)

0 comments on commit cc3f7f5

Please sign in to comment.