internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: frappe/frappe

Issue:
ImportError in bench get-untranslated: Circular Import Issue
I encountered an ImportError while running the '**bench get-untranslated**' command in Frappe. This issue seems to be related to a circular import in the '**frappe.translate**' module. When I execute '**bench get-untranslated RU RU_UNTRANSLATED.TXT**', the system throws an ImportError, stating that it cannot import '**get_all_translations**' from the partially initialized module '**frappe.translate**'. This error points towards a potential circular import problem.

### Steps to Reproduce:

1. Run  in Frappe Bench environment:

> bench get-untranslated RU RU_UNTRANSLATED.TXT

2. Observe the ImportError related to 'get_all_translations'.

I suspect this might be due to a cyclical dependency within the Frappe's translate module. Any insights or fixes would be greatly appreciated.


Discussion:
I also have this problem and it seems like a bug. I would be grateful if someone could fix it.
Same problem, a fix is needed.
Yes, v15 the error is present
I have first spotted this error around 2 months ago so the cause is from a long time ago
Hello @barredterra , it should be one of your modification which is linked with this issue, based on the timeline.
Do you have an idea ? 


Resolving patch:
diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py
--- a/frappe/model/__init__.py
+++ b/frappe/model/__init__.py
@@ -134,22 +134,22 @@
 )
 
 std_fields = [
-	{"fieldname": "name", "fieldtype": "Link", "label": _("ID")},
-	{"fieldname": "owner", "fieldtype": "Link", "label": _("Created By"), "options": "User"},
-	{"fieldname": "idx", "fieldtype": "Int", "label": _("Index")},
-	{"fieldname": "creation", "fieldtype": "Datetime", "label": _("Created On")},
-	{"fieldname": "modified", "fieldtype": "Datetime", "label": _("Last Updated On")},
+	{"fieldname": "name", "fieldtype": "Link", "label": "ID"},
+	{"fieldname": "owner", "fieldtype": "Link", "label": "Created By", "options": "User"},
+	{"fieldname": "idx", "fieldtype": "Int", "label": "Index"},
+	{"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
+	{"fieldname": "modified", "fieldtype": "Datetime", "label": "Last Updated On"},
 	{
 		"fieldname": "modified_by",
 		"fieldtype": "Link",
-		"label": _("Last Updated By"),
+		"label": "Last Updated By",
 		"options": "User",
 	},
-	{"fieldname": "_user_tags", "fieldtype": "Data", "label": _("Tags")},
-	{"fieldname": "_liked_by", "fieldtype": "Data", "label": _("Liked By")},
-	{"fieldname": "_comments", "fieldtype": "Text", "label": _("Comments")},
-	{"fieldname": "_assign", "fieldtype": "Text", "label": _("Assigned To")},
-	{"fieldname": "docstatus", "fieldtype": "Int", "label": _("Document Status")},
+	{"fieldname": "_user_tags", "fieldtype": "Data", "label": "Tags"},
+	{"fieldname": "_liked_by", "fieldtype": "Data", "label": "Liked By"},
+	{"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
+	{"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
+	{"fieldname": "docstatus", "fieldtype": "Int", "label": "Document Status"},
 ]
 
 


Test patch:
Repository: frappe/frappe

Issue:
No `before_rename` and `after_rename` server scripts / hooks
There are no listeners for rename events

The `after_rename` seems to be used only to push notifications

The only way to check for this I have found, is to keep an "old_id" field updated on insert/saves, and use a cron job to check for changes regularly to trigger my external API


Discussion:
As a "better" workaround, I am now hooking to `Comment` inserts

Currently, only restoring or renaming a doctype use the `Edit` comment type, and parsing the content I can differentiate and get the old and new document names

Resolving patch:
diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py
--- a/frappe/core/doctype/server_script/server_script.py
+++ b/frappe/core/doctype/server_script/server_script.py
@@ -36,6 +36,8 @@ class ServerScript(Document):
 			"Before Save",
 			"After Insert",
 			"After Save",
+			"Before Rename",
+			"After Rename",
 			"Before Submit",
 			"After Submit",
 			"Before Cancel",
diff --git a/frappe/core/doctype/server_script/server_script_utils.py b/frappe/core/doctype/server_script/server_script_utils.py
--- a/frappe/core/doctype/server_script/server_script_utils.py
+++ b/frappe/core/doctype/server_script/server_script_utils.py
@@ -9,6 +9,8 @@
 	"before_validate": "Before Validate",
 	"validate": "Before Save",
 	"on_update": "After Save",
+	"before_rename": "Before Rename",
+	"after_rename": "After Rename",
 	"before_submit": "Before Submit",
 	"on_submit": "After Submit",
 	"before_cancel": "Before Cancel",


Test patch:
diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py
--- a/frappe/core/doctype/server_script/test_server_script.py
+++ b/frappe/core/doctype/server_script/test_server_script.py
@@ -82,6 +82,26 @@
 		disabled=1,
 		script="""
 frappe.db.add_index("Todo", ["color", "date"])
+""",
+	),
+	dict(
+		name="test_before_rename",
+		script_type="DocType Event",
+		doctype_event="After Rename",
+		reference_doctype="Role",
+		script="""
+doc.desk_access =0
+doc.save()
+""",
+	),
+	dict(
+		name="test_after_rename",
+		script_type="DocType Event",
+		doctype_event="After Rename",
+		reference_doctype="Role",
+		script="""
+doc.disabled =1
+doc.save()
 """,
 	),
 ]
@@ -121,6 +141,12 @@ def test_doctype_event(self):
 			frappe.ValidationError, frappe.get_doc(doctype="ToDo", description="validate me").insert
 		)
 
+		role = frappe.get_doc(doctype="Role", role_name="_Test Role 9").insert(ignore_if_duplicate=True)
+		role.rename("_Test Role 10")
+		role.reload()
+		self.assertEqual(role.disabled, 1)
+		self.assertEqual(role.desk_access, 0)
+
 	def test_api(self):
 		response = requests.post(get_site_url(frappe.local.site) + "/api/method/test_server_script")
 		self.assertEqual(response.status_code, 200)
Repository: frappe/frappe

Issue:
Bug: list index out of range error in User Permission
When I want to set in a User Permission the field applicable_for, I always get an out of range error:

` File "apps/frappe/frappe/app.py", line 110, in application
    response = frappe.api.handle(request)
  File "apps/frappe/frappe/api/__init__.py", line 49, in handle
    data = endpoint(**arguments)
  File "apps/frappe/frappe/api/v1.py", line 36, in handle_rpc_call
    return frappe.handler.handle()
  File "apps/frappe/frappe/handler.py", line 49, in handle
    data = execute_cmd(cmd)
  File "apps/frappe/frappe/handler.py", line 85, in execute_cmd
    return frappe.call(method, **frappe.form_dict)
  File "apps/frappe/frappe/__init__.py", line 1682, in call
    return fn(*args, **newargs)
  File "apps/frappe/frappe/utils/typing_validations.py", line 31, in wrapper
    return func(*args, **kwargs)
  File "apps/frappe/frappe/desk/search.py", line 57, in search_link
    return build_for_autosuggest(results, doctype=doctype)
  File "apps/frappe/frappe/desk/search.py", line 263, in build_for_autosuggest
    label = item[1]  # use title as label
IndexError: list index out of range`

This happens with the most recent Frappe/ERPNext but persists already since quite some time.



Discussion:

Resolving patch:
diff --git a/frappe/desk/search.py b/frappe/desk/search.py
--- a/frappe/desk/search.py
+++ b/frappe/desk/search.py
@@ -260,6 +260,8 @@ def to_string(parts):
 	if meta.show_title_field_in_link:
 		for item in res:
 			item = list(item)
+			if len(item) == 1:
+				item = [item[0], item[0]]
 			label = item[1]  # use title as label
 			item[1] = item[0]  # show name in description instead of title
 			if len(item) >= 3 and item[2] == label:


Test patch:
Repository: frappe/frappe

Issue:
Default Home / Workspace per user
Users should be able to make a workspace their default when they visit desk.


Discussion:

Resolving patch:
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -63,6 +63,7 @@ class User(Document):
 		birth_date: DF.Date | None
 		block_modules: DF.Table[BlockModule]
 		bypass_restrict_ip_check_if_2fa_enabled: DF.Check
+		default_workspace: DF.Link | None
 		defaults: DF.Table[DefaultValue]
 		desk_theme: DF.Literal["Light", "Dark", "Automatic"]
 		document_follow_frequency: DF.Literal["Hourly", "Daily", "Weekly"]
diff --git a/frappe/utils/user.py b/frappe/utils/user.py
--- a/frappe/utils/user.py
+++ b/frappe/utils/user.py
@@ -226,6 +226,7 @@ def load_user(self):
 				"send_me_a_copy",
 				"user_type",
 				"onboarding_status",
+				"default_workspace",
 			],
 			as_dict=True,
 		)
@@ -233,6 +234,10 @@ def load_user(self):
 		if not self.can_read:
 			self.build_permissions()
 
+		if d.get("default_workspace"):
+			public = frappe.get_cached_value("Workspace", d.default_workspace, "public")
+			d.default_workspace = {"name": d.default_workspace, "public": public}
+
 		d.name = self.name
 		d.onboarding_status = frappe.parse_json(d.onboarding_status)
 		d.roles = self.get_roles()


Test patch:
Repository: frappe/frappe

Issue:
Literal values vs. linting
It seems like our linter (ruff) doesn't resolve our self-defined and imported types:

```python
from frappe.types import DF

a: DF.Literal["A"] = "A"
              ^^^ "F821 Undefined-name `A`"
```

while the following works fine:

```python
from typing import Literal

a: Literal["A"] = "A"
```

Because of this error, we have to exclude F821 from the linter, which leads to people (like me, today) committing code with undefined variables.

See also: https://github.com/astral-sh/ruff/issues/6014


Discussion:

Resolving patch:
diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py
--- a/frappe/utils/boilerplate.py
+++ b/frappe/utils/boilerplate.py
@@ -369,9 +369,9 @@ def _create_parent_folder_if_not_exists(self):
     "F403", # can't detect undefined names from * import
     "F405", # can't detect undefined names from * import
     "F722", # syntax error in forward type annotation
-    "F821", # undefined name
     "W191", # indentation contains tabs
 ]
+typing-modules = ["frappe.types.DF"]
 
 [tool.ruff.format]
 quote-style = "double"


Test patch:
Repository: frappe/frappe

Issue:
limit rating field type values to max 1
Rating field stores a floating point value between 0-1, this is required to allow converting number of stars without patching data. 

However, you can end up writing some buggy code that stores value more than 1.0, this should be prevented by server-side validation. 

Currently no validation on:
1. API
2. Frontend (client script can modify it)




Implementation:
- `base_document.py` contains many fieldtype specific validations, add a new one there for rating fieldtype.



Discussion:
@ankush  you mean minimum 1?
@bhavesh95863 no, maximum. 

Rating field values are stored as floating-point between 0 and 1 (version-14). This is for allowing variable number of stars. 

Resolving patch:
diff --git a/frappe/model/document.py b/frappe/model/document.py
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -540,6 +540,7 @@ def _validate(self):
 		self._validate_selects()
 		self._validate_non_negative()
 		self._validate_length()
+		self._fix_rating_value()
 		self._validate_code_fields()
 		self._sync_autoname_field()
 		self._extract_images_from_text_editor()
@@ -586,6 +587,15 @@ def get_msg(df):
 				msg = get_msg(df)
 				frappe.throw(msg, frappe.NonNegativeError, title=_("Negative Value"))
 
+	def _fix_rating_value(self):
+		for field in self.meta.get("fields", {"fieldtype": "Rating"}):
+			value = self.get(field.fieldname)
+			if not isinstance(value, float):
+				value = flt(value)
+
+			# Make sure rating is between 0 and 1
+			self.set(field.fieldname, max(0, min(value, 1)))
+
 	def validate_workflow(self):
 		"""Validate if the workflow transition is valid"""
 		if frappe.flags.in_install == "frappe":


Test patch:
diff --git a/frappe/tests/test_rating.py b/frappe/tests/test_rating.py
new file mode 100644
--- /dev/null
+++ b/frappe/tests/test_rating.py
@@ -0,0 +1,29 @@
+import frappe
+from frappe.core.doctype.doctype.test_doctype import new_doctype
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestRating(FrappeTestCase):
+	def setUp(self):
+		doc = new_doctype(
+			fields=[
+				{
+					"fieldname": "rating",
+					"fieldtype": "Rating",
+					"label": "rating",
+					"reqd": 1,  # mandatory
+				},
+			],
+		)
+		doc.insert()
+		self.doctype_name = doc.name
+
+	def test_negative_rating(self):
+		doc = frappe.get_doc(doctype=self.doctype_name, rating=-1)
+		doc.insert()
+		self.assertEqual(doc.rating, 0)
+
+	def test_positive_rating(self):
+		doc = frappe.get_doc(doctype=self.doctype_name, rating=5)
+		doc.insert()
+		self.assertEqual(doc.rating, 1)
Repository: frappe/frappe

Issue:
Default Home / Workspace per user
Users should be able to make a workspace their default when they visit desk.


Discussion:

Resolving patch:
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -61,6 +61,7 @@ class User(Document):
 		birth_date: DF.Date | None
 		block_modules: DF.Table[BlockModule]
 		bypass_restrict_ip_check_if_2fa_enabled: DF.Check
+		default_workspace: DF.Link | None
 		defaults: DF.Table[DefaultValue]
 		desk_theme: DF.Literal["Light", "Dark", "Automatic"]
 		document_follow_frequency: DF.Literal["Hourly", "Daily", "Weekly"]
diff --git a/frappe/utils/user.py b/frappe/utils/user.py
--- a/frappe/utils/user.py
+++ b/frappe/utils/user.py
@@ -226,6 +226,7 @@ def load_user(self):
 				"send_me_a_copy",
 				"user_type",
 				"onboarding_status",
+				"default_workspace",
 			],
 			as_dict=True,
 		)
@@ -233,6 +234,10 @@ def load_user(self):
 		if not self.can_read:
 			self.build_permissions()
 
+		if d.get("default_workspace"):
+			public = frappe.get_cached_value("Workspace", d.default_workspace, "public")
+			d.default_workspace = {"name": d.default_workspace, "public": public}
+
 		d.name = self.name
 		d.onboarding_status = frappe.parse_json(d.onboarding_status)
 		d.roles = self.get_roles()


Test patch:
Repository: frappe/frappe

Issue:
Add jitter on scheduled jobs
hourly, daily long processes if all started at once can cause sudden increase in workload if you have many sites/benches. 


Adding simple jitter to scheduled time can lessen the impact of such issues. Jitter is common pattern used for solving problems with "frequency" becomes a problem. E.g. gunicorn adds jitter to avoid restarting all workers at same time, profilers add jitter to avoid amplifying some pattern of repeated work. retry/backoff implementations also use to avoid creating patterns.


Possible implementation: When importing scheduled job types add some random delays in cron. E.g. daily jobs will start in the range of  12:00-12:15 AM instead of all starting at 12:00 AM.


Cons: Some jobs are required to be executed at specific times e.g. birthday reminders. So adding negative offset can introduce bugs for them, positive offset however should be fine AFAIK. 


Discussion:

Resolving patch:
diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
--- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
+++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
@@ -2,7 +2,8 @@
 # License: MIT. See LICENSE
 
 import json
-from datetime import datetime
+from datetime import datetime, timedelta
+from random import randint
 
 import click
 from croniter import CroniterBadCronError, croniter
@@ -97,7 +98,12 @@ def get_next_execution(self):
 		# immediately, even when it's meant to be daily.
 		# A dynamic fallback like current time might miss the scheduler interval and job will never start.
 		last_execution = get_datetime(self.last_execution or self.creation)
-		return croniter(self.cron_format, last_execution).get_next(datetime)
+		next_execution = croniter(self.cron_format, last_execution).get_next(datetime)
+
+		jitter = 0
+		if self.frequency in ("Hourly Long", "Daily Long"):
+			jitter = randint(1, 600)
+		return next_execution + timedelta(seconds=jitter)
 
 	def execute(self):
 		self.scheduler_log = None


Test patch:
Repository: frappe/frappe

Issue:
Add jitter on scheduled jobs
hourly, daily long processes if all started at once can cause sudden increase in workload if you have many sites/benches. 


Adding simple jitter to scheduled time can lessen the impact of such issues. Jitter is common pattern used for solving problems with "frequency" becomes a problem. E.g. gunicorn adds jitter to avoid restarting all workers at same time, profilers add jitter to avoid amplifying some pattern of repeated work. retry/backoff implementations also use to avoid creating patterns.


Possible implementation: When importing scheduled job types add some random delays in cron. E.g. daily jobs will start in the range of  12:00-12:15 AM instead of all starting at 12:00 AM.


Cons: Some jobs are required to be executed at specific times e.g. birthday reminders. So adding negative offset can introduce bugs for them, positive offset however should be fine AFAIK. 


Discussion:

Resolving patch:
diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
--- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
+++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
@@ -2,7 +2,8 @@
 # License: MIT. See LICENSE
 
 import json
-from datetime import datetime
+from datetime import datetime, timedelta
+from random import randint
 
 import click
 from croniter import CroniterBadCronError, croniter
@@ -123,7 +124,12 @@ def get_next_execution(self):
 		# immediately, even when it's meant to be daily.
 		# A dynamic fallback like current time might miss the scheduler interval and job will never start.
 		last_execution = get_datetime(self.last_execution or self.creation)
-		return croniter(self.cron_format, last_execution).get_next(datetime)
+		next_execution = croniter(self.cron_format, last_execution).get_next(datetime)
+
+		jitter = 0
+		if self.frequency in ("Hourly Long", "Daily Long"):
+			jitter = randint(1, 600)
+		return next_execution + timedelta(seconds=jitter)
 
 	def execute(self):
 		self.scheduler_log = None


Test patch:
Repository: frappe/frappe

Issue:
Werkzeug built-in redirect for double slashes not working as expected (`/app//note`)
Small issue, but `raise RequestRedirect` is not caught, thus it becomes a **500** error :smile: 

```
Traceback (most recent call last):
  File "apps/frappe/frappe/website/serve.py", line 19, in get_response
    endpoint, renderer_instance = path_resolver.resolve()
                                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/website/path_resolver.py", line 45, in resolve
    endpoint = resolve_path(self.path)
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/website/path_resolver.py", line 162, in resolve_path
    path = resolve_from_map(path)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/website/path_resolver.py", line 173, in resolve_from_map
    return evaluate_dynamic_routes(rules, path) or path
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/website/router.py", line 72, in evaluate_dynamic_routes
    endpoint, args = urls.match("/" + path)
                     ^^^^^^^^^^^^^^^^^^^^^^
  File "env/lib/python3.11/site-packages/werkzeug/routing/map.py", line 611, in match
    raise RequestRedirect(
werkzeug.routing.exceptions.RequestRedirect: 308 Permanent Redirect: http://localhost:8000/app/notification-log
```

See screenshot below (sorry it's in French).
![screenshot](https://github.com/frappe/frappe/assets/10946971/65d5af28-75b2-4837-8bd5-0333014f7f1d)



Discussion:

Resolving patch:
diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py
--- a/frappe/website/path_resolver.py
+++ b/frappe/website/path_resolver.py
@@ -1,6 +1,7 @@
 import re
 
 import click
+import werkzeug.routing.exceptions
 from werkzeug.routing import Rule
 
 import frappe
@@ -42,7 +43,11 @@ def resolve(self):
 			for handler in frappe.get_hooks("website_path_resolver"):
 				endpoint = frappe.get_attr(handler)(self.path)
 		else:
-			endpoint = resolve_path(self.path)
+			try:
+				endpoint = resolve_path(self.path)
+			except werkzeug.routing.exceptions.RequestRedirect as e:
+				frappe.flags.redirect_location = e.new_url
+				return frappe.flags.redirect_location, RedirectPage(e.new_url, e.code)
 
 		# WARN: Hardcoded for better performance
 		if endpoint == "app":
diff --git a/frappe/website/router.py b/frappe/website/router.py
--- a/frappe/website/router.py
+++ b/frappe/website/router.py
@@ -1,7 +1,6 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: MIT. See LICENSE
 
-import io
 import os
 import re
 
@@ -71,12 +70,10 @@ def evaluate_dynamic_routes(rules, path):
 		urls = route_map.bind_to_environ(frappe.local.request.environ)
 		try:
 			endpoint, args = urls.match("/" + path)
-			path = endpoint
 			if args:
 				# don't cache when there's a query string!
 				frappe.local.no_cache = 1
 				frappe.local.form_dict.update(args)
-
 		except NotFound:
 			pass
 


Test patch: