Skip to content

Commit 17f876f

Browse files
author
HenryVisuri
committed
Merge branch 'development' of github.com:City-of-Helsinki/kaavapino into development
2 parents 4df27e6 + 29aac05 commit 17f876f

1 file changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import logging
2+
3+
from django.core.management.base import BaseCommand, CommandError
4+
from django.db import transaction
5+
6+
from projects.models import Project
7+
8+
logger = logging.getLogger(__name__)
9+
10+
XL_ATTRIBUTES = {
11+
'jarjestetaan_periaatteet_esillaolo_1': True,
12+
'jarjestetaan_periaatteet_esillaolo_2': False,
13+
'jarjestetaan_periaatteet_esillaolo_3': False,
14+
'periaatteet_lautakuntaan_1': True,
15+
'periaatteet_lautakuntaan_2': False,
16+
'periaatteet_lautakuntaan_3': False,
17+
'periaatteet_lautakuntaan_4': False,
18+
'jarjestetaan_luonnos_esillaolo_1': True,
19+
'jarjestetaan_luonnos_esillaolo_2': False,
20+
'jarjestetaan_luonnos_esillaolo_3': False,
21+
'kaavaluonnos_lautakuntaan_1': True,
22+
'kaavaluonnos_lautakuntaan_2': False,
23+
'kaavaluonnos_lautakuntaan_3': False,
24+
'kaavaluonnos_lautakuntaan_4': False,
25+
'kaavaehdotus_lautakuntaan_1': True,
26+
'kaavaehdotus_lautakuntaan_2': False,
27+
'kaavaehdotus_lautakuntaan_3': False,
28+
'kaavaehdotus_lautakuntaan_4': False,
29+
}
30+
31+
L_ATTRIBUTES = {
32+
'kaavaehdotus_lautakuntaan_1': True,
33+
'kaavaehdotus_lautakuntaan_2': False,
34+
'kaavaehdotus_lautakuntaan_3': False,
35+
'kaavaehdotus_lautakuntaan_4': False,
36+
}
37+
38+
COMMON_ATTRIBUTES = {
39+
'jarjestetaan_oas_esillaolo_1': True,
40+
'jarjestetaan_oas_esillaolo_2': False,
41+
'jarjestetaan_oas_esillaolo_3': False,
42+
'kaavaehdotus_nahtaville_1': True,
43+
'kaavaehdotus_uudelleen_nahtaville_2': False,
44+
'kaavaehdotus_uudelleen_nahtaville_3': False,
45+
'kaavaehdotus_uudelleen_nahtaville_4': False,
46+
'tarkistettu_ehdotus_lautakuntaan_1': True,
47+
'tarkistettu_ehdotus_lautakuntaan_2': False,
48+
'tarkistettu_ehdotus_lautakuntaan_3': False,
49+
'tarkistettu_ehdotus_lautakuntaan_4': False
50+
}
51+
52+
class Command(BaseCommand):
53+
help = "Adds default values for missing visibility boolean attributes in existing projects"
54+
55+
def add_arguments(self, parser):
56+
parser.add_argument("--id", nargs="?", type=int)
57+
parser.add_argument(
58+
"--dry-run",
59+
action="store_true",
60+
help="Show planned changes without saving them",
61+
)
62+
parser.add_argument(
63+
"--execute",
64+
action="store_true",
65+
help="Apply the changes after confirmation",
66+
)
67+
68+
def handle(self, *args, **options):
69+
project_id = options.get("id")
70+
dry_run = options.get("dry_run", False)
71+
execute = options.get("execute", False)
72+
73+
if dry_run == execute:
74+
raise CommandError("Specify exactly one of --dry-run or --execute")
75+
76+
projects = self._get_projects(project_id)
77+
project_changes, change_log = self._collect_changes(projects)
78+
79+
self._print_summary(project_changes, dry_run=dry_run)
80+
81+
if not project_changes:
82+
logger.info("No missing visibility boolean attributes found")
83+
return
84+
85+
if dry_run:
86+
self._maybe_write_log(change_log)
87+
return
88+
89+
if (input("Apply changes to database? (y/n): ") or "").lower() != "y":
90+
raise CommandError("Aborting without saving changes to database")
91+
92+
with transaction.atomic():
93+
for project, changes in project_changes:
94+
for attribute, default_value in changes:
95+
project.attribute_data[attribute] = default_value
96+
project.save()
97+
98+
logger.info(
99+
"Finished updating missing visibility boolean attributes for %s projects",
100+
len(project_changes),
101+
)
102+
self._maybe_write_log(change_log)
103+
104+
def _get_projects(self, project_id=None):
105+
if project_id is None:
106+
return Project.objects.filter(archived=False).select_related("subtype")
107+
108+
try:
109+
project = Project.objects.select_related("subtype").get(pk=project_id)
110+
except Project.DoesNotExist as error:
111+
raise CommandError(f"Project with id={project_id} does not exist") from error
112+
113+
return [project]
114+
115+
def _collect_changes(self, projects):
116+
project_changes = []
117+
change_log = []
118+
119+
for project in projects:
120+
changes = self._collect_project_changes(project)
121+
if not changes:
122+
continue
123+
124+
project_changes.append((project, changes))
125+
for attribute, default_value in changes:
126+
logger.info(
127+
"Set %s to %s for project %s",
128+
attribute,
129+
default_value,
130+
project.name,
131+
)
132+
change_log.append(
133+
f"Set {attribute} to {default_value} for project {project.name}"
134+
)
135+
136+
logger.info("Would update attribute_data for project %s %s", project.id, project.name)
137+
change_log.append(
138+
f"Updated attribute_data for project {project.id} {project.name}\n---"
139+
)
140+
141+
return project_changes, change_log
142+
143+
def _collect_project_changes(self, project):
144+
changes = []
145+
current_values = project.attribute_data
146+
147+
for attribute, default_value in COMMON_ATTRIBUTES.items():
148+
if current_values.get(attribute) is None:
149+
changes.append((attribute, default_value))
150+
151+
subtype_attributes = {}
152+
if project.subtype.name == "XL":
153+
subtype_attributes = XL_ATTRIBUTES
154+
elif project.subtype.name == "L":
155+
subtype_attributes = L_ATTRIBUTES
156+
157+
for attribute, default_value in subtype_attributes.items():
158+
if (not project.create_draft and "luonnos" in attribute) or (
159+
not project.create_principles and "periaatteet" in attribute
160+
):
161+
continue
162+
if current_values.get(attribute) is None:
163+
changes.append((attribute, default_value))
164+
165+
return changes
166+
167+
def _print_summary(self, project_changes, dry_run=False):
168+
mode = "DRY RUN" if dry_run else "EXECUTE"
169+
self.stdout.write(self.style.NOTICE(
170+
f"Running add_missing_vis_bools in {mode} mode"
171+
))
172+
173+
if not project_changes:
174+
self.stdout.write(self.style.SUCCESS("No projects need updates"))
175+
return
176+
177+
self.stdout.write(
178+
self.style.WARNING(f"Projects to update: {len(project_changes)}")
179+
)
180+
for project, changes in project_changes:
181+
self.stdout.write(f"- {project.id} {project.name}: {len(changes)} changes")
182+
for attribute, default_value in changes:
183+
self.stdout.write(f" {attribute} -> {default_value}")
184+
185+
def _maybe_write_log(self, change_log):
186+
if (input("Write changes to file? (y/n): ") or "").lower() != "y":
187+
return
188+
189+
try:
190+
with open("missing_vis_bools_changes.txt", "w") as file_obj:
191+
file_obj.write("\n".join(change_log))
192+
logger.info("Changes written to missing_vis_bools_changes.txt")
193+
except Exception as error:
194+
logger.error("Failed to write changes to file: %s", error)

0 commit comments

Comments
 (0)