Initial commit from ZIP
This commit is contained in:
commit
378d4692a9
454 files changed
+40367
No files matched your search
+11
@@ -0,0 +1,11 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class ProductsAppConfig(AppConfig):
|
||||
name = "outbo.apps.products"
|
||||
label = "products"
|
||||
verbose_name = _("Products")
|
||||
|
||||
|
||||
default_app_config = "outbo.apps.products.ProductsAppConfig"
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
from django.contrib import admin
|
||||
from nested_admin import nested
|
||||
|
||||
|
||||
from outbo.apps.core.admin import (
|
||||
NestedTabularSingleItemInline,
|
||||
NestedStackedSingleItemInline,
|
||||
)
|
||||
from outbo.apps.products.models import (
|
||||
ProductStage,
|
||||
PackagingType,
|
||||
Product,
|
||||
DraftFile,
|
||||
FirstFinalFile,
|
||||
PrintingFilesFile,
|
||||
BriefConfig,
|
||||
Brief,
|
||||
Photo,
|
||||
Blanking,
|
||||
)
|
||||
|
||||
admin.site.register(PackagingType)
|
||||
admin.site.register(ProductStage)
|
||||
|
||||
|
||||
class PhotoInline(nested.NestedStackedInline):
|
||||
model = Photo
|
||||
readonly_fields = ["brief"]
|
||||
extra = 0
|
||||
|
||||
|
||||
class BlankingInline(nested.NestedStackedInline):
|
||||
model = Blanking
|
||||
readonly_fields = ["brief"]
|
||||
extra = 0
|
||||
|
||||
|
||||
class BriefConfigInline(NestedTabularSingleItemInline):
|
||||
model = BriefConfig
|
||||
readonly_fields = ["brief"]
|
||||
can_delete = False
|
||||
|
||||
|
||||
class BriefInline(NestedStackedSingleItemInline):
|
||||
model = Brief
|
||||
readonly_fields = ["product"]
|
||||
can_delete = False
|
||||
inlines = [PhotoInline, BlankingInline, BriefConfigInline]
|
||||
|
||||
|
||||
@admin.register(Product)
|
||||
class ProductAdmin(nested.NestedModelAdmin):
|
||||
readonly_fields = ["stage"]
|
||||
search_fields = ["project__name"]
|
||||
inlines = [BriefInline]
|
||||
|
||||
|
||||
admin.site.register(DraftFile)
|
||||
admin.site.register(FirstFinalFile)
|
||||
admin.site.register(PrintingFilesFile)
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FileType(Enum):
|
||||
INVALID = "<invalid>"
|
||||
DRAFT = "Draft file"
|
||||
FIRST_FINAL = "First final file"
|
||||
PRINTING_FILE = "Printing file"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
from outbo.apps.products.models import Product
|
||||
|
||||
|
||||
class ProductFilter(filters.FilterSet):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ("project",)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
from outbo.apps.core.models import SoftDeletableManager
|
||||
from outbo.apps.products.enums import FileType
|
||||
|
||||
|
||||
class DraftFileManager(SoftDeletableManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(file_type=FileType.DRAFT)
|
||||
|
||||
|
||||
class FirstFinalFileManager(SoftDeletableManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(file_type=FileType.FIRST_FINAL)
|
||||
|
||||
|
||||
class PrintingFileManager(SoftDeletableManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(file_type=FileType.PRINTING_FILE)
|
||||
@@ -0,0 +1,795 @@
|
||||
# Generated by Django 2.2.7 on 2019-11-29 08:57
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import outbo.apps.core.fields
|
||||
import outbo.apps.products.models.blanking
|
||||
import outbo.apps.products.models.design_stage_file
|
||||
import outbo.apps.products.models.photo
|
||||
import rules.contrib.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("projects", "0001_initial"),
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DesignStageFile",
|
||||
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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"version",
|
||||
models.CharField(
|
||||
editable=False, max_length=3, verbose_name="version"
|
||||
),
|
||||
),
|
||||
("description", models.TextField()),
|
||||
(
|
||||
"polymorphic_ctype",
|
||||
models.ForeignKey(
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="polymorphic_products.designstagefile_set+",
|
||||
to="contenttypes.ContentType",
|
||||
),
|
||||
),
|
||||
(
|
||||
"uploaded_by",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="uploaded by",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"abstract": False,},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PackagingType",
|
||||
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"
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=200, verbose_name="name")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "packaging type",
|
||||
"verbose_name_plural": "packaging types",
|
||||
"ordering": ["name"],
|
||||
},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Product",
|
||||
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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="products",
|
||||
related_query_name="product",
|
||||
to="projects.Project",
|
||||
verbose_name="project",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ProductStage",
|
||||
fields=[
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"id",
|
||||
models.CharField(
|
||||
max_length=50,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("label", models.CharField(max_length=2, verbose_name="label")),
|
||||
("group", models.PositiveSmallIntegerField(verbose_name="group")),
|
||||
(
|
||||
"description",
|
||||
models.CharField(max_length=254, verbose_name="description"),
|
||||
),
|
||||
(
|
||||
"next_action",
|
||||
models.CharField(max_length=254, verbose_name="next action"),
|
||||
),
|
||||
("order", models.PositiveSmallIntegerField(verbose_name="order")),
|
||||
],
|
||||
options={"ordering": ["label"],},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Brief",
|
||||
fields=[
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"product",
|
||||
models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
primary_key=True,
|
||||
related_name="brief",
|
||||
related_query_name="brief",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name_pol",
|
||||
models.CharField(
|
||||
blank=True, max_length=200, verbose_name="product name (PL)"
|
||||
),
|
||||
),
|
||||
(
|
||||
"name_eng",
|
||||
models.CharField(
|
||||
blank=True, max_length=200, verbose_name="product name (EN)"
|
||||
),
|
||||
),
|
||||
(
|
||||
"model_no",
|
||||
models.CharField(
|
||||
blank=True, max_length=200, verbose_name="model number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"batch_no",
|
||||
models.CharField(
|
||||
blank=True, max_length=200, verbose_name="batch number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"ean",
|
||||
models.CharField(blank=True, max_length=13, verbose_name="EAN"),
|
||||
),
|
||||
(
|
||||
"main_description",
|
||||
models.TextField(blank=True, verbose_name="main description"),
|
||||
),
|
||||
(
|
||||
"extra_description",
|
||||
models.TextField(blank=True, verbose_name="extra description"),
|
||||
),
|
||||
(
|
||||
"certificate",
|
||||
models.TextField(blank=True, verbose_name="certificate"),
|
||||
),
|
||||
(
|
||||
"information_signs",
|
||||
models.TextField(blank=True, verbose_name="information signs"),
|
||||
),
|
||||
(
|
||||
"capabilities_icons",
|
||||
models.TextField(blank=True, verbose_name="capabilities icons"),
|
||||
),
|
||||
(
|
||||
"product_icons",
|
||||
models.TextField(blank=True, verbose_name="product icons"),
|
||||
),
|
||||
(
|
||||
"has_description_eng",
|
||||
models.BooleanField(default=False, verbose_name="description (EN)"),
|
||||
),
|
||||
(
|
||||
"has_description_pol",
|
||||
models.BooleanField(default=False, verbose_name="description (PL)"),
|
||||
),
|
||||
(
|
||||
"has_description_por",
|
||||
models.BooleanField(default=False, verbose_name="description (PT)"),
|
||||
),
|
||||
(
|
||||
"has_description_rus",
|
||||
models.BooleanField(default=False, verbose_name="description (RU)"),
|
||||
),
|
||||
(
|
||||
"has_description_spa",
|
||||
models.BooleanField(default=False, verbose_name="description (ES)"),
|
||||
),
|
||||
(
|
||||
"illustration",
|
||||
models.TextField(blank=True, verbose_name="illustration"),
|
||||
),
|
||||
(
|
||||
"cleaning_method",
|
||||
models.TextField(blank=True, verbose_name="cleaning method"),
|
||||
),
|
||||
(
|
||||
"country_of_origin",
|
||||
models.TextField(blank=True, verbose_name="country of origin"),
|
||||
),
|
||||
("manual", models.TextField(blank=True, verbose_name="manual")),
|
||||
("material", models.TextField(blank=True, verbose_name="material")),
|
||||
("warning", models.TextField(blank=True, verbose_name="warning")),
|
||||
(
|
||||
"arrangement_photo",
|
||||
models.TextField(blank=True, verbose_name="arrangement photo"),
|
||||
),
|
||||
("packshot", models.TextField(blank=True, verbose_name="packshot")),
|
||||
("clipping", models.TextField(blank=True, verbose_name="clippings")),
|
||||
(
|
||||
"extra_photo",
|
||||
models.TextField(blank=True, verbose_name="extra photo"),
|
||||
),
|
||||
(
|
||||
"packaging_type",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to="products.PackagingType",
|
||||
verbose_name="packaging type",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "brief", "verbose_name_plural": "briefs",},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Draft",
|
||||
fields=[
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"product",
|
||||
models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
primary_key=True,
|
||||
related_name="draft",
|
||||
related_query_name="draft",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "draft", "verbose_name_plural": "drafts",},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FirstFinal",
|
||||
fields=[
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"product",
|
||||
models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
primary_key=True,
|
||||
related_name="first_final",
|
||||
related_query_name="first_final",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "first final",
|
||||
"verbose_name_plural": "first finals",
|
||||
},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PrintingFiles",
|
||||
fields=[
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"product",
|
||||
models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
primary_key=True,
|
||||
related_name="printing_files",
|
||||
related_query_name="printing_files",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "printing files",
|
||||
"verbose_name_plural": "printing files",
|
||||
},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="product",
|
||||
name="stage",
|
||||
field=outbo.apps.core.fields.FSMKeyField(
|
||||
default="waiting_for_brief",
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
protected=True,
|
||||
to="products.ProductStage",
|
||||
verbose_name="stage",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="BriefConfig",
|
||||
fields=[
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"brief",
|
||||
models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
primary_key=True,
|
||||
related_name="config",
|
||||
related_query_name="config",
|
||||
serialize=False,
|
||||
to="products.Brief",
|
||||
),
|
||||
),
|
||||
(
|
||||
"illustration",
|
||||
models.BooleanField(default=False, verbose_name="illustrations"),
|
||||
),
|
||||
(
|
||||
"cleaning_method",
|
||||
models.BooleanField(default=False, verbose_name="cleaning method"),
|
||||
),
|
||||
(
|
||||
"country_of_origin",
|
||||
models.BooleanField(
|
||||
default=False, verbose_name="country of origin"
|
||||
),
|
||||
),
|
||||
("manual", models.BooleanField(default=False, verbose_name="manual")),
|
||||
(
|
||||
"material",
|
||||
models.BooleanField(default=False, verbose_name="material"),
|
||||
),
|
||||
(
|
||||
"warning",
|
||||
models.BooleanField(default=False, verbose_name="warnings"),
|
||||
),
|
||||
(
|
||||
"photobrief",
|
||||
models.BooleanField(default=False, verbose_name="photobrief"),
|
||||
),
|
||||
(
|
||||
"packshot",
|
||||
models.BooleanField(default=False, verbose_name="packshot"),
|
||||
),
|
||||
(
|
||||
"clipping",
|
||||
models.BooleanField(default=False, verbose_name="clipping"),
|
||||
),
|
||||
(
|
||||
"extra_photo",
|
||||
models.BooleanField(default=False, verbose_name="extra photo"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "brief configuration",
|
||||
"verbose_name_plural": "brief configurations",
|
||||
},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PrintingFilesFile",
|
||||
fields=[
|
||||
(
|
||||
"designstagefile_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="products.DesignStageFile",
|
||||
),
|
||||
),
|
||||
(
|
||||
"file",
|
||||
models.FileField(
|
||||
upload_to=outbo.apps.products.models.design_stage_file.FormatFilename(
|
||||
dir_name="first_finals",
|
||||
filename_prefix="FF",
|
||||
parent_name="first_final",
|
||||
),
|
||||
verbose_name="file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"printing_files",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
to="products.PrintingFiles",
|
||||
verbose_name="printing files",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "printing file",
|
||||
"verbose_name_plural": "printing files",
|
||||
},
|
||||
bases=("products.designstagefile",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Photo",
|
||||
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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"file",
|
||||
models.ImageField(
|
||||
upload_to=outbo.apps.products.models.photo.Photo.product_photo_filename,
|
||||
verbose_name="file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"brief",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="photos",
|
||||
related_query_name="photo",
|
||||
to="products.Brief",
|
||||
verbose_name="brief",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FirstFinalFile",
|
||||
fields=[
|
||||
(
|
||||
"designstagefile_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="products.DesignStageFile",
|
||||
),
|
||||
),
|
||||
(
|
||||
"file",
|
||||
models.FileField(
|
||||
upload_to=outbo.apps.products.models.design_stage_file.FormatFilename(
|
||||
dir_name="first_finals",
|
||||
filename_prefix="FF",
|
||||
parent_name="first_final",
|
||||
),
|
||||
verbose_name="file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"first_final",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
to="products.FirstFinal",
|
||||
verbose_name="first final",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "first final file",
|
||||
"verbose_name_plural": "first final files",
|
||||
},
|
||||
bases=("products.designstagefile",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="DraftFile",
|
||||
fields=[
|
||||
(
|
||||
"designstagefile_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="products.DesignStageFile",
|
||||
),
|
||||
),
|
||||
(
|
||||
"file",
|
||||
models.FileField(
|
||||
upload_to=outbo.apps.products.models.design_stage_file.FormatFilename(
|
||||
dir_name="drafts",
|
||||
filename_prefix="DRAFT",
|
||||
parent_name="draft",
|
||||
),
|
||||
verbose_name="file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"draft",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
to="products.Draft",
|
||||
verbose_name="draft",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "draft file",
|
||||
"verbose_name_plural": "draft files",
|
||||
},
|
||||
bases=("products.designstagefile",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Blanking",
|
||||
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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"file",
|
||||
models.FileField(
|
||||
upload_to=outbo.apps.products.models.blanking.product_blanking_filename,
|
||||
verbose_name="file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"brief",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="blankings",
|
||||
related_query_name="blanking",
|
||||
to="products.Brief",
|
||||
verbose_name="brief",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "blanking", "verbose_name_plural": "blankings",},
|
||||
bases=(rules.contrib.models.RulesModelMixin, models.Model),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.7 on 2019-11-29 12:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="draft",
|
||||
name="max_version",
|
||||
field=models.PositiveSmallIntegerField(default=1),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 2.2.7 on 2019-11-29 12:54
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0002_draft_max_version"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="designstagefile", old_name="uploaded_by", new_name="created_by",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 2.2.7 on 2019-12-03 13:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0003_auto_20191129_1354"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="draftfile",
|
||||
options={
|
||||
"ordering": ["-created_at"],
|
||||
"verbose_name": "draft file",
|
||||
"verbose_name_plural": "draft files",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="firstfinal",
|
||||
name="max_version",
|
||||
field=models.PositiveSmallIntegerField(default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="printingfiles",
|
||||
name="max_version",
|
||||
field=models.PositiveSmallIntegerField(default=1),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 2.2.8 on 2020-02-11 13:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import outbo.apps.products.models.design_stage_file
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0004_auto_20191203_1421"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="printingfilesfile",
|
||||
name="file",
|
||||
field=models.FileField(
|
||||
upload_to=outbo.apps.products.models.design_stage_file.FormatFilename(
|
||||
dir_name="printing_files",
|
||||
filename_prefix="PF",
|
||||
parent_name="printing_files",
|
||||
),
|
||||
verbose_name="file",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,201 @@
|
||||
# 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
|
||||
import outbo.apps.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0005_auto_20200211_1405"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="firstfinalfile",
|
||||
options={
|
||||
"ordering": ["-created_at"],
|
||||
"verbose_name": "first final file",
|
||||
"verbose_name_plural": "first final files",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="printingfilesfile",
|
||||
options={
|
||||
"ordering": ["-created_at"],
|
||||
"verbose_name": "printing file",
|
||||
"verbose_name_plural": "printing files",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="blanking",
|
||||
name="brief",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="blankings",
|
||||
related_query_name="blanking",
|
||||
to="products.Brief",
|
||||
verbose_name="brief",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="brief",
|
||||
name="packaging_type",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to="products.PackagingType",
|
||||
verbose_name="packaging type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="brief",
|
||||
name="product",
|
||||
field=models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
related_name="brief",
|
||||
related_query_name="brief",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="briefconfig",
|
||||
name="brief",
|
||||
field=models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
related_name="config",
|
||||
related_query_name="config",
|
||||
serialize=False,
|
||||
to="products.Brief",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="designstagefile",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="uploaded by",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="draft",
|
||||
name="product",
|
||||
field=models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
related_name="draft",
|
||||
related_query_name="draft",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="draftfile",
|
||||
name="draft",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
to="products.Draft",
|
||||
verbose_name="draft",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="firstfinal",
|
||||
name="product",
|
||||
field=models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
related_name="first_final",
|
||||
related_query_name="first_final",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="firstfinalfile",
|
||||
name="first_final",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
to="products.FirstFinal",
|
||||
verbose_name="first final",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="photo",
|
||||
name="brief",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="photos",
|
||||
related_query_name="photo",
|
||||
to="products.Brief",
|
||||
verbose_name="brief",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="printingfiles",
|
||||
name="product",
|
||||
field=models.OneToOneField(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
related_name="printing_files",
|
||||
related_query_name="printing_files",
|
||||
serialize=False,
|
||||
to="products.Product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="printingfilesfile",
|
||||
name="printing_files",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
to="products.PrintingFiles",
|
||||
verbose_name="printing files",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="project",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="products",
|
||||
related_query_name="product",
|
||||
to="projects.Project",
|
||||
verbose_name="project",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="stage",
|
||||
field=outbo.apps.core.fields.FSMKeyField(
|
||||
default="waiting_for_brief",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
protected=True,
|
||||
to="products.ProductStage",
|
||||
verbose_name="stage",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.2.16 on 2021-05-18 09:28
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0006_auto_20200525_1025'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='draftfile',
|
||||
name='opened_by',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=15), blank=True, null=True, size=None),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='firstfinalfile',
|
||||
name='opened_by',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=15), blank=True, null=True, size=None),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='printingfilesfile',
|
||||
name='opened_by',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=15), blank=True, null=True, size=None),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.2.16 on 2021-05-18 10:47
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0007_auto_20210518_1128'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='draftfile',
|
||||
name='opened_by',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), blank=True, null=True, size=None),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='firstfinalfile',
|
||||
name='opened_by',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), blank=True, null=True, size=None),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='printingfilesfile',
|
||||
name='opened_by',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), blank=True, null=True, size=None),
|
||||
),
|
||||
]
|
||||
Whitespace-only changes.
+19
@@ -0,0 +1,19 @@
|
||||
from .packaging_type import PackagingType
|
||||
from .product_stage import ProductStage
|
||||
from .brief import Brief
|
||||
from .brief_config import BriefConfig
|
||||
|
||||
from .draft import Draft
|
||||
from .first_final import FirstFinal
|
||||
from .printing_files import PrintingFiles
|
||||
|
||||
from .product import Product
|
||||
|
||||
from .blanking import Blanking
|
||||
from .photo import Photo
|
||||
from .design_stage_file import (
|
||||
DesignStageFile,
|
||||
DraftFile,
|
||||
FirstFinalFile,
|
||||
PrintingFilesFile,
|
||||
)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules import always_allow
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
|
||||
def product_blanking_filename(instance, filename):
|
||||
return f"projects/{instance.brief.product.project.slug[1:]}/blankings/{filename}"
|
||||
|
||||
|
||||
class Blanking(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"delete": rules.can_delete_blanking,
|
||||
"view": always_allow,
|
||||
}
|
||||
verbose_name = _("blanking")
|
||||
verbose_name_plural = _("blankings")
|
||||
|
||||
brief = models.ForeignKey(
|
||||
verbose_name=_("brief"),
|
||||
to="Brief",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="blankings",
|
||||
related_query_name="blanking",
|
||||
)
|
||||
|
||||
file = models.FileField(verbose_name=_("file"), upload_to=product_blanking_filename)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
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.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class Brief(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_brief,
|
||||
"delete": rules.can_delete_brief,
|
||||
"change": rules.can_update_brief,
|
||||
"view": rules.can_read_brief,
|
||||
}
|
||||
verbose_name = _("brief")
|
||||
verbose_name_plural = _("briefs")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="brief",
|
||||
related_query_name="brief",
|
||||
)
|
||||
name_pol = models.CharField(verbose_name=_("product name (PL)"), max_length=200, blank=True, null=False)
|
||||
name_eng = models.CharField(verbose_name=_("product name (EN)"), max_length=200, blank=True, null=False)
|
||||
model_no = models.CharField(verbose_name=_("model number"), max_length=200, blank=True, null=False)
|
||||
batch_no = models.CharField(verbose_name=_("batch number"), max_length=200, blank=True, null=False)
|
||||
ean = models.CharField(verbose_name=_("EAN"), max_length=13, blank=True, null=False)
|
||||
packaging_type = models.ForeignKey(
|
||||
verbose_name=_("packaging type"),
|
||||
to="PackagingType",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
)
|
||||
main_description = models.TextField(verbose_name=_("main description"), blank=True, null=False)
|
||||
extra_description = models.TextField(verbose_name=_("extra description"), blank=True, null=False)
|
||||
certificate = models.TextField(verbose_name=_("certificate"), blank=True, null=False)
|
||||
information_signs = models.TextField(verbose_name=_("information signs"), blank=True, null=False)
|
||||
capabilities_icons = models.TextField(verbose_name=_("capabilities icons"), blank=True, null=False)
|
||||
product_icons = models.TextField(verbose_name=_("product icons"), blank=True, null=False)
|
||||
has_description_eng = models.BooleanField(verbose_name=_("description (EN)"), default=False, null=False)
|
||||
has_description_pol = models.BooleanField(verbose_name=_("description (PL)"), default=False, null=False)
|
||||
has_description_por = models.BooleanField(verbose_name=_("description (PT)"), default=False, null=False)
|
||||
has_description_rus = models.BooleanField(verbose_name=_("description (RU)"), default=False, null=False)
|
||||
has_description_spa = models.BooleanField(verbose_name=_("description (ES)"), default=False, null=False)
|
||||
illustration = models.TextField(verbose_name=_("illustration"), blank=True, null=False)
|
||||
cleaning_method = models.TextField(verbose_name=_("cleaning method"), blank=True, null=False)
|
||||
country_of_origin = models.TextField(verbose_name=_("country of origin"), blank=True, null=False)
|
||||
manual = models.TextField(verbose_name=_("manual"), blank=True, null=False)
|
||||
material = models.TextField(verbose_name=_("material"), blank=True, null=False)
|
||||
warning = models.TextField(verbose_name=_("warning"), blank=True, null=False)
|
||||
arrangement_photo = models.TextField(verbose_name=_("arrangement photo"), blank=True, null=False)
|
||||
packshot = models.TextField(verbose_name=_("packshot"), blank=True, null=False)
|
||||
clipping = models.TextField(verbose_name=_("clippings"), blank=True, null=False)
|
||||
extra_photo = models.TextField(verbose_name=_("extra photo"), blank=True, null=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"""{self.product.project.slug} / {self.name_pol or "<no name>"}"""
|
||||
|
||||
# serializer methods
|
||||
|
||||
def editable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
return user.is_supplier and self.product.stage.label == "1"
|
||||
|
||||
def acceptable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
return (user.is_agency and self.product.stage.pk == "waiting_for_brief_accept_by_agency") or (
|
||||
user.is_client and self.product.stage.pk == "waiting_for_brief_accept_by_client"
|
||||
)
|
||||
|
||||
def publishable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
return self.product.project.published and self.product.has_brief_publishable_for_user(user=user)
|
||||
@@ -0,0 +1,60 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
|
||||
|
||||
class BriefConfig(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
verbose_name = _("brief configuration")
|
||||
verbose_name_plural = _("brief configurations")
|
||||
|
||||
brief = models.OneToOneField(
|
||||
to="Brief",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="config",
|
||||
related_query_name="config",
|
||||
)
|
||||
illustration = models.BooleanField(
|
||||
verbose_name=_("illustrations"), default=False, null=False
|
||||
)
|
||||
cleaning_method = models.BooleanField(
|
||||
verbose_name=_("cleaning method"), default=False, null=False
|
||||
)
|
||||
country_of_origin = models.BooleanField(
|
||||
verbose_name=_("country of origin"), default=False, null=False
|
||||
)
|
||||
manual = models.BooleanField(verbose_name=_("manual"), default=False, null=False)
|
||||
material = models.BooleanField(
|
||||
verbose_name=_("material"), default=False, null=False
|
||||
)
|
||||
warning = models.BooleanField(verbose_name=_("warnings"), default=False, null=False)
|
||||
photobrief = models.BooleanField(
|
||||
verbose_name=_("photobrief"), default=False, null=False
|
||||
)
|
||||
packshot = models.BooleanField(
|
||||
verbose_name=_("packshot"), default=False, null=False
|
||||
)
|
||||
clipping = models.BooleanField(
|
||||
verbose_name=_("clipping"), default=False, null=False
|
||||
)
|
||||
extra_photo = models.BooleanField(
|
||||
verbose_name=_("extra photo"), default=False, null=False
|
||||
)
|
||||
|
||||
def save(
|
||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||
):
|
||||
result = super().save(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
)
|
||||
if self.brief:
|
||||
self.brief.save(using=using)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,190 @@
|
||||
import os
|
||||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from text_unidecode import unidecode
|
||||
|
||||
from outbo.apps.core.models import (
|
||||
TimestampableDeletableMixin,
|
||||
RulesPolymorphicModel,
|
||||
SoftDeletablePolymorphicManager,
|
||||
)
|
||||
from outbo.apps.core.rules import is_agency
|
||||
from outbo.apps.products import rules
|
||||
|
||||
|
||||
@deconstructible
|
||||
class FormatFilename:
|
||||
def __init__(self, parent_name: str, filename_prefix: str, dir_name: str):
|
||||
self.parent_name = parent_name
|
||||
self.filename_prefix = filename_prefix
|
||||
self.dir_name = dir_name
|
||||
|
||||
def __call__(self, instance, filename):
|
||||
obj = getattr(instance, self.parent_name)
|
||||
filename = "-".join(
|
||||
(
|
||||
self.filename_prefix,
|
||||
obj.product.brief.model_no,
|
||||
slugify(unidecode(obj.product.brief.name_pol)),
|
||||
instance.version,
|
||||
)
|
||||
)
|
||||
return "/".join(
|
||||
(
|
||||
"projects",
|
||||
obj.product.project.slug[1:],
|
||||
self.dir_name,
|
||||
f"{filename}.pdf",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DesignStageFile(TimestampableDeletableMixin, RulesPolymorphicModel):
|
||||
objects = SoftDeletablePolymorphicManager()
|
||||
all_objects = SoftDeletablePolymorphicManager(alive_only=False)
|
||||
|
||||
created_by = models.ForeignKey(
|
||||
verbose_name=_("uploaded by"),
|
||||
to="authentication.User",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="+",
|
||||
)
|
||||
version = models.CharField(verbose_name=_("version"), max_length=3, editable=False)
|
||||
description = models.TextField()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
parent_name = kwargs.pop("parent_name", None)
|
||||
parent = getattr(self, parent_name)
|
||||
|
||||
if self._state.adding is True and parent_name is not None:
|
||||
if is_agency.test(self.created_by):
|
||||
self.version = f"v{parent.max_version}"
|
||||
parent.max_version = F("max_version") + 1
|
||||
parent.save()
|
||||
else:
|
||||
self.version = "P"
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class DraftFile(DesignStageFile):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"view": rules.can_read_draft_file,
|
||||
"add": rules.can_create_draft_file,
|
||||
"change": rules.can_update_draft_file,
|
||||
"delete": rules.can_delete_draft_file,
|
||||
}
|
||||
verbose_name = _("draft file")
|
||||
verbose_name_plural = _("draft files")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
draft = models.ForeignKey(
|
||||
verbose_name=_("draft"),
|
||||
to="Draft",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
)
|
||||
file = models.FileField(
|
||||
verbose_name=_("file"),
|
||||
upload_to=FormatFilename(
|
||||
filename_prefix="DRAFT", dir_name="drafts", parent_name="draft"
|
||||
),
|
||||
)
|
||||
opened_by = ArrayField(models.IntegerField(), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super().save(*args, **kwargs, parent_name="draft")
|
||||
|
||||
|
||||
class FirstFinalFile(DesignStageFile):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"view": rules.can_read_ff_file,
|
||||
"add": rules.can_create_ff_file,
|
||||
"change": rules.can_update_ff_file,
|
||||
"delete": rules.can_delete_ff_file,
|
||||
}
|
||||
verbose_name = _("first final file")
|
||||
verbose_name_plural = _("first final files")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
first_final = models.ForeignKey(
|
||||
verbose_name=_("first final"),
|
||||
to="FirstFinal",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
)
|
||||
file = models.FileField(
|
||||
verbose_name=_("file"),
|
||||
upload_to=FormatFilename(
|
||||
filename_prefix="FF", dir_name="first_finals", parent_name="first_final"
|
||||
),
|
||||
)
|
||||
opened_by = ArrayField(models.IntegerField(), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super().save(*args, **kwargs, parent_name="first_final")
|
||||
|
||||
|
||||
class PrintingFilesFile(DesignStageFile):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"view": rules.can_read_pf_file,
|
||||
"add": rules.can_create_pf_file,
|
||||
"change": rules.can_update_pf_file,
|
||||
"delete": rules.can_delete_pf_file,
|
||||
}
|
||||
verbose_name = _("printing file")
|
||||
verbose_name_plural = _("printing files")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
printing_files = models.ForeignKey(
|
||||
verbose_name=_("printing files"),
|
||||
to="PrintingFiles",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
)
|
||||
file = models.FileField(
|
||||
verbose_name=_("file"),
|
||||
upload_to=FormatFilename(
|
||||
filename_prefix="PF",
|
||||
dir_name="printing_files",
|
||||
parent_name="printing_files",
|
||||
),
|
||||
)
|
||||
opened_by = ArrayField(models.IntegerField(), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super().save(*args, **kwargs, parent_name="printing_files")
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.reverse import reverse_lazy, reverse
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class Draft(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_draft,
|
||||
"delete": rules.can_delete_draft,
|
||||
"change": rules.can_update_draft,
|
||||
"view": rules.can_read_draft,
|
||||
}
|
||||
verbose_name = _("draft")
|
||||
verbose_name_plural = _("drafts")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="draft",
|
||||
related_query_name="draft",
|
||||
)
|
||||
|
||||
# internals
|
||||
|
||||
max_version = models.PositiveSmallIntegerField(default=1)
|
||||
|
||||
def editable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_agency:
|
||||
# initial publish
|
||||
return self.product.stage.pk == "waiting_for_draft"
|
||||
elif user.is_supplier:
|
||||
# improvements from supplier
|
||||
return self.product.stage.pk == "waiting_for_draft_accept_by_supplier"
|
||||
if user.is_client:
|
||||
# improvements from agency
|
||||
return self.product.stage.pk in [
|
||||
"waiting_for_draft_accept_by_supplier",
|
||||
"waiting_for_draft_accept_by_client",
|
||||
]
|
||||
return False
|
||||
|
||||
def acceptable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_supplier:
|
||||
return self.product.stage.pk == "waiting_for_draft_accept_by_supplier"
|
||||
if user.is_client:
|
||||
return self.product.stage.pk == "waiting_for_draft_accept_by_client"
|
||||
return False
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
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.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class FirstFinal(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_ff,
|
||||
"delete": rules.can_delete_ff,
|
||||
"change": rules.can_update_ff,
|
||||
"view": rules.can_read_ff,
|
||||
}
|
||||
verbose_name = _("first final")
|
||||
verbose_name_plural = _("first finals")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="first_final",
|
||||
related_query_name="first_final",
|
||||
)
|
||||
|
||||
# internals
|
||||
|
||||
max_version = models.PositiveSmallIntegerField(default=1)
|
||||
|
||||
# serializer methods
|
||||
|
||||
def editable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_agency:
|
||||
# initial publish
|
||||
return self.product.stage.pk == "waiting_for_ff"
|
||||
elif user.is_supplier:
|
||||
# improvements from supplier
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_ff" and self.files.count() > 0
|
||||
) or self.product.stage.pk == "waiting_for_ff_accept_by_supplier"
|
||||
if user.is_client:
|
||||
# improvements from agency
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_ff" and self.files.count() > 0
|
||||
) or self.product.stage.pk in [
|
||||
"waiting_for_ff_accept_by_supplier",
|
||||
"waiting_for_ff_accept_by_client",
|
||||
]
|
||||
return False
|
||||
|
||||
def acceptable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_supplier:
|
||||
return self.product.stage.pk == "waiting_for_ff_accept_by_supplier"
|
||||
if user.is_client:
|
||||
return self.product.stage.pk == "waiting_for_ff_accept_by_client"
|
||||
return False
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
|
||||
|
||||
class PackagingType(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
verbose_name = _("packaging type")
|
||||
verbose_name_plural = _("packaging types")
|
||||
ordering = ["name"]
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules import always_allow
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
|
||||
class Photo(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {"view": always_allow, "delete": rules.can_delete_photo}
|
||||
|
||||
brief = models.ForeignKey(
|
||||
verbose_name=_("brief"),
|
||||
to="Brief",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="photos",
|
||||
related_query_name="photo",
|
||||
)
|
||||
|
||||
def product_photo_filename(self, filename):
|
||||
return f"projects/{self.brief.product.project.slug[1:]}/photos/{filename}"
|
||||
|
||||
file = models.ImageField(verbose_name=_("file"), upload_to=product_photo_filename)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
@@ -0,0 +1,69 @@
|
||||
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.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class PrintingFiles(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_pf,
|
||||
"delete": rules.can_delete_pf,
|
||||
"change": rules.can_update_pf,
|
||||
"view": rules.can_read_pf,
|
||||
}
|
||||
verbose_name = _("printing files")
|
||||
verbose_name_plural = _("printing files")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="printing_files",
|
||||
related_query_name="printing_files",
|
||||
)
|
||||
|
||||
# internals
|
||||
|
||||
max_version = models.PositiveSmallIntegerField(default=1)
|
||||
|
||||
# serializer methods
|
||||
|
||||
def editable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_agency:
|
||||
# initial publish
|
||||
return self.product.stage.pk == "waiting_for_pf"
|
||||
elif user.is_supplier:
|
||||
# improvements from supplier
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_pf" and self.files.count() > 0
|
||||
) or self.product.stage.pk == "waiting_for_pf_accept_by_supplier"
|
||||
if user.is_client:
|
||||
# improvements from agency
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_pf" and self.files.count() > 0
|
||||
) or self.product.stage.pk in [
|
||||
"waiting_for_pf_accept_by_supplier",
|
||||
"waiting_for_pf_accept_by_client",
|
||||
]
|
||||
return False
|
||||
|
||||
def acceptable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_supplier:
|
||||
return self.product.stage.pk == "waiting_for_pf_accept_by_supplier"
|
||||
if user.is_client:
|
||||
return self.product.stage.pk == "waiting_for_pf_accept_by_client"
|
||||
return False
|
||||
+621
@@ -0,0 +1,621 @@
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_fsm import transition, RETURN_VALUE
|
||||
from outbo.apps.activity_log.enums import MessageType
|
||||
from outbo.apps.activity_log.models import ProductLogItem
|
||||
from outbo.apps.authentication.models import User
|
||||
from outbo.apps.core.fields import FSMKeyField
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.photobriefing.models import Brief as PhotoBrief
|
||||
from outbo.apps.products import rules
|
||||
from outbo.apps.products.models import (
|
||||
ProductStage,
|
||||
Draft,
|
||||
Brief,
|
||||
BriefConfig,
|
||||
FirstFinal,
|
||||
PrintingFiles,
|
||||
)
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
|
||||
class Product(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"retrieve": rules.can_create_product,
|
||||
"view": rules.can_read_product,
|
||||
"update": rules.can_update_product,
|
||||
"partial_update": rules.can_update_product,
|
||||
"delete": rules.can_delete_product,
|
||||
}
|
||||
|
||||
project = models.ForeignKey(
|
||||
verbose_name=_("project"),
|
||||
to="projects.Project",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="products",
|
||||
related_query_name="product",
|
||||
null=False,
|
||||
)
|
||||
stage = FSMKeyField(
|
||||
verbose_name=_("stage"),
|
||||
to="ProductStage",
|
||||
on_delete=models.CASCADE,
|
||||
default="waiting_for_brief",
|
||||
protected=True,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"""{self.brief}"""
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return (
|
||||
f"""{self.brief.model_no or "<no model number>"} / {self.brief.name_pol or "<no model name>"}"""
|
||||
)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
result = super().save(
|
||||
force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields,
|
||||
)
|
||||
if self.project:
|
||||
self.project.save(using=using)
|
||||
|
||||
def has_complete_brief(self):
|
||||
conditions = [
|
||||
self.brief.name_pol,
|
||||
self.brief.photos.count() > 0,
|
||||
self.brief.blankings.count() > 0,
|
||||
self.brief.name_eng,
|
||||
self.brief.model_no,
|
||||
self.brief.batch_no,
|
||||
self.brief.ean,
|
||||
self.brief.packaging_type,
|
||||
self.brief.main_description,
|
||||
self.brief.extra_description,
|
||||
self.brief.certificate,
|
||||
self.brief.information_signs,
|
||||
self.brief.capabilities_icons,
|
||||
self.brief.product_icons,
|
||||
]
|
||||
if self.brief.config.illustration:
|
||||
conditions += [self.brief.illustration]
|
||||
if self.brief.config.cleaning_method:
|
||||
conditions += [self.brief.cleaning_method]
|
||||
if self.brief.config.country_of_origin:
|
||||
conditions += [self.brief.country_of_origin]
|
||||
if self.brief.config.manual:
|
||||
conditions += [self.brief.manual]
|
||||
if self.brief.config.material:
|
||||
conditions += [self.brief.material]
|
||||
if self.brief.config.warning:
|
||||
conditions += [self.brief.warning]
|
||||
if self.brief.config.photobrief:
|
||||
conditions += [self.brief.arrangement_photo]
|
||||
if self.brief.config.packshot:
|
||||
conditions += [self.brief.packshot]
|
||||
if self.brief.config.clipping:
|
||||
conditions += [self.brief.clipping]
|
||||
if self.brief.config.extra_photo:
|
||||
conditions += [self.brief.extra_photo]
|
||||
|
||||
return reduce(lambda accumulator, current: accumulator and bool(current), conditions, True)
|
||||
|
||||
def has_brief_publishable_for_user(self, *, user: "User") -> bool:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief")
|
||||
return user.is_supplier and self.stage == stage and self.has_complete_brief()
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief", target="waiting_for_brief_accept_by_agency",
|
||||
)
|
||||
def publish_brief_as_user(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
def accept_brief_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
product_ids = [member.user.id for member in self.project.members.all()]
|
||||
traders = User.objects.filter(is_trader=True, category=self.project.category).exclude(
|
||||
id__in=product_ids
|
||||
)
|
||||
if self.stage.pk == "waiting_for_brief_accept_by_agency":
|
||||
self._accept_brief_as_agency(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in traders and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
elif self.stage.pk == "waiting_for_brief_accept_by_client":
|
||||
self._accept_brief_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_brief_accept_by_agency",
|
||||
target="waiting_for_brief_accept_by_client",
|
||||
)
|
||||
def _accept_brief_as_agency(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_agency")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_brief_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief_accept_by_client", target="waiting_for_draft",
|
||||
)
|
||||
def _accept_brief_as_client(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
Draft.objects.create(product=self)
|
||||
if self.brief.config.photobrief:
|
||||
PhotoBrief.objects.create(product=self)
|
||||
return "waiting_for_draft"
|
||||
|
||||
def reject_brief_as_user(self, *, user: "User") -> None:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.pk == "waiting_for_brief_accept_by_agency":
|
||||
self._reject_brief_as_agency(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
elif self.stage.pk == "waiting_for_brief_accept_by_client":
|
||||
self._reject_brief_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief_accept_by_agency", target="waiting_for_brief",
|
||||
)
|
||||
def _reject_brief_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_agency")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief rejected",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief_accept_by_client", target="waiting_for_brief",
|
||||
)
|
||||
def _reject_brief_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief rejected",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
def publish_draft_file_as_user(self, user: "User"):
|
||||
if self.stage.pk == "waiting_for_draft":
|
||||
return self._publish_draft_file_as_agency(user=user)
|
||||
if self.stage.pk == "waiting_for_draft_accept_by_supplier":
|
||||
return self._publish_draft_file_as_supplier(user=user)
|
||||
elif self.stage.pk == "waiting_for_draft_accept_by_client":
|
||||
return self._publish_draft_file_as_client(user=user)
|
||||
raise NotImplementedError
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft", target="waiting_for_draft_accept_by_supplier",
|
||||
)
|
||||
def _publish_draft_file_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft_accept_by_supplier", target="waiting_for_draft",
|
||||
)
|
||||
def _publish_draft_file_as_supplier(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft_accept_by_client", target="waiting_for_draft",
|
||||
)
|
||||
def _publish_draft_file_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
def accept_draft_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.id == "waiting_for_draft_accept_by_supplier":
|
||||
result = self._accept_draft_as_supplier(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/draft",
|
||||
)
|
||||
return result
|
||||
elif self.stage.id == "waiting_for_draft_accept_by_client":
|
||||
result = self._accept_draft_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/draft",
|
||||
)
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_draft_accept_by_supplier",
|
||||
target=RETURN_VALUE("waiting_for_draft_accept_by_client"),
|
||||
)
|
||||
def _accept_draft_as_supplier(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_draft_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft_accept_by_client", target=RETURN_VALUE("waiting_for_ff"),
|
||||
)
|
||||
def _accept_draft_as_client(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
FirstFinal.objects.create(product=self)
|
||||
return "waiting_for_ff"
|
||||
|
||||
##########################
|
||||
def publish_ff_file_as_user(self, user: "User"):
|
||||
if self.stage.pk == "waiting_for_ff":
|
||||
return self._publish_ff_file_as_agency(user=user)
|
||||
if self.stage.pk == "waiting_for_ff_accept_by_supplier":
|
||||
return self._publish_ff_file_as_supplier(user=user)
|
||||
elif self.stage.pk == "waiting_for_ff_accept_by_client":
|
||||
return self._publish_ff_file_as_client(user=user)
|
||||
raise NotImplementedError
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff", target="waiting_for_ff_accept_by_supplier",
|
||||
)
|
||||
def _publish_ff_file_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/ff",
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff_accept_by_supplier", target="waiting_for_ff",
|
||||
)
|
||||
def _publish_ff_file_as_supplier(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff_accept_by_client", target="waiting_for_ff",
|
||||
)
|
||||
def _publish_ff_file_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
def accept_ff_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.id == "waiting_for_ff_accept_by_supplier":
|
||||
product_ids = [member.user.id for member in self.project.members.all()]
|
||||
traders = User.objects.filter(is_trader=True, category=self.project.category).exclude(
|
||||
id__in=product_ids
|
||||
)
|
||||
result = self._accept_ff_as_supplier(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/ff",
|
||||
)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in traders and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
return result
|
||||
elif self.stage.id == "waiting_for_ff_accept_by_client":
|
||||
result = self._accept_ff_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_agency and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/ff",
|
||||
)
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_ff_accept_by_supplier",
|
||||
target=RETURN_VALUE("waiting_for_ff_accept_by_client"),
|
||||
)
|
||||
def _accept_ff_as_supplier(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_ff_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff_accept_by_client", target=RETURN_VALUE("waiting_for_pf"),
|
||||
)
|
||||
def _accept_ff_as_client(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
PrintingFiles.objects.create(product=self)
|
||||
return "waiting_for_pf"
|
||||
|
||||
###########
|
||||
def publish_pf_file_as_user(self, user: "User"):
|
||||
if self.stage.pk == "waiting_for_pf":
|
||||
return self._publish_pf_file_as_agency(user=user)
|
||||
if self.stage.pk == "waiting_for_pf_accept_by_supplier":
|
||||
return self._publish_pf_file_as_supplier(user=user)
|
||||
elif self.stage.pk == "waiting_for_pf_accept_by_client":
|
||||
return self._publish_pf_file_as_client(user=user)
|
||||
raise NotImplementedError
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf", target="waiting_for_pf_accept_by_supplier",
|
||||
)
|
||||
def _publish_pf_file_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/pf",
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf_accept_by_supplier", target="waiting_for_pf",
|
||||
)
|
||||
def _publish_pf_file_as_supplier(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf_accept_by_client", target="waiting_for_pf",
|
||||
)
|
||||
def _publish_pf_file_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
def accept_pf_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.id == "waiting_for_pf_accept_by_supplier":
|
||||
result = self._accept_pf_as_supplier(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/pf",
|
||||
)
|
||||
return result
|
||||
elif self.stage.id == "waiting_for_pf_accept_by_client":
|
||||
result = self._accept_pf_as_client(user=user)
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_pf_accept_by_supplier",
|
||||
target=RETURN_VALUE("waiting_for_pf_accept_by_client"),
|
||||
)
|
||||
def _accept_pf_as_supplier(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_pf_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf_accept_by_client", target=RETURN_VALUE("completed"),
|
||||
)
|
||||
def _accept_pf_as_client(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users, action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
return "completed"
|
||||
|
||||
def send_action_email_to(self, *, user: User, action_url: str, reject: bool):
|
||||
if reject is False:
|
||||
stage = self.stage.next
|
||||
else:
|
||||
stage = self.stage.reset_stage
|
||||
product_status = f"{stage.label} - {stage.description}"
|
||||
msg_html = render_to_string(
|
||||
"email/user_notification.html",
|
||||
{
|
||||
"project_slug": self.project.slug,
|
||||
"project_description": self.project.description,
|
||||
"product_model": self.brief.model_no,
|
||||
"product_name_pol": self.brief.name_pol,
|
||||
"product_status": product_status,
|
||||
"product_current_action": f"{stage.next_action}",
|
||||
"action_url": action_url.format(id=self.project_id, pid=self.id),
|
||||
},
|
||||
)
|
||||
msg_txt = strip_tags(msg_html)
|
||||
user.email_user(
|
||||
subject=f"{self.project.slug} / {product_status} / {self.brief.name_pol}",
|
||||
message=msg_txt,
|
||||
html_message=msg_html,
|
||||
)
|
||||
|
||||
def send_action_emails_for_users_passing_test(self, *, test, action_url, reject: bool = False):
|
||||
users = [*User.objects.all()]
|
||||
client_users = filter(test, users)
|
||||
for u in client_users:
|
||||
self.send_action_email_to(user=u, action_url=action_url, reject=reject)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Product)
|
||||
def create_brief(sender, instance: Product, created, **kwargs):
|
||||
if created:
|
||||
Brief.objects.get_or_create(product=instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Brief)
|
||||
def create_brief_config(sender, instance: Brief, created, **kwargs):
|
||||
if created:
|
||||
BriefConfig.objects.get_or_create(brief=instance)
|
||||
@@ -0,0 +1,41 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
|
||||
|
||||
class ProductStage(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
ordering = ["label"]
|
||||
|
||||
id = models.CharField(verbose_name=_("ID"), primary_key=True, max_length=50)
|
||||
label = models.CharField(verbose_name=_("label"), max_length=2)
|
||||
group = models.PositiveSmallIntegerField(verbose_name=_("group"), null=False)
|
||||
description = models.CharField(verbose_name=_("description"), max_length=254)
|
||||
next_action = models.CharField(verbose_name=_("next action"), max_length=254)
|
||||
order = models.PositiveSmallIntegerField(verbose_name=_("order"))
|
||||
|
||||
def __str__(self):
|
||||
return f"[{self.label}] {self.description}"
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
try:
|
||||
return ProductStage.objects.get(order=self.order + 1)
|
||||
except Exception:
|
||||
return ProductStage.objects.order_by("-order").first()
|
||||
|
||||
@property
|
||||
def reset_stage(self):
|
||||
stage_map = {
|
||||
"waiting_for_brief_accept_by_agency": "waiting_for_brief",
|
||||
"waiting_for_brief_accept_by_client": "waiting_for_brief",
|
||||
"waiting_for_draft_accept_by_supplier": "waiting_for_draft",
|
||||
"waiting_for_draft_accept_by_client": "waiting_for_draft",
|
||||
"waiting_for_ff_accept_by_client": "waiting_for_ff",
|
||||
"waiting_for_ff_accept_by_supplier": "waiting_for_ff",
|
||||
"waiting_for_pf_accept_by_supplier": "waiting_for_pf",
|
||||
"waiting_for_pf_accept_by_client": "waiting_for_pf",
|
||||
}
|
||||
return ProductStage.objects.get(id=stage_map[self.id])
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from outbo.apps.core.rules import is_client, is_agency, is_supplier
|
||||
|
||||
|
||||
def read_product_query(user):
|
||||
assigned_to_user_query = (
|
||||
Q(project__member__user=user, project__member__deleted_at__isnull=True)
|
||||
if user.is_observer is False and user.is_trader is False
|
||||
else Q()
|
||||
)
|
||||
if is_client.test(user):
|
||||
return assigned_to_user_query & Q(project__published=True)
|
||||
elif (is_agency | is_supplier).test(user):
|
||||
return assigned_to_user_query & Q()
|
||||
return ~Q()
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
from rules import predicate, always_deny, always_allow
|
||||
|
||||
from outbo.apps.projects.rules import can_read_project
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
from outbo.apps.products.models import (
|
||||
Product,
|
||||
Brief,
|
||||
Draft,
|
||||
FirstFinal,
|
||||
PrintingFiles,
|
||||
)
|
||||
|
||||
|
||||
# Product
|
||||
@predicate
|
||||
def _has_readable_project(user: "User", product: "Product"):
|
||||
return can_read_project.test(user, product.project)
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_product = always_deny
|
||||
can_read_product = _has_readable_project
|
||||
can_update_product = always_deny
|
||||
can_delete_product = always_deny
|
||||
|
||||
# Shared
|
||||
@predicate
|
||||
def _has_readable_product(user: "User", obj: Union["Brief", "Draft"]):
|
||||
return can_read_product.test(user, obj.product)
|
||||
|
||||
|
||||
# Brief
|
||||
@predicate
|
||||
def can_publish_brief(user: "User", brief: "Brief"):
|
||||
return brief.publishable_for_user(user=user)
|
||||
|
||||
|
||||
@predicate
|
||||
def can_edit_brief(user: "User", brief: "Brief"):
|
||||
return brief.editable_for_user(user=user)
|
||||
|
||||
|
||||
@predicate
|
||||
def can_accept_brief(user: "User", brief: "Brief"):
|
||||
return brief.acceptable_for_user(user=user)
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_brief = always_deny
|
||||
can_read_brief = _has_readable_product
|
||||
can_update_brief = can_edit_brief | can_accept_brief
|
||||
can_delete_brief = always_deny
|
||||
|
||||
|
||||
@predicate
|
||||
def _has_editable_brief(user, photo_or_blanking):
|
||||
return can_edit_brief.test(user, photo_or_blanking.brief)
|
||||
|
||||
|
||||
# Brief files:
|
||||
can_delete_photo = _has_editable_brief
|
||||
can_delete_blanking = _has_editable_brief
|
||||
|
||||
|
||||
# Draft
|
||||
@predicate
|
||||
def can_edit_draft(user: "User", draft: "Draft"):
|
||||
return draft.editable_for_user(user=user)
|
||||
|
||||
|
||||
@predicate
|
||||
def can_accept_draft(user: "User", draft: "Draft"):
|
||||
return draft.acceptable_for_user(user=user)
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_draft = always_deny
|
||||
can_read_draft = _has_readable_product
|
||||
can_update_draft = can_edit_draft | can_accept_draft
|
||||
can_delete_draft = always_deny
|
||||
|
||||
# Draft Files
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_draft_file = always_allow
|
||||
can_read_draft_file = always_allow
|
||||
can_update_draft_file = always_deny
|
||||
can_delete_draft_file = always_deny
|
||||
|
||||
# FF
|
||||
@predicate
|
||||
def can_edit_ff(user: "User", ff: "FirstFinal"):
|
||||
return ff.editable_for_user(user=user)
|
||||
|
||||
|
||||
@predicate
|
||||
def can_accept_ff(user: "User", ff: "FirstFinal"):
|
||||
return ff.acceptable_for_user(user=user)
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_ff = always_deny
|
||||
can_read_ff = _has_readable_product
|
||||
can_update_ff = can_edit_ff | can_accept_ff
|
||||
can_delete_ff = always_deny
|
||||
|
||||
# Draft Files
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_ff_file = always_allow
|
||||
can_read_ff_file = always_allow
|
||||
can_update_ff_file = always_deny
|
||||
can_delete_ff_file = always_deny
|
||||
|
||||
|
||||
# PF
|
||||
@predicate
|
||||
def can_edit_pf(user: "User", pf: "PrintingFiles"):
|
||||
return pf.editable_for_user(user=user)
|
||||
|
||||
|
||||
@predicate
|
||||
def can_accept_pf(user: "User", pf: "PrintingFiles"):
|
||||
return pf.acceptable_for_user(user=user)
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_pf = always_deny
|
||||
can_read_pf = _has_readable_product
|
||||
can_update_pf = can_edit_pf | can_accept_pf
|
||||
can_delete_pf = always_deny
|
||||
|
||||
# Draft Files
|
||||
|
||||
|
||||
# CRUD
|
||||
can_create_pf_file = always_allow
|
||||
can_read_pf_file = always_allow
|
||||
can_update_pf_file = always_deny
|
||||
can_delete_pf_file = always_deny
|
||||
@@ -0,0 +1,13 @@
|
||||
from .photo import PhotoSerializer
|
||||
from .blanking import BlankingSerializer
|
||||
from .brief_config import BriefConfigSerializer
|
||||
from .brief import BriefSerializer
|
||||
from .product import ProductSerializer
|
||||
from .packaging_type import PackagingTypeSerializer
|
||||
from .product_stage import ProductStageSerializer
|
||||
from .design_stage_file import (
|
||||
DraftFileSerializer,
|
||||
FirstFinalFileSerializer,
|
||||
PrintingFilesFileSerializer,
|
||||
)
|
||||
from .design_stage import DraftSerializer, FirstFinalSerializer, PrintingFilesSerializer
|
||||
@@ -0,0 +1,19 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import Blanking
|
||||
|
||||
|
||||
class BlankingSerializer:
|
||||
class Create(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Blanking
|
||||
fields = ["file"]
|
||||
|
||||
class List(serializers.ModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name="products:blanking-detail")
|
||||
filename = serializers.ReadOnlyField()
|
||||
size = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = Blanking
|
||||
fields = ["id", "filename", "url", "size"]
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
from outbo.apps.products.models import Brief, Photo, Blanking
|
||||
from outbo.apps.products.serializers import (
|
||||
PhotoSerializer,
|
||||
BlankingSerializer,
|
||||
BriefConfigSerializer,
|
||||
)
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
|
||||
class BaseBriefSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Brief
|
||||
|
||||
|
||||
class BriefSerializer:
|
||||
class Retrieve(BaseBriefSerializer):
|
||||
photos = PhotoSerializer.List(many=True)
|
||||
blankings = BlankingSerializer.List(many=True)
|
||||
config = BriefConfigSerializer.Retrieve()
|
||||
editable = serializers.SerializerMethodField()
|
||||
acceptable = serializers.SerializerMethodField()
|
||||
publishable = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(BaseBriefSerializer.Meta):
|
||||
fields = [
|
||||
"config",
|
||||
"name_pol",
|
||||
"name_eng",
|
||||
"model_no",
|
||||
"batch_no",
|
||||
"ean",
|
||||
"packaging_type",
|
||||
"main_description",
|
||||
"extra_description",
|
||||
"photos",
|
||||
"blankings",
|
||||
"certificate",
|
||||
"information_signs",
|
||||
"capabilities_icons",
|
||||
"product_icons",
|
||||
"has_description_eng",
|
||||
"has_description_pol",
|
||||
"has_description_por",
|
||||
"has_description_rus",
|
||||
"has_description_spa",
|
||||
"illustration",
|
||||
"cleaning_method",
|
||||
"country_of_origin",
|
||||
"manual",
|
||||
"material",
|
||||
"warning",
|
||||
"arrangement_photo",
|
||||
"packshot",
|
||||
"clipping",
|
||||
"extra_photo",
|
||||
"editable",
|
||||
"acceptable",
|
||||
"publishable",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
def get_editable(self, brief: Brief):
|
||||
return brief.editable_for_user(user=self.context["request"].user)
|
||||
|
||||
def get_acceptable(self, brief: Brief):
|
||||
return brief.acceptable_for_user(user=self.context["request"].user)
|
||||
|
||||
def get_publishable(self, brief: Brief):
|
||||
return brief.publishable_for_user(user=self.context["request"].user)
|
||||
|
||||
class Publish(BaseBriefSerializer):
|
||||
publish = serializers.BooleanField()
|
||||
|
||||
class Meta(BaseBriefSerializer.Meta):
|
||||
fields = ["publish"]
|
||||
|
||||
def validate(self, attrs):
|
||||
instance: "Brief" = self.instance
|
||||
if not instance.publishable_for_user(user=self.context["request"].user):
|
||||
raise PermissionDenied()
|
||||
return super().validate(attrs)
|
||||
|
||||
def update(self, instance: "Brief", validated_data):
|
||||
members = [m.user for m in instance.product.project.members.all()]
|
||||
current_user = self.context["request"].user
|
||||
|
||||
product = instance.product
|
||||
product.publish_brief_as_user(user=current_user)
|
||||
product.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in members and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
product.save()
|
||||
return instance
|
||||
|
||||
class Accept(BaseBriefSerializer):
|
||||
accept = serializers.BooleanField()
|
||||
|
||||
class Meta(BaseBriefSerializer.Meta):
|
||||
fields = ["accept"]
|
||||
|
||||
def validate(self, attrs):
|
||||
instance: "Brief" = self.instance
|
||||
if not instance.acceptable_for_user(user=self.context["request"].user):
|
||||
raise PermissionDenied()
|
||||
return super().validate(attrs)
|
||||
|
||||
def update(self, instance: "Brief", validated_data):
|
||||
current_user = self.context["request"].user
|
||||
product = instance.product
|
||||
if validated_data["accept"] is True:
|
||||
product.accept_brief_as_user(user=current_user)
|
||||
elif validated_data["accept"] is False:
|
||||
product.reject_brief_as_user(user=current_user)
|
||||
product.save()
|
||||
return instance
|
||||
|
||||
class Clone(BaseBriefSerializer):
|
||||
clone_from = serializers.IntegerField()
|
||||
|
||||
class Meta(BaseBriefSerializer.Meta):
|
||||
fields = ["clone_from"]
|
||||
|
||||
def update(self, instance: "Brief", validated_data):
|
||||
clone_from = validated_data.pop("clone_from", None)
|
||||
if clone_from is not None:
|
||||
clone_from = Brief.objects.get(pk=clone_from)
|
||||
clonable_fields = [
|
||||
"packaging_type",
|
||||
"main_description",
|
||||
"extra_description",
|
||||
"certificate",
|
||||
"information_signs",
|
||||
"capabilities_icons",
|
||||
"product_icons",
|
||||
"has_description_eng",
|
||||
"has_description_pol",
|
||||
"has_description_por",
|
||||
"has_description_rus",
|
||||
"has_description_spa",
|
||||
"illustration",
|
||||
"cleaning_method",
|
||||
"country_of_origin",
|
||||
"manual",
|
||||
"material",
|
||||
"warning",
|
||||
"arrangement_photo",
|
||||
"packshot",
|
||||
"clipping",
|
||||
"extra_photo",
|
||||
]
|
||||
clonable_config_fields = [
|
||||
"illustration",
|
||||
"cleaning_method",
|
||||
"country_of_origin",
|
||||
"manual",
|
||||
"material",
|
||||
"warning",
|
||||
"photobrief",
|
||||
"packshot",
|
||||
"clipping",
|
||||
"extra_photo",
|
||||
]
|
||||
for item in clonable_config_fields:
|
||||
setattr(instance.config, item, getattr(clone_from.config, item))
|
||||
for item in clonable_fields:
|
||||
setattr(instance, item, getattr(clone_from, item))
|
||||
instance.config.save()
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
class Update(BaseBriefSerializer):
|
||||
accept = serializers.BooleanField()
|
||||
clone_from = serializers.IntegerField()
|
||||
|
||||
publish = serializers.BooleanField()
|
||||
|
||||
config = BriefConfigSerializer.Update()
|
||||
photos = PhotoSerializer.Create(many=True)
|
||||
blankings = BlankingSerializer.Create(many=True)
|
||||
|
||||
class Meta(BaseBriefSerializer.Meta):
|
||||
fields = [
|
||||
"clone_from",
|
||||
"accept",
|
||||
"publish",
|
||||
"config",
|
||||
"name_pol",
|
||||
"name_eng",
|
||||
"model_no",
|
||||
"batch_no",
|
||||
"ean",
|
||||
"packaging_type",
|
||||
"main_description",
|
||||
"extra_description",
|
||||
"photos",
|
||||
"blankings",
|
||||
"certificate",
|
||||
"information_signs",
|
||||
"capabilities_icons",
|
||||
"product_icons",
|
||||
"has_description_eng",
|
||||
"has_description_pol",
|
||||
"has_description_por",
|
||||
"has_description_rus",
|
||||
"has_description_spa",
|
||||
"illustration",
|
||||
"cleaning_method",
|
||||
"country_of_origin",
|
||||
"manual",
|
||||
"material",
|
||||
"warning",
|
||||
"arrangement_photo",
|
||||
"packshot",
|
||||
"clipping",
|
||||
"extra_photo",
|
||||
]
|
||||
|
||||
def update(self, instance: Brief, validated_data):
|
||||
if validated_data.get("clone_from", None) is not None:
|
||||
serializer = BriefSerializer.Clone(
|
||||
instance=instance, data=validated_data, context=self.context
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.update(
|
||||
instance=instance, validated_data=validated_data
|
||||
) # NOTE: looks weird
|
||||
|
||||
if validated_data.get("accept", None) is not None:
|
||||
serializer = BriefSerializer.Accept(
|
||||
instance=instance, data=validated_data, context=self.context
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.update(
|
||||
instance=instance, validated_data=validated_data
|
||||
) # NOTE: looks weird
|
||||
|
||||
if validated_data.get("publish", None) is not None:
|
||||
serializer = BriefSerializer.Publish(
|
||||
instance=instance, data=validated_data, context=self.context
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.update(
|
||||
instance=instance, validated_data=validated_data
|
||||
) # NOTE: looks weird
|
||||
|
||||
config = validated_data.pop("config", None)
|
||||
|
||||
photos = validated_data.pop("photos", [])
|
||||
blankings = validated_data.pop("blankings", [])
|
||||
|
||||
for photo in photos:
|
||||
Photo.objects.create(brief=instance, **photo)
|
||||
for blanking in blankings:
|
||||
Blanking.objects.create(brief=instance, **blanking)
|
||||
|
||||
if config:
|
||||
for (key, value) in config.items():
|
||||
setattr(instance.config, key, value)
|
||||
instance.config.save()
|
||||
|
||||
result = super().update(instance=instance, validated_data=validated_data)
|
||||
product = result.product
|
||||
product.save() # bump updated_at
|
||||
return result
|
||||
@@ -0,0 +1,33 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import BriefConfig
|
||||
|
||||
|
||||
class _BaseBriefConfigSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = BriefConfig
|
||||
fields = [
|
||||
"illustration",
|
||||
"cleaning_method",
|
||||
"country_of_origin",
|
||||
"manual",
|
||||
"material",
|
||||
"warning",
|
||||
"photobrief",
|
||||
"packshot",
|
||||
"clipping",
|
||||
"extra_photo",
|
||||
]
|
||||
|
||||
|
||||
class BriefConfigSerializer:
|
||||
class Retrieve(_BaseBriefConfigSerializer):
|
||||
class Meta(_BaseBriefConfigSerializer.Meta):
|
||||
fields = _BaseBriefConfigSerializer.Meta.fields + [
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
class Update(_BaseBriefConfigSerializer):
|
||||
class Meta(_BaseBriefConfigSerializer.Meta):
|
||||
pass
|
||||
@@ -0,0 +1,159 @@
|
||||
from typing import Union
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from outbo.apps.products.models import Draft, PrintingFiles, FirstFinal
|
||||
from outbo.apps.products.serializers import (
|
||||
DraftFileSerializer,
|
||||
FirstFinalFileSerializer,
|
||||
PrintingFilesFileSerializer,
|
||||
)
|
||||
|
||||
|
||||
class DesignStageSerializer(serializers.ModelSerializer):
|
||||
class Retrieve(serializers.ModelSerializer):
|
||||
acceptable = serializers.SerializerMethodField()
|
||||
editable = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
fields = ["acceptable", "editable", "files", "created_at", "updated_at"]
|
||||
|
||||
def get_acceptable(self, instance: Union[Draft, FirstFinal, PrintingFiles]):
|
||||
return instance.acceptable_for_user(user=self.context["request"].user)
|
||||
|
||||
def get_editable(self, instance: Union[Draft, FirstFinal, PrintingFiles]):
|
||||
return instance.editable_for_user(user=self.context["request"].user)
|
||||
|
||||
class Accept(serializers.ModelSerializer):
|
||||
accept = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
fields = ["accept"]
|
||||
|
||||
class Update(serializers.ModelSerializer):
|
||||
accept = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
fields = ["accept"]
|
||||
|
||||
|
||||
class DraftSerializer:
|
||||
class Retrieve(DesignStageSerializer.Retrieve):
|
||||
files = DraftFileSerializer.Retrieve(many=True)
|
||||
|
||||
class Meta(DesignStageSerializer.Retrieve.Meta):
|
||||
model = Draft
|
||||
|
||||
class Accept(DesignStageSerializer.Accept):
|
||||
class Meta(DesignStageSerializer.Accept.Meta):
|
||||
model = Draft
|
||||
|
||||
def validate(self, attrs):
|
||||
instance: "Draft" = self.instance
|
||||
if not instance.acceptable_for_user(user=self.context["request"].user):
|
||||
raise PermissionDenied()
|
||||
return super().validate(attrs)
|
||||
|
||||
def update(self, instance: "Draft", validated_data):
|
||||
current_user = self.context["request"].user
|
||||
product = instance.product
|
||||
if validated_data["accept"] is True:
|
||||
product.accept_draft_as_user(user=current_user)
|
||||
product.save()
|
||||
return instance
|
||||
|
||||
class Update(DesignStageSerializer.Update):
|
||||
class Meta(DesignStageSerializer.Update.Meta):
|
||||
model = Draft
|
||||
|
||||
def update(self, instance: Draft, validated_data):
|
||||
serializer = DraftSerializer.Accept(
|
||||
instance=instance, data=validated_data, context=self.context
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.update(
|
||||
instance=instance, validated_data=validated_data
|
||||
) # NOTE: looks weird
|
||||
|
||||
|
||||
class FirstFinalSerializer:
|
||||
class Retrieve(DesignStageSerializer.Retrieve):
|
||||
files = FirstFinalFileSerializer.Retrieve(many=True)
|
||||
|
||||
class Meta(DesignStageSerializer.Retrieve.Meta):
|
||||
model = FirstFinal
|
||||
|
||||
class Accept(DesignStageSerializer.Accept):
|
||||
class Meta(DesignStageSerializer.Accept.Meta):
|
||||
model = FirstFinal
|
||||
|
||||
def validate(self, attrs):
|
||||
instance: "FirstFinal" = self.instance
|
||||
if not instance.acceptable_for_user(user=self.context["request"].user):
|
||||
raise PermissionDenied()
|
||||
return super().validate(attrs)
|
||||
|
||||
def update(self, instance: "FirstFinal", validated_data):
|
||||
current_user = self.context["request"].user
|
||||
product = instance.product
|
||||
if validated_data["accept"] is True:
|
||||
product.accept_ff_as_user(user=current_user)
|
||||
elif validated_data["accept"] is False:
|
||||
product.reject_ff_as_user(user=current_user)
|
||||
product.save()
|
||||
return instance
|
||||
|
||||
class Update(DesignStageSerializer.Update):
|
||||
class Meta(DesignStageSerializer.Update.Meta):
|
||||
model = FirstFinal
|
||||
|
||||
def update(self, instance: FirstFinal, validated_data):
|
||||
serializer = FirstFinalSerializer.Accept(
|
||||
instance=instance, data=validated_data, context=self.context
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.update(
|
||||
instance=instance, validated_data=validated_data
|
||||
) # NOTE: looks weird
|
||||
|
||||
|
||||
class PrintingFilesSerializer:
|
||||
class Retrieve(DesignStageSerializer.Retrieve):
|
||||
files = PrintingFilesFileSerializer.Retrieve(many=True)
|
||||
|
||||
class Meta(DesignStageSerializer.Retrieve.Meta):
|
||||
model = PrintingFiles
|
||||
|
||||
class Accept(DesignStageSerializer.Accept):
|
||||
class Meta(DesignStageSerializer.Accept.Meta):
|
||||
model = PrintingFiles
|
||||
|
||||
def validate(self, attrs):
|
||||
instance: "PrintingFiles" = self.instance
|
||||
if not instance.acceptable_for_user(user=self.context["request"].user):
|
||||
raise PermissionDenied()
|
||||
return super().validate(attrs)
|
||||
|
||||
def update(self, instance: "PrintingFiles", validated_data):
|
||||
current_user = self.context["request"].user
|
||||
product = instance.product
|
||||
if validated_data["accept"] is True:
|
||||
product.accept_pf_as_user(user=current_user)
|
||||
elif validated_data["accept"] is False:
|
||||
product.reject_pf_as_user(user=current_user)
|
||||
product.save()
|
||||
return instance
|
||||
|
||||
class Update(DesignStageSerializer.Update):
|
||||
class Meta(DesignStageSerializer.Update.Meta):
|
||||
model = PrintingFiles
|
||||
|
||||
def update(self, instance: PrintingFiles, validated_data):
|
||||
serializer = PrintingFilesSerializer.Accept(
|
||||
instance=instance, data=validated_data, context=self.context
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.update(
|
||||
instance=instance, validated_data=validated_data
|
||||
) # NOTE: looks weird
|
||||
@@ -0,0 +1,150 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import DraftFile, FirstFinalFile, PrintingFilesFile
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class DesignStageFileSerializer:
|
||||
class Retrieve(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
fields = [
|
||||
"id",
|
||||
"url",
|
||||
"filename",
|
||||
"size",
|
||||
"version",
|
||||
"description",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"opened_by",
|
||||
]
|
||||
|
||||
filename = serializers.ReadOnlyField()
|
||||
size = serializers.ReadOnlyField()
|
||||
|
||||
class Create(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
fields = ["file", "description"]
|
||||
|
||||
def save(self, **kwargs):
|
||||
created_by = self.context["request"].user
|
||||
kwargs["created_by"] = created_by
|
||||
return super().save(**kwargs)
|
||||
|
||||
|
||||
class BaseDraftFileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DraftFile
|
||||
|
||||
|
||||
class BaseFirstFinalFileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FirstFinalFile
|
||||
|
||||
|
||||
class BasePrintingFilesFileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PrintingFilesFile
|
||||
|
||||
|
||||
class DraftFileSerializer:
|
||||
class Retrieve(DesignStageFileSerializer.Retrieve):
|
||||
class Meta(
|
||||
DesignStageFileSerializer.Retrieve.Meta, BaseDraftFileSerializer.Meta
|
||||
):
|
||||
pass
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="products:draftfile-detail"
|
||||
)
|
||||
|
||||
class Create(DesignStageFileSerializer.Create):
|
||||
class Meta(DesignStageFileSerializer.Create.Meta, BaseDraftFileSerializer.Meta):
|
||||
pass
|
||||
|
||||
def create(self, validated_data):
|
||||
current_user: "User" = self.context["request"].user
|
||||
result: DraftFile = super().create(validated_data)
|
||||
|
||||
draft = result.draft
|
||||
draft.save()
|
||||
|
||||
product = draft.product
|
||||
product.publish_draft_file_as_user(user=current_user)
|
||||
|
||||
product.save()
|
||||
product.project.save()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class FirstFinalFileSerializer:
|
||||
class Retrieve(DesignStageFileSerializer.Retrieve):
|
||||
class Meta(
|
||||
DesignStageFileSerializer.Retrieve.Meta, BaseFirstFinalFileSerializer.Meta
|
||||
):
|
||||
pass
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="products:firstfinalfile-detail"
|
||||
)
|
||||
|
||||
class Create(DesignStageFileSerializer.Create):
|
||||
class Meta(
|
||||
DesignStageFileSerializer.Create.Meta, BaseFirstFinalFileSerializer.Meta
|
||||
):
|
||||
pass
|
||||
|
||||
def create(self, validated_data):
|
||||
current_user = self.context["request"].user
|
||||
result: DraftFile = super().create(validated_data)
|
||||
|
||||
ff = result.first_final
|
||||
ff.save()
|
||||
|
||||
product = ff.product
|
||||
product.publish_ff_file_as_user(user=current_user)
|
||||
|
||||
product.save()
|
||||
product.project.save()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class PrintingFilesFileSerializer:
|
||||
class Retrieve(DesignStageFileSerializer.Retrieve):
|
||||
class Meta(
|
||||
DesignStageFileSerializer.Retrieve.Meta,
|
||||
BasePrintingFilesFileSerializer.Meta,
|
||||
):
|
||||
pass
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="products:printingfilesfile-detail"
|
||||
)
|
||||
|
||||
class Create(DesignStageFileSerializer.Create):
|
||||
class Meta(
|
||||
DesignStageFileSerializer.Create.Meta, BasePrintingFilesFileSerializer.Meta
|
||||
):
|
||||
pass
|
||||
|
||||
def create(self, validated_data):
|
||||
current_user = self.context["request"].user
|
||||
result: PrintingFilesFile = super().create(validated_data)
|
||||
|
||||
pf = result.printing_files
|
||||
pf.save()
|
||||
|
||||
product = pf.product
|
||||
product.publish_pf_file_as_user(user=current_user)
|
||||
|
||||
product.save()
|
||||
product.project.save()
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,10 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import PackagingType
|
||||
|
||||
|
||||
class PackagingTypeSerializer:
|
||||
class List(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PackagingType
|
||||
fields = ["id", "name"]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import Photo
|
||||
|
||||
|
||||
class PhotoSerializer:
|
||||
class Create(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Photo
|
||||
fields = ["file"]
|
||||
|
||||
class List(serializers.ModelSerializer):
|
||||
filename = serializers.ReadOnlyField()
|
||||
url = serializers.HyperlinkedIdentityField(view_name="products:photo-detail")
|
||||
|
||||
class Meta:
|
||||
model = Photo
|
||||
fields = [
|
||||
"id",
|
||||
"url",
|
||||
"filename",
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import Product
|
||||
|
||||
|
||||
class BaseProductSerializer(serializers.ModelSerializer):
|
||||
brief = serializers.HyperlinkedRelatedField(
|
||||
view_name="products:brief-detail", read_only=True
|
||||
)
|
||||
|
||||
draft = serializers.HyperlinkedRelatedField(
|
||||
view_name="products:draft-detail", read_only=True
|
||||
)
|
||||
first_final = serializers.HyperlinkedRelatedField(
|
||||
view_name="products:firstfinal-detail", read_only=True
|
||||
)
|
||||
printing_files = serializers.HyperlinkedRelatedField(
|
||||
view_name="products:printingfiles-detail", read_only=True
|
||||
)
|
||||
photo_brief = serializers.HyperlinkedRelatedField(
|
||||
view_name="photobriefing:brief-detail", read_only=True
|
||||
)
|
||||
display_name = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = [
|
||||
"display_name",
|
||||
"project",
|
||||
"stage",
|
||||
"brief",
|
||||
"draft",
|
||||
"first_final",
|
||||
"printing_files",
|
||||
"photo_brief",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class ProductSerializer:
|
||||
class Retrieve(BaseProductSerializer):
|
||||
pass
|
||||
|
||||
class List(BaseProductSerializer):
|
||||
class Meta(BaseProductSerializer.Meta):
|
||||
fields = ["id"] + BaseProductSerializer.Meta.fields
|
||||
@@ -0,0 +1,16 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from outbo.apps.products.models import ProductStage
|
||||
|
||||
|
||||
class ProductStageSerializer:
|
||||
class List(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ProductStage
|
||||
fields = [
|
||||
"id",
|
||||
"label",
|
||||
"group",
|
||||
"description",
|
||||
"next_action",
|
||||
]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
def brief_has_languages(brief):
|
||||
languages = []
|
||||
if brief.has_description_pol:
|
||||
languages.append("Polski")
|
||||
if brief.has_description_eng:
|
||||
languages.append("Angielski")
|
||||
if brief.has_description_por:
|
||||
languages.append("Portugalski")
|
||||
if brief.has_description_rus:
|
||||
languages.append("Rosyjski")
|
||||
if brief.has_description_spa:
|
||||
languages.append("Hiszpański")
|
||||
return ",".join(languages)
|
||||
Whitespace-only changes.
@@ -0,0 +1,8 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def html_new_line(text_field: str):
|
||||
return text_field.replace("\n", "<br>")
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
from rest_framework_extensions.routers import ExtendedSimpleRouter
|
||||
|
||||
from outbo.apps.activity_log.viewsets import ProductLogItemNestedViewSet
|
||||
from outbo.apps.products.viewsets import (
|
||||
ProductViewSet,
|
||||
PackagingTypeViewSet,
|
||||
ProductStageViewSet,
|
||||
PhotoViewSet,
|
||||
BlankingViewSet,
|
||||
BriefViewSet,
|
||||
DraftViewSet,
|
||||
FirstFinalViewSet,
|
||||
PrintingFilesViewSet,
|
||||
DraftFileViewSet,
|
||||
DraftFileNestedViewSet,
|
||||
)
|
||||
from outbo.apps.products.viewsets.first_final_file import (
|
||||
FirstFinalFileNestedViewSet,
|
||||
FirstFinalFileViewSet,
|
||||
)
|
||||
from outbo.apps.products.viewsets.printing_files_file import (
|
||||
PrintingFilesFileNestedViewSet,
|
||||
PrintingFilesFileViewSet,
|
||||
)
|
||||
|
||||
app_name = "products"
|
||||
router = ExtendedSimpleRouter()
|
||||
router.register(prefix="products", viewset=ProductViewSet).register(
|
||||
prefix="messages",
|
||||
viewset=ProductLogItemNestedViewSet,
|
||||
basename="product-message-item",
|
||||
parents_query_lookups=["product_id"],
|
||||
)
|
||||
|
||||
router.register(prefix="briefs", viewset=BriefViewSet)
|
||||
|
||||
router.register(prefix="drafts", viewset=DraftViewSet).register(
|
||||
prefix="files",
|
||||
viewset=DraftFileNestedViewSet,
|
||||
basename="drafts-file",
|
||||
parents_query_lookups=["draft_id"],
|
||||
)
|
||||
router.register(prefix="draft-files", viewset=DraftFileViewSet)
|
||||
|
||||
router.register(prefix="ffs", viewset=FirstFinalViewSet).register(
|
||||
prefix="files",
|
||||
viewset=FirstFinalFileNestedViewSet,
|
||||
basename="first-finals-file",
|
||||
parents_query_lookups=["first_final_id"],
|
||||
)
|
||||
router.register(prefix="first-final-files", viewset=FirstFinalFileViewSet)
|
||||
|
||||
router.register(prefix="pfs", viewset=PrintingFilesViewSet).register(
|
||||
prefix="files",
|
||||
viewset=PrintingFilesFileNestedViewSet,
|
||||
basename="printing-files-file",
|
||||
parents_query_lookups=["printing_files_id"],
|
||||
)
|
||||
router.register(prefix="printing-files-files", viewset=PrintingFilesFileViewSet)
|
||||
|
||||
router.register(prefix="packaging-types", viewset=PackagingTypeViewSet)
|
||||
router.register(prefix="stages", viewset=ProductStageViewSet)
|
||||
router.register(prefix="photos", viewset=PhotoViewSet)
|
||||
router.register(prefix="blankings", viewset=BlankingViewSet)
|
||||
|
||||
urlpatterns = router.urls
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
from django.conf import settings
|
||||
from django.views.generic import TemplateView
|
||||
from django_weasyprint import WeasyTemplateResponseMixin
|
||||
|
||||
from outbo.apps.products.models import Brief
|
||||
from outbo.apps.products.services import brief_has_languages
|
||||
|
||||
|
||||
class ExportBriefToPDFApiView(WeasyTemplateResponseMixin, TemplateView):
|
||||
template_name = "pdf/brief_pdf.html"
|
||||
pdf_stylesheets = [f"{settings.BASE_DIR}/outbo/templates/pdf/pdf.css"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data()
|
||||
brief = Brief.objects.get(pk=kwargs["pk"])
|
||||
context["brief"] = brief
|
||||
context["languages"] = brief_has_languages(brief)
|
||||
return context
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
from .blanking import BlankingViewSet
|
||||
from .brief import BriefViewSet
|
||||
from .draft import DraftViewSet
|
||||
from .draft_file import DraftFileViewSet, DraftFileNestedViewSet
|
||||
from .first_final import FirstFinalViewSet
|
||||
from .packaging_type import PackagingTypeViewSet
|
||||
from .photo import PhotoViewSet
|
||||
from .product import ProductViewSet
|
||||
from .product_stage import ProductStageViewSet
|
||||
from .printing_files import PrintingFilesViewSet
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
DestroyModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
)
|
||||
from outbo.apps.products.models import Blanking
|
||||
from outbo.apps.products.serializers import BlankingSerializer
|
||||
|
||||
|
||||
class BlankingViewSet(
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
DestroyModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = Blanking.objects.all()
|
||||
serializer_class_set = BlankingSerializer
|
||||
retrieve_permission_classes = [AllowAny]
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "retrieve":
|
||||
return [permission() for permission in self.retrieve_permission_classes]
|
||||
return super().get_permissions()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
blanking: Blanking = self.get_object()
|
||||
return HttpResponseRedirect(redirect_to=blanking.file.url)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
PartialUpdateModelMixin,
|
||||
)
|
||||
from outbo.apps.products.models import Brief
|
||||
from outbo.apps.products.serializers import BriefSerializer
|
||||
|
||||
|
||||
class BriefViewSet(
|
||||
AutoPermissionViewSetMixin, RetrieveModelMixin, PartialUpdateModelMixin, MultipleSerializerViewSet
|
||||
):
|
||||
queryset = Brief.objects.all()
|
||||
serializer_class_set = BriefSerializer
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
MultipleSerializerViewSet,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
)
|
||||
from outbo.apps.products.models import Draft
|
||||
from outbo.apps.products.serializers import DraftSerializer
|
||||
|
||||
|
||||
class DraftViewSet(
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = Draft.objects.all()
|
||||
serializer_class_set = DraftSerializer
|
||||
@@ -0,0 +1,78 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.status import HTTP_200_OK
|
||||
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
CreateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
NestedViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
)
|
||||
from outbo.apps.products import rules
|
||||
from outbo.apps.products.models import DraftFile, Draft
|
||||
from outbo.apps.products.serializers import DraftFileSerializer
|
||||
|
||||
|
||||
class OpenFileAsUser:
|
||||
def __update_opened_by(self):
|
||||
object = self.get_object()
|
||||
if object.opened_by is None:
|
||||
object.opened_by = [self.request.user.id]
|
||||
else:
|
||||
if self.request.user.id not in object.opened_by:
|
||||
object.opened_by.append(self.request.user.id)
|
||||
object.save()
|
||||
|
||||
@action(methods=['get'], detail=True, url_path="open-file")
|
||||
def open_file(self, request, pk=None):
|
||||
if self.request.user is not None:
|
||||
self.__update_opened_by()
|
||||
return Response(data="ok", status=HTTP_200_OK)
|
||||
|
||||
|
||||
class DraftFileViewSet(
|
||||
NestedViewSetMixin,
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
OpenFileAsUser,
|
||||
):
|
||||
queryset = DraftFile.objects.all()
|
||||
serializer_class_set = DraftFileSerializer
|
||||
retrieve_permission_classes = [AllowAny]
|
||||
permission_type_map = {
|
||||
**AutoPermissionViewSetMixin.permission_type_map,
|
||||
"open_file": "view",
|
||||
}
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "retrieve":
|
||||
return [permission() for permission in self.retrieve_permission_classes]
|
||||
return super().get_permissions()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return HttpResponseRedirect(redirect_to=self.get_object().file.url)
|
||||
|
||||
|
||||
class DraftFileNestedViewSet(
|
||||
NestedViewSetMixin,
|
||||
AutoPermissionViewSetMixin,
|
||||
CreateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = DraftFile.objects.all()
|
||||
serializer_class_set = DraftFileSerializer
|
||||
serializer_map = {**MultipleSerializerViewSet.serializer_map, "open_file": "Retrieve"}
|
||||
|
||||
def perform_create(self, *, serializer):
|
||||
current_user = self.request.user
|
||||
parents = self.get_parents_query_dict()
|
||||
draft = Draft.objects.get(pk=parents["draft_id"])
|
||||
if not rules.can_edit_draft.test(current_user, draft):
|
||||
raise PermissionDenied() # NOTE: Should it be here?
|
||||
|
||||
return serializer.save(draft=draft)
|
||||
@@ -0,0 +1,18 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
MultipleSerializerViewSet,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
)
|
||||
from outbo.apps.products.models import FirstFinal
|
||||
from outbo.apps.products.serializers import FirstFinalSerializer
|
||||
|
||||
|
||||
class FirstFinalViewSet(
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = FirstFinal.objects.all()
|
||||
serializer_class_set = FirstFinalSerializer
|
||||
@@ -0,0 +1,58 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
CreateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
NestedViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
)
|
||||
from outbo.apps.products import rules
|
||||
from outbo.apps.products.models import FirstFinal, FirstFinalFile
|
||||
from outbo.apps.products.serializers import FirstFinalFileSerializer
|
||||
from outbo.apps.products.viewsets.draft_file import OpenFileAsUser
|
||||
|
||||
|
||||
class FirstFinalFileViewSet(
|
||||
NestedViewSetMixin,
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
OpenFileAsUser
|
||||
):
|
||||
queryset = FirstFinalFile.objects.all()
|
||||
serializer_class_set = FirstFinalFileSerializer
|
||||
retrieve_permission_classes = [AllowAny]
|
||||
permission_type_map = {
|
||||
**AutoPermissionViewSetMixin.permission_type_map,
|
||||
"open_file": "view",
|
||||
}
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "retrieve":
|
||||
return [permission() for permission in self.retrieve_permission_classes]
|
||||
return super().get_permissions()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return HttpResponseRedirect(redirect_to=self.get_object().file.url)
|
||||
|
||||
|
||||
class FirstFinalFileNestedViewSet(
|
||||
NestedViewSetMixin,
|
||||
AutoPermissionViewSetMixin,
|
||||
CreateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = FirstFinalFile.objects.all()
|
||||
serializer_class_set = FirstFinalFileSerializer
|
||||
|
||||
def perform_create(self, *, serializer):
|
||||
current_user = self.request.user
|
||||
parents = self.get_parents_query_dict()
|
||||
ff = FirstFinal.objects.get(pk=parents["first_final_id"])
|
||||
if not rules.can_edit_ff.test(current_user, ff):
|
||||
raise PermissionDenied() # NOTE: Should it be here?
|
||||
|
||||
return serializer.save(first_final=ff)
|
||||
@@ -0,0 +1,14 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
ListModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
)
|
||||
from outbo.apps.products.models import PackagingType
|
||||
from outbo.apps.products.serializers import PackagingTypeSerializer
|
||||
|
||||
|
||||
class PackagingTypeViewSet(
|
||||
AutoPermissionViewSetMixin, ListModelMixin, MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = PackagingType.objects.all()
|
||||
serializer_class_set = PackagingTypeSerializer
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
DestroyModelMixin,
|
||||
)
|
||||
from outbo.apps.products.models import Photo
|
||||
from outbo.apps.products.serializers import PhotoSerializer
|
||||
|
||||
|
||||
class PhotoViewSet(
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
DestroyModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = Photo.objects.all()
|
||||
serializer_class_set = PhotoSerializer
|
||||
retrieve_permission_classes = [AllowAny]
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "retrieve":
|
||||
return [permission() for permission in self.retrieve_permission_classes]
|
||||
return super().get_permissions()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
photo: Photo = self.get_object()
|
||||
return HttpResponseRedirect(redirect_to=photo.file.url)
|
||||
@@ -0,0 +1,18 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
MultipleSerializerViewSet,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
)
|
||||
from outbo.apps.products.models import PrintingFiles
|
||||
from outbo.apps.products.serializers import PrintingFilesSerializer
|
||||
|
||||
|
||||
class PrintingFilesViewSet(
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = PrintingFiles.objects.all()
|
||||
serializer_class_set = PrintingFilesSerializer
|
||||
@@ -0,0 +1,58 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
CreateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
NestedViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
)
|
||||
from outbo.apps.products import rules
|
||||
from outbo.apps.products.models import PrintingFilesFile, PrintingFiles
|
||||
from outbo.apps.products.serializers import PrintingFilesFileSerializer
|
||||
from outbo.apps.products.viewsets.draft_file import OpenFileAsUser
|
||||
|
||||
|
||||
class PrintingFilesFileViewSet(
|
||||
NestedViewSetMixin,
|
||||
AutoPermissionViewSetMixin,
|
||||
RetrieveModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
OpenFileAsUser
|
||||
):
|
||||
queryset = PrintingFilesFile.objects.all()
|
||||
serializer_class_set = PrintingFilesFileSerializer
|
||||
retrieve_permission_classes = [AllowAny]
|
||||
permission_type_map = {
|
||||
**AutoPermissionViewSetMixin.permission_type_map,
|
||||
"open_file": "view",
|
||||
}
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "retrieve":
|
||||
return [permission() for permission in self.retrieve_permission_classes]
|
||||
return super().get_permissions()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return HttpResponseRedirect(redirect_to=self.get_object().file.url)
|
||||
|
||||
|
||||
class PrintingFilesFileNestedViewSet(
|
||||
NestedViewSetMixin,
|
||||
AutoPermissionViewSetMixin,
|
||||
CreateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = PrintingFilesFile.objects.all()
|
||||
serializer_class_set = PrintingFilesFileSerializer
|
||||
|
||||
def perform_create(self, *, serializer):
|
||||
current_user = self.request.user
|
||||
parents = self.get_parents_query_dict()
|
||||
pf = PrintingFiles.objects.get(pk=parents["printing_files_id"])
|
||||
if not rules.can_edit_pf.test(current_user, pf):
|
||||
raise PermissionDenied() # NOTE: Should it be here?
|
||||
|
||||
return serializer.save(printing_files=pf)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
ListModelMixin,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
)
|
||||
from outbo.apps.products.filters import ProductFilter
|
||||
from outbo.apps.products.models import Product
|
||||
from outbo.apps.products.queries import read_product_query
|
||||
from outbo.apps.products.serializers import ProductSerializer
|
||||
|
||||
|
||||
class ProductViewSet(
|
||||
AutoPermissionViewSetMixin,
|
||||
ListModelMixin,
|
||||
RetrieveModelMixin,
|
||||
PartialUpdateModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
):
|
||||
queryset = Product.objects.all()
|
||||
serializer_class_set = ProductSerializer
|
||||
filterset_class = ProductFilter
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
qs = qs.prefetch_related("brief", "draft", "first_final", "printing_files")
|
||||
if self.action == "list":
|
||||
qs_filter = read_product_query(self.request.user)
|
||||
qs = qs.filter(qs_filter)
|
||||
return qs
|
||||
@@ -0,0 +1,14 @@
|
||||
from outbo.apps.core.viewsets import (
|
||||
AutoPermissionViewSetMixin,
|
||||
ListModelMixin,
|
||||
MultipleSerializerViewSet,
|
||||
)
|
||||
from outbo.apps.products.models import ProductStage
|
||||
from outbo.apps.products.serializers import ProductStageSerializer
|
||||
|
||||
|
||||
class ProductStageViewSet(
|
||||
AutoPermissionViewSetMixin, ListModelMixin, MultipleSerializerViewSet
|
||||
):
|
||||
queryset = ProductStage.objects.all()
|
||||
serializer_class_set = ProductStageSerializer
|
||||
Reference in new issue
Block a user