Skip to content

Commit 77d1883

Browse files
authored
Merge pull request #531 from PROCOLLAB-github/feature/project-mospolytech-data
Добавлена связка проектов и программ через промежуточную модель
2 parents cc304fe + 42830e6 commit 77d1883

9 files changed

Lines changed: 673 additions & 30 deletions

partner_programs/admin.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
from mailing.views import MailingTemplateRender
1313
from partner_programs.models import (
1414
PartnerProgram,
15+
PartnerProgramField,
16+
PartnerProgramFieldValue,
1517
PartnerProgramMaterial,
18+
PartnerProgramProject,
1619
PartnerProgramUserProfile,
1720
)
1821
from partner_programs.services import ProjectScoreDataPreparer
@@ -27,9 +30,14 @@ class PartnerProgramMaterialInline(admin.StackedInline):
2730
readonly_fields = ("datetime_created", "datetime_updated")
2831

2932

33+
class PartnerProgramFieldInline(admin.TabularInline):
34+
model = PartnerProgramField
35+
extra = 0
36+
37+
3038
@admin.register(PartnerProgram)
3139
class PartnerProgramAdmin(admin.ModelAdmin):
32-
inlines = [PartnerProgramMaterialInline]
40+
inlines = [PartnerProgramMaterialInline, PartnerProgramFieldInline]
3341
list_display = ("id", "name", "tag", "city", "datetime_created")
3442
list_display_links = (
3543
"id",
@@ -286,3 +294,38 @@ def has_file(self, obj):
286294

287295
has_file.boolean = True
288296
has_file.short_description = "Файл"
297+
298+
299+
class PartnerProgramFieldValueInline(admin.TabularInline):
300+
model = PartnerProgramFieldValue
301+
extra = 0
302+
autocomplete_fields = ("field",)
303+
readonly_fields = ("get_display_value",)
304+
305+
def get_display_value(self, obj):
306+
return obj.value_text or "-"
307+
308+
get_display_value.short_description = "Значение"
309+
310+
311+
@admin.register(PartnerProgramProject)
312+
class PartnerProgramProjectAdmin(admin.ModelAdmin):
313+
list_display = ("project", "partner_program", "datetime_created")
314+
list_filter = ("partner_program",)
315+
search_fields = ("project__name", "partner_program__name")
316+
inlines = [PartnerProgramFieldValueInline]
317+
autocomplete_fields = ("project", "partner_program")
318+
319+
320+
@admin.register(PartnerProgramField)
321+
class PartnerProgramFieldAdmin(admin.ModelAdmin):
322+
list_display = (
323+
"partner_program",
324+
"name",
325+
"label",
326+
"field_type",
327+
"is_required",
328+
"show_filter",
329+
)
330+
list_filter = ("partner_program",)
331+
search_fields = ("name", "label", "help_text")
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Generated by Django 4.2.11 on 2025-07-28 07:45
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("files", "0007_auto_20230929_1727"),
11+
("projects", "0027_alter_defaultprojectcover_datetime_created_and_more"),
12+
("partner_programs", "0008_partnerprogram_managers_and_more"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="PartnerProgramField",
18+
fields=[
19+
(
20+
"id",
21+
models.BigAutoField(
22+
auto_created=True,
23+
primary_key=True,
24+
serialize=False,
25+
verbose_name="ID",
26+
),
27+
),
28+
("name", models.CharField(max_length=128, verbose_name="Служебное имя")),
29+
(
30+
"label",
31+
models.CharField(
32+
max_length=256, verbose_name="Отображаемое название"
33+
),
34+
),
35+
(
36+
"field_type",
37+
models.CharField(
38+
choices=[
39+
("text", "Однострочный текст"),
40+
("textarea", "Многострочный текст"),
41+
("checkbox", "Чекбокс"),
42+
("select", "Выпадающий список"),
43+
("radio", "Радио-кнопка"),
44+
("file", "Файл"),
45+
],
46+
max_length=20,
47+
),
48+
),
49+
("is_required", models.BooleanField(default=False)),
50+
("help_text", models.TextField(blank=True, null=True)),
51+
("show_filter", models.BooleanField(default=False)),
52+
(
53+
"options",
54+
models.TextField(
55+
blank=True,
56+
help_text="Опции через | (для select/radio/checkbox)",
57+
null=True,
58+
),
59+
),
60+
(
61+
"partner_program",
62+
models.ForeignKey(
63+
on_delete=django.db.models.deletion.CASCADE,
64+
related_name="fields",
65+
to="partner_programs.partnerprogram",
66+
),
67+
),
68+
],
69+
options={
70+
"verbose_name": "Дополнительное поле программы",
71+
"verbose_name_plural": "Дополнительные поля программы",
72+
"unique_together": {("partner_program", "name")},
73+
},
74+
),
75+
migrations.CreateModel(
76+
name="PartnerProgramProject",
77+
fields=[
78+
(
79+
"id",
80+
models.BigAutoField(
81+
auto_created=True,
82+
primary_key=True,
83+
serialize=False,
84+
verbose_name="ID",
85+
),
86+
),
87+
("datetime_created", models.DateTimeField(auto_now_add=True)),
88+
("datetime_updated", models.DateTimeField(auto_now=True)),
89+
(
90+
"partner_program",
91+
models.ForeignKey(
92+
on_delete=django.db.models.deletion.CASCADE,
93+
related_name="program_projects",
94+
to="partner_programs.partnerprogram",
95+
),
96+
),
97+
(
98+
"project",
99+
models.ForeignKey(
100+
on_delete=django.db.models.deletion.CASCADE,
101+
related_name="program_links",
102+
to="projects.project",
103+
),
104+
),
105+
],
106+
options={
107+
"verbose_name": "Участие проекта в программе",
108+
"verbose_name_plural": "Участия проекта в программах",
109+
"unique_together": {("partner_program", "project")},
110+
},
111+
),
112+
migrations.CreateModel(
113+
name="PartnerProgramFieldValue",
114+
fields=[
115+
(
116+
"id",
117+
models.BigAutoField(
118+
auto_created=True,
119+
primary_key=True,
120+
serialize=False,
121+
verbose_name="ID",
122+
),
123+
),
124+
("value_text", models.TextField(blank=True, null=True)),
125+
(
126+
"field",
127+
models.ForeignKey(
128+
on_delete=django.db.models.deletion.CASCADE,
129+
related_name="values",
130+
to="partner_programs.partnerprogramfield",
131+
),
132+
),
133+
(
134+
"program_project",
135+
models.ForeignKey(
136+
on_delete=django.db.models.deletion.CASCADE,
137+
related_name="field_values",
138+
to="partner_programs.partnerprogramproject",
139+
),
140+
),
141+
(
142+
"value_file",
143+
models.ForeignKey(
144+
blank=True,
145+
null=True,
146+
on_delete=django.db.models.deletion.SET_NULL,
147+
related_name="custom_field_usages",
148+
to="files.userfile",
149+
),
150+
),
151+
],
152+
options={
153+
"verbose_name": "Значение поля программы для проекта",
154+
"verbose_name_plural": "Значения полей программы для проекта",
155+
"unique_together": {("program_project", "field")},
156+
},
157+
),
158+
]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Generated by Django 4.2.11 on 2025-07-30 08:30
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("partner_programs", "0009_partnerprogramfield_partnerprogramproject_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="partnerprogramfield",
15+
options={
16+
"verbose_name": "Дополнительное поле программы",
17+
"verbose_name_plural": "Дополнительные поля программ",
18+
},
19+
),
20+
migrations.AlterModelOptions(
21+
name="partnerprogramproject",
22+
options={
23+
"verbose_name": "Проект участующий в программе",
24+
"verbose_name_plural": "Проекеты участвующие в программах",
25+
},
26+
),
27+
migrations.RemoveField(
28+
model_name="partnerprogramfieldvalue",
29+
name="value_file",
30+
),
31+
migrations.AlterField(
32+
model_name="partnerprogramfieldvalue",
33+
name="value_text",
34+
field=models.TextField(default="Нет значения"),
35+
preserve_default=False,
36+
),
37+
migrations.AddIndex(
38+
model_name="partnerprogramfieldvalue",
39+
index=models.Index(
40+
fields=["program_project"], name="partner_pro_program_8cb59d_idx"
41+
),
42+
),
43+
migrations.AddIndex(
44+
model_name="partnerprogramfieldvalue",
45+
index=models.Index(fields=["field"], name="partner_pro_field_i_6a74d2_idx"),
46+
),
47+
]

partner_programs/models.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,84 @@ def save(self, *args, **kwargs):
227227

228228
def __str__(self):
229229
return f"{self.title} для программы {self.program.name}"
230+
231+
232+
class PartnerProgramProject(models.Model):
233+
partner_program = models.ForeignKey(
234+
PartnerProgram, on_delete=models.CASCADE, related_name="program_projects"
235+
)
236+
project = models.ForeignKey(
237+
Project, on_delete=models.CASCADE, related_name="program_links"
238+
)
239+
datetime_created = models.DateTimeField(auto_now_add=True)
240+
datetime_updated = models.DateTimeField(auto_now=True)
241+
242+
class Meta:
243+
unique_together = ("partner_program", "project")
244+
verbose_name = "Проект участующий в программе"
245+
verbose_name_plural = "Проекеты участвующие в программах"
246+
247+
def __str__(self):
248+
return f"{self.project} в программе {self.partner_program}"
249+
250+
251+
class PartnerProgramField(models.Model):
252+
FIELD_TYPES = [
253+
("text", "Однострочный текст"),
254+
("textarea", "Многострочный текст"),
255+
("checkbox", "Чекбокс"),
256+
("select", "Выпадающий список"),
257+
("radio", "Радио-кнопка"),
258+
("file", "Файл"),
259+
]
260+
261+
partner_program = models.ForeignKey(
262+
PartnerProgram, on_delete=models.CASCADE, related_name="fields"
263+
)
264+
name = models.CharField(max_length=128, verbose_name="Служебное имя")
265+
label = models.CharField(max_length=256, verbose_name="Отображаемое название")
266+
field_type = models.CharField(max_length=20, choices=FIELD_TYPES)
267+
is_required = models.BooleanField(default=False)
268+
help_text = models.TextField(blank=True, null=True)
269+
show_filter = models.BooleanField(default=False)
270+
options = models.TextField(
271+
blank=True, null=True, help_text="Опции через | (для select/radio/checkbox)"
272+
)
273+
274+
def __str__(self):
275+
return f"{self.partner_program.name}{self.label} ({self.name})"
276+
277+
class Meta:
278+
verbose_name = "Дополнительное поле программы"
279+
verbose_name_plural = "Дополнительные поля программ"
280+
unique_together = ("partner_program", "name")
281+
282+
def get_options_list(self) -> list[str]:
283+
opts = self.options.split("|") if self.options else []
284+
return [opt.strip() for opt in opts if opt.strip()]
285+
286+
287+
class PartnerProgramFieldValue(models.Model):
288+
program_project = models.ForeignKey(
289+
PartnerProgramProject, on_delete=models.CASCADE, related_name="field_values"
290+
)
291+
field = models.ForeignKey(
292+
PartnerProgramField, on_delete=models.CASCADE, related_name="values"
293+
)
294+
value_text = models.TextField(blank=False)
295+
296+
class Meta:
297+
unique_together = ("program_project", "field")
298+
verbose_name = "Значение поля программы для проекта"
299+
verbose_name_plural = "Значения полей программы для проекта"
300+
indexes = [
301+
models.Index(fields=["program_project"]),
302+
models.Index(fields=["field"]),
303+
]
304+
305+
def save(self, *args, **kwargs):
306+
self.clean()
307+
super().save(*args, **kwargs)
308+
309+
def get_value(self):
310+
return self.value_text

0 commit comments

Comments
 (0)