Initial commit from ZIP

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

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class ProductsAppConfig(AppConfig):
name = "outbo.apps.products"
label = "products"
verbose_name = _("Products")
default_app_config = "outbo.apps.products.ProductsAppConfig"
+60
View File
@@ -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)
+8
View File
@@ -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"
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+60
View File
@@ -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
View File
@@ -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
View File
@@ -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
+18
View File
@@ -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
View File
@@ -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)
+69
View File
@@ -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
View File
@@ -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)
+41
View File
@@ -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])
+16
View File
@@ -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()
+146
View File
@@ -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
+13
View File
@@ -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
+19
View File
@@ -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
View File
@@ -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
View File
@@ -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",
]
+47
View File
@@ -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
View File
@@ -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>")
+66
View File
@@ -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
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+78
View File
@@ -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)
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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