-
Notifications
You must be signed in to change notification settings - Fork 10
docs(vi): fix translation mismatches for DOL011-DOL015 and update index #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,24 @@ | ||
| # DOL012 — Thêm `db_index=True` cho các trường dùng thường xuyên trong `order_by()` | ||
| # DOL012 — Model không có phương thức __str__ | ||
|
|
||
| **Mức độ mặc định:** info · **Khả năng áp dụng:** unsafe · **Danh mục:** model-definition | ||
| **Mức độ mặc định:** info · **Khả năng áp dụng:** suggestion · **Danh mục:** model | ||
|
|
||
| Phát hiện các trường model (ngoại trừ `ForeignKey` đã được DOL011 xử lý) xuất hiện làm đối số duy nhất trong các lời gọi `order_by()` từ ba lần trở lên trong cùng file, mà không có khai báo `db_index=True` hoặc `unique=True`. Sắp xếp lặp lại trên cột không có index sẽ buộc database thực hiện filesort cho mỗi query; một index sẽ chuyển điều đó thành index scan. | ||
|
|
||
| Khả năng áp dụng là `unsafe` vì thêm index là một thay đổi schema. | ||
| Phát hiện một lớp kế thừa từ `models.Model` mà phần thân không định nghĩa phương thức `__str__` (các model trừu tượng với `abstract = True` trong `Meta` sẽ bị bỏ qua). Nếu không có `__str__`, các danh sách thay đổi trong admin, dropdown của `ForeignKey`, chuỗi hiển thị trên shell và `{{ obj }}` trong template đều sẽ hiển thị dạng `ModelName object (1)` — vô dụng với người đọc và gây khó khăn khi debug. Không có QuickFix: để tạo ra một thân hàm có ý nghĩa, bạn cần chọn trường nào để hiển thị, và chỉ có bạn mới làm được điều này. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(DOL012|.*dol012.*|.*DOL012.*)|iterClassBody|rules/.+\.(js|ts|py)$' | head -200
printf '%s\n' '--- symbol references ---'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'iterClassBody|DOL012|abstract\s*=' .Repository: FROWNINGdev/django-orm-lens Length of output: 6844 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- src/rules/models.ts outline ---'
ast-grep outline src/rules/models.ts
printf '%s\n' '--- src/rules/models.ts relevant source ---'
sed -n '1,190p' src/rules/models.ts
printf '%s\n' '--- model rule tests ---'
sed -n '1,125p' test/rules/models.test.js
printf '%s\n' '--- parser abstract handling ---'
sed -n '380,425p' src/parser.tsRepository: FROWNINGdev/django-orm-lens Length of output: 12408 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
import re
class_ctx = [
("Meta.abstract", [
"class Base(models.Model):",
" class Meta:",
" abstract = True",
]),
("class attribute", [
"class Base(models.Model):",
" abstract = True",
]),
("field-body assignment", [
"class Base(models.Model):",
" field = models.CharField(max_length=10)",
" abstract = True",
]),
]
head_re = re.compile(r"^(\s*)class\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*:")
abstract_re = re.compile(r"^\s*abstract\s*=\s*True\b")
def dol012_skips(lines):
match = head_re.match(lines[0])
assert match
class_indent = match.group(1)
has_abstract = False
for text in lines[1:]:
if text.strip() == "":
continue
indent = re.match(r"^(\s*)", text).group(1)
if len(indent) <= len(class_indent):
break
if re.match(r"^\s*def\s+__str__\s*\(", text):
return False
if abstract_re.match(text):
has_abstract = True
return has_abstract
for label, lines in class_ctx:
print(f"{label}: skips={dol012_skips(lines)}")
PYRepository: FROWNINGdev/django-orm-lens Length of output: 253 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
import re
class_ctx = [
("Meta.abstract", [
"class Base(models.Model):",
" class Meta:",
" abstract = True",
]),
("class attribute", [
"class Base(models.Model):",
" abstract = True",
]),
("field-body assignment", [
"class Base(models.Model):",
" field = models.CharField(max_length=10)",
" abstract = True",
]),
]
class_head = re.compile(r"^(\s*)class\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*:")
abstract_assignment = re.compile(r"^\s*abstract\s*=\s*True\b")
def dol012_skips(lines):
match = class_head.match(lines[0])
assert match
class_indent = match.group(1)
for text in lines[1:]:
if text.strip() == "":
continue
indent = re.match(r"^(\s*)", text).group(1)
if len(indent) <= len(class_indent):
break
if re.match(r"^\s*def\s+__str__\s*\(", text):
return False
if abstract_assignment.match(text):
return True
return False
for label, lines in class_ctx:
print(f"{label}: skips={dol012_skips(lines)}")
PYRepository: FROWNINGdev/django-orm-lens Length of output: 253 Scope the abstract-model check to 🤖 Prompt for AI Agents |
||
|
|
||
| ## Sai | ||
|
|
||
| ```python | ||
| class Article(models.Model): | ||
| published_at = models.DateTimeField() | ||
| # ở nơi khác: Article.objects.order_by("published_at") — từ ba lần trở lên | ||
| title = models.CharField(max_length=255) | ||
| ``` | ||
|
|
||
| ## Đúng | ||
|
|
||
| ```python | ||
| class Article(models.Model): | ||
| published_at = models.DateTimeField(db_index=True) | ||
| title = models.CharField(max_length=255) | ||
|
|
||
| def __str__(self) -> str: | ||
| return self.title | ||
| ``` | ||
|
|
||
| ## Bỏ qua (Suppress) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,21 @@ | ||
| # DOL014 — Dùng `prefetch_related` cho các truy cập ngược `ForeignKey` / `ManyToManyField` | ||
| # DOL014 — CharField thiếu max_length | ||
|
|
||
| **Mức độ mặc định:** warning · **Khả năng áp dụng:** unsafe · **Danh mục:** model-definition | ||
| **Mức độ mặc định:** error · **Khả năng áp dụng:** suggestion · **Danh mục:** model | ||
|
|
||
| Phát hiện các truy cập FK ngược hoặc M2M (ví dụ `post.comments.all()`, `user.groups.all()`) bên trong vòng lặp hoặc serializer mà không có `prefetch_related()` tương ứng. Mỗi lần truy cập sẽ kích hoạt một query riêng biệt cho mỗi đối tượng cha. | ||
|
|
||
| Khả năng áp dụng là `unsafe` vì cần thêm `prefetch_related()` tại call site của queryset. | ||
| Phát hiện một lời gọi `CharField(...)` không có từ khóa `max_length=`. Django yêu cầu `max_length` trên `CharField`; nếu không có nó, model sẽ không qua được các bước kiểm tra của Django lúc load — lại một lớp lỗi nữa mà thông thường bạn chỉ gặp vào lần chạy `runserver` hoặc `makemigrations` tiếp theo. QuickFix ("Thêm max_length=255 (chỉnh sửa khi cần)") sẽ chèn `max_length=255` dưới dạng một template — 255 là một quy ước phổ biến, không phải là một hằng số kỳ diệu; hãy xác định kích thước cột phù hợp với dữ liệu. Đây là một đề xuất vì giới hạn đúng là do bạn chọn, và nếu văn bản thực sự không có giới hạn, `TextField` sẽ là trường tốt hơn. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target document ---'
cat -n docs/i18n/rules/vi/DOL014.md
printf '%s\n' '--- related DOL014 documents and references ---'
git ls-files | rg 'DOL014|django|pyproject|setup\.cfg|setup\.py|requirements|tox|Pipfile|README' | head -200
printf '%s\n' '--- DOL014 references ---'
rg -n -i 'DOL014|max_length|supported.*(django|backend)|django.*(version|backend)|backend.*django' . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300Repository: FROWNINGdev/django-orm-lens Length of output: 39202 🌐 Web query:
💡 Result: In Django 6.0 and 5.2, the max_length argument for a CharField is mandatory for all supported database backends, with the specific exception of PostgreSQL and SQLite [1][2]. For PostgreSQL and SQLite, Django supports unlimited VARCHAR columns, allowing you to define a CharField without specifying a max_length (i.e., max_length=None) [1][2]. This functionality was previously added for PostgreSQL and more recently enabled for SQLite in Django 5.2 [2][3][4][5]. For other database backends (such as MySQL, MariaDB, and Oracle), you must provide a positive integer for max_length [1][6]. If you are developing an application intended to be portable across different database backends, it is recommended to specify a max_length to avoid compatibility issues, as different databases have varying restrictions and requirements for VARCHAR columns [1][6][5]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- declared Django support ---'
sed -n '1,80p' README.md
sed -n '575,610p' README.md
cat -n pyproject.toml | sed -n '1,80p'
cat -n cli/pyproject.toml | sed -n '1,100p'
printf '%s\n' '--- rule implementation and user-facing descriptions ---'
cat -n src/rules/models.ts | sed -n '210,255p'
cat -n src/decorations.ts | sed -n '55,72p'
cat -n docs/rules/DOL014.md | sed -n '1,35p'
printf '%s\n' '--- backend/version configuration references ---'
rg -n -i 'django.*(4\.0|5\.2|version)|database|backend|postgres|sqlite|mysql|mariadb|oracle' README.md docs src package.json pyproject.toml cli/pyproject.toml --glob '!**/node_modules/**' | head -250Repository: FROWNINGdev/django-orm-lens Length of output: 30103 Qualify the 🤖 Prompt for AI Agents |
||
|
|
||
| ## Sai | ||
|
|
||
| ```python | ||
| for post in Post.objects.all(): | ||
| comments = post.comments.all() # một query cho mỗi post | ||
| class Tag(models.Model): | ||
| name = models.CharField() | ||
| ``` | ||
|
|
||
| ## Đúng | ||
|
|
||
| ```python | ||
| for post in Post.objects.prefetch_related("comments"): | ||
| comments = post.comments.all() # chỉ hai query tổng cộng | ||
| class Tag(models.Model): | ||
| name = models.CharField(max_length=255) | ||
| ``` | ||
|
|
||
| ## Bỏ qua (Suppress) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,27 @@ | ||
| # DOL015 — Tránh lưu trữ dữ liệu văn bản hoặc nhị phân lớn trực tiếp trên model | ||
| # DOL015 — TextField có max_length không tác động đến DB | ||
|
|
||
| **Mức độ mặc định:** info · **Khả năng áp dụng:** unsafe · **Danh mục:** model-definition | ||
| **Mức độ mặc định:** hint · **Khả năng áp dụng:** suggestion · **Danh mục:** model | ||
|
|
||
| Phát hiện các khai báo `TextField` hoặc `BinaryField` không có giới hạn `max_length`, đặc biệt khi tên trường gợi ý lưu nội dung (ví dụ `body`, `content`, `data`, `blob`, `payload`). Lưu trữ payload lớn trực tiếp làm phình kích thước row, tăng I/O cho mọi query trên bảng đó, và có thể gây TOAST thrashing trong PostgreSQL. Giải pháp thông thường là chuyển payload sang object storage và chỉ lưu URL hoặc key trên model. | ||
|
|
||
| Khả năng áp dụng là `unsafe` vì đây là thay đổi kiến trúc. | ||
| Phát hiện `max_length=` trên một `TextField(...)`. `TextField` tương ứng với `TEXT`/`CLOB`; Django chỉ thực thi `max_length` của nó trong widget form được tạo tự động, không bao giờ ở cấp độ cơ sở dữ liệu. Do đó, kwarg này đọc có vẻ như là một giới hạn cứng nhưng thực chất không phải vậy — các lệnh ghi ORM thô, các phép toán hàng loạt (bulk operations) và các lệnh lưu trực tiếp từ admin đều có thể vượt quá giới hạn này. Nếu bạn cần một giới hạn được thực thi ở cơ sở dữ liệu, hãy sử dụng `CharField(max_length=...)`; nếu văn bản thực sự không có giới hạn, hãy bỏ kwarg này. QuickFix ("Xóa max_length khỏi TextField") sẽ gỡ bỏ kwarg; đây là một đề xuất vì bạn có thể sẽ muốn chuyển đổi sang `CharField` thay thế. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n docs/i18n/rules/vi/DOL015.md
printf '%s\n' '--- related DOL015 files and references ---'
git ls-files | rg 'DOL015|docs/.*/rules/.*/DOL015'
rg -n -C 3 'TextField|max_length|ModelForm|admin' docs/i18n/rules/vi/DOL015.md docs/i18n/rules 2>/dev/null | head -200Repository: FROWNINGdev/django-orm-lens Length of output: 12936 🌐 Web query:
💡 Result: In Django 6.0, specifying a max_length attribute on a TextField model field serves two primary purposes, both related to form and validation behavior rather than database constraints [1][2]. First, if you define max_length on a TextField, it is automatically reflected as a maxlength HTML attribute in the Textarea widget of any auto-generated ModelForm [1][2]. This enables browser-side length limiting for the input [3]. Second, Django's ModelForm generation process uses this max_length value to add a MaxLengthValidator to the corresponding form field [4][5][6]. Consequently, when a ModelForm is validated, it will check the input length against this specified limit [4][3]. It is important to note that the max_length attribute on a TextField is not enforced at the database level [1][2]. Unlike CharField, which enforces max_length both in validation and at the database schema level (e.g., using VARCHAR columns), the TextField max_length is purely a form and application-layer validation feature [1][2]. If you require database-level enforcement, you should use a CharField instead [1][2]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from urllib.request import urlopen
from re import search
urls = {
"TextField": "https://raw.githubusercontent.com/django/django/stable/6.0.x/django/db/models/fields/__init__.py",
"ModelForm": "https://raw.githubusercontent.com/django/django/stable/6.0.x/django/forms/models.py",
"ModelAdmin": "https://raw.githubusercontent.com/django/django/stable/6.0.x/django/contrib/admin/options.py",
}
needles = {
"TextField": ["class TextField", "def formfield"],
"ModelForm": ["class BaseModelForm", "def _get_validation_exclusions", "def _post_clean"],
"ModelAdmin": ["def changeform_view", "form.is_valid()", "save_form", "save_model"],
}
for name, url in urls.items():
print(f"\n--- {name}: {url} ---")
text = urlopen(url, timeout=20).read().decode()
lines = text.splitlines()
wanted = needles[name]
starts = []
for i, line in enumerate(lines):
if any(needle in line for needle in wanted):
starts.append(i)
for start in starts:
lo, hi = max(0, start - 3), min(len(lines), start + 35)
print(f"[lines {lo+1}-{hi}]")
for i in range(lo, hi):
print(f"{i+1}:{lines[i]}")
PYRepository: FROWNINGdev/django-orm-lens Length of output: 2569 🏁 Script executed: #!/bin/bash
set -eu
fetch() {
curl -fsSLk --max-time 20 "$1"
}
for spec in \
"TextField|https://raw.githubusercontent.com/django/django/stable/6.0.x/django/db/models/fields/__init__.py|class TextField|def formfield" \
"ModelForm|https://raw.githubusercontent.com/django/django/stable/6.0.x/django/forms/models.py|class BaseModelForm|def _post_clean" \
"ModelAdmin|https://raw.githubusercontent.com/django/django/stable/6.0.x/django/contrib/admin/options.py|def changeform_view|form.is_valid()|save_form|save_model"
do
IFS='|' read -r name url needles
printf '\n--- %s ---\n' "$name"
content="$(fetch "$url")"
printf '%s\n' "$content" | rg -n -C 8 "$needles" | head -160
doneRepository: FROWNINGdev/django-orm-lens Length of output: 165 Describe form-layer enforcement accurately. Line 5 incorrectly limits 🤖 Prompt for AI Agents |
||
|
|
||
| ## Sai | ||
|
|
||
| ```python | ||
| class Document(models.Model): | ||
| content = models.TextField() # không giới hạn — có thể chiếm hàng megabyte mỗi row | ||
| class Comment(models.Model): | ||
| body = models.TextField(max_length=500) | ||
| ``` | ||
|
|
||
| ## Đúng | ||
|
|
||
| ```python | ||
| class Document(models.Model): | ||
| storage_key = models.CharField(max_length=255) # trỏ đến S3 / GCS / v.v. | ||
| class Comment(models.Model): | ||
| body = models.TextField() | ||
| ``` | ||
|
|
||
| Hoặc, khi giới hạn phải được giữ ở cấp độ DB: | ||
|
|
||
| ```python | ||
| body = models.CharField(max_length=500) | ||
| ``` | ||
|
|
||
| ## Bỏ qua (Suppress) | ||
|
|
@@ -26,4 +30,4 @@ class Document(models.Model): | |
| # django-orm-lens-disable-next-line DOL015 | ||
| ``` | ||
|
|
||
| Hoặc theo từng workspace trong `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL015": "off"}}`. | ||
| Bỏ qua (suppress) nếu bạn cố ý sử dụng `max_length` hoàn toàn như một giới hạn ở tầng form. Hoặc theo từng workspace trong `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL015": "off"}}`. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: FROWNINGdev/django-orm-lens
Length of output: 29062
🌐 Web query:
Django 6.0 model field reference unique=True blank=True string fields multiple empty values exception💡 Result:
In Django 6.0, using a string-based field (such as CharField or TextField) with both unique=True and blank=True can lead to database integrity errors when attempting to save multiple objects with empty values [1]. This occurs because Django's default convention for string-based fields is to store "no data" as an empty string (""), rather than NULL [1]. When a field is marked unique=True, the database treats every empty string as a value that must be unique; consequently, attempting to save more than one record with an empty string results in a unique constraint violation [2][3][1]. To resolve this and allow multiple "empty" entries while maintaining uniqueness for populated values, you must set both null=True and blank=True on the field [1]. By enabling null=True, Django will store "no data" as NULL instead of an empty string [1]. Since most relational databases (such as PostgreSQL, MySQL, and SQLite) typically do not consider NULL values to be equal to other NULL values for the purpose of unique constraints, this allows multiple records to have a "blank" (NULL) value without triggering a violation [2][3]. In summary: 1. Ensure your model field uses: models.CharField(..., unique=True, blank=True, null=True) [1]. 2. When null=True is set, Django model forms and the Django admin will automatically handle empty input by storing it as NULL rather than an empty string, preventing the exception [3].
Citations:
🏁 Script executed:
Repository: FROWNINGdev/django-orm-lens
Length of output: 32510
🏁 Script executed:
Repository: FROWNINGdev/django-orm-lens
Length of output: 2452
Narrow the
unique=Trueexception.Line 27 should require both
unique=Trueandblank=True. Django documents this exception for fields with both options; otherwise, readers may suppress DOL011 for required unique fields.🤖 Prompt for AI Agents