Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion projects/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from projects.models import (
Achievement,
Collaborator,
Company,
DefaultProjectAvatar,
DefaultProjectCover,
Project,
ProjectCompany,
ProjectGoal,
ProjectLink,
ProjectNews,
Resource,
)


Expand All @@ -20,6 +23,31 @@ class ProjectGoalInline(admin.TabularInline):
autocomplete_fields = ("responsible",)


class ProjectCompanyInline(admin.TabularInline):
model = ProjectCompany
extra = 1
autocomplete_fields = ("company", "decision_maker")
fields = ("company", "contribution", "decision_maker")
verbose_name = "Партнёр проекта"
verbose_name_plural = "Партнёры проекта"


class ResourceInline(admin.StackedInline):
model = Resource
extra = 0
fields = ("type", "description", "partner_company")
show_change_link = True
verbose_name = "Ресурс"
verbose_name_plural = "Ресурсы"

def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
if obj is not None:
qs = obj.companies.all()
formset.form.base_fields["partner_company"].queryset = qs
return formset


@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
list_display = (
Expand Down Expand Up @@ -85,7 +113,28 @@ class ProjectAdmin(admin.ModelAdmin):
),
)
readonly_fields = ("datetime_created", "datetime_updated")
inlines = [ProjectGoalInline]
inlines = [ProjectGoalInline, ProjectCompanyInline, ResourceInline]


@admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
list_display = ("id", "name", "inn")
list_display_links = ("id", "name")
search_fields = ("name", "inn")
list_filter = ()
ordering = ("name",)
readonly_fields = ()
fieldsets = (
(
"Компания",
{
"fields": (
"name",
"inn",
)
},
),
)


@admin.register(ProjectGoal)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Generated by Django 4.2.24 on 2025-10-06 09:13

from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("projects", "0029_projectgoal"),
]

operations = [
migrations.CreateModel(
name="Company",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
(
"inn",
models.CharField(
max_length=12,
unique=True,
validators=[
django.core.validators.RegexValidator(
message="ИНН должен содержать 10 или 12 цифр.",
regex="^\\d{10}(\\d{2})?$",
)
],
),
),
],
options={
"verbose_name": "Компания",
"verbose_name_plural": "Компании",
"ordering": ["name"],
},
),
migrations.CreateModel(
name="Resource",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"type",
models.CharField(
choices=[
("infrastructure", "Инфраструктурный"),
("staff", "Кадровый"),
("financial", "Финансовый"),
("information", "Информационный"),
],
max_length=32,
),
),
("description", models.TextField()),
(
"partner_company",
models.ForeignKey(
blank=True,
help_text="Если не указано — ресурс в поиске партнёра.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="resources",
to="projects.company",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="resources",
to="projects.project",
),
),
],
options={
"verbose_name": "Ресурс",
"verbose_name_plural": "Ресурсы",
"ordering": ["project", "type", "id"],
},
),
migrations.CreateModel(
name="ProjectCompany",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("contribution", models.TextField(blank=True)),
(
"company",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="project_links",
to="projects.company",
),
),
(
"decision_maker",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="partner_decisions",
to=settings.AUTH_USER_MODEL,
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="project_companies",
to="projects.project",
),
),
],
options={
"verbose_name": "Связь проекта и компании",
"verbose_name_plural": "Связи проекта и компании",
},
),
migrations.AddField(
model_name="project",
name="companies",
field=models.ManyToManyField(
related_name="projects",
through="projects.ProjectCompany",
to="projects.company",
),
),
migrations.AddConstraint(
model_name="projectcompany",
constraint=models.UniqueConstraint(
fields=("project", "company"), name="uq_project_company_unique_pair"
),
),
]
118 changes: 118 additions & 0 deletions projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from files.models import UserFile
from industries.models import Industry
from projects.managers import AchievementManager, CollaboratorManager, ProjectManager
from projects.validators import inn_validator
from users.models import CustomUser

User = get_user_model()
Expand Down Expand Up @@ -170,6 +171,12 @@ class Project(models.Model):
User, verbose_name="Подписчики", related_name="subscribed_projects"
)

companies = models.ManyToManyField(
"Company",
through="ProjectCompany",
related_name="projects",
)

datetime_created = models.DateTimeField(
verbose_name="Дата создания", null=False, auto_now_add=True
)
Expand Down Expand Up @@ -426,3 +433,114 @@ def __str__(self) -> str:
class Meta:
verbose_name = "Цель"
verbose_name_plural = "Цели"


class Company(models.Model):
name = models.CharField(max_length=255)
inn = models.CharField(max_length=12, unique=True, validators=[inn_validator])

class Meta:
verbose_name = "Компания"
verbose_name_plural = "Компании"
ordering = ["name"]

def __str__(self):
return f"{self.name} ({self.inn})"


class ProjectCompany(models.Model):
project = models.ForeignKey(
"projects.Project",
on_delete=models.CASCADE,
related_name="project_companies",
)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE,
related_name="project_links",
)
contribution = models.TextField(blank=True)
decision_maker = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="partner_decisions",
)

class Meta:
verbose_name = "Связь проекта и компании"
verbose_name_plural = "Связи проекта и компании"
constraints = [
models.UniqueConstraint(
fields=["project", "company"],
name="uq_project_company_unique_pair",
),
]

def __str__(self):
return f"{self.project} - {self.company}"


class Resource(models.Model):
class ResourceType(models.TextChoices):
INFRASTRUCTURE = "infrastructure", "Инфраструктурный"
STAFF = "staff", "Кадровый"
FINANCIAL = "financial", "Финансовый"
INFORMATION = "information", "Информационный"

project = models.ForeignKey(
"projects.Project",
on_delete=models.CASCADE,
related_name="resources",
)
type = models.CharField(
max_length=32,
choices=ResourceType.choices,
)
description = models.TextField()

partner_company = models.ForeignKey(
Company,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="resources",
help_text="Если не указано — ресурс в поиске партнёра.",
)

class Meta:
verbose_name = "Ресурс"
verbose_name_plural = "Ресурсы"
ordering = ["project", "type", "id"]

def __str__(self):
base = f"{self.get_type_display()} ресурс для {self.project}"
return f"{base} — {self.partner_display}"

@property
def partner_display(self):
return (
self.partner_company.name
if self.partner_company
else "в поиске партнёра для данного ресурса"
)

def clean(self):
"""
Проверяет, что выбранная partner_company действительно является партнёром проекта.
"""
super().clean()
if self.partner_company:
exists = ProjectCompany.objects.filter(
project=self.project, company=self.partner_company
).exists()
if not exists:
raise ValidationError(
{
"partner_company": (
"Эта компания не является партнёром данного проекта. "
"Сначала добавьте её в партнёры проекта."
)
}
)
Loading