diff --git a/CHANGES.rst b/CHANGES.rst
index f0925e1e3..923b94331 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -11,7 +11,10 @@ https://github.com/zopefoundation/Zope/blob/4.0a6/CHANGES.rst
 4.0b2 (unreleased)
 ------------------
 
-- Nothing changed yet.
+Bugfixes
+++++++++
+
+- Fix special double under methods on `HTTPRequest.record` class.
 
 
 4.0b1 (2017-09-15)
diff --git a/src/ZPublisher/HTTPRequest.py b/src/ZPublisher/HTTPRequest.py
index 2d5bef13e..0ba7a0beb 100644
--- a/src/ZPublisher/HTTPRequest.py
+++ b/src/ZPublisher/HTTPRequest.py
@@ -1797,17 +1797,22 @@ class record(object):
                    'keys',
                    'items',
                    'values',
-                   'copy',
-                   'has_key',
-                   '__contains__',
-                   '__iter__',
-                   '__len__'):
+                   'copy'):
             return getattr(self.__dict__, key)
         raise AttributeError(key)
 
+    def __contains__(self, key):
+        return key in self.__dict__
+
     def __getitem__(self, key):
         return self.__dict__[key]
 
+    def __iter__(self):
+        return iter(self.__dict__)
+
+    def __len__(self):
+        return len(self.__dict__)
+
     def __str__(self):
         return ", ".join("%s: %s" % item for item in
                          sorted(self.__dict__.items()))

Form-value gets turned into a request-record instead of a dict
Form-value gets turned into a request-record instead of a dict

A doctest with a item

    <input type="checkbox" value="1" name="index.enabled:records" id="created_enabled" checked="checked" />

fails in Zope 4 since index is transformed to a ZPublisher.HTTPRequest.record instance.

In the button-handler the value is gotten from the rest and checked for some value:

    data = REQUEST.get('index', [])
    for index in data:
        enabled = 'enabled' in index

The last line results in "KeyError: 0" since data is a list of ZPublisher.HTTPRequest.record instances which calls `self.__dict__[key]` on `__getitem__`.

I fixed the respective code using `enabled = 'enabled' in index.keys()` in https://github.com/plone/Products.ATContentTypes/pull/41 and the feature in which this happened is deprecated anyway. But there might be a issue with processInputs in HTTPRequest (or a issue with the form itself).

diff --git a/src/ZPublisher/tests/testHTTPRequest.py b/src/ZPublisher/tests/testHTTPRequest.py
index 7d12a1ccf..5dd152cec 100644
--- a/src/ZPublisher/tests/testHTTPRequest.py
+++ b/src/ZPublisher/tests/testHTTPRequest.py
@@ -33,9 +33,54 @@ if sys.version_info >= (3, ):
 
 class RecordTests(unittest.TestCase):
 
-    def test_repr(self):
+    def _makeOne(self):
         from ZPublisher.HTTPRequest import record
-        rec = record()
+        return record()
+
+    def test_dict_methods(self):
+        rec = self._makeOne()
+        rec.a = 1
+        self.assertEqual(rec['a'], 1)
+        self.assertEqual(rec.get('a'), 1)
+        self.assertEqual(list(rec.keys()), ['a'])
+        self.assertEqual(list(rec.values()), [1])
+        self.assertEqual(list(rec.items()), [('a', 1)])
+
+    def test_dict_special_methods(self):
+        rec = self._makeOne()
+        rec.a = 1
+        self.assertTrue('a' in rec)
+        self.assertFalse('b' in rec)
+        self.assertEqual(len(rec), 1)
+        self.assertEqual(list(iter(rec)), ['a'])
+
+    def test_copy(self):
+        rec = self._makeOne()
+        rec.a = 1
+        rec.b = 'foo'
+        new_rec = rec.copy()
+        self.assertIsInstance(new_rec, dict)
+        self.assertEqual(new_rec, {'a': 1, 'b': 'foo'})
+
+    def test_eq(self):
+        rec1 = self._makeOne()
+        self.assertFalse(rec1, {})
+        rec2 = self._makeOne()
+        self.assertEqual(rec1, rec2)
+        rec1.a = 1
+        self.assertNotEqual(rec1, rec2)
+        rec2.a = 1
+        self.assertEqual(rec1, rec2)
+        rec2.b = 'foo'
+        self.assertNotEqual(rec1, rec2)
+
+    def test_str(self):
+        rec = self._makeOne()
+        rec.a = 1
+        self.assertEqual(str(rec), 'a: 1')
+
+    def test_repr(self):
+        rec = self._makeOne()
         rec.a = 1
         rec.b = 'foo'
         r = repr(rec)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y python3-dev gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
AccessControl==7.2
Acquisition==6.1
AuthEncoding==6.0
beautifulsoup4==4.13.3
BTrees==6.1
cffi==1.17.1
Chameleon==4.6.0
coverage==7.8.0
DateTime==5.5
DocumentTemplate==4.6
exceptiongroup==1.2.2
execnet==2.1.1
ExtensionClass==6.0
importlib_metadata==8.6.1
iniconfig==2.1.0
MultiMapping==5.0
multipart==1.2.1
packaging==24.2
PasteDeploy==3.1.0
Persistence==5.1
persistent==6.1.1
pluggy==1.5.0
pycparser==2.22
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
python-gettext==5.0
pytz==2025.2
RestrictedPython==8.0
roman==5.0
six==1.17.0
soupsieve==2.6
tomli==2.2.1
transaction==5.0
typing_extensions==4.13.0
waitress==3.0.2
WebOb==1.8.9
WebTest==3.0.4
WSGIProxy2==0.5.1
z3c.pt==4.5
zc.lockfile==3.0.post1
ZConfig==4.2
zExceptions==5.0
zipp==3.21.0
ZODB==6.0
zodbpickle==4.2
-e git+https://github.com/zopefoundation/Zope.git@571aada50a4c7390e716a7bf027810cac481f632#egg=Zope
zope.annotation==5.1
zope.browser==3.1
zope.browsermenu==5.1
zope.browserpage==5.0
zope.browserresource==5.2
zope.cachedescriptors==5.1
zope.component==6.0
zope.configuration==6.0
zope.container==6.1
zope.contentprovider==6.0
zope.contenttype==5.2
zope.deferredimport==5.0
zope.deprecation==5.1
zope.dottedname==6.0
zope.event==5.0
zope.exceptions==5.2
zope.filerepresentation==6.1
zope.globalrequest==2.0
zope.hookable==7.0
zope.i18n==5.2
zope.i18nmessageid==7.0
zope.interface==7.2
zope.lifecycleevent==5.1
zope.location==5.1
zope.pagetemplate==5.1
zope.processlifetime==3.1
zope.proxy==6.1
zope.ptresource==5.1
zope.publisher==7.3
zope.schema==7.0.1
zope.security==7.3
zope.sequencesort==5.1
zope.site==5.1
zope.size==5.1
zope.structuredtext==5.0
zope.tal==5.1
zope.tales==6.1
zope.testbrowser==7.0.1
zope.testing==5.1
zope.traversing==5.1
zope.viewlet==5.1

name: Zope
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - accesscontrol==7.2
      - acquisition==6.1
      - authencoding==6.0
      - beautifulsoup4==4.13.3
      - btrees==6.1
      - cffi==1.17.1
      - chameleon==4.6.0
      - coverage==7.8.0
      - datetime==5.5
      - documenttemplate==4.6
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - extensionclass==6.0
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - multimapping==5.0
      - multipart==1.2.1
      - packaging==24.2
      - pastedeploy==3.1.0
      - persistence==5.1
      - persistent==6.1.1
      - pluggy==1.5.0
      - pycparser==2.22
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - python-gettext==5.0
      - pytz==2025.2
      - restrictedpython==8.0
      - roman==5.0
      - six==1.17.0
      - soupsieve==2.6
      - tomli==2.2.1
      - transaction==5.0
      - typing-extensions==4.13.0
      - waitress==3.0.2
      - webob==1.8.9
      - webtest==3.0.4
      - wsgiproxy2==0.5.1
      - z3c-pt==4.5
      - zc-lockfile==3.0.post1
      - zconfig==4.2
      - zexceptions==5.0
      - zipp==3.21.0
      - zodb==6.0
      - zodbpickle==4.2
      - zope-annotation==5.1
      - zope-browser==3.1
      - zope-browsermenu==5.1
      - zope-browserpage==5.0
      - zope-browserresource==5.2
      - zope-cachedescriptors==5.1
      - zope-component==6.0
      - zope-configuration==6.0
      - zope-container==6.1
      - zope-contentprovider==6.0
      - zope-contenttype==5.2
      - zope-deferredimport==5.0
      - zope-deprecation==5.1
      - zope-dottedname==6.0
      - zope-event==5.0
      - zope-exceptions==5.2
      - zope-filerepresentation==6.1
      - zope-globalrequest==2.0
      - zope-hookable==7.0
      - zope-i18n==5.2
      - zope-i18nmessageid==7.0
      - zope-interface==7.2
      - zope-lifecycleevent==5.1
      - zope-location==5.1
      - zope-pagetemplate==5.1
      - zope-processlifetime==3.1
      - zope-proxy==6.1
      - zope-ptresource==5.1
      - zope-publisher==7.3
      - zope-schema==7.0.1
      - zope-security==7.3
      - zope-sequencesort==5.1
      - zope-site==5.1
      - zope-size==5.1
      - zope-structuredtext==5.0
      - zope-tal==5.1
      - zope-tales==6.1
      - zope-testbrowser==7.0.1
      - zope-testing==5.1
      - zope-traversing==5.1
      - zope-viewlet==5.1
prefix: /opt/conda/envs/Zope

src/ZPublisher/tests/testHTTPRequest.py::RecordTests::test_dict_special_methods src/ZPublisher/tests/testHTTPRequest.py::RecordTests::test_eq
src/ZPublisher/tests/testHTTPRequest.py::TestHTTPRequestZope3Views::test_no_traversal_of_view_request_attribute
src/ZPublisher/tests/testHTTPRequest.py::RecordTests::test_copy src/ZPublisher/tests/testHTTPRequest.py::RecordTests::test_dict_methods src/ZPublisher/tests/testHTTPRequest.py::RecordTests::test_repr src/ZPublisher/tests/testHTTPRequest.py::RecordTests::test_str src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test___bobo_traverse___raises src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test__authUserPW_simple src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test__authUserPW_with_embedded_colon src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_doesnt_re_clean_environ src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_keeps_only_last_PARENT src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_keeps_preserves__auth src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_preserves_direct_interfaces src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_preserves_request_subclass src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_preserves_response_class src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_clone_updates_method_to_GET src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_close_removes_stdin_references src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_debug_in_qs_gets_form_var src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_debug_not_in_qs_still_gets_attr src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_debug_override_via_form_other src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getClientAddr_one_trusted_proxy src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getClientAddr_trusted_proxy_last src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getClientAddr_trusted_proxy_no_REMOTE_ADDR src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getClientAddr_wo_trusted_proxy src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getHeader_case_insensitive src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getHeader_exact src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getHeader_literal_turns_off_case_normalization src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getHeader_nonesuch src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getHeader_nonesuch_with_default src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getHeader_underscore_is_dash src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_getVirtualRoot src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_locale_fallback src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_locale_in_qs src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_locale_property_accessor src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_locale_property_override_via_form_other src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_locale_semantics src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_method_GET src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_method_POST src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_no_docstring_on_instance src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_parses_json_cookies src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_postProcessInputs src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_cookie_parsing src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_defaults src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_defaults_w_taints src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_dotted_name_as_tuple src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_large_input_gets_tempfile src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_marshalling_into_sequences src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_records_w_sequences src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_records_w_sequences_tainted src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_simple_containers src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_simple_containers_w_taints src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_simple_marshalling src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_simple_marshalling_w_taints src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_tainted_attribute_raises src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_tainted_values_cleans_exceptions src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_unicode_conversions src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_w_unicode_w_taints src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_with_file_upload_gets_iterator src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_wo_marshalling src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_wo_marshalling_w_Taints src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_processInputs_wo_query_string src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_resolve_url_doesnt_send_endrequestevent src/ZPublisher/tests/testHTTPRequest.py::HTTPRequestTests::test_resolve_url_errorhandling
src/ZPublisher/HTTPRequest.py CHANGES.rst
src/ZPublisher/HTTPRequest.py CHANGES.rst