diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG
index 9213277c116e9a0973c43bddd195602dff285090_dHJ5dG9uZC9DSEFOR0VMT0c=..cf8f1399bc1b398fd976e6ca4a035ecca6e4ec43_dHJ5dG9uZC9DSEFOR0VMT0c= 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -1,3 +1,4 @@
+* Do not copy readonly relational fields
 * Support list of single value in sortable_values tools
 * Allow resetting password of user via RPC
 * Support cached_property on object with __slots__
diff --git a/trytond/doc/ref/models.rst b/trytond/doc/ref/models.rst
index 9213277c116e9a0973c43bddd195602dff285090_dHJ5dG9uZC9kb2MvcmVmL21vZGVscy5yc3Q=..cf8f1399bc1b398fd976e6ca4a035ecca6e4ec43_dHJ5dG9uZC9kb2MvcmVmL21vZGVscy5yc3Q= 100644
--- a/trytond/doc/ref/models.rst
+++ b/trytond/doc/ref/models.rst
@@ -390,6 +390,13 @@
 
    New records are returned following the input order.
 
+   .. note::
+
+      The :class:`~fields.One2Many` and :class:`~fields.Many2Many` relation
+      fields are not copied if their :attr:`~fields.Field.readonly` attribute
+      is ``True`` or if their relationional :class:`Model` is not a
+      :class:`ModelStorage` or if it has a :meth:`ModelSQL.table_query`.
+
 .. classmethod:: ModelStorage.search(domain[, offset[, limit[, order[, count]]]])
 
    Return a list of records that match the :ref:`domain <topics-domain>`.
diff --git a/trytond/trytond/model/modelstorage.py b/trytond/trytond/model/modelstorage.py
index 9213277c116e9a0973c43bddd195602dff285090_dHJ5dG9uZC90cnl0b25kL21vZGVsL21vZGVsc3RvcmFnZS5weQ==..cf8f1399bc1b398fd976e6ca4a035ecca6e4ec43_dHJ5dG9uZC90cnl0b25kL21vZGVsL21vZGVsc3RvcmFnZS5weQ== 100644
--- a/trytond/trytond/model/modelstorage.py
+++ b/trytond/trytond/model/modelstorage.py
@@ -382,7 +382,15 @@
         else:
             default = default.copy()
 
-        def is_readonly(Model):
+        def is_readonly(field):
+            if field.readonly:
+                return True
+            if hasattr(field, 'get_relation'):
+                Model = field.get_relation()
+            elif hasattr(field, 'get_target'):
+                Model = field.get_target()
+            else:
+                return False
             return (not issubclass(Model, ModelStorage)
                 or callable(getattr(Model, 'table_query', None)))
 
@@ -424,10 +432,10 @@
                     except Exception:
                         pass
                 elif ftype in ('one2many',):
-                    if is_readonly(field.get_target()):
+                    if is_readonly(field):
                         del data[field_name]
                     elif data[field_name]:
                         data[field_name] = [(
                                 'copy', data[field_name],
                                 get_default(field_name))]
                 elif ftype == 'many2many':
@@ -428,10 +436,10 @@
                         del data[field_name]
                     elif data[field_name]:
                         data[field_name] = [(
                                 'copy', data[field_name],
                                 get_default(field_name))]
                 elif ftype == 'many2many':
-                    if is_readonly(pool.get(field.relation_name)):
+                    if is_readonly(field):
                         del data[field_name]
                     elif data[field_name]:
                         data[field_name] = [('add', data[field_name])]
diff --git a/trytond/trytond/tests/test_copy.py b/trytond/trytond/tests/test_copy.py
index 9213277c116e9a0973c43bddd195602dff285090_dHJ5dG9uZC90cnl0b25kL3Rlc3RzL3Rlc3RfY29weS5weQ==..cf8f1399bc1b398fd976e6ca4a035ecca6e4ec43_dHJ5dG9uZC90cnl0b25kL3Rlc3RzL3Rlc3RfY29weS5weQ== 100644
--- a/trytond/trytond/tests/test_copy.py
+++ b/trytond/trytond/tests/test_copy.py
@@ -2,6 +2,7 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
 import unittest
+from unittest.mock import patch
 
 from trytond.model import fields
 from trytond.model.exceptions import AccessError
@@ -93,6 +94,22 @@
                 [x.name for x in one2many_copy.one2many])
 
     @with_transaction()
+    def test_one2many_readonly(self):
+        "Test copy one2many readonly"
+        pool = Pool()
+        Model = pool.get('test.copy.one2many')
+        Target = pool.get('test.copy.one2many.target')
+
+        record = Model(name="Test")
+        record.one2many = [Target(name="Target")]
+        record.save()
+
+        with patch.object(Model.one2many, 'readonly', True):
+            copy, = Model.copy([record])
+
+            self.assertEqual(copy.one2many, ())
+
+    @with_transaction()
     def test_one2many_default(self):
         "Test copy one2many with default"
         pool = Pool()
@@ -158,6 +175,22 @@
                 [x.name for x in many2many_copy.many2many])
 
     @with_transaction()
+    def test_many2many_readonly(self):
+        "test copy many2many readonly"
+        pool = Pool()
+        Model = pool.get('test.copy.many2many')
+        Target = pool.get('test.copy.many2many.target')
+
+        record = Model(name="Test")
+        record.many2many = [Target(name="Target")]
+        record.save()
+
+        with patch.object(Model.many2many, 'readonly', True):
+            copy, = Model.copy([record])
+
+            self.assertEqual(copy.many2many, ())
+
+    @with_transaction()
     def test_many2many_default(self):
         "Test copy many2many with default"
         pool = Pool()