Example case of how to hook an autoname to a WebsiteGenerator

I am puzzled how to hook an autoname to a WebsiteGenerator - I aim to write an example test to learn from.

My grep and sql searches did not find one example case.

select name, autoname from tabDocType where autoname like ‘%#%’;
±--------------------------±-------------------+
| name | autoname |
±--------------------------±-------------------+
| Activity Cost | AC-.##### |
| Announcement | announcement.##### |
| Asset Movement | AM-.##### |
| Authorization Rule | AR.#### |
| Bin | BIN/.####### |
| Custom Script | CustomScript.#### |
| Customize Form | DL.#### |
| Discussion | Discussion.#### |
| Error Log | Error-.##### |
| Event | EV.##### |
| Foobar | Foobar-.# |
| GL Entry | GL.####### |
| Guardian | GARD.#### |
| Item Price | ITEM-PRICE-.##### |
| Job Applicant | JA-.###### |
| Leave Application | LAP/.##### |
| Offer Letter | Offer-.##### |
| Packing Slip | PS.####### |
| Patch Log | PATCHLOG.##### |
| Period Closing Voucher | PCE/.### |
| Program Enrollment | PE.##### |
| Quotation Item | QUOD/.##### |
| Room | RM.#### |
| Sales Taxes and Charges | INVTD.###### |
| SMS Log | SMSLOG/.######## |
| Stock Ledger Entry | SLE/.######## |
| Student Attendance | SA.###### |
| Student Leave Application | SLA.###### |
| Student Log | SLog.#### |
| Task | TASK.##### |
| Tax Rule | TR.#### |
| Topic | Topic.#### |
| Training Feedback | Feedback.#### |
| Training Result | TRES.##### |
| Version | _VER.###### |
±--------------------------±-------------------+
35 rows in set (0.00 sec)

frappe@erpnext:~/frappe-bench$ find . -name *.py | xargs grep 'class ’ | grep ‘WebsiteGenerator’
./apps/erpnext/erpnext/stock/doctype/item/item.py:class Item(WebsiteGenerator):
./apps/erpnext/erpnext/setup/doctype/sales_partner/sales_partner.py:class SalesPartner(WebsiteGenerator):
./apps/erpnext/erpnext/setup/doctype/item_group/item_group.py:class ItemGroup(NestedSet, WebsiteGenerator):
./apps/erpnext/erpnext/hr/doctype/job_opening/job_opening.py:class JobOpening(WebsiteGenerator):
./apps/erpnext/erpnext/schools/doctype/student_admission/student_admission.py:class StudentAdmission(WebsiteGenerator):
./apps/frappe/frappe/website/website_generator.py:class WebsiteGenerator(Document):
./apps/frappe/frappe/website/doctype/help_category/help_category.py:class HelpCategory(WebsiteGenerator):
./apps/frappe/frappe/website/doctype/web_form/web_form.py:class WebForm(WebsiteGenerator):
./apps/frappe/frappe/website/doctype/help_article/help_article.py:class HelpArticle(WebsiteGenerator):
./apps/frappe/frappe/website/doctype/web_page/web_page.py:class WebPage(WebsiteGenerator):
./apps/frappe/frappe/website/doctype/blog_post/blog_post.py:class BlogPost(WebsiteGenerator):
./apps/frappe/frappe/website/doctype/blog_category/blog_category.py:class BlogCategory(WebsiteGenerator):

To state my problem case:

My Auto Name spec is Foobar-.# that corresponds to this [series] - Series by prefix (separated by a dot)

When Foobar is a subclass of Document, autoname generates Foobar-1 as expected.

But when Foobar is a subclass of WebsiteGenerator I get this stack trace:

frappe@erpnext:~/frappe-bench$ bench run-tests --doctype Foobar --test test_create_Foobar
E

ERROR: test_create_Foobar (newapp.new_app.doctype.foobar.test_foobar.TestFoobar)

Traceback (most recent call last):
File “/home/frappe/frappe-bench/apps/newapp/newapp/new_app/doctype/foobar/test_foobar.py”, line 20, in test_create_Foobar
fb.insert()
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py”, line 184, in insert
self.set_new_name()
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py”, line 322, in set_new_name
set_new_name(self)
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/naming.py”, line 38, in set_new_name
doc.run_method(“autoname”)
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py”, line 651, in run_method
out = Document.hook(fn)(self, *args, **kwargs)
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py”, line 858, in composer
return composed(self, method, *args, **kwargs)
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py”, line 841, in runner
add_to_return_value(self, fn(self, *args, **kwargs))
File “/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py”, line 645, in
fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs)
File “/home/frappe/frappe-bench/apps/frappe/frappe/website/website_generator.py”, line 26, in autoname
self.name = self.scrub(self.get(self.website.page_title_field or “title”))
File “/home/frappe/frappe-bench/apps/frappe/frappe/website/website_generator.py”, line 53, in scrub
return cleanup_page_name(text).replace(’_’, ‘-’)
AttributeError: ‘NoneType’ object has no attribute ‘replace’


Ran 1 test in 0.047s

FAILED (errors=1)
<unittest.runner.TextTestResult run=1 errors=1 failures=0>

For website generator, you must call it directly using the make_autoname function from inside the autoname controller of your model

Edit: I guess the design needs a name to be set using the autoname method.

Thanks for your pointer and bait for a PR if I am not mistaken!

So Foobar required this and problem solved:

    def autoname(self):                                                                                            
            dt = frappe.get_meta("Foobar")
            key = dt.get("autoname")
            self.name = make_autoname(key)

For others to learn too, my fix and basic test case suite are posted here https://github.com/clarkejj/webview-erpnext

Thanks to Guarav Naik for his basic custom app example https://github.com/gaurav-naik/webview-erpnext

1 Like

i m getting error like (make_autoname not defined)

from frappe.model.naming import make_autoname

Thank you sir.
how i can rename the doc name after edit.
Sir i have two field from_date and to_date.
so i want my name should be like … (from_date + “To” + to_date).
But the problem is autoname function setting only when doc will save first time.
If i ll change from_date or to_date name is not changing.
Sir can you help me what should i do in this case.

autoname is designed to modify column name value which is primary key, so it only work at the first time save.

If you change value other field which is not primary key, you can use function validate instead.

def validate(self): 
    self.whatever = make_autoname(key)