Repository: frappe/frappe

Issue:
AttributeError When Setting Up A Document Link to a  Document with a Child Table
## Description of the issue

Copying this from ERPNext's forums to here as a bug report. Further details including screenshots can be viewed at the original link: https://discuss.erpnext.com/t/create-connection-to-child-document-using-linked-documents/81148

## Context information (for bug reports)

**Output of `bench version`**
```
erpnext 13.12.1
frappe 13.12.1
```

## Steps to reproduce the issue

1. Create a Document with a Child Table containing a link field. I used a custom DocType for the document and a custom DocType for the Child Table.
2. Edit the Doctype being linked to
3. Under the "Linked Documents" section, fill in the form:
    - **Link DocType:** fill in with the DocType of the Child Table
    - **Link Fieldname:** fill in with the fieldname from the Child Table
    - **Parent DocType:** fill in with the DocType containing the Child Table
    - **Table Fieldname:** fill in with the name of the Child Table field
4. Save your changes

### Observed result

```
Traceback (most recent call last):
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs
    doc.save()
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 285, in save
    return self._save(*args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 320, in _save
    self.run_before_save_methods()
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 968, in run_before_save_methods
    self.run_method("validate")
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 860, in run_method
    out = Document.hook(fn)(self, *args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1158, in composer
    return composed(self, method, *args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1141, in runner
    add_to_return_value(self, fn(self, *args, **kwargs))
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 854, in <lambda>
    fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 82, in validate
    validate_links_table_fieldnames(self)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 735, in validate_links_table_fieldnames
    if link.is_child_table and not meta.get_field(link.table_fieldname):
AttributeError: 'DocType' object has no attribute 'get_field'

request.js:387 Traceback (most recent call last):
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/app.py", line 68, in application
    response = frappe.api.handle()
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/api.py", line 55, in handle
    return frappe.handler.handle()
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/handler.py", line 31, in handle
    data = execute_cmd(cmd)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/handler.py", line 67, in execute_cmd
    return frappe.call(method, **frappe.form_dict)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/__init__.py", line 1199, in call
    return fn(*args, **newargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs
    doc.save()
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 285, in save
    return self._save(*args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 320, in _save
    self.run_before_save_methods()
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 968, in run_before_save_methods
    self.run_method("validate")
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 860, in run_method
    out = Document.hook(fn)(self, *args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1158, in composer
    return composed(self, method, *args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1141, in runner
    add_to_return_value(self, fn(self, *args, **kwargs))
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 854, in <lambda>
    fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 82, in validate
    validate_links_table_fieldnames(self)
  File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 735, in validate_links_table_fieldnames
    if link.is_child_table and not meta.get_field(link.table_fieldname):
AttributeError: 'DocType' object has no attribute 'get_field'
```

### Expected result

The customization should save and the number of linked documents should display on the panel of the DocType being customized

### Stacktrace / full error message

```
AttributeError: ‘DocType’ object has no attribute ‘get_field’
```

## Additional information

OS version / distribution, `Frappe` install method, etc.



Discussion:
> ## Description of the issue
> Copying this from ERPNext's forums to here as a bug report. Further details including screenshots can be viewed at the original link: https://discuss.erpnext.com/t/create-connection-to-child-document-using-linked-documents/81148
> 
> ## Context information (for bug reports)
> **Output of `bench version`**
> 
> ```
> erpnext 13.12.1
> frappe 13.12.1
> ```
> 
> ## Steps to reproduce the issue
> 1. Create a Document with a Child Table containing a link field. I used a custom DocType for the document and a custom DocType for the Child Table.
> 2. Edit the Doctype being linked to
> 3. Under the "Linked Documents" section, fill in the form:
>    
>    * **Link DocType:** fill in with the DocType of the Child Table
>    * **Link Fieldname:** fill in with the fieldname from the Child Table
>    * **Parent DocType:** fill in with the DocType containing the Child Table
>    * **Table Fieldname:** fill in with the name of the Child Table field
> 4. Save your changes
> 
> ### Observed result
> ```
> Traceback (most recent call last):
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs
>     doc.save()
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 285, in save
>     return self._save(*args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 320, in _save
>     self.run_before_save_methods()
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 968, in run_before_save_methods
>     self.run_method("validate")
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 860, in run_method
>     out = Document.hook(fn)(self, *args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1158, in composer
>     return composed(self, method, *args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1141, in runner
>     add_to_return_value(self, fn(self, *args, **kwargs))
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 854, in <lambda>
>     fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 82, in validate
>     validate_links_table_fieldnames(self)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 735, in validate_links_table_fieldnames
>     if link.is_child_table and not meta.get_field(link.table_fieldname):
> AttributeError: 'DocType' object has no attribute 'get_field'
> 
> request.js:387 Traceback (most recent call last):
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/app.py", line 68, in application
>     response = frappe.api.handle()
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/api.py", line 55, in handle
>     return frappe.handler.handle()
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/handler.py", line 31, in handle
>     data = execute_cmd(cmd)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/handler.py", line 67, in execute_cmd
>     return frappe.call(method, **frappe.form_dict)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/__init__.py", line 1199, in call
>     return fn(*args, **newargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs
>     doc.save()
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 285, in save
>     return self._save(*args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 320, in _save
>     self.run_before_save_methods()
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 968, in run_before_save_methods
>     self.run_method("validate")
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 860, in run_method
>     out = Document.hook(fn)(self, *args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1158, in composer
>     return composed(self, method, *args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 1141, in runner
>     add_to_return_value(self, fn(self, *args, **kwargs))
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/model/document.py", line 854, in <lambda>
>     fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 82, in validate
>     validate_links_table_fieldnames(self)
>   File "/home/jbulcher/workspace/erpnext/frappe-bench/apps/frappe/frappe/core/doctype/doctype/doctype.py", line 735, in validate_links_table_fieldnames
>     if link.is_child_table and not meta.get_field(link.table_fieldname):
> AttributeError: 'DocType' object has no attribute 'get_field'
> ```
> 
> ### Expected result
> The customization should save and the number of linked documents should display on the panel of the DocType being customized
> 
> ### Stacktrace / full error message
> ```
> AttributeError: ‘DocType’ object has no attribute ‘get_field’
> ```
> 
> ## Additional information
> OS version / distribution, `Frappe` install method, etc.

I have the same issue
I think there is more than one aspect to this problem.

# Validation

First is the obvious validation issue that occurs when attempting to save a link from a child table. This issue takes place in the definition of `validate_links_table_fieldnames(meta)` in `frappe/core/doctype/doctype/doctype.py`, unless I am mistaken. The fix for this issue might be as simple as calling `get_field` on the correct object, although I don't understand the code well enough to say for sure.

# Link

The other aspect is obtaining the link data to display. This happens in the definition of `get_open_count(doctype, name, items=[])` in `frappe/desk/notifications.py`. In this case, it looks like the task was completely overlooked when implementing the GUI. Here I found this:

```
@frappe.whitelist()
@frappe.read_only()
def get_open_count(doctype, name, items=[]):

        ...

	for d in items:
		if d in links.get("internal_links", {}):
			# internal link
			continue

                ...
```

I believe `internal_links` refers to links in child tables. If that is the case, no code is run to obtain the links to the child tables.
I'm having the same issue. Any update on this, or does anyone know how to circumvent or solve this?
same here.
same here
Same Here

Resolving patch:
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -755,28 +755,30 @@ def validate_series(dt, autoname=None, name=None):
 
 def validate_links_table_fieldnames(meta):
 	"""Validate fieldnames in Links table"""
-	if frappe.flags.in_patch: return
-	if frappe.flags.in_fixtures: return
-	if not meta.links: return
+	if not meta.links or frappe.flags.in_patch or frappe.flags.in_fixtures:
+		return
 
-	for index, link in enumerate(meta.links):
+	fieldnames = tuple(field.fieldname for field in meta.fields)
+	for index, link in enumerate(meta.links, 1):
 		link_meta = frappe.get_meta(link.link_doctype)
 		if not link_meta.get_field(link.link_fieldname):
-			message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
+			message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
 			frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))
 
-		if link.is_child_table and not meta.get_field(link.table_fieldname):
-			message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.table_fieldname), frappe.bold(meta.name))
-			frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname"))
+		if not link.is_child_table:
+			continue
 
-		if link.is_child_table:
-			if not link.parent_doctype:
-				message = _("Document Links Row #{0}: Parent DocType is mandatory for internal links").format(index+1)
-				frappe.throw(message, frappe.ValidationError, _("Parent Missing"))
+		if not link.parent_doctype:
+			message = _("Document Links Row #{0}: Parent DocType is mandatory for internal links").format(index)
+			frappe.throw(message, frappe.ValidationError, _("Parent Missing"))
 
-			if not link.table_fieldname:
-				message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index+1)
-				frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing"))
+		if not link.table_fieldname:
+			message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index)
+			frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing"))
+
+		if link.table_fieldname not in fieldnames:
+			message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.table_fieldname), frappe.bold(meta.name))
+			frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname"))
 
 def validate_fields_for_doctype(doctype):
 	meta = frappe.get_meta(doctype, cached=False)


Test patch: