Initial commit from ZIP

This commit is contained in:
kusz committed 2024-11-27 13:18:16 +01:00
commit 378d4692a9
454 files changed
+40367

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class ProjectsAppConfig(AppConfig):
name = "outbo.apps.projects"
label = "projects"
verbose_name = _("Projects")
default_app_config = "outbo.apps.projects.ProjectsAppConfig"
+53
View File
@@ -0,0 +1,53 @@
from django import forms
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from outbo.apps.projects.models import Project, Member
class ProjectCreationForm(forms.ModelForm):
products_amount = forms.IntegerField(
label=_("Products amount"), widget=forms.NumberInput, min_value=1, max_value=20
)
class Meta:
model = Project
fields = [
"name",
"description",
"products_amount",
"category",
"brand",
"printing_files_date",
"promo_date",
]
class MemberInline(admin.TabularInline):
model = Member
readonly_fields = ["project"]
extra = 1
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
add_form = ProjectCreationForm
inlines = [MemberInline]
list_display = ('__str__', 'created_at', 'max_product_stage_description')
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults["form"] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
def save_model(self, request, obj, form, change):
if not change:
obj.save(products_amount=form.cleaned_data["products_amount"])
obj.save()
def get_readonly_fields(self, request, obj=None):
if obj:
return super().get_readonly_fields(request, obj=obj) + ("slug",)
return super().get_readonly_fields(request, obj=obj)
+14
View File
@@ -0,0 +1,14 @@
from django.db.models.fields import CharField
class SlugField(CharField):
def __init__(self, *args, slugify=None, **kwargs):
super().__init__(*args, **kwargs)
self.slugify = slugify
def pre_save(self, model_instance, add):
if add:
value = self.slugify(model_instance)
setattr(model_instance, self.attname, value)
return value
return super().pre_save(model_instance=model_instance, add=add)
+33
View File
@@ -0,0 +1,33 @@
from __future__ import annotations
from django.db.models import QuerySet, Q
from django_filters import rest_framework as filters, CharFilter
from outbo.apps.projects.models import Project
class ProjectFilterSet(filters.FilterSet):
query = CharFilter(method='query_projects')
order_by = CharFilter(method='order_projects')
class Meta:
model = Project
fields = ('query',)
def query_projects(self, queryset: QuerySet, _name: str, value: str):
return queryset.filter(
Q(slug__icontains=value)
| Q(name__icontains=value)
| Q(description__icontains=value)
| Q(product__brief__name_pol__icontains=value)
| Q(product__brief__name_eng__icontains=value)
).distinct()
def order_projects(self, queryset: QuerySet, name: str, value: str):
if value.lower() == "mod":
return queryset.order_by("-updated_at__date", "slug")
if value.lower() == "pf":
return queryset.order_by("printing_files_date", "slug")
return queryset
@@ -0,0 +1,122 @@
# Generated by Django 2.2.7 on 2019-11-29 08:57
from django.db import migrations, models
import django.db.models.deletion
import outbo.apps.core.fields
import outbo.apps.projects.fields
import rules.contrib.models
class Migration(migrations.Migration):
initial = True
dependencies = [
("organizations", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="ProjectNumber",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created_at",
outbo.apps.core.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="Created at"
),
),
(
"updated_at",
outbo.apps.core.fields.ModificationDateTimeField(
auto_now=True, verbose_name="Updated at"
),
),
(
"deleted_at",
outbo.apps.core.fields.RemovalDateTimeField(
blank=True, editable=False, null=True, verbose_name="Deleted at"
),
),
("prefix", models.CharField(max_length=4)),
("value", models.PositiveIntegerField(default=1)),
],
options={"abstract": False,},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name="Project",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created_at",
outbo.apps.core.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="Created at"
),
),
(
"updated_at",
outbo.apps.core.fields.ModificationDateTimeField(
auto_now=True, verbose_name="Updated at"
),
),
(
"deleted_at",
outbo.apps.core.fields.RemovalDateTimeField(
blank=True, editable=False, null=True, verbose_name="Deleted at"
),
),
(
"slug",
outbo.apps.projects.fields.SlugField(
max_length=15, verbose_name="project identifier"
),
),
("name", models.CharField(max_length=254, verbose_name="name")),
("description", models.TextField(verbose_name="description")),
(
"printing_files_date",
models.DateField(verbose_name="printing files delivery date"),
),
("promo_date", models.DateField(verbose_name="promo date")),
(
"published",
models.BooleanField(default=False, verbose_name="published"),
),
(
"brand",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="organizations.Brand",
verbose_name="brand",
),
),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="organizations.Category",
verbose_name="category",
),
),
],
options={"ordering": ["promo_date"],},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
]
+101
View File
@@ -0,0 +1,101 @@
# Generated by Django 2.2.8 on 2020-01-14 10:02
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import outbo.apps.core.fields
import outbo.apps.projects.models.member
import rules.contrib.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("projects", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Member",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created_at",
outbo.apps.core.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="Created at"
),
),
(
"updated_at",
outbo.apps.core.fields.ModificationDateTimeField(
auto_now=True, verbose_name="Updated at"
),
),
(
"deleted_at",
outbo.apps.core.fields.RemovalDateTimeField(
blank=True, editable=False, null=True, verbose_name="Deleted at"
),
),
(
"role",
models.CharField(
choices=[
(
outbo.apps.projects.models.member.MembershipType(
"agency"
),
"agency",
),
(
outbo.apps.projects.models.member.MembershipType(
"client"
),
"client",
),
(
outbo.apps.projects.models.member.MembershipType(
"supplier"
),
"supplier",
),
],
default=outbo.apps.projects.models.member.MembershipType(
"supplier"
),
max_length=25,
verbose_name="membership type",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="memberships",
related_query_name="membership",
to="projects.Project",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="memberships",
related_query_name="membership",
to=settings.AUTH_USER_MODEL,
),
),
],
options={"abstract": False,},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
]
@@ -0,0 +1,35 @@
# Generated by Django 2.2.8 on 2020-01-14 13:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("projects", "0002_member"),
]
operations = [
migrations.AlterField(
model_name="member",
name="project",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="members",
related_query_name="member",
to="projects.Project",
),
),
migrations.AlterField(
model_name="member",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="member",
related_query_name="member",
to=settings.AUTH_USER_MODEL,
),
),
]
@@ -0,0 +1,25 @@
# Generated by Django 2.2.8 on 2020-02-11 14:09
from django.db import migrations
import outbo.apps.core.fields
import outbo.apps.projects.models.member
class Migration(migrations.Migration):
dependencies = [
("projects", "0003_auto_20200114_1400"),
]
operations = [
migrations.AlterField(
model_name="member",
name="role",
field=outbo.apps.core.fields.EnumCharField(
default=outbo.apps.projects.models.member.MembershipType("supplier"),
enum=outbo.apps.projects.models.member.MembershipType,
max_length=25,
verbose_name="membership type",
),
),
]
@@ -0,0 +1,25 @@
# Generated by Django 2.2.10 on 2020-02-11 16:05
from django.db import migrations
import outbo.apps.core.fields
import outbo.apps.projects.models.member
class Migration(migrations.Migration):
dependencies = [
("projects", "0004_auto_20200211_1509"),
]
operations = [
migrations.AlterField(
model_name="member",
name="role",
field=outbo.apps.core.fields.EnumCharField(
default=outbo.apps.projects.models.member.MembershipType("supplier"),
enum=outbo.apps.projects.models.member.MembershipType,
max_length=25,
verbose_name="membership type",
),
),
]
@@ -0,0 +1,53 @@
# Generated by Django 2.2.10 on 2020-05-25 08:25
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("projects", "0005_auto_20200211_1705"),
]
operations = [
migrations.AlterField(
model_name="member",
name="project",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="members",
related_query_name="member",
to="projects.Project",
),
),
migrations.AlterField(
model_name="member",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="member",
related_query_name="member",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="project",
name="brand",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="organizations.Brand",
verbose_name="brand",
),
),
migrations.AlterField(
model_name="project",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="organizations.Category",
verbose_name="category",
),
),
]
@@ -0,0 +1,17 @@
# Generated by Django 2.2.16 on 2020-10-07 07:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('projects', '0006_auto_20200525_1025'),
]
operations = [
migrations.AlterModelOptions(
name='project',
options={'ordering': ['updated_at__date', 'slug']},
),
]
@@ -0,0 +1,17 @@
# Generated by Django 2.2.16 on 2020-10-07 10:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('projects', '0007_auto_20201007_0948'),
]
operations = [
migrations.AlterModelOptions(
name='project',
options={'ordering': ['-updated_at__date', 'slug']},
),
]
Whitespace-only changes.
+3
View File
@@ -0,0 +1,3 @@
from .project_number import ProjectNumber
from .project import Project
from .member import Member
+47
View File
@@ -0,0 +1,47 @@
from django.utils.translation import gettext_lazy as _
from enum import Enum
from django.db import models
from rules.contrib.models import RulesModel
from outbo.apps.core.fields import EnumCharField
from outbo.apps.core.models import TimestampableDeletableMixin
from outbo.apps.projects import rules
from outbo.apps.projects.models import Project
class MembershipType(Enum):
AGENCY = "agency"
CLIENT = "client"
SUPPLIER = "supplier"
class Member(TimestampableDeletableMixin, RulesModel):
class Meta:
rules_permissions = {
"view": rules.can_read_project_member,
"add": rules.can_create_project_member,
"change": rules.can_update_project_member,
"delete": rules.can_delete_project_member,
}
user = models.ForeignKey(
to="authentication.User",
on_delete=models.CASCADE,
related_name="member",
related_query_name="member",
)
project = models.ForeignKey(
to=Project,
on_delete=models.CASCADE,
related_name="members",
related_query_name="member",
)
role = EnumCharField(
verbose_name=_("membership type"),
max_length=25,
blank=False,
null=False,
default=MembershipType.SUPPLIER,
enum=MembershipType,
)
+146
View File
@@ -0,0 +1,146 @@
from typing import TYPE_CHECKING
from django.db import models
from django.utils.translation import gettext_lazy as _
from rules.contrib.models import RulesModel
from outbo.apps.activity_log.enums import MessageType
from outbo.apps.activity_log.models import ProjectLogItem
from outbo.apps.core.models import TimestampableDeletableMixin, SoftDeletableManager
from outbo.apps.products.models import Product
from outbo.apps.projects import rules
from outbo.apps.projects.fields import SlugField
from outbo.apps.projects.models import ProjectNumber
if TYPE_CHECKING:
from outbo.apps.authentication.models import User
class ProjectManager(SoftDeletableManager):
def create(self, **kwargs):
products_amount = kwargs.pop("products_amount", None)
project: "Project" = super().create(**kwargs)
if products_amount is not None:
project.add_products(products_amount=products_amount)
project.save()
return project
class Project(TimestampableDeletableMixin, RulesModel):
objects = ProjectManager()
all_objects = ProjectManager(alive_only=False)
class Meta:
rules_permissions = {
"view": rules.can_read_project,
"add": rules.can_create_project,
"change": rules.can_update_project,
"delete": rules.can_delete_project,
}
ordering = ["-updated_at__date", "slug"]
def slugify(self):
brand_prefix = self.category.name[:2].upper()
category_prefix = self.brand.name[:2].upper()
number = ProjectNumber.objects.get_next_number(
prefix=f"{brand_prefix}{category_prefix}"
)
week_year = self.promo_date.strftime("%U%y")
return f"#{brand_prefix}{category_prefix}{number}-{week_year}"
slug = SlugField(
verbose_name=_("project identifier"), max_length=15, slugify=slugify
)
name = models.CharField(
verbose_name=_("name"), max_length=254, blank=False, null=False
)
description = models.TextField(
verbose_name=_("description"), blank=False, null=False
)
category = models.ForeignKey(
verbose_name=_("category"),
to="organizations.Category",
on_delete=models.CASCADE,
null=False,
)
brand = models.ForeignKey(
verbose_name=_("brand"),
to="organizations.Brand",
on_delete=models.CASCADE,
null=False,
)
printing_files_date = models.DateField(
verbose_name=_("printing files delivery date"), null=False
)
promo_date = models.DateField(verbose_name=_("promo date"), null=False)
# internal fields
published = models.BooleanField(
verbose_name=_("published"), blank=False, null=False, default=False
)
@property
def _max_product_stage_obj(self):
max_product_stage_obj = (
self.products.order_by("-stage__order").first()
)
if not max_product_stage_obj:
return None
return max_product_stage_obj
@property
def max_product_stage(self):
return self._max_product_stage_obj.stage.id
@property
def max_product_stage_description(self):
return self._max_product_stage_obj.stage.description
def __str__(self):
if self.slug:
return f"[{self.slug}] {self.name}"
else:
return self.name
def publishable_for_user(self, *, user: "User"):
if user.is_observer or user.is_trader:
return False
return not self.published and all(
[
product.has_brief_publishable_for_user(user=user)
for product in self.products.all()
]
)
def save(self, *args, **kwargs):
products_amount = kwargs.pop("products_amount", None)
super().save(*args, **kwargs)
if products_amount is not None:
self.add_products(products_amount=products_amount)
def add_products(self, *, products_amount):
[Product.objects.create(project=self) for _ in range(products_amount)]
self.save()
def publish_as_user(self, *, user: "User"):
self.published = True
for product in self.products.all():
product.send_action_emails_for_users_passing_test(
test=lambda u: u in [m.user for m in self.members.all()]
and u.is_agency,
action_url="https://outbo.pl/projects/{id}/products/{pid}",
)
product.publish_brief_as_user(user=user)
product.save()
ProjectLogItem.objects.create(
project=self,
created_by=user,
message="Project published",
message_type=MessageType.SUCCESS,
)
self.save()
+26
View File
@@ -0,0 +1,26 @@
from django.utils.translation import gettext_lazy as _
from django.db import models
from django.db.models import F
from rules.contrib.models import RulesModel
from outbo.apps.core.models import TimestampableDeletableMixin, SoftDeletableManager
class ProjectNumberManager(SoftDeletableManager):
def get_next_number(self, prefix):
current = self.get_or_create(prefix=prefix)[0]
value = current.value
current.value = F("value") + 1
current.save()
return value
class ProjectNumber(TimestampableDeletableMixin, RulesModel):
objects = ProjectNumberManager()
all_objects = ProjectNumberManager(alive_only=False)
prefix = models.CharField(max_length=4)
value = models.PositiveIntegerField(default=1)
+12
View File
@@ -0,0 +1,12 @@
from django.db.models import Q
from outbo.apps.core.rules import is_agency, is_supplier, is_client
def read_project_query(user):
assigned_to_user_query = Q(member__user=user, member__deleted_at__isnull=True)
if is_client.test(user):
return assigned_to_user_query & Q(published=True)
elif (is_agency | is_supplier).test(user):
return assigned_to_user_query & Q()
return ~Q()
+42
View File
@@ -0,0 +1,42 @@
from typing import TYPE_CHECKING
from rules import predicate, always_deny
from outbo.apps.core.rules import is_supplier, is_agency, is_client
if TYPE_CHECKING:
from outbo.apps.projects.models import Project, Member
@predicate
def _project_published(_, project: "Project"):
return project.published
can_publish_project = is_supplier & ~_project_published
can_create_project = is_supplier
can_read_project = is_supplier | is_agency | (is_client & _project_published)
can_update_project = can_publish_project
can_delete_project = always_deny
@predicate
def _can_remove_this_member(user: "User", member: "Member"):
project = member.project
all_memberships = project.members
current_user_membership = all_memberships.filter(user=user).first()
if current_user_membership is None:
return False
current_role_memberships = all_memberships.filter(role=current_user_membership.role)
return (
member.role == current_user_membership.role
and current_role_memberships.count() > 1
)
can_create_project_member = is_supplier
can_read_project_member = always_deny
can_update_project_member = always_deny
can_delete_project_member = _can_remove_this_member
+186
View File
@@ -0,0 +1,186 @@
from rest_framework import serializers
from rest_framework.reverse import reverse_lazy
from outbo.apps.projects.models import Project, Member
from outbo.apps.projects.models.member import MembershipType
class _BaseProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ["name"]
class BaseMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
class MemberSerializer:
class Create(BaseMemberSerializer):
class Meta(BaseMemberSerializer.Meta):
fields = ["user"]
class AfterCreate(BaseMemberSerializer):
role = serializers.SerializerMethodField()
class Meta(BaseMemberSerializer.Meta):
fields = ["id", "user", "role"]
def get_role(self, instance):
t, val = str(instance.role).split(".", 1)
return MembershipType[val].value
class List(BaseMemberSerializer):
role = serializers.SerializerMethodField()
class Meta(BaseMemberSerializer.Meta):
fields = ["id", "user", "role"]
def get_role(self, instance):
t, val = str(instance.role).split(".", 1)
return MembershipType[val].value
class ProjectSerializer:
class Retrieve(_BaseProjectSerializer):
products = serializers.SerializerMethodField()
publishable = serializers.SerializerMethodField()
products_amount = serializers.SerializerMethodField()
max_product_stage = serializers.ReadOnlyField()
members = MemberSerializer.List(many=True)
class Meta(_BaseProjectSerializer.Meta):
fields = [
"slug",
*_BaseProjectSerializer.Meta.fields,
"description",
"category",
"members",
"brand",
"products_amount",
"products",
"printing_files_date",
"promo_date",
"max_product_stage",
"published",
"publishable",
"created_at",
"updated_at",
]
def get_publishable(self, project: "Project"):
current_user = self.context["request"].user
return project.publishable_for_user(user=current_user)
def get_products(self, project: "Project"):
url = reverse_lazy("products:product-list")
return f"{url}?project={project.pk}"
def get_products_amount(self, project: "Project"):
return project.products.count()
class AfterCreate(_BaseProjectSerializer):
products = serializers.SerializerMethodField()
publishable = serializers.SerializerMethodField()
products_amount = serializers.SerializerMethodField()
max_product_stage = serializers.ReadOnlyField()
members = MemberSerializer.List(many=True)
class Meta(_BaseProjectSerializer.Meta):
fields = [
"id",
"slug",
*_BaseProjectSerializer.Meta.fields,
"description",
"category",
"brand",
"products_amount",
"products",
"printing_files_date",
"promo_date",
"members",
# Computed
"max_product_stage",
"published",
"publishable",
"created_at",
"updated_at",
]
def get_publishable(self, project: "Project"):
current_user = self.context["request"].user
return project.publishable_for_user(user=current_user)
def get_products(self, project: "Project"):
# TODO: Hyperlink?
url = reverse_lazy("products:product-list")
return f"{url}?project={project.pk}"
def get_products_amount(self, project: "Project"):
return project.products.count()
class List(_BaseProjectSerializer):
products = serializers.SerializerMethodField()
max_product_stage = serializers.ReadOnlyField()
members = MemberSerializer.List(many=True)
class Meta(_BaseProjectSerializer.Meta):
fields = [
"id",
"slug",
*_BaseProjectSerializer.Meta.fields,
"members",
"products",
"printing_files_date",
"category",
"brand",
# Computed
"max_product_stage",
"published",
"created_at",
"updated_at",
]
def get_products(self, instance):
# TODO: refactor, maybe custom serializerfield
url = reverse_lazy("products:product-list")
return f"{url}?project={instance.pk}"
class Create(_BaseProjectSerializer):
products_amount = serializers.IntegerField(min_value=1, write_only=True)
class Meta(_BaseProjectSerializer.Meta):
fields = [
*_BaseProjectSerializer.Meta.fields,
"description",
"category",
"brand",
"products_amount",
"printing_files_date",
"promo_date",
"created_at",
"updated_at",
]
# NOTE: business logic?
def create(self, validated_data):
products_amount = validated_data.pop("products_amount")
project = super().create(validated_data)
project.add_products(products_amount=products_amount)
membership = Member.objects.create(
user=self.context["request"].user,
role=MembershipType.SUPPLIER,
project=project,
)
project.members.add(membership)
return project
class Update(serializers.Serializer):
publish = serializers.BooleanField(default=False)
def update(self, instance: Project, validated_data):
current_user = self.context["request"].user
if validated_data["publish"] is True:
instance.publish_as_user(user=current_user)
return instance
+23
View File
@@ -0,0 +1,23 @@
from rest_framework_extensions.routers import ExtendedSimpleRouter
from outbo.apps.activity_log.viewsets import ProjectLogItemNestedViewSet
from outbo.apps.projects.viewsets import ProjectViewSet, ProjectMemberNestedViewSet
app_name = "projects"
router = ExtendedSimpleRouter()
_project_routes = router.register(prefix="projects", viewset=ProjectViewSet)
_project_routes.register(
prefix="messages",
viewset=ProjectLogItemNestedViewSet,
basename="project-message-item",
parents_query_lookups=["project_id"],
)
_project_routes.register(
prefix="members",
viewset=ProjectMemberNestedViewSet,
basename="project-member-item",
parents_query_lookups=["project_id"],
)
urlpatterns = router.urls
+82
View File
@@ -0,0 +1,82 @@
from django.db.models import Q
from django_filters import rest_framework as filters
from outbo.apps.core.viewsets import (
AutoPermissionViewSetMixin,
MultipleSerializerViewSet,
CreateModelMixin,
PartialUpdateModelMixin,
RetrieveModelMixin,
ListModelMixin,
DestroyModelMixin,
NestedViewSetMixin,
)
from outbo.apps.projects.filters import ProjectFilterSet
from outbo.apps.projects.models import Project, Member
from outbo.apps.projects.models.member import MembershipType
from outbo.apps.projects.queries import read_project_query
from outbo.apps.projects.serializers import ProjectSerializer, MemberSerializer
class ProjectViewSet(
RetrieveModelMixin,
ListModelMixin,
CreateModelMixin,
PartialUpdateModelMixin,
AutoPermissionViewSetMixin,
MultipleSerializerViewSet,
):
queryset = Project.objects.all()
serializer_class_set = ProjectSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ProjectFilterSet
def get_queryset(self):
qs = super().get_queryset()
if self.action == "list":
qs_filter = read_project_query(self.request.user)
qs = qs.filter(qs_filter)
# TODO: try to refactor this poor loops
if self.request.user.is_observer or self.request.user.is_trader:
projects_id = []
for project in Project.objects.filter(published=True):
members = project.members.filter(role=self.request.user.user_role)
for member in members:
if member.user.organization == self.request.user.organization:
projects_id.append(project.id)
qs_id = qs.values_list("id", flat=True)
qs = Project.objects.filter(Q(id__in=projects_id) | Q(id__in=qs_id))
if self.request.user.is_trader and self.request.user.category is not None:
qs = qs.filter(category=self.request.user.category)
return qs
def partial_update(self, request, *args, **kwargs):
return super().partial_update(request=request, *args, **kwargs)
class ProjectMemberNestedViewSet(
ListModelMixin,
CreateModelMixin,
DestroyModelMixin,
NestedViewSetMixin,
AutoPermissionViewSetMixin,
MultipleSerializerViewSet,
):
queryset = Member.objects.all()
serializer_class_set = MemberSerializer
def perform_create(self, *, serializer: MemberSerializer.Create):
user = serializer.validated_data["user"]
role = None
if user.is_agency:
role = MembershipType.AGENCY
elif user.is_client:
role = MembershipType.CLIENT
elif user.is_supplier:
role = MembershipType.SUPPLIER
parents = self.get_parents_query_dict()
project = Project.objects.get(pk=parents["project_id"])
membership = Member.objects.filter(project=project, user=user).first()
if membership:
serializer.instance = membership
return serializer.save(project=project, role=role)