|
1 | 1 | #!/usr/bin/env python3 |
2 | | -"""Simple linter to spot denylisted model usage within Django view modules.""" |
| 2 | +"""Check for model usage without provider scoping within Django view modules.""" |
3 | 3 |
|
4 | 4 | import argparse |
| 5 | +import re |
5 | 6 | import sys |
| 7 | +from dataclasses import dataclass |
6 | 8 | from pathlib import Path |
7 | 9 |
|
8 | | -# Expected to be run from repository root |
9 | | -REPO_ROOT = Path.cwd() |
10 | | - |
11 | | -DENYLISTED_MODELS = ( |
12 | | - "Appointment", |
13 | | - "Clinic", |
14 | | - "Participant", |
15 | | - "Symptom", |
16 | | - "BreastCancerHistoryItem", |
| 10 | +REPO_ROOT = Path(__file__).parent.parent |
| 11 | + |
| 12 | +ALLOWLISTED_MODELS = ( |
| 13 | + "User", |
| 14 | + "Provider", |
| 15 | + "BenignLumpHistoryItem", # Temporarily allowed |
| 16 | + "BreastAugmentationHistoryItem", # Temporarily allowed |
| 17 | + "CystHistoryItem", # Temporarily allowed |
| 18 | + "ImplantedMedicalDeviceHistoryItem", # Temporarily allowed |
| 19 | + "MastectomyOrLumpectomyHistoryItem", # Temporarily allowed |
| 20 | + "OtherProcedureHistoryItem", # Temporarily allowed |
| 21 | + "ParticipantReportedMammogram", # Temporarily allowed |
17 | 22 | ) |
18 | | -DENYLISTED_HELPERS = ("get_object_or_404(",) |
19 | 23 |
|
| 24 | +TARGETS = { |
| 25 | + "model.objects": re.compile(r"(?P<model_name>\w+)\.objects"), |
| 26 | + "model.get_object_or_404": re.compile(r"(?P<model_name>\w+)\.get_object_or_404"), |
| 27 | +} |
20 | 28 |
|
21 | | -class Match: |
22 | | - __slots__ = ("path", "line_number", "target", "line") |
23 | 29 |
|
24 | | - def __init__(self, path, line_number, target, line): |
25 | | - self.path = path |
26 | | - self.line_number = line_number |
27 | | - self.target = target |
28 | | - self.line = line |
| 30 | +@dataclass |
| 31 | +class Match: |
| 32 | + path: Path |
| 33 | + line_number: int |
| 34 | + target: str |
| 35 | + line: str |
29 | 36 |
|
30 | 37 |
|
31 | 38 | def parse_args(): |
@@ -63,28 +70,23 @@ def find_view_modules(base_dir): |
63 | 70 |
|
64 | 71 |
|
65 | 72 | def find_matches(paths): |
66 | | - targets = { |
67 | | - f"{model_name}.objects": f"{model_name}.objects" |
68 | | - for model_name in DENYLISTED_MODELS |
69 | | - } |
70 | | - for helper in DENYLISTED_HELPERS: |
71 | | - targets[helper] = helper |
72 | | - |
73 | 73 | for path in paths: |
74 | 74 | try: |
75 | 75 | text = path.read_text(encoding="utf-8") |
76 | 76 | except UnicodeDecodeError: |
77 | 77 | continue |
78 | 78 |
|
79 | 79 | for line_number, line in enumerate(text.splitlines(), start=1): |
80 | | - for needle, label in targets.items(): |
81 | | - if needle in line: |
82 | | - yield Match( |
83 | | - path=path, |
84 | | - line_number=line_number, |
85 | | - target=label, |
86 | | - line=line.strip(), |
87 | | - ) |
| 80 | + for label, regex in TARGETS.items(): |
| 81 | + if match := regex.search(line): |
| 82 | + model_name = match.group("model_name") |
| 83 | + if model_name not in ALLOWLISTED_MODELS: |
| 84 | + yield Match( |
| 85 | + path=path, |
| 86 | + line_number=line_number, |
| 87 | + target=label, |
| 88 | + line=line.strip(), |
| 89 | + ) |
88 | 90 |
|
89 | 91 |
|
90 | 92 | def main(): |
|
0 commit comments